ZYNQ-Vitis(SDK)裸机开发之(八)PS端QSPI读写flash操作(包括SPI、Dual SPI、Qual SPI的配置使用)

目录

一、Flash知识简介

二、SPI知识简介

1.SPI引脚介绍

2.SPI协议介绍

3.ZYNQ Quad SPI说明

三、Vivado工程搭建

四、编写Vitis程序

1.ZYNQ QSPI Flash操作的格式:

2.头文件:qspi_hdl.h

3.源文件:qspi_hdl.c

4.编写QSPI Flash读写测试函数

五、实测结果


例程开发环境:

SOC芯片:ZYNQ7020

开发环境:Vivado2020.2,Vitis2020.2

       Flash芯片:W25Q256,即256Mb,32MB

一、Flash知识简介

Flash存储器也叫闪存,是一种非易失性存储器,说白了就是数据掉电不丢失,所以一般用来存放运行程序或者需要掉电保存的数据,并且flash具有操作方便、读写速度快的优点。

Flash存储数据时,只能将1写为0,不能将0写为1,因此对flash进行擦除操作时,就是将flash对应区域全部置1,写数据时,就将对应bit置0即可。

       Flash内部区域划分:级别从大到小一般是:整片(chip)>块(block、bulk、bank)>扇区(sector)>页(page)

其中页为最小划分的区域单位,其内部一般包含若干字节,例如一页包含256字节等,但是目前大部分厂商常用的最小单位划分基本都是扇区,具体是啥还是要看芯片手册来确定;

       下面是我是用的W25Q256 flash芯片的区域划分情况

  1. 最小单位为扇区,每个扇区容量大小4KB,即4096字节
  2. 每个块包含16个扇区,所以每个块大小64KB
  3. 整片flash共有512个块,共32MB存储空间

二、SPI知识简介

1.SPI引脚介绍

/CS:片选引脚,低电平有效,拉低时,表明使能该芯片的操作

VCC:电源引脚

GND:接地引脚

CLK:输入时钟引脚,用于SPI通信同步

IO0(MISO):数据输入引脚

IO1(MOSI)数据输出引脚

IO2(WP):写保护引脚,低电平时,flash无法被写入数据,在Quad SPI模式下复用为数据引脚

IO3(HOLD):暂停通讯引脚,拉低时,DO为高阻态,flash暂停其余操作保持现有状态,等待HOLD拉高,再恢复之前的通讯,在Quad SPI模式下复用为数据引脚

2.SPI协议介绍

SPI为标准通信协议,不仅可以操作flash,还可以与其他类型器件进行通信,但是由于SPI标准通信协议为全双工,且速度较慢,因此实际读写flash时,一般都使用其扩展协议,即Dual flash和Quad flash

  1. SPI接口协议:使用IO0(MISO)和IO1(MOSI)这两个进行读写,IO0输入使用,IO1输出使用,可同时进行读取操作,因此为全双工通信,但是一般读写flash时很少使用全双工模式,所以操作flash时,标准SPI协议用的很少
  2. Dual SPI接口协议:同样使用IO0和IO1这两个进行读写,IO0和IO1只能同时向一个方向发送数据或同时读取数据,因此Dual SPI属于半双工通信,但是同一时刻可以读取2bit或写入2bit数据,是标准SPI协议速度的两倍
  3. Quad SPI接口协议:增加两个读写flash的IO(WP和HOLD复用为数据IO),同时使用IO0- IO3四线进行读或写,同样为半双工通信,同一时刻可以传输4bit数据,是标准SPI通信速度的4倍,常用于操作flash的读写

注意:Dual SPI和Quad SPI一般只用于读写flash使用,不控其他类型器件

3.ZYNQ Quad SPI说明

       ZYNQ QSPI Flash控制器通过MIO与外部 Flash 器件连接,支持三种模式:单个从

器件模式、双从器件并行模式和双从器件堆模式:

(1)单个从器件模式:即外接单个 flash,通过 4bit I/O(即 quad 、dual 或单线)与 flash 进行通信。

(2)双从器件并行模式:把每个 flash 的 IO 进行了单独的连接,扩展成 8bit 用于同时访问两块 flash,实现扩展 QSPI Flash 容量。

(3)双从器件堆叠模式:使用片选 SS 信号进行区分 flash的使能。对 flash 仍然是 4bit,即同一时间只能操作一块 flash。通过使用双从器件模式可以扩展 QSPI Flash

的存储容量

下图是ZYNQ QSPI Flash三种使用模式的框图以及block design中的配置使用方法

三、Vivado工程搭建

ZYNQ QSPI Flash为PS核内置功能,属于硬核,直接对PS端进行配置即可使用,不需要增加PL端的任何IP核;因此本项目工程是在ZYNQ-Vitis(SDK)裸机开发之(一)串口实验工程基础上开发的,一些block design的设计方法,Vitis工程的建立方法等,均在该篇文章中进行了详细的讲解,大家可以去参考:

ZYNQ-Vitis(SDK)裸机开发之(一)串口收发使用:PS串口+PL串口、多个串口使用方法

       PS核需要勾选上QSPI,我的只有一片flash,因此选的Signal SS 4-bit IO选项,具体引脚约束根据自己项目原理图确定

四、编写Vitis程序

1.ZYNQ QSPI Flash操作的格式:

(1)在向flash中写数据时,传入的buffer中结构应按照如下放置数据:

第0个字节:放要下发的指令号

第1-3个字节:放要操作数据的起始地址,当然如果某些指令不需要操作数据,例如读取flash ID,这种的话1-3字节就不需要填写数据,后者随便填就行,作为空闲字节使用

从第4个字节开始:为纯数据区,即需要写入flash内部的数据

(2)在从flash中读数据时,读取的buffer中的数据结构如下所示:

使用普通读指令READ_CMD读取数据时,返回的数据结构与写入时的数据结构一致,提取数据时从第4个字节开始提取,可见下图所示:      

       使用Fast、Dual、Quad这三种模式读取的时候,读取回来的数据结构中,多出一个空闲字节Dummy,在数据区的前面,因此此时提取数据时,应该从第5个字节开始提取

2.头文件:qspi_hdl.h

(1)定义QSPI器件ID号

(2)定义flash芯片操作指令,这个需要根据自己使用芯片的手册进行修改

(3)定义flash操作指令、起始地址、空闲字节、数据等的偏移地址

(4)定义空闲字节Dummy、读ID、擦除指令、buffer头部所占字节数量

(5)定义flash芯片的容量参数,包括页数、页字节、扇区数、扇区字节等

(6)定义要操作的flash部分区域,包括读写起始地址,读写范围、读写的字节数量等等

(7)声明QSPI Flash操作相关的函数,QSPI初始化、读写操作、擦除操作、读flash ID、使能Quad SPI模式等

/*!
    \file    qspi_hdl.h
    \brief   firmware functions to manage qspi
    \version 2024-04-15, V1.0.0
	\author  tbj
*/

#ifndef QSPI_HDL_H
#define QSPI_HDL_H

#include "xqspips.h"

//QSPI器件ID
#define QSPI_DEVICE_ID		XPAR_XQSPIPS_0_DEVICE_ID

//flash操作指令
#define READ_CMD			0x03	//读指令
#define WRITE_CMD			0x02	//写指令
#define READ_STATUS_CMD		0x05	//读状态指令
#define WRITE_STATUS_CMD	0x01	//写状态指令
#define WRITE_ENABLE_CMD	0x06	//写使能指令
#define WRITE_DISABLE_CMD	0x04	//禁止写使能指令
#define FAST_READ_CMD		0x0B	//单通道读取
#define DUAL_READ_CMD		0x3B	//双通道读取-半双工
#define QUAD_READ_CMD		0x6B	//四通道读取-半双工
#define BULK_ERASE_CMD		0xC7	//擦除整片flash-全部写1
#define	SEC_ERASE_CMD		0xD8	//擦除一个扇区-全部写1
#define READ_ID				0x9F	//读取flash ID指令

//定义flash操作指令、地址、数据等在读写buffer中的位置
#define COMMAND_OFFSET		0 //flash操作指令在写buffer中的位置(第0个字节)
#define ADDRESS_1_OFFSET	1 //操作flash数据起始地址的高字节在写buffer中的位置(第1个字节)
#define ADDRESS_2_OFFSET	2 //操作flash数据起始地址的中字节在写buffer中的位置(第2个字节)
#define ADDRESS_3_OFFSET	3 //操作flash数据起始地址的低字节在写buffer中的位置(第3个字节)
#define DATA_OFFSET			4 //操作flash的数据,读取或写入的数据,在写buffer中的位置(第4个字节开始是纯数据区)
#define DUMMY_OFFSET		4 //空闲字节的位置,当使用快速、双线、四线模式读取数据时,空闲字节占读buffer的第4个字节,纯数据区从第5个字节开始

//定义各种操作所需字节长度
#define DUMMY_SIZE			1 //空闲字节大小占1个字节(当使用快速、双线、四线模式读取数据时存在dummy byte)
#define RD_ID_SIZE			4 //读取flash ID占字节数,其中第0个字节为读取ID指令号,后3个字节为读取到的flash ID号
#define BULK_ERASE_SIZE		1 //清空整片flash指令占字节数,只需要一个清空整片flash的指令号
#define SEC_ERASE_SIZE		4 //按扇区清空flash指令占字节数,其中第0字节为按扇区清空flash的指令号,后3个字节是起始清空的flash地址
#define OVERHEAD_SIZE		4 //定义读写buffer头部数据长度,包括指令号1字节和操作地址3字节

//定义flash的参数数据
#define SECTOR_SIZE 0x10000	  //定义单个扇区大小-64KB(根据自己flash芯片手册确定)
#define NUM_SECTORS 0x200	  //定义扇区数量-256个(根据自己flash芯片手册确定)
#define NUM_PAGES 0x20000	  //定义页数量-65536个(根据自己flash芯片手册确定)
#define PAGE_SIZE 256		  //定义每页字节数-256个字节(根据自己flash芯片手册确定)

//定义实际读写操作的范围和数据大小
#define PAGE_COUNT 16         //定义需要操作读写的页数
#define TEST_ADDRESS 0x01FF0000//0x00055000		//定义读写操作的起始地址
#define UNIQUE_VALUE 0x05			//定义读写操作的起始值
#define MAX_DATA (PAGE_COUNT * PAGE_SIZE)	//定义读写操作的最大数据量

//定义QSPI操作结构体对象
XQspiPs QspiInstance;

#ifdef __cplusplus
 extern "C" {
#endif

 //初始化QSPI控制器
 int Qspi_Init(XQspiPs *QspiInstancePtr);
 //通过QSPI将数据写入flash中
 void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command);
 //通过QSPI读取flash中的数据
 void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command);
 //擦除flash
 void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);
 //读取flash ID
 int FlashReadID(void);
 //使能四线模式
 void FlashQuadEnable(XQspiPs *QspiPtr);

#ifdef __cplusplus
}
#endif

#endif /* QSPI_HDL_H */

3.源文件:qspi_hdl.c

(1)对头文件总QSPI初始化、读写操作、擦除操作、读flash ID、使能Quad SPI模式等函数进行实现

/*!
    \file    qspi_hdl.c
    \brief   firmware functions to manage qspi
    \version 2024-04-15, V1.0.0
	\author  tbj
*/

#include "qspi_hdl.h"

//QSPI读写flash使用的buffer,内部使用
static u8 FlashReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
static u8 FlashWriteBuffer[PAGE_SIZE + DATA_OFFSET];

/* 功能:初始化QSPI控制器
 * 入参1:QSPI控制器实例化对象指针
 */
int Qspi_Init(XQspiPs *QspiInstancePtr){

	int Status;
	XQspiPs_Config *QspiConfig;

	//初始化QSPI控制器
	QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);
	if (QspiConfig == NULL) {
		return XST_FAILURE;
	}

	Status = XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig,
					QspiConfig->BaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	//QSPI控制器自检,保证初始化成功
	Status = XQspiPs_SelfTest(QspiInstancePtr);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	//清空读写操作的buffer
	memset(FlashWriteBuffer, 0x00, sizeof(FlashWriteBuffer));
	memset(FlashReadBuffer, 0x00, sizeof(FlashReadBuffer));

	//将flash配置为手动启动、手动片选模式,将hold(reset)引脚配置为高电平,hold低电平,暂停收发数据,高电平恢复收发数据
	Status |= XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |
			XQSPIPS_FORCE_SSELECT_OPTION |
			XQSPIPS_HOLD_B_DRIVE_OPTION);

	//设置QSPI预分频系数
	Status |= XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);

	//将片选信号置为有效
	Status |= XQspiPs_SetSlaveSelect(QspiInstancePtr);

	//读取flash ID
	Status |= FlashReadID();

	//使能QSPI Quad模式
	FlashQuadEnable(QspiInstancePtr);

	return Status;
}


/**
* @brief 通过QSPI将数据写入flash中
* @param QSPI结构体指针
* @param 要写入数据的起始地址
* @param 要写入数据的数量-按字节
* @param 写数据指令
* @return 无
* @note 无
* */
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command)
{
	u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
	u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 FlashStatus[2];

	//发送写使能指令
	XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				sizeof(WriteEnableCmd));

	//将写操作指令以及数据地址写入对应待发送buffer的前4个字节,第五个字节开始才是写入的数据
	FlashWriteBuffer[COMMAND_OFFSET]   = Command;
	FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
	FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
	FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

	//将要写入的纯数据,写入到flash写操作buffer中(flash写操作buffer包括写指令、地址、数据等内容)
	memcpy(FlashWriteBuffer + 4, WriteBuf, ByteCount);

	//将写指令、写起始地址信息、写入数据内容,写到flash中
	XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL,
				ByteCount + OVERHEAD_SIZE);
	//等待数据写入完毕
	while (1) {

		//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
		XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
					sizeof(ReadStatusCmd));

		//如果读取的状态值是0xff,则证明数据还未写完
		FlashStatus[1] |= FlashStatus[0];
		if ((FlashStatus[1] & 0x01) == 0) {
			break;
		}
	}
}

/**
* @brief 通过QSPI读取flash中的数据
* @param QSPI结构体指针
* @param 读取数据的起始地址
* @param 读取数据的数量-按字节
* @param 读取数据指令-普通读取、快速、双线、四线读取等
* @return 无
* @note 无
* */
void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command)
{
	//将读指令和读数据首地址写入到要发送的buffer中,它们占4个字节
	FlashWriteBuffer[COMMAND_OFFSET]   = Command;
	FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
	FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
	FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

	//如果是快速读取、双线读取和四线读取,则需要增加一个空闲字节的长度DUMMY_SIZE
	if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
	    (Command == QUAD_READ_CMD)) {
		ByteCount += DUMMY_SIZE;
	}

	//将读指令和读地址发送到通过QSPI发送到flash,等待数据读取至ReadBuffer中
	XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, FlashReadBuffer,
				ByteCount + OVERHEAD_SIZE);


	//如果是快速读取、双线读取和四线读取,则需要增加一个虚拟字节的长度DUMMY_SIZE
	if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
	    (Command == QUAD_READ_CMD)) {
		//非普通模式读取,有空闲字节,从第5个字节开始是纯数据
		memcpy(ReadBuf, FlashReadBuffer + 5, ByteCount - 1);
	}else{
		//普通模式读取,无空闲字节,从第4个字节开始是纯数据
		memcpy(ReadBuf, FlashReadBuffer + 4, ByteCount);
	}

}

/**
* @brief 擦除flash
* @param QSPI结构体指针
* @param 擦除的起始地址
* @param 擦除数据的数量-按字节
* @return 无
* @note 无
* */
void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount)
{
	u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
	u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 FlashStatus[2];
	int Sector;

	//如果是擦除整片flash,则使用整片擦除指令chip erase
	if (ByteCount == (NUM_SECTORS * SECTOR_SIZE)) {
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				  sizeof(WriteEnableCmd));

		//将整片擦除指令写入到发送buffer的首个字节的位置
		FlashWriteBuffer[COMMAND_OFFSET]   = BULK_ERASE_CMD;
		//将整片擦除指令发送到flash
		XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL,
					BULK_ERASE_SIZE);

		//等待擦除完成
		while (1) {
			//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
						FlashStatus,
						sizeof(ReadStatusCmd));

			//如果读取的状态值是0xff,则证明数据还未写完
			FlashStatus[1] |= FlashStatus[0];
			if ((FlashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		return;
	}

	//如果是部分擦除,则使用扇区sector擦除的指令进行擦除操作
	for (Sector = 0; Sector < ((ByteCount / SECTOR_SIZE) + 1); Sector++) {
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
					sizeof(WriteEnableCmd));

		//将扇区擦除指令,以及开始擦除首地址写入到发送buffer的前四个字节
		FlashWriteBuffer[COMMAND_OFFSET]   = SEC_ERASE_CMD;
		FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)(Address >> 16);
		FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)(Address >> 8);
		FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

		//将扇区擦除指令,以及开始擦除首地址发送到flash
		XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, NULL,
					SEC_ERASE_SIZE);

		//等待擦除完成
		while (1) {
			//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
						FlashStatus,
						sizeof(ReadStatusCmd));

			//如果读取的状态值是0xff,则证明数据还未写完
			FlashStatus[1] |= FlashStatus[0];
			if ((FlashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		Address += SECTOR_SIZE;
	}
}

/**
* @brief 读取flash ID
* @param 无
* @return 无
* @note 无
* */
int FlashReadID(void)
{
	int Status;

	//读取ID指令,后三个字节是空闲字节,填不填都行,填什么也无所谓
	FlashWriteBuffer[COMMAND_OFFSET]   = READ_ID;
	FlashWriteBuffer[ADDRESS_1_OFFSET] = 0x23;
	FlashWriteBuffer[ADDRESS_2_OFFSET] = 0x08;
	FlashWriteBuffer[ADDRESS_3_OFFSET] = 0x09;

	//将读ID指令发送到flash
	Status = XQspiPs_PolledTransfer(&QspiInstance, FlashWriteBuffer, FlashReadBuffer,
				RD_ID_SIZE);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	xil_printf("FlashID=0x%x 0x%x 0x%x\n\r", FlashReadBuffer[1], FlashReadBuffer[2],
			FlashReadBuffer[3]);

	return XST_SUCCESS;
}

/**
* @brief 使能四线模式
* @param QSPI结构体指针
* @return 无
* @note 无
* */
void FlashQuadEnable(XQspiPs *QspiPtr)
{
	u8 WriteEnableCmd = {WRITE_ENABLE_CMD};
	u8 ReadStatusCmd[] = {READ_STATUS_CMD, 0};
	u8 QuadEnableCmd[] = {WRITE_STATUS_CMD, 0};
	u8 FlashStatus[2];

	//判断读取的flash ID是否正确,不加这个判断也行
	if (FlashReadBuffer[1] == 0xEF) {
		//获取读状态
		XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
					FlashStatus,
					sizeof(ReadStatusCmd));

		QuadEnableCmd[1] = FlashStatus[1] | 1 << 6;
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				  sizeof(WriteEnableCmd));
		//发送Quad配置指令
		XQspiPs_PolledTransfer(QspiPtr, QuadEnableCmd, NULL,
					sizeof(QuadEnableCmd));
		while (1) {
			//获取读状态,等待指令写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
					sizeof(ReadStatusCmd));
			/*
			 * 第6it置1,第0bit置0,则Quad模式设置成功、且设备状态准备就绪
			 */
			if ((FlashStatus[0] == 0x40) && (FlashStatus[1] == 0x40)) {
				break;
			}
		}
	}
}

4.编写QSPI Flash读写测试函数

//读写buffer数据长度
#define test_buf_len 255
//QSPI读写flash测试
void QSPI_Flash_Opt(){

	u8 nRet = XST_SUCCESS;
	u8 write_buf[test_buf_len] = {0};
	u8 read_buf[test_buf_len] = {0};

	//初始化QSPI控制器
	Qspi_Init(&QspiInstance);

	//write buffer填写数据
	for(int i = 0; i < test_buf_len; i++){
		write_buf[i] = i + 1;
	}

	//清除要写入的flash区域
	FlashErase(&QspiInstance, TEST_ADDRESS, test_buf_len);
	//将write buffer数据写入flash中
	FlashWrite(&QspiInstance, TEST_ADDRESS, write_buf, test_buf_len, WRITE_CMD);
	//将写入flash中的数据再进行读取
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, READ_CMD);
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, FAST_READ_CMD);
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, DUAL_READ_CMD);
	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, QUAD_READ_CMD);

	//打印写buffer区数据
	for(int i = 0; i < test_buf_len; i++){
		printf("%d ", write_buf[i]);
		if(i == test_buf_len - 1)
			printf("\n");
	}

	//打印读buffer区数据
	for(int i = 0; i < test_buf_len; i++){
		printf("%d ", read_buf[i]);
		if(i == test_buf_len - 1)
			printf("\n");
	}

	//对比写入和读出的数据是否一致
	for(int i = 0; i < test_buf_len; i++){
		if(read_buf[i] != write_buf[i]){
			nRet = XST_FAILURE;
		}
	}

	if(nRet == XST_SUCCESS){
		printf("QSPI Operate flash successful!\n");
	}else{
		printf("QSPI Operate flash failed!\n");
	}
}

5.main函数调用

五、实测结果

创作不易,希望大家点赞、收藏、关注哦!!!ヾ(o◕∀◕)ノ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/557149.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

已经下载了pytorch,但在正确使用一段时间后出现No module named torch的错误

问题描述 使用的是叫做m2release的虚拟环境&#xff0c;在此环境下使用conda list可以发现是存在pytorch的&#xff0c;但是运行代码时却报No module named torch的错误。 解决方案 想尝试卸掉这个pytorch重新装一次&#xff0c;但是想卸载会提示找不到&#xff0c;想重新…

redis写入和查询

import redis #redis的表名 redis_biao "Ruijieac_sta" #redis连接信息 redis_obj redis.StrictRedis(hostIP地址, port6379, db1, password密码) # keyytressdfg # value22 ##写入 # redis_obj.hset(redis_biao, key, value) #查询 req_redisredis_obj.hget(red…

Python面试十问

深浅拷贝的区别&#xff1f; 浅拷⻉&#xff1a; 拷⻉的是对象的引⽤&#xff0c;如果原对象改变&#xff0c;相应的拷⻉对象也会发⽣改变。 深拷⻉&#xff1a; 拷⻉对象中的每个元素&#xff0c;拷⻉对象和原有对象不在有关系&#xff0c;两个是独⽴的对象。 浅拷⻉(copy)…

selenium_定位tag_name

第一个input """需求&#xff1a;1. 使用tag_name定位方式&#xff0c;使用注册A.html页面&#xff0c;用户名输入admin方法&#xff1a;1. driver.find_element_by_tag_name("") # 定位元素方法2. send_keys() # 输入方法3. driver.quit() # 退出方…

最小生成树算法的实现c++

最小生成树算法的实现c 题目链接&#xff1a;1584. 连接所有点的最小费用 - 力扣&#xff08;LeetCode&#xff09; 主要思路&#xff1a;使用krusal算法&#xff0c;将边的权值进行排序&#xff08;从小到大排序&#xff09;&#xff0c;每次将权值最小且未加入到连通分量中…

6、Lagent AgentLego 智能体应用搭建(homework)

基础作业 完成 Lagent Web Demo 使用&#xff0c;并在作业中上传截图。文档可见 Lagent Web Demo 0 环境准备 conda create -n agent conda activate agent conda install python3.10 conda install pytorch2.1.2 torchvision0.16.2 torchaudio2.1.2 pytorch-cuda11.8 -c py…

【Linux】动态扩容根目录

Linux&#xff1a;解决centos-root 根目录磁盘空间不足&#xff0c;动态扩容&#xff0c;不删数据 默认安装的root分区只有50G&#xff0c;/home分区有大几百G&#xff0c;可以考虑重新挂载分配空间&#xff0c;不用删除数据&#xff0c;不需要停业务。 查看系统空间 df -h解…

【PDF技巧】PDF文件带有密码,该如何解密?

PDF文件带有打开密码、限制编辑&#xff0c;这两种密码设置了之后如何解密&#xff1f; 不管是打开密码或者是限制编辑&#xff0c;在知道密码的情况下&#xff0c;解密PDF密码&#xff0c;我们只需要在PDF编辑器中打开文件 – 属性 – 安全&#xff0c;将权限状态修改为无保护…

深入理解C语言结构体和位域

目录标题 1. **结构体基础**2. **结构体的定义和使用**3. **结构体内存布局**4. **结构体与函数**5. **位域的定义和使用**6. **位域的实际应用**7. **结构体与位域的混合使用**8. **注意事项和最佳实践**9. **结语** C语言中的结构体和位域是存储和管理数据的重要工具&#xf…

孟德尔随机化(三)—— 再也不用担心网络或其他各种报错啦 | 从数据库下载数据到本地的数据处理方法

前几天咱们分享了看完不会来揍我 | 孟德尔随机化万字长文详解&#xff08;二&#xff09;—— 代码实操 | 附代码注释 结果解读&#xff0c;很多小伙伴们反映在使用代码下载数据时会遇到各种网络或其他报错问题&#xff0c;令人头大的那种&#xff01;不要慌&#xff01;从数据…

每日一题---合并两个有序数组

文章目录 1.前言2.题目2,代码思路3.参考代码 1.前言 上次我们做了移除元素这道题&#xff0c;下来我们看一下合并两个有序数组 2.题目 2,代码思路 创建三个变量&#xff0c;创建三个变量&#xff0c;分别是n1&#xff0c;n2&#xff0c;n3&#xff0c;分别指向nums1[m-1],nums…

华为ensp中Hybrid接口原理和配置命令

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月19日14点03分 Hybrid接口是ENSP虚拟化中的一种重要技术&#xff0c;它既可以连接普通终端的接入链路&#xff0c;又可以连接交换机间的干道链路。Hybrid接口允许多…

排序算法。

***冒泡排序: 基本&#xff1a; private static void sort(int[] a){for (int i 0; i < a.length-1; i) {for (int j 0; j < a.length-i-1; j) {if (a[j]>a[j1]){swap(a,j,j1);}}}} private static void swap(int[] a,int i,int j){int tempa[i];a[i]a[j];a[j]temp…

【YOLOv5】使用yolov5训练模型时报错合集

文章目录 前言问题1 -- VsCode终端无法进入Anaconda创建的虚拟环境【问题描述】【问题分析】【解决方式】方法一方法二 问题2 -- 怎么在VsCode中为项目配置Anaconda创建的虚拟环境【问题描述】【解决方式】 问题3 -- yolov5训练模型时报错RuntimeError: result type Float cant…

代码随想录-算法训练营day12【休息,复习与总结】

代码随想录-035期-算法训练营【博客笔记汇总表】-CSDN博客 ● day 12 周日休息&#xff08;4.14&#xff09; 目录 复习与总结 0417_图论-太平洋大西洋水流问题 0827_图论-最大人工岛 复习与总结 二刷做题速度提升了一大截&#xff0c;ヾ(◍∇◍)&#xff89;&#xff9e;加…

Linux重启网络后导致容器网络无法连接的解决办法

背景 有时候莫名奇妙的在一顿操作后&#xff0c;容器网络就坏了。然后外部无法访问容器的服务了。以前以为是操作了防火墙导致了容器无法被访问。但是最近经过仔细比较&#xff0c;防火墙规则并没有变化&#xff0c;但是容器就无法访问了。 分析 对于容器网络的讨论&#xff…

(六)PostgreSQL的组织结构(3)-默认角色和schema

PostgreSQL的组织结构(3)-默认角色和schema 基础信息 OS版本&#xff1a;Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本&#xff1a;16.2 pg软件目录&#xff1a;/home/pg16/soft pg数据目录&#xff1a;/home/pg16/data 端口&#xff1a;57771 默认角色 Post…

校园水电预付费系统

一、引言 校园水电预付费系统是一种现代化的管理方式&#xff0c;它改变了传统的后付费模式&#xff0c;实现了先付费后使用的理念&#xff0c;有效提高了校园资源的使用效率和管理效能。本文将从系统功能、操作流程、优点和实施策略四个方面详细介绍这一系统。 二、系统功能…

【五十六】【算法分析与设计】线段树之add+query操作,pair表示节点,自定义类型表示节点,真树结构实现线段树与数组实现线段树

线段树解决的问题 给你一个nums数组&#xff0c;1.L~R区间上的值全部加C。2.L~R区间上的值全部变成C。3.对L~R区间上求和操作。 对于第一个方法&#xff0c;如果正常遍历L~R加C&#xff0c;时间复杂度是O(N)。 对于第二个方法&#xff0c;如果正常遍历L~RC&#xff0c;时间复…

实时数据同步之Maxwell和Canal

文章目录 一、概述1、实时同步工具概述1.1 Maxwell 概述1.2 Canal概述 2、数据同步工作原理2.1 MySQL 主从复制过程2.2 两种工具工作原理 3、MySQL 的 binlog详解3.1 什么是 binlog3.2 binlog 的开启3.3 binlog 的分类设置 4、Maxwell和Canal对比5、环境安装 二、Maxwell 使用1…
最新文章