认识QSPI
QSPI(Quad SPI)是Motorola公司在SPI接口基础上推出的扩展协议,通过增加两条数据线和队列传输机制,扩展了SPI的功能与应用场景,尤其在连接SPI Flash存储介质方面应用广泛。
SPI与QSPI的区别
普通SPI协议有多种扩展形式,如Dual SPI、QSPI等,三者在接口线序、数据传输方式及功能上存在显著差异:
普通SPI
- 接口线序:包含4根控制线,分别为CS(片选)、SCK(时钟)、MOSI(主机发送数据)、MISO(主机接收数据)。
- 传输方式:主机通过MOSI向从机发送数据,通过MISO接收从机数据,收发各使用1条数据线,为全双工通信。
Dual SPI(双线串行外设接口)
- 接口线序:同样为4根线,即CS、SCK、IO0、IO1。
- 传输方式:使用IO0和IO1两根数据线进行数据收发,而非SPI的单条收发线。在单向数据传输时,速度为普通SPI的2倍,但属于半双工通信。
QSPI(Queued SPI)
- 接口特性:作为SPI的增强版本,QSPI增加了队列传输机制,支持单、双或四条数据线连接SPI Flash存储介质,属于6线SPI(在原有基础上扩展数据线)。
- 核心优势:相比SPI和Dual SPI,应用范围更广泛,且通过队列传输机制提升了数据传输的灵活性与效率。
QSPI的工作模式
QSPI接口支持三种工作模式,以适配不同的应用场景:
- 间接模式:通过QSPI寄存器执行所有操作,需手动配置寄存器完成数据传输控制。
- 状态轮询模式:周期性读取外部Flash的状态寄存器,当特定标志位置1(如擦除或烧写完成)时,会触发中断,便于实时监控Flash操作状态。
- 内存映射模式:将外部Flash映射到微控制器的地址空间,系统可将其视为内部存储器,简化了对Flash的访问操作。
QSPI的扩展应用
在双闪存模式下,QSPI可同时访问两个Quad-SPI Flash,不仅使存储容量翻倍,还能将数据吞吐量提升至原来的2倍,适用于对存储容量和传输速度有较高要求的场景。
此外,QSPI支持一次性传输包含多达16个8位或16位数据的传输队列,启动传输后可自动完成队列内数据的连续传输,减少了主机的干预,进一步优化了通信效率。
地奇星 Qspi 功能框架
QSPI I/O pins
QSPI
程序编写
c
#include "app.h"
#include "uart/bsp_uart.h"
#include "qspi_flash/bsp_qspi_flash.h"
#include <stdlib.h>
// 定义Flash芯片型号的ID标识
#define FLASH_ID_W25Q32JV 0xEF6016 // W25Q32JV型号Flash的ID
#define FLASH_ID_W25Q64JV 0xEF6017 // W25Q64JV型号Flash的ID
#define TEST_SIZE 20 // 测试数据数量
/* 存储要写入Flash的整数数组 */
int wbuffer[TEST_SIZE] = {0};
/* 存放从Flash读取的数据的数组 */
int rbuffer[TEST_SIZE] = {0};
/**
* @brief 主运行函数,实现Flash的检测、数据写入和读取验证
* @note 流程包括:初始化硬件、检测Flash、生成测试数据、写入Flash、读取Flash、验证数据
* @retval 无
*/
void Run(void)
{
unsigned int FlashID = 0; // 用于存储读取到的Flash ID
uint16_t i; // 循环计数器
// 初始化UART9,用于调试信息输出
UART9_Init();
/* 初始化QSPI接口和Flash设备 */
W25Q_Init();
printf("欢迎使用立创·地奇星RA6E2开发板\r\n");
printf("接下来开始 QSPI_Flash 实验;\r\n");
// 读取Flash的ID信息,用于识别Flash型号
FlashID = Flash_ReadID();
// 输出Flash的制造商ID和设备ID(高位为制造商ID,低位为设备ID)
printf("FlashID:%x \r\n", (FlashID >> 8)); // 输出高8位的制造商ID
printf("FlashDeviceID:%x \r\n", FlashID & 0xff); // 输出低8位的设备ID
// 判断是否成功检测到Flash(0xFFFFFFFF表示未检测到有效设备)
if (FlashID != 0xFFFFFFFF)
{
// 根据读取到的ID判断Flash型号并输出
printf("检测到FLASH:%s \r\n",
(FlashID >> 8) == FLASH_ID_W25Q32JV ? "W25Q32JV" : "其他型号");
/* 生成要写入Flash的测试数据 */
for (i = 0; i < TEST_SIZE; i++)
{
// 生成0-255之间的随机数并存储到发送缓冲区
wbuffer[i] = (uint8_t)(rand() % 256);
}
/* 将发送缓冲区的数据写入到Flash中 */
// 第一个参数为写入地址,第二个参数为数据源,第三个参数为数据长度
W25Q_Write(TEST_SIZE, (void*)wbuffer, sizeof(wbuffer));
/* 从Flash中读取数据到接收缓冲区 */
// 第一个参数为读取地址,第二个参数为数据存储目标,第三个参数为读取长度
W25Q_Read(TEST_SIZE, (void*)rbuffer, sizeof(wbuffer));
// 输出写入和读取的数据,用于验证是否一致
printf("\r\n数据测试:\n\t");
for (i = 0; i < TEST_SIZE; i++)
{
printf("wbuffer[%d] = %d \t rbuffer[%d] = %d\r\n\t ",
i, wbuffer[i], i, rbuffer[i]);
}
printf("\r\n");
}
else
{
// 未检测到Flash设备时输出提示信息
printf("未检测到FLASH设备!!\r\n");
}
}
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
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
c
#ifndef APP_H_
#define APP_H_
void Run(void);
#endif
1
2
3
4
5
6
2
3
4
5
6
c
#include "bsp_qspi_flash.h"
/**
* @brief QSPI Flash初始化函数
* @note 初始化QSPI控制器,配置与Flash的通信参数
* @retval 无
*/
void W25Q_Init(void)
{
fsp_err_t err = R_QSPI_Open(&g_qspi0_flash_ctrl, g_qspi0_flash.p_cfg);
if (err != FSP_SUCCESS)
{
printf("QSPI启动错误\n");
}
}
/**
* @brief 读取Flash的ID信息
* @note 读取Flash的制造商ID和设备ID,用于识别Flash型号
* @retval 32位ID信息,包含制造商ID和设备ID;0xFFFFFFFF表示读取失败,高24位为JEDEC ID,低8位为设备ID
*/
uint32_t Flash_ReadID(void)
{
uint8_t cmd = 0x9F; // 读取JEDEC ID指令
uint8_t cmd_seq[4] = {0xAB, 0xFF, 0xFF, 0xFF}; // 读取设备ID的指令序列
uint8_t id_buf[3] = {0}; // 存储读取到的ID信息
uint8_t dev_id = 0; // 存储设备ID
// 发送读取JEDEC ID指令
fsp_err_t err = R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, &cmd, 1, true);
if (err != FSP_SUCCESS)
{
printf("ID指令发送失败\n");
return 0xFFFFFFFF;
}
// 读取3字节ID信息(制造商ID和设备ID高字节)
err = R_QSPI_DirectRead(&g_qspi0_flash_ctrl, id_buf, 3);
if (err != FSP_SUCCESS)
{
printf("ID读取失败\n");
return 0xFFFFFFFF;
}
// 发送读取设备ID的指令序列(0xAB为Release Power-Down/Device ID指令)
err = R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, cmd_seq, 4, true);
if (err != FSP_SUCCESS)
{
printf("设备ID指令失败\n");
return 0xFFFFFFFF;
}
// 读取1字节设备ID
err = R_QSPI_DirectRead(&g_qspi0_flash_ctrl, &dev_id, 1);
if (err != FSP_SUCCESS)
{
printf("设备ID读取失败\n");
return 0xFFFFFFFF;
}
// 组合32位ID返回(高24位为JEDEC ID,低8位为设备ID)
return (id_buf[0] << 24) | (id_buf[1] << 16) | (id_buf[2] << 8) | dev_id;
}
/**
* @brief 等待Flash操作完成
* @note 轮询Flash状态,直到当前操作(如擦除、写入)完成
* @retval FSP_SUCCESS表示操作完成;其他错误码表示失败
*/
static fsp_err_t W25Q_WaitReady(void)
{
spi_flash_status_t flash_stat; // Flash状态结构体
flash_stat.write_in_progress = true; // 初始化为操作中状态
// 循环等待,直到写入操作完成
while (flash_stat.write_in_progress)
{
// 获取Flash当前状态
fsp_err_t err = R_QSPI_StatusGet(&g_qspi0_flash_ctrl, &flash_stat);
if (err != FSP_SUCCESS)
{
return err;
}
}
return FSP_SUCCESS;
}
/**
* @brief 从Flash指定地址读取数据
* @param addr: 读取起始地址
* @param buf: 接收数据的缓冲区指针
* @param Size: 要读取的数据长度(字节)
* @retval 成功读取的字节数;0表示失败
*/
int W25Q_Read(uint32_t addr, uint8_t *buf, uint32_t Size)
{
// 参数合法性检查:缓冲区为空或长度为0则返回失败
if (buf == NULL || Size == 0)
{
printf("读取参数错误\n");
return 0;
}
// 计算实际地址
uint32_t real_addr = addr + QSPI_START_ADDR;
// 读取指令帧(0x03为Read Data指令,后3字节为地址)
uint8_t read_cmd[4] = {0x03, 0x00, 0x00, 0x00};
// 填充地址到指令帧
read_cmd[1] = (real_addr >> 16) & 0xFF;
read_cmd[2] = (real_addr >> 8) & 0xFF;
read_cmd[3] = real_addr & 0xFF;
// 等待Flash就绪
if (W25Q_WaitReady() != FSP_SUCCESS)
return 0;
// 发送读取指令
fsp_err_t err = R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, read_cmd, 4, true);
if (err != FSP_SUCCESS)
{
printf("读指令发送失败\n");
return 0;
}
// 读取数据到缓冲区
err = R_QSPI_DirectRead(&g_qspi0_flash_ctrl, buf, Size);
if (err != FSP_SUCCESS)
{
printf("数据读取失败\n");
return 0;
}
// 返回成功读取的字节数
return (int)Size;
}
/**
* @brief 擦除Flash中需要写入数据的区域
* @note 基于Flash特性,写入前需要先擦除对应扇区(4096字节/扇区)
* @param addr: 起始地址
* @param Size: 数据长度
* @retval FSP_SUCCESS表示成功;其他错误码表示失败
*/
static fsp_err_t W25Q_Clean_Page(uint32_t addr, uint32_t Size)
{
// 计算需要擦除的扇区数量(向上取整)
uint32_t sec_count = (Size + addr % 4096) / 4096 + 1;
uint32_t curr_sec = addr; // 当前要擦除的扇区地址
// 逐个擦除扇区
while (sec_count--)
{
// 擦除指定扇区(4096字节)
fsp_err_t err = R_QSPI_Erase(&g_qspi0_flash_ctrl,
(uint8_t*)(QSPI_START_ADDR + curr_sec),
4096);
if (err != FSP_SUCCESS)
{
printf("扇区0x%08lX擦除失败\n", curr_sec);
return 0;
}
// 等待擦除完成
if (W25Q_WaitReady() != FSP_SUCCESS)
return 0;
// 移动到下一个扇区
curr_sec += 4096;
}
return FSP_SUCCESS;
}
/**
* @brief 向Flash指定地址写入数据
* @note 支持跨页写入,自动处理页边界;写入前会先擦除对应扇区
* @param addr: 写入起始地址(相对于Flash内部地址)
* @param data: 要写入的数据缓冲区指针
* @param Size: 要写入的数据长度(字节)
* @retval 成功写入的字节数;0表示失败
*/
int W25Q_Write(uint32_t addr, uint8_t *data, uint32_t Size)
{
fsp_err_t err;
// 计算需要写入的页数(256字节/页),向上取整
uint32_t dwPageCount = (Size + addr % 256) / 256 + 1;
uint32_t curr_addr = addr; // 当前写入地址
uint32_t P_Size = Size; // 剩余写入长度
uint8_t *p_data = data; // 当前数据指针
// 参数合法性检查:数据为空或长度为0则返回失败
if (data == NULL || Size == 0)
{
printf("写入参数错误\n");
return 0;
}
// 擦除需要写入的区域
W25Q_Clean_Page(curr_addr, P_Size);
// 等待擦除完成
if (W25Q_WaitReady() != FSP_SUCCESS)
return 0;
// 单页写入(数据不跨页)
if (dwPageCount == 1)
{
err = R_QSPI_Write(&g_qspi0_flash_ctrl,
(uint8_t*)p_data,
(uint8_t*)(QSPI_START_ADDR + curr_addr),
P_Size);
if (err != FSP_SUCCESS)
{
printf("单页写入失败\n");
return 0;
}
}
// 多页写入(数据跨页)
else
{
// 计算第一页可写入的字节数
unsigned int first_Size = 256 - (addr % 256);
// 写入第一页数据
err = R_QSPI_Write(&g_qspi0_flash_ctrl,
(uint8_t*)p_data,
(uint8_t*)(QSPI_START_ADDR + curr_addr),
first_Size);
if (err != FSP_SUCCESS)
{
printf("首页写入失败\n");
return 0;
}
// 等待第一页写入完成
if (W25Q_WaitReady() != FSP_SUCCESS)
return 0;
// 更新地址和数据指针
curr_addr += first_Size;
p_data += first_Size;
// 写入剩余数据(按页处理)
for (uint32_t remain = P_Size - first_Size; remain > 0; remain -= 256) {
// 计算当前页要写入的字节数(不超过一页)
uint32_t write_Size = (remain > 256) ? 256 : remain;
// 写入当前页数据
R_QSPI_Write(&g_qspi0_flash_ctrl,
p_data,
(uint8_t*)(QSPI_START_ADDR + curr_addr),
write_Size);
// 等待当前页写入完成
if (W25Q_WaitReady() != FSP_SUCCESS)
return 0;
// 更新地址和数据指针
curr_addr += write_Size;
p_data += write_Size;
}
}
// 返回成功写入的字节数
return (int)P_Size;
}
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
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
c
#ifndef BSP_QSPI_FLASH_H_
#define BSP_QSPI_FLASH_H_
#include "hal_data.h"
#include <stdio.h>
#define QSPI_START_ADDR 0x60000000
void W25Q_Init(void);
uint32_t Flash_ReadID(void);
int W25Q_Read(uint32_t addr, uint8_t *buf, uint32_t Size);
int W25Q_Write(uint32_t addr, uint8_t *data, uint32_t Size);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
c
#include "hal_data.h"
#include "uart\bsp_uart.h"
#include "Apply/app.h"
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
Run();
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
实验现象
实物连接
测试结果