《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​

时间:2023-02-17 14:58:00

QSPI Flash读写测试实验​

PS的输入/输出外设(IOP)有两个具有不同功能特性和IO接口性能的QSPI控制器。它们共享相同的APB从接口和MIO引脚。一次只能使用控制器中的一个。QSPI控制器可以访问多比特位宽的Flash设备,以实现较少的引脚数达到高吞吐量的应用。

本章我们将通过QSPI Flash控制器,来完成对QSPI Flash读写操作。本章包括以下几个部分:

  1. 简介
  2. 实验任务
  3. 硬件设计
  4. 软件设计
  5. 下载验证



简介

MPSOC的QSPI Flash控制器分为传统QSPI控制器(LQSPI)和通用QSPI控制器(GQSPI)两个。传统QSPI控制器通过AXI从接口提供了线性可寻址的内存空间。支持引导配置(BOOT)和应用软件配置的就地执行(execute-in-place)。通用QSPI控制器提供I/O,DMA和SPI三种接口模式,不支持引导(BOOT)和就地执行(execute-in-place)。I/O接口配置如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.1.1 QSPI I/O接口配置

传统QSPI控制器(LQSPI)只在线性寻址模式下工作。在这种模式下,可以连接一个或两个Flash器件,为了最小化引脚数量,两块Flash可以通过8bit并行模式或者4bit堆叠排列模式连接到传统QSPI控制器。线性地址模式下,控制器通过使用部分器件操作来消除读Flash时的软件开销。QSPI控制器给Flash发送命令,控制Flash总线到AXI接口的数据流。控制器响应AXI接口上的Flash存储器请求,把Flash存储器当作ROM存储器。

通用QSPI控制器(GQSPI)满足软件对通用低级访问的要求。由于QSPI控制器的通用性,软件可以在任何模式下生成任何命令序列。同时,QSPI控制器支持SPI、Dual SPI和Quad SPI模式下的功能。QSPI控制器运行在I/O、DMA和SPI三种模式下。通用QSPI控制器也支持连接一块或两块Flash设备,为了最小化引脚数量,两块Flash可以通过8bit并行模式或者4bit堆叠排列模式连接到通用QSPI控制器。

在通用I/O模式下,软件和存储设备密切交互。软件将Flash命令写到通用FIFO中,并将数据写到TXFIFO。软件读取RXD寄存器,获取从Flash设备接收到的数据。在I/O模式下,通用QSPI控制器消除了写TXFIFO时产生的软件开销。

在通用DMA模式下,内部DMA模块将Flash设备中的数据传输到系统内存中。这种模式避免了用处理器从Flash中读数据,并且消除了TXFIFO中写满来自Flash数据时产生的软件开销。

在SPI模式下,通用QSPI控制器可以作为标准SPI控制器使用。

双QSPI结构框图如下图所示,控制器由一个传统线性QSPI控制器和一个通用QSPI控制器组成。当控制寄存器设置为1时,选择通用QSPI控制器。传统QSPI控制器和通用QSPI控制器共享带延迟线的接收捕获逻辑。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.1.2 双QSPI控制器

实验任务

本章的实验任务是使用QSPI Flash控制,先后对开发板上的QSPI Flash进行读操作。通过对比读出的数据否等于写入的数据,从而验证读写操作否正确。

硬件设计

根据实验任务我们可以画出本次实验的系统框图,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.3.1 系统框图

13.3.1中可以看出,本次实验是在Hello Wold实验的基础上增加了一个QSPI Flash控制。我们将通过该控制器对QSPI Flash进行读写操作,并通过打印读写数据对比之后的结果。

首先创建Vivado工程,程名为“qspi_flash_test”,然后创建Block Design设计system.bd添加Zynq Ultrascale+ MPSOC模块。接下来按照“Hello World”实验》步骤对Zynq Ultrascale+ MPSOC模块进行配置,配置完成我们要添加本次实验所使用的QSPI Flash控制器模块。如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.3.2 QSPI配置界面

13.3.2,在左侧导航栏中选择“I/O Configuraton”,然后右侧勾选“QSPI”,并选择“Single”模式,QSPI Data Mode选择“x4”,QSPI使用默认的“MIO0..5”。“Single”指的是单个Flash器件。以看,该模式下控制器使用了MIO0至MIO5共6个引脚。

最后点击右下角的“OK”,本次实验Zynq Ultrascale+ MPSOC处理系统就配置完成了。配置完成后的模块如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.3.3 Zynq Ultrascale+ MPSOC模块

这里我们的Block Design设计完成了,在Diagram窗口空白处右击,然后选择Validate Design”验证设计。验证完成后弹出对框提示Validation Successful”表明设计无误点击“OK”确认。最后按快捷键Ctrl + S”保存设计

下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。

然后在菜单栏中选择 File > Export > Export hardware,将硬件文件导出到新建的vitis文件夹下最后在菜单栏选择Tools > Launch Vitis,将路径设置到本工程的vitis文件夹下,启动Vitis软件。

软件设计

Vitis软件建名为qspi_flash_test”的应用工程。然后为应用工程新建一源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:

1 #include "xparameters.h" /* Vitis generated parameters */​
2 #include "xqspipsu.h" /* QSPIPSU device driver */​
3 #include "xil_printf.h"​
4 #include "xil_cache.h"​
5 ​
6 //定义flash读写命令​
7 #define WRITE_STATUS_CMD 0x01​
8 #define WRITE_CMD 0x02​
9 #define READ_CMD 0x03​
10 #define WRITE_DISABLE_CMD 0x04​
11 #define READ_STATUS_CMD 0x05​
12 #define WRITE_ENABLE_CMD 0x06​
13 #define VOLATILE_WRITE_ENABLE_CMD 0x50​
14 #define QUAD_MODE_ENABLE_BIT 0x06​
15 #define FAST_READ_CMD 0x0B​
16 #define DUAL_READ_CMD 0x3B​
17 #define QUAD_READ_CMD 0x6B​
18 #define BULK_ERASE_CMD 0xC7​
19 #define SEC_ERASE_CMD 0xD8​
20 #define READ_ID 0x9F​
21 #define READ_CONFIG_CMD 0x35​
22 #define WRITE_CONFIG_CMD 0x01​
23 ​
24 #define READ_CMD_4B 0x13​
25 #define FAST_READ_CMD_4B 0x0C​
26 #define DUAL_READ_CMD_4B 0x3C​
27 #define QUAD_READ_CMD_4B 0x6C​
28 ​
29 #define READ_FLAG_STATUS_CMD 0x70​
30 ​
31 #define COMMAND_OFFSET 0 //Flash instruction​
32 #define ADDRESS_1_OFFSET 1 //数据偏移地址的最高位​
33 #define ADDRESS_2_OFFSET 2 //数据偏移地址的中间位​
34 #define ADDRESS_3_OFFSET 3 //数据偏移地址的最低位​
35 #define ADDRESS_4_OFFSET 4 //数据偏移地址为四字节时 最低位​
36 ​
37 #define DATA_OFFSET 5 //Start of Data for Read/Write​
38 #define DUMMY_OFFSET 4 //Dummy byte offset for fast, dual and quad reads​
39 ​
40 #define DUMMY_SIZE 1 //Number of dummy bytes for fast, dual and quad reads​
41 ​
42 #define DUMMY_CLOCKS 8 //Number of dummy bytes for fast, dual and quad reads​
43 ​
44 #define RD_ID_SIZE 4 //Read ID command + 3 bytes ID response ​
45 #define BULK_ERASE_SIZE 1 //Bulk Erase command size​
46 #define SEC_ERASE_SIZE 4 //Sector Erase command + Sector address ​
47 #define BANK_SEL_SIZE 2 //BRWR or EARWR command + 1 byte bank value​
48 ​
49 #define RD_CFG_SIZE 2 //1 byte Configuration register + RD CFG command​
50 ​
51 #define WR_CFG_SIZE 3 //WRR command + 1 byte each Status and Config Reg​
52 ​
53 #define DIE_ERASE_SIZE 4 //Die Erase command + Die address​
54 ​
55 #define OVERHEAD_SIZE 4​
56 ​
57 //flash基地址​
58 #define FLASH1BASE 0x0000000​
59 ​
60 //16MB​
61 #define SIXTEENMB 0x1000000​
62 ​
63 //quad enable mask bit​
64 #define FLASH_QUAD_EN_MASK 0x02​
65 ​
66 #define FLASH_SRWD_MASK 0x80​
67 ​
68 // Bank mask​
69 #define BANKMASK 0xF000000​
70 ​
71 ​
72 // Identification of Flash​
73 // Micron:​
74 // Byte 0 is Manufacturer ID;​
75 // Byte 1 is first byte of Device ID - 0xBB or 0xBA​
76 // Byte 2 is second byte of Device ID describes flash size:​
77 // 128Mbit : 0x18; 256Mbit : 0x19; 512Mbit : 0x20​
78 #define MICRON_ID_BYTE0 0x20​
79 #define MICRON_ID_BYTE2_128 0x18​
80 #define MICRON_ID_BYTE2_256 0x19​
81 #define MICRON_ID_BYTE2_512 0x20​
82 #define MICRON_ID_BYTE2_1G 0x21​
83 #define MICRON_ID_BYTE2_2G 0x22​
84 ​
85 // Spansion:​
86 // Byte 0 is Manufacturer ID;​
87 // Byte 1 is Device ID - Memory Interface type - 0x20 or 0x02​
88 // Byte 2 is second byte of Device ID describes flash size:​
89 // 128Mbit : 0x18; 256Mbit : 0x19; 512Mbit : 0x20​
90 #define SPANSION_ID_BYTE0 0x01​
91 #define SPANSION_ID_BYTE2_64 0x17​
92 #define SPANSION_ID_BYTE2_128 0x18​
93 #define SPANSION_ID_BYTE2_256 0x19​
94 #define SPANSION_ID_BYTE2_512 0x20​
95 ​
96 #define WINBOND_ID_BYTE0 0xEF​
97 #define WINBOND_ID_BYTE2_128 0x18​
98 ​
99 #define ISSI_ID_BYTE0 0x9D​
100 #define ISSI_ID_BYTE2_08 0x14​
101 #define ISSI_ID_BYTE2_16 0x15​
102 #define ISSI_ID_BYTE2_32 0x16​
103 #define ISSI_ID_BYTE2_64 0x17​
104 #define ISSI_ID_BYTE2_128 0x18​
105 #define ISSI_ID_BYTE2_256 0x19​
106 #define ISSI_ID_BYTE2_512 0x1a​
107 ​
108 #define QSPIPSU_DEVICE_ID XPAR_XQSPIPSU_0_DEVICE_ID​
109 ​
110 //flash页的数量​
111 #define PAGE_COUNT 32​
112 ​
113 //页大小的最大值​
114 #define MAX_PAGE_SIZE 1024​
115 ​
116 #define TEST_ADDRESS 0x000000​
117 ​
118 #define UNIQUE_VALUE 0x06​
119 ​
120 /**************************** Type Definitions *******************************/​
121 typedef struct{​
122 u32 SectSize; //扇区大小​
123 u32 NumSect; //扇区总个数​
124 u32 PageSize; //页大小​
125 u32 NumPage; //总页数​
126 u32 FlashDeviceSize; //一个存储器件的大小​
127 u8 ManufacturerID; //制造商ID​
128 u8 DeviceIDMemSize; //指出存储容量的器件ID​
129 u32 SectMask; //扇区开始地址掩码​
130 u8 NumDie; // No. of die forming a single flash​
131 } FlashInfo;​
132 ​
133 u8 ReadCmd;​
134 u8 WriteCmd;​
135 u8 StatusCmd;​
136 u8 SectorEraseCmd;​
137 u8 FSRFlag;​
138 ​
139 /************************** Function Prototypes ******************************/​
140 ​
141 int QspiPsuPolledFlashExample(XQspiPsu *QspiPsuInstancePtr, u16 QspiPsuDeviceId);​
142 int FlashReadID(XQspiPsu *QspiPsuPtr);​
143 int FlashErase(XQspiPsu *QspiPsuPtr, u32 Address, u32 ByteCount, u8 *WriteBfrPtr);​
144 int FlashWrite(XQspiPsu *QspiPsuPtr, u32 Address, u32 ByteCount, u8 Command,​
145 u8 *WriteBfrPtr);​
146 int FlashRead(XQspiPsu *QspiPsuPtr, u32 Address, u32 ByteCount, u8 Command,​
147 u8 *WriteBfrPtr, u8 *ReadBfrPtr);​
148 u32 GetRealAddr(XQspiPsu *QspiPsuPtr, u32 Address);​
149 int BulkErase(XQspiPsu *QspiPsuPtr, u8 *WriteBfrPtr);​
150 int FlashEnableQuadMode(XQspiPsu *QspiPsuPtr);​
151 /************************** Variable Definitions *****************************/​
152 u8 TxBfrPtr;​
153 u8 ReadBfrPtr[3];​
154 FlashInfo Flash_Config_Table[] = {​
155 {SECTOR_SIZE_64K, NUM_OF_SECTORS128, BYTES256_PER_PAGE,​
156 0x8000, 0x800000, SPANSION_ID_BYTE0,​
157 SPANSION_ID_BYTE2_64, 0xFFFF0000, 1}​
158 };​
159 ​
160 u32 FlashMake;​
161 u32 FCTIndex; //闪存配置表的索引​
162 ​
163 //QSPI实例​
164 static XQspiPsu QspiPsuInstance;​
165 ​
166 static XQspiPsu_Msg FlashMsg[5];​
167 ​
168 //测试变量 用于产生发送数据​
169 int Test = 1;​
170 ​
171 //用于存储读写数据的变量​
172 u8 ReadBuffer[(PAGE_COUNT * MAX_PAGE_SIZE) + (DATA_OFFSET + DUMMY_SIZE)*8] __attribute__ ((aligned(64)));​
173 u8 WriteBuffer[(PAGE_COUNT * MAX_PAGE_SIZE) + DATA_OFFSET];​
174 u8 CmdBfr[8];​
175 ​
176 u32 MaxData = PAGE_COUNT*256;​
177 ​
178 //主函数​
179 int main(void)​
180 {​
181 int Status;​
182 ​
183 xil_printf("QSPIPSU Generic Flash Polled Example Test \r\n");​
184 ​
185 //调用QspiPsu Polled example​
186 Status = QspiPsuPolledFlashExample(&QspiPsuInstance, QSPIPSU_DEVICE_ID);​
187 if (Status != XST_SUCCESS) {​
188 xil_printf("QSPIPSU Generic Flash Polled Example Failed\r\n");​
189 return XST_FAILURE;​
190 }​
191 ​
192 xil_printf("Successfully ran QSPIPSU Generic Flash Polled Example\r\n");​
193 return XST_SUCCESS;​
194 }

首先本次实验的C程序是在官方提供的示例程序“xqspipsu_generic_flash_polled_example.c”的基础上修改得到的该示例程序演示了如使用轮询模式对QSPI Flash进行读写操作

在程序的开头,我们定义了一系列的参数,包括Flash器件的指令、Flash BUFFER各数据段的偏移量、Flash器件PAGE、SECTOR的数目和大小等信息。这信息针对型号的Flash器件有所不同,需要通过查看器件的数据手册得到。

接下来程序第141至150行声明了八个函数,这些函数是前面我们提到的示例程序中所提供的。我们对其中的函数QspiPsuPolledFlashExample(XQspiPsu *QspiPsuInstancePtr, u16 QspiPsuDeviceId)、FlashReadID(XQspiPsu *QspiPsuPtr)进行修改,从而简化读写测试过程。其他的函数如擦除FlashErase( )、写操作FlashWrite( )读操作FlashRead( ),改动不大。

程序的主函数特别简单,就是通过调用修改之后的示例函数QspiPsuPolledFlashExample ( )Flash进行读写测试,并打印最终的测试结果。下面是该示例函数的代码:

198 int QspiPsuPolledFlashExample(XQspiPsu *QspiPsuInstancePtr, u16 QspiPsuDeviceId)​
199 {​
200 u8 UniqueValue;​
201 int Count;​
202 int Page;​
203 XQspiPsu_Config *QspiPsuConfig;​
204 int ReadBfrSize;​
205 ​
206 ReadBfrSize = (PAGE_COUNT * MAX_PAGE_SIZE) +​
207 (DATA_OFFSET + DUMMY_SIZE)*8;​
208 ​
209 //根据ID查找Qspi配置信息​
210 QspiPsuConfig = XQspiPsu_LookupConfig(QspiPsuDeviceId);​
211 if (QspiPsuConfig == NULL) {​
212 return XST_FAILURE;​
213 }​
214 ​
215 //初始化Qspi​
216 XQspiPsu_CfgInitialize(QspiPsuInstancePtr, QspiPsuConfig,​
217 QspiPsuConfig->BaseAddress);​
218 ​
219 //设置Options​
220 XQspiPsu_SetOptions(QspiPsuInstancePtr, XQSPIPSU_MANUAL_START_OPTION);​
221 ​
222 //为qspi时钟设置分频系数​
223 XQspiPsu_SetClkPrescaler(QspiPsuInstancePtr, XQSPIPSU_CLK_PRESCALE_8);​
224 ​
225 XQspiPsu_SelectFlash(QspiPsuInstancePtr,​
226 XQSPIPSU_SELECT_FLASH_CS_LOWER,​
227 XQSPIPSU_SELECT_FLASH_BUS_LOWER);​
228 ​
229 //读Flash ID​
230 FlashReadID(QspiPsuInstancePtr);​
231 ​
232 ​
233 //Initialize MaxData according to page size.​
234 ​
235 MaxData = PAGE_COUNT * (Flash_Config_Table[FCTIndex].PageSize);​
236 ​
237 //使能flash quad模式​
238 FlashEnableQuadMode(QspiPsuInstancePtr);​
239 ​
242 //Address size and read command selection​
243 ReadCmd = QUAD_READ_CMD;​
244 WriteCmd = WRITE_CMD;​
245 SectorEraseCmd = SEC_ERASE_CMD;​
246 ​
247 /* Status cmd - SR or FSR selection */​
248 if ((Flash_Config_Table[FCTIndex].NumDie > 1) &&​
249 (FlashMake == MICRON_ID_BYTE0)) {​
250 StatusCmd = READ_FLAG_STATUS_CMD;​
251 FSRFlag = 1;​
252 } else {​
253 StatusCmd = READ_STATUS_CMD;​
254 FSRFlag = 0;​
255 }​
256 ​
257 for (UniqueValue = UNIQUE_VALUE, Count = 0;​
258 Count < Flash_Config_Table[FCTIndex].PageSize;​
259 Count++, UniqueValue++) {​
260 WriteBuffer[Count] = (u8)(UniqueValue + Test);​
261 }​
262 ​
263 for (Count = 0; Count < ReadBfrSize; Count++) {​
264 ReadBuffer[Count] = 0;​
265 }​
266 ​
267 //擦除flash​
268 FlashErase(QspiPsuInstancePtr, TEST_ADDRESS, MaxData, CmdBfr);​
269 ​
270 for (Page = 0; Page < PAGE_COUNT; Page++) {​
271 FlashWrite(QspiPsuInstancePtr,​
272 (Page * Flash_Config_Table[FCTIndex].PageSize) + TEST_ADDRESS,​
273 Flash_Config_Table[FCTIndex].PageSize,​
274 WriteCmd, WriteBuffer);​
275 }​
276 ​
277 //从flash中读出数据​
278 FlashRead(QspiPsuInstancePtr, TEST_ADDRESS, MaxData, ReadCmd,​
279 CmdBfr, ReadBuffer);​
280 ​
281 //读出的数据和写入的数据对比​
282 for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < MaxData;​
283 Count++, UniqueValue++) {​
284 if (ReadBuffer[Count] != (u8)(UniqueValue + Test)) {​
285 return XST_FAILURE;​
286 }​
287 }​
288 ​
289 return XST_SUCCESS;​
290 }

示例函数中,首先查找QSPI控制器配置信息,接着控制器驱动进行初始化,如代码的第210行至217行所示。向WriteBuffer中写入数据,然后将WriteBuffer中的数据写到Flash中,如代码第270行至第275行所示。注意在写Flash之前,调用FlashErase( )函数对Flash进行擦除,这是因为Flash写操作只能将1写成0,不能将0写成1,而擦除操作才能将0写成1。

最后,在程序的第282至287行,通过对比写入BUFFER的数据与BUFFER中的数据是否一致,从而判断Flash读写测试实验是否成功。

程序的剩余部分是前面所声明的系列操作Flash的函数实现,为我们将其当作函数直接调用因此代码就不再贴出来了。大家兴趣的话也可以研究一下,这些函数是如何将读写指令和数据转换成QSPI Flash要求的命令格式的。实际上,这些函数功能也都是通过调用xqspipsu.h文件的库函数XQspiPsu_PolledTransfer( )实现的。

下载验证

首先我将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接然后使用USB连接线将开发板的USB_UART接口(PS PORT)与电脑连接,用于口通信。最后连接开发板的电源,给开发板供电。

打开Vitis Terminal终端,设置连接串口。然后下载本次实验程序下载完成后,在下方的Terminal中可以看到应用程序打印的信息Successfully ran QSPIPSU Generic Flash Polled Test”,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第十三章 QSPI Flash读写测试实验​


13.5.1 口打印结果

13.5.1可以看出,次实验实现的QSPI Flash读写测试功能,在MPSOC开发板上面下载验证成功。