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◕∀◕)ノ 

<think>好的,我现在需要帮助用户了解Zynq设备中Flash配置使用方法。首先,我需要整理用户提供的引用内容,看看有哪些相关信息。用户提到了三个引用,分别关于QSPI Flash固化程序、从Flash启动Linux的配置,以及使用AXI EMC IP核控制外部Flash的指南。 首先,用户的问题是关于Zynq Flash配置教程和文档,所以需要结合这三个引用内容,分步骤说明配置方法。可能需要包括使用SDK烧写QSPI Flash的步骤,Vivado中的硬件配置,比如外设选择、Flash类型选择、DDR配置,以及AXI EMC IP核的使用方法。此外,用户可能需要示例代码和注意事项,比如启动模式设置和电平标准配置。 接下来,我需要确保回答结构清晰,分点说明。可能需要分为几个部分:使用SDK烧写QSPI Flash、Vivado硬件配置指南、AXI EMC IP核控制外部Flash,以及常见问题。每个部分都需要引用对应的文献,比如引用[1]提到使用SDK的Program Flash工具,引用[2]涉及Vivado中的外设和Flash类型选择,引用[3]关于AXI EMC的配置和代码示例。 同时,要检查是否需要生成相关问题,按照用户的要求,在最后添加相关问题部分。问题应该围绕用户的需求,比如启动模式设置、不同Flash接口的区别、固化程序的步骤、AXI EMC的应用场景等。 还要注意使用正确的中文表达,避免技术术语错误,并且确保LaTeX格式正确,虽然用户的问题中没有涉及数学公式,但回答中如果有变量或代码块需要正确格式化。例如,代码示例需要用反引号包裹,并标明语言类型。 最后,需要验证所有引用是否准确对应到用户提供的引用内容,确保每个引用标识正确,比如引用[1]对应QSPI Flash固化程序,引用[2]对应从Flash启动Linux的配置,引用[3]对应AXI EMC IP核的使用。回答中的每个步骤都应引用相应的来源,并在末尾列出参考文献。 可能还需要考虑用户的实际应用场景,比如他们是否在开发嵌入式系统,需要启动配置或外部存储控制,因此相关问题应涵盖这些方面,帮助用户进一步深入理解。同时,提醒注意事项,如启动模式引脚设置和电平匹配,避免常见配置错误。</think>### Zynq设备Flash配置使用指南 #### 一、QSPI Flash基础配置方法 1. **SDK烧写QSPI Flash** 通过SDK菜单选择`Xilinx -> Program Flash`,设置QSPI接口参数(频率、模式),选择要烧录的`.bin`或`.mcs`文件完成固化[^1]。示例代码结构: ```c // 初始化QSPI控制器 XQspiPs_Config *QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID); XQspiPs_CfgInitialize(&QspiPs, QspiConfig, QspiConfig->BaseAddress); // 执行Flash擦除和编程操作 XQspiPs_Erase(&QspiPs, FLASH_ADDR, ERASE_SIZE); XQspiPs_Write(&QspiPs, data_buffer, FLASH_ADDR, data_length); ``` 2. **Vivado硬件配置** -Zynq Processing System配置界面选择`QSPI Flash`接口类型(需与硬件原理图匹配,如Micron MT25QL系列)[^2] - 配置Bank电平标准(例如1.8V/3.3V) - 设置DDR参数(型号、时钟频率、时序参数) - 启用必要外设(UART用于调试,ETH0用于网络通信) $$ \text{Flash时钟频率} = \frac{\text{系统时钟}}{2^{(CLK\_PHASE + CLK\_POLARITY)}} $$ #### 二、AXI EMC控制外部Flash(NOR型) 1. **IP核配置** 在Vivado中添加AXI EMC IP核,设置: - 存储器类型:`Parallel NOR Flash` - 数据宽度(8/16位) - 时序参数(tACC, tCE, tOE) 2. **软件驱动开发** 参考Xilinx提供的EMC驱动程序模板: ```c // 初始化EMC控制器 XEmc_Initialize(&emc, XPAR_EMC_0_DEVICE_ID); // 设置读写时序 XEmc_SetTiming(&emc, XEMC_MEM_TYPE_NOR, &nor_timing); // 执行数据读写 XEmc_WriteMemory(&emc, TARGET_ADDR, data_buffer, data_length); uint32_t read_data = XEmc_ReadMemory(&emc, SOURCE_ADDR); ``` #### 三、启动模式配置要点 1. **启动模式选择** 通过BOOT[MIO:6-2]引脚设置: - 00110: QSPI启动模式 - 11100: NAND启动模式 - 01110: NOR启动模式 2. **FSBL配置** 在First Stage Bootloader中指定: ```c // boot.S文件设置栈指针 ldr sp, =0xFFFFFFFC // 设备初始化序列 Initialize_Clock(); DDR_Calibration(); QSPI_Init(); ``` #### 四、常见问题处理 1. **启动失败排查** - 检查Bank电压是否与Flash芯片匹配[^2] - 验证时序参数是否符合Flash手册要求(tCH/QV为关键参数) - 使用SDK XSCT工具读取0xFC000000地址的启动状态寄存器 2. **多镜像存储配置** 在Vivado中设置`Boot Image Settings`: ```tcl set_property CONFIG.BOOTIMAGE_OFFSET 0x3E80000 [get_bd_cells sys_ps7] set_property CONFIG.BOOTIMAGE_SIZE 0x800000 [get_bd_cells sys_ps7] ``` #### 五、推荐调试工具 1. SDK XSCT命令行工具 2. Vitis Serial Terminal(UART调试) 3. ChipScope ILA(时序分析)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

披着假发的程序唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值