0%

ZYNQ嵌入式之QSPI Flash(基于PYNQ-Z2开发板)

本节通过XIlinx官方的Flash示例程序演示了Flash的使用

Flash简介

  • Flash存储器(又称闪存)是一种非易失性存储器,具有操作方便、读写速度快等优点

  • 一般用于存储操作系统和程序代码,或者用于数码相机、U盘、MP3播放器做数据存储

  • Flash的存储单元组织为块阵列

    • 块是擦除操作的最小单位,擦除操作将块内的所有位,置为“1”
    • 页是读和写操作的基本单位,在对页进行写操作之前,需要判断该页内所有的位是否为“1”,如果全部为“1”,可以进行写操作,否则,需要对整个块进行擦除操作
    • Flash的特性是只能从1翻转到0
  • Flash的分类:

    image-20230927093742282 image-20230927094018734
  • SPI是串口外设接口的缩写,是一种高速的,全双工,同步的通信总线

    • 优点:支持全双工通信、通信简单、传输速率快
    • 缺点:没有应答机制确认是否接收到数据,所以跟IIC总线协议比较在数据可靠性上有一定的缺陷
  • QSPI通信方式(Q是quad,代表有4根数据线):主从模式

    image-20230927110305340
  • QSPI flash控制器是IO外设的一部分,在PS端

    image-20230927111837475

QSPI Flash读写测试

  • 实验任务:使用QSPI Flash控制器,先后对ZYNQ核心板上的QSPI Flash进行写、读操作。通过对比读出的数据是否等于写入的数据,从而验证读写操作是否正确

    image-20230927112722162

1.Vivado中的相关配置

image-20230928100235889

image-20230928100327932

  • 然而,因为实验是基于pynq-z2的boards的,自动配置时已经将这些都配置好了

2.Vitis中的代码编写

  • 使用Xilinx官方提供的历程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#include "xparameters.h"	/* SDK generated parameters */
#include "xqspips.h" /* QSPI device driver */
#include "xil_printf.h"

#define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID

//发送到FLASH器件的指令
#define WRITE_STATUS_CMD 0x01
#define WRITE_CMD 0x02
#define READ_CMD 0x03
#define WRITE_DISABLE_CMD 0x04
#define READ_STATUS_CMD 0x05
#define WRITE_ENABLE_CMD 0x06
#define FAST_READ_CMD 0x0B
#define DUAL_READ_CMD 0x3B
#define QUAD_READ_CMD 0x6B
#define BULK_ERASE_CMD 0xC7
#define SEC_ERASE_CMD 0xD8
#define READ_ID 0x9F

//FLASH BUFFER中各数据的偏移量
#define COMMAND_OFFSET 0 // FLASH instruction
#define ADDRESS_1_OFFSET 1 // MSB byte of address to read or write
#define ADDRESS_2_OFFSET 2 // Middle byte of address to read or write
#define ADDRESS_3_OFFSET 3 // LSB byte of address to read or write
#define DATA_OFFSET 4 // Start of Data for Read/Write
#define DUMMY_OFFSET 4 // Dummy byte offset for reads

#define DUMMY_SIZE 1 // Number of dummy bytes for reads
#define RD_ID_SIZE 4 // Read ID command + 3 bytes ID response
#define BULK_ERASE_SIZE 1 // Bulk Erase command size
#define SEC_ERASE_SIZE 4 // Sector Erase command + Sector address

#define OVERHEAD_SIZE 4 // control information: command and address

#define SECTOR_SIZE 0x10000
#define NUM_SECTORS 0x100
#define NUM_PAGES 0x10000
#define PAGE_SIZE 256

// 需要写入的flash页数
#define PAGE_COUNT 16

// 要写入数据的FLASH地址
#define TEST_ADDRESS 0x00055000
#define UNIQUE_VALUE 0x05

#define MAX_DATA (PAGE_COUNT * PAGE_SIZE)

void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);
void FlashRead(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);
int FlashReadID(void);
void FlashQuadEnable(XQspiPs *QspiPtr);
int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId);

static XQspiPs QspiInstance;

int Test = 5;

u8 ReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
u8 WriteBuffer[PAGE_SIZE + DATA_OFFSET];

int main(void)
{

xil_printf("QSPI FLASH Polled Example Test \r\n");
/* Run the Qspi Interrupt example.*/
QspiFlashPolledExample(&QspiInstance, QSPI_DEVICE_ID);
xil_printf("Successfully ran QSPI FLASH Polled Example Test\r\n");
return XST_SUCCESS;
}

int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId)
{
u8 *BufferPtr;
u8 UniqueValue;
int Count;
int Page;
XQspiPs_Config *QspiConfig;

//初始化QSPI驱动
QspiConfig = XQspiPs_LookupConfig(QspiDeviceId);
XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig, QspiConfig->BaseAddress);
//初始化读写BUFFER
for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < PAGE_SIZE;
Count++, UniqueValue++) {
WriteBuffer[DATA_OFFSET + Count] = (u8)(UniqueValue + Test);
}
memset(ReadBuffer, 0x00, sizeof(ReadBuffer));

//设置手动启动和手动片选模式
XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |
XQSPIPS_FORCE_SSELECT_OPTION |
XQSPIPS_HOLD_B_DRIVE_OPTION);
//设置QSPI时钟的分频系数
XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);
//片选信号置为有效
XQspiPs_SetSlaveSelect(QspiInstancePtr);
//读FLASH ID
FlashReadID();
//使能FLASH Quad模式
FlashQuadEnable(QspiInstancePtr);
//擦除FLASH
FlashErase(QspiInstancePtr, TEST_ADDRESS, MAX_DATA);
//向FLASH中写入数据
for (Page = 0; Page < PAGE_COUNT; Page++) {
FlashWrite(QspiInstancePtr, (Page * PAGE_SIZE) + TEST_ADDRESS,
PAGE_SIZE, WRITE_CMD);
}
//使用QUAD模式从FLASH中读出数据
FlashRead(QspiInstancePtr, TEST_ADDRESS, MAX_DATA, QUAD_READ_CMD);

//对比写入FLASH与从FLASH中读出的数据
BufferPtr = &ReadBuffer[DATA_OFFSET + DUMMY_SIZE];
for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < MAX_DATA;
Count++, UniqueValue++) {
if (BufferPtr[Count] != (u8)(UniqueValue + Test)) {
return XST_FAILURE;
}
}

return XST_SUCCESS;
}

/* 该函数写入连接到QSPI接口的串行FLASH。所有放入缓冲区的数据必须位于设备的同一页中,
* 页边界位于256字节边界上。
*/
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command)
{
u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 }; /* must send 2 bytes */
u8 FlashStatus[2];

/*
* Send the write enable command to the FLASH so that it can be
* written to, this needs to be sent as a seperate transfer before
* the write
* 发送write enable命令到FLASH,以便它可以被写入,这需要在写入之前作为单独的传输发送
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));

/*
* Setup the write command with the specified address and data for the
* FLASH
* 使用指定的地址和数据为FLASH设置写命令
*/
WriteBuffer[COMMAND_OFFSET] = Command;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

/*
* Send the write command, address, and data to the FLASH to be
* written, no receive buffer is specified since there is nothing to
* receive
* 将写命令、地址和数据发送到要写的FLASH,没有指定接收缓冲区,因为没有什么可接收的
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
ByteCount + OVERHEAD_SIZE);

/*
* Wait for the write command to the FLASH to be completed, it takes
* some time for the data to be written
* 等待向FLASH写入命令完成,数据写入需要一段时间
*/
while (1) {
/*
* Poll the status register of the FLASH to determine when it
* completes, by sending a read status command and receiving the
* status byte
* 通过发送读取状态命令并接收状态字节,轮询FLASH的状态寄存器以确定何时完成
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
sizeof(ReadStatusCmd));

/*
* If the status indicates the write is done, then stop waiting,
* if a value of 0xFF in the status byte is read from the
* device and this loop never exits, the device slave select is
* possibly incorrect such that the device status is not being
* read
* 如果状态指示写操作已经完成,那么停止等待,如果从设备读取状态字节中的0xFF值并且该循环永远不会退出,
* 则设备从属选择可能不正确,因此没有读取设备状态
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}
}

// 这个函数从连接到QSPI接口的串行FLASH中读取数据。
void FlashRead(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command)
{
/*
* Setup the write command with the specified address and data for the
* FLASH
* 使用指定的地址和数据为FLASH设置写命令
*/
WriteBuffer[COMMAND_OFFSET] = Command;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
(Command == QUAD_READ_CMD)) {
ByteCount += DUMMY_SIZE;
}
/*
* Send the read command to the FLASH to read the specified number
* of bytes from the FLASH, send the read command and address and
* receive the specified number of bytes of data in the data buffer
* 向FLASH发送read命令,从FLASH中读取指定字节数,发送read命令和地址,从数据缓冲区中接收指定字节数的数据
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, ReadBuffer,
ByteCount + OVERHEAD_SIZE);
}

// 此功能擦除连接到QSPI接口的串行FLASH中的扇区。
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;

/*
* If erase size is same as the total size of the flash, use bulk erase
* command
* 如果擦除大小等于flash的总大小,则使用bulk erase命令
*/
if (ByteCount == (NUM_SECTORS * SECTOR_SIZE)) {
/*
* Send the write enable command to the FLASH so that it can be
* written to, this needs to be sent as a seperate transfer
* before the erase
* 如果擦除大小与flash的总大小相同,则使用bulk erase命令发送write enable命令到flash,
* 以便写入,这需要在擦除之前作为单独的传输发送
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));

/* Setup the bulk erase command*/
WriteBuffer[COMMAND_OFFSET] = BULK_ERASE_CMD;

/*
* Send the bulk erase command; no receive buffer is specified
* since there is nothing to receive
* 发送批量擦除命令;没有指定接收缓冲区,因为没有要接收的东西
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
BULK_ERASE_SIZE);

/* Wait for the erase command to the FLASH to be completed*/
while (1) {
/*
* Poll the status register of the device to determine
* when it completes, by sending a read status command
* and receiving the status byte
* 通过发送读取状态命令和接收状态字节,轮询设备的状态寄存器以确定何时完成
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
FlashStatus,
sizeof(ReadStatusCmd));

/*
* If the status indicates the write is done, then stop
* waiting; if a value of 0xFF in the status byte is
* read from the device and this loop never exits, the
* device slave select is possibly incorrect such that
* the device status is not being read
* 如果状态显示写操作完成,则停止等待;如果从设备中读取状态字节中的0xFF值,
* 并且该循环从未退出,则设备从属选择可能不正确,因此没有读取设备状态
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}

return;
}

/*
* If the erase size is less than the total size of the flash, use
* sector erase command
* 如果擦除的大小小于flash的总大小,则使用扇区擦除命令
*/
for (Sector = 0; Sector < ((ByteCount / SECTOR_SIZE) + 1); Sector++) {
/*
* Send the write enable command to the SEEPOM so that it can be
* written to, this needs to be sent as a seperate transfer
* before the write
* 将写启用命令发送到SEEPOM,以便可以写入,这需要在写入之前作为单独的传输发送
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));

/*
* Setup the write command with the specified address and data
* for the FLASH
* 使用指定的地址和数据为FLASH设置写命令
*/
WriteBuffer[COMMAND_OFFSET] = SEC_ERASE_CMD;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)(Address >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)(Address >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

/*
* Send the sector erase command and address; no receive buffer
* is specified since there is nothing to receive
* 发送扇区擦除命令和地址; 没有指定接收缓冲区,因为没有要接收的东西
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
SEC_ERASE_SIZE);

/*
* Wait for the sector erse command to the
* FLASH to be completed
*/
while (1) {
/*
* Poll the status register of the device to determine
* when it completes, by sending a read status command
* and receiving the status byte
* 通过发送读取状态命令和接收状态字节,轮询设备的状态寄存器以确定何时完成
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
FlashStatus,
sizeof(ReadStatusCmd));

/*
* If the status indicates the write is done, then stop
* waiting, if a value of 0xFF in the status byte is
* read from the device and this loop never exits, the
* device slave select is possibly incorrect such that
* the device status is not being read
* 如果状态指示写操作已经完成,那么停止等待,如果从设备读取状态字节中的0xFF值
* 并且该循环永远不会退出,则设备从属选择可能不正确,因此没有读取设备状态
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}

Address += SECTOR_SIZE;
}
}

//该功能读取连接到SPI接口的串行FLASH ID
int FlashReadID(void)
{
/* Read ID in Auto mode.*/
WriteBuffer[COMMAND_OFFSET] = READ_ID;
WriteBuffer[ADDRESS_1_OFFSET] = 0x23; /* 3 dummy bytes */
WriteBuffer[ADDRESS_2_OFFSET] = 0x08;
WriteBuffer[ADDRESS_3_OFFSET] = 0x09;

XQspiPs_PolledTransfer(&QspiInstance, WriteBuffer, ReadBuffer,
RD_ID_SIZE);

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

return XST_SUCCESS;
}

//该功能在连接到SPI接口的串行闪存中启用QUAD模式。
void FlashQuadEnable(XQspiPs *QspiPtr)
{
u8 WriteEnableCmd = {WRITE_ENABLE_CMD};
u8 ReadStatusCmd[] = {READ_STATUS_CMD, 0};
u8 QuadEnableCmd[] = {WRITE_STATUS_CMD, 0};
u8 FlashStatus[2];


if (ReadBuffer[1] == 0x9D) {

XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
FlashStatus,
sizeof(ReadStatusCmd));

QuadEnableCmd[1] = FlashStatus[1] | 1 << 6;

XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));

XQspiPs_PolledTransfer(QspiPtr, QuadEnableCmd, NULL,
sizeof(QuadEnableCmd));
}
}
欢迎来到ssy的世界