本文介绍如何使用STM32标准外设库驱动FLASH,本例程驱动的FLASH为W25Q64。
本文介绍如何使用STM32标准外设库驱动FLASH,本例程驱动的FLASH为W25Q64。
本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。
1. FLASH简介
FLASH存储器又称为闪存,为可重复擦写的存储器,容量比EEPROM大的多。
FLASH在写入数据时只能把1改成0,而0无法直接改成1,因此要写入数据时,必须先执行擦除操作,而一次擦除操作无法仅擦除一个字节,必须将一整块区域的数据全部改成1。因此FLASH操作的特性是擦除时必须一次擦除一整块区域;写入时可以按字节或按块写入;读取则不受限制,可以读取一个字节和任意多个字节。
FLASH可分为NOR FLASH和NAND FLASH,两者特性有所区别,NOR FLASH读取速度快、可以按字节读写,但容量相同的情况下价格较高,而NAND FLASH读取速度较慢,只能按块为单位读写,但容量相同的情况下价格较低。一般NOR FLASH适用于存储程序代码,NAND FLASH适用于存储大数据量存储。
2. 常用FLASH
一般常用的NOR FLASH为Winbond公司的W25Qxx系列,常用容量从16M到256Mbit不等,换算成字节为2M到32MBytes,可以根据项目需求和价格综合考虑选型。
3.FLASH操作说明
以W25Q64举例,W25Q64容量为64Mbit,即8MByte,地址范围0~0x800000,3个字节即可表示,因此地址长度为3字节。
W25Q64共分为128个Block,每个Block为64Kbytes,每个Block又分为16个Sector,每个Sector为4Kbytes,每个Sector又可分为16个Pages,每个Page有256个字节,每次擦除时至少需要擦除一整个Sector,写入时则可以单字节写入,也可以写入多个字节,但最多写入一个Page,即256个字节。
3.1. 设备ID
W25Q64厂商号为0xEF,FLASH型号为0x4017,可以读取这些信息判定FLASH是否正常。
3.2. 指令
该表中的第一列为指令名,第二列为指令编码,第三至第 N 列的具体内容根据指令的不同而有不同的含义。其中带括号的字节参数,方向为 FLASH 向主机传输,即命令响应,不带括号的则为主机向 FLASH 传输。表中“A0~A23”指 FLASH 芯片内部存储器组织的地址;“M0~M7”为厂商号(MANUFACTURER ID);“ID0-ID15”为 FLASH 芯片的ID;“dummy”指该处可为任意数据;“D0~D7”为 FLASH 内部存储矩阵的内容。
3.3. 读取
使用读取命令(指令编码为03h),发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回内部存储的数据,读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。
3.4. 写使能、写禁用和状态读取
在向 FLASH 写入数据或者擦除前,首先要使能写操作,通过发送“Write Enable”命令使FLASH可写,写入或者擦除完毕之后,FLASH自动进入写禁用状态,无需单独发送写禁用命令,当FLASH为写禁用状态时,任何写入或擦除操作无效,这样可避免误写入或误擦除。
由于FLASH写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认 FLASH 芯片“空闲”时才能进行再次写入。为了表示自己的工作状态,FLASH 芯片定义了一个状态寄存器,这个状态寄存器的第 0 位为“BUSY”,当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在进行“擦除”或“数据写入”的操作。利用指令表中的“Read Status Register”指令可以获取 FLASH 状态寄存器的内容,只要向 FLASH 芯片发送了读状态寄存器的指令,FLASH 芯片就会持续向主机返回最新的状态寄存器内容,直到收到 SPI通讯的停止信号。因此可以通过查看该位,直到该位为0时,即可对FLASH进行擦除或者写操作。如果刚写完数据就执行读操作,也需要等待。
3.5. 擦除
FLASH写入数据之前需要先擦除,擦除可分为扇区擦除(Sector Erase)、块擦除(Block Erase)和整片擦除(Chip Erase)。指令编码分别为20h、D8h,而整片擦除支持2个命令,即2个命令均可使用,为C7h和60h。要实现擦除操作时先发送指令编码,扇区擦除和块擦除需要继续发送要擦除区域的地址,而整片擦除无需发送地址。要执行擦除操作之前需要确保FLASH处于写使能状态,可通过发送写使能命令实现。
3.6. 写入
使用页写入命令(指令编码为02h),先发送指令编码,然后发送要写的起始地址,然后继续发送要写入的内容,一次写入操作最多写入256字节数据。 进行写入之前需要确保FLASH处于写使能状态,可通过发送写使能命令实现。如果想要一次写入超过256字节,那么就需要对页写入命令进行封装。
完整代码(仅自己编写的部分)
1 #include "flash.h"
2 #include "delay.h"
3 #include <stdio.h>
4
5 #define FLASH_PAGE_SIZE 256 //W25Q64每页256个字节
6
7 #define W25X_WriteEnable 0x06
8 #define W25X_WriteDisable 0x04
9 #define W25X_ReadStatusReg 0x05
10 #define W25X_WriteStatusReg 0x01
11 #define W25X_ReadData 0x03
12 #define W25X_FastReadData 0x0B
13 #define W25X_FastReadDual 0x3B
14 #define W25X_PageProgram 0x02
15 #define W25X_BlockErase 0xD8
16 #define W25X_SectorErase 0x20
17 #define W25X_ChipErase 0xC7
18 #define W25X_PowerDown 0xB9
19 #define W25X_ReleasePowerDown 0xAB
20 #define W25X_DeviceID 0xAB
21 #define W25X_ManufactDeviceID 0x90
22 #define W25X_JedecDeviceID 0x9F
23
24 /* WIP(busy)标志,FLASH内部正在写入 */
25 #define WIP_Flag 0x01
26
27 //初始化FLASH接口
28 void FLASH_Init(void)
29 {
30 SPI_IoInit();
31 }
32
33 //查看W25Q64是否空闲
34 //返回值: 1,FLASH忙,无法读写
35 // 0,FLASH空闲,可以读写
36 //注意执行完此函数后,FLASH已取消选中,如果要写入,必须重新选中
37 uint8_t FLASH_WaitReady(void)
38 {
39 uint32_t i = 0;
40 uint8_t ret = 1;
41 uint8_t status = 0;
42
43 SPI_CS_0;
44
45 SPI_WriteByte(W25X_ReadStatusReg);
46
47 for(i = 0; i < 1000; i++){
48 status = SPI_ReadByte();
49 if((status & WIP_Flag) == RESET){
50 ret = 0;
51 break;
52 }
53 delay_ms(10);
54 }
55
56 SPI_CS_1;
57
58 return ret;
59 }
60
61 /*
62 FLASH擦除、写入数据完毕后会自动禁用写使能,因此无需再执行写禁用操作
63 注意执行此函数前,必须先选中FLASH
64 */
65 void FLASH_WriteEnable(void)
66 {
67 /* 发送写使能命令*/
68 SPI_WriteByte(W25X_WriteEnable);
69 }
70
71 uint32_t FLASH_ReadJedecID(void)
72 {
73 uint32_t temp, temp0, temp1, temp2;
74
75 SPI_CS_0;
76
77 /* 发送JEDEC指令,读取ID */
78 SPI_WriteByte(W25X_JedecDeviceID);
79
80 temp0 = SPI_ReadByte();
81 temp1 = SPI_ReadByte();
82 temp2 = SPI_ReadByte();
83
84 /*把数据组合起来,作为函数的返回值*/
85 temp = (temp0 << 16) | (temp1 << 8) | temp2;
86
87 SPI_CS_1;
88
89 return temp;
90 }
91
92 uint8_t FLASH_SectorErase(uint32_t addr)
93 {
94 /* 判断FLASH是否可写,如果不可写,直接返回错误 */
95 if(FLASH_WaitReady()){
96 return 1;
97 }
98
99 SPI_CS_0;
100
101 /* 发送FLASH写使能命令 */
102 FLASH_WriteEnable();
103
104 /* 发送扇区擦除指令*/
105 SPI_WriteByte(W25X_SectorErase);
106 /*发送擦除扇区地址的高位*/
107 SPI_WriteByte((addr & 0xFF0000) >> 16);
108 /* 发送擦除扇区地址的中位 */
109 SPI_WriteByte((addr & 0xFF00) >> 8);
110 /* 发送擦除扇区地址的低位 */
111 SPI_WriteByte(addr & 0xFF);
112 /* 发送FLASH写禁用命令 */
113 // FLASH_WriteDisable();
114
115 SPI_CS_1;
116
117 if(FLASH_WaitReady()){
118 return 2;
119 }
120
121 return 0;
122 }
123
124 uint8_t FLASH_ChipErase(void)
125 {
126 /* 判断FLASH是否可写,如果不可写,直接返回错误 */
127 if(FLASH_WaitReady()){
128 return 1;
129 }
130
131 SPI_CS_0;
132
133 /* 发送FLASH写使能命令 */
134 FLASH_WriteEnable();
135
136 /* 发送扇区擦除指令*/
137 SPI_WriteByte(W25X_ChipErase);
138
139 SPI_CS_1;
140
141 if(FLASH_WaitReady()){
142 return 2;
143 }
144
145 return 0;
146 }
147
148 //在W25Q64里面的指定地址开始读出指定个数的数据
149 //addr: 开始读数的地址
150 //pBuffer: 需要读取数据的指针
151 //numToRead:要读出数据的个数
152 //返回值: 1,读取失败
153 // 0,读取成功
154 uint8_t FLASH_Read(uint32_t addr, uint8_t *pBuffer, uint32_t numToRead)
155 {
156 SPI_CS_0;
157
158 SPI_WriteByte(W25X_ReadData);
159
160 /* 发送 读 地址高位 */
161 SPI_WriteByte((addr & 0xFF0000) >> 16);
162 /* 发送 读 地址中位 */
163 SPI_WriteByte((addr & 0xFF00) >> 8);
164 /* 发送 读 地址低位 */
165 SPI_WriteByte(addr & 0xFF);
166
167 /* 读取数据 */
168 while (numToRead--) /* while there is data to be read */
169 {
170 /* 读取一个字节*/
171 *pBuffer++ = SPI_ReadByte();
172 }
173
174 SPI_CS_1;
175
176 return 0;
177 }
178
179 //在W25Q64指定地址读出一个数据
180 //addr: 开始读数的地址
181 //pReadData:需要读取数据的指针
182 //返回值: 1,读取失败
183 // 0,读取成功
184 uint8_t FLASH_ByteRead(uint32_t addr, uint8_t * pReadData)
185 {
186 SPI_CS_0;
187
188 SPI_WriteByte(W25X_ReadData);
189
190 /* 发送 读 地址高位 */
191 SPI_WriteByte((addr & 0xFF0000) >> 16);
192 /* 发送 读 地址中位 */
193 SPI_WriteByte((addr & 0xFF00) >> 8);
194 /* 发送 读 地址低位 */
195 SPI_WriteByte(addr & 0xFF);
196
197 /* 读取一个字节*/
198 *pReadData = SPI_ReadByte();
199
200 SPI_CS_1;
201
202 return 0;
203 }
204
205 //在W25Q64指定地址写入一个数据
206 //addr: 写入数据的目的地址
207 //dataToWrite: 要写入的数据
208 //返回值: 1,写入失败
209 // 0,写入成功
210 uint8_t FLASH_ByteWrite(uint32_t addr, uint8_t dataToWrite)
211 {
212 /* 判断FLASH是否可写,如果不可写,直接返回错误 */
213 if(FLASH_WaitReady()){
214 return 1;
215 }
216
217 SPI_CS_0;
218
219 /* 发送FLASH写使能命令 */
220 FLASH_WriteEnable();
221
222 /* 写页写指令*/
223 SPI_WriteByte(W25X_PageProgram);
224 /*发送写地址的高位*/
225 SPI_WriteByte((addr & 0xFF0000) >> 16);
226 /*发送写地址的中位*/
227 SPI_WriteByte((addr & 0xFF00) >> 8);
228 /*发送写地址的低位*/
229 SPI_WriteByte(addr & 0xFF);
230
231 /* 发送当前要写入的字节数据 */
232 SPI_WriteByte(dataToWrite);
233
234 SPI_CS_1;
235
236 if(FLASH_WaitReady()){
237 return 2;
238 }
239
240 return 0;
241 }
242
243 uint8_t FLASH_PageWrite(uint32_t addr, uint8_t *pBuffer, uint32_t numToWrite)
244 {
245 /* 判断FLASH是否可写,如果不可写,直接返回错误 */
246 if(FLASH_WaitReady()){
247 return 1;
248 }
249
250 SPI_CS_0;
251
252 /* 发送FLASH写使能命令 */
253 FLASH_WriteEnable();
254
255 /* 写页写指令*/
256 SPI_WriteByte(W25X_PageProgram);
257 /*发送写地址的高位*/
258 SPI_WriteByte((addr & 0xFF0000) >> 16);
259 /*发送写地址的中位*/
260 SPI_WriteByte((addr & 0xFF00) >> 8);
261 /*发送写地址的低位*/
262 SPI_WriteByte(addr & 0xFF);
263
264 /* 写入数据*/
265 while(numToWrite--)
266 {
267 /* 发送当前要写入的字节数据 */
268 SPI_WriteByte(*pBuffer++);
269 }
270
271 SPI_CS_1;
272
273 if(FLASH_WaitReady()){
274 return 2;
275 }
276
277 return 0;
278 }
279
280 /*
281 根据要写入的地址、长度、页大小计算如何分页
282 输入参数:addr: 写入起始地址
283 len: 写入数据长度
284 pageSize:每页存储的数据,对于W25Q64来说,该值为256
285 要写入参数:pFirstPageLen: 首页要写入的字节
286 pLastPageLen: 尾页要写入的字节
287 pPageNum: 总共要写入的页数
288 */
289 void FLASH_GetWritePages(uint32_t addr, uint32_t len, uint32_t pageSize,
290 uint32_t * pFirstPageLen, uint32_t * pLastPageLen, uint32_t * pPageNum)
291 {
292 uint32_t firstPageOffset; //首页偏移
293 uint32_t otherLen; //去除首页之后剩余长度
294 uint32_t otherPageNum; //去除首页之后剩余整数页数量
295
296 firstPageOffset = addr % pageSize;
297 *pFirstPageLen = pageSize - firstPageOffset;
298
299 if(len < *pFirstPageLen){
300 *pFirstPageLen = len;
301 }
302
303 otherLen = len - *pFirstPageLen;
304 otherPageNum = otherLen / pageSize;
305 *pLastPageLen = otherLen % pageSize;
306
307 *pPageNum = otherPageNum + 1;
308
309 if(*pLastPageLen){
310 (*pPageNum)++;
311 }
312 }
313
314
315 //在W25Q64里面的指定地址开始写入指定个数的数据
316 //addr: 开始读数的地址
317 //pBuffer: 需要读取数据的指针
318 //NumToWrite:要写入数据的个数
319 //返回值: 1,读取失败
320 // 0,读取成功
321 uint8_t FLASH_Write(uint32_t addr, uint8_t *pBuffer, uint32_t numToWrite)
322 {
323 uint32_t i;
324 uint32_t firstPageLen, lastPageLen, pageNum;
325
326 FLASH_GetWritePages(addr, numToWrite, FLASH_PAGE_SIZE,
327 &firstPageLen, &lastPageLen, &pageNum);
328
329 printf("addr:%#x, numToWrite:%d, firstPageLen:%d, lastPageLen:%d, pageNum:%d\n",
330 addr, numToWrite, firstPageLen, lastPageLen, pageNum);
331
332 for(i = 0; i < pageNum; i++)
333 {
334 if(i == 0){ //首页写入长度为firstPageLen
335 if(FLASH_PageWrite(addr, pBuffer, firstPageLen)){
336 goto write_fail;
337 }
338 addr += firstPageLen;
339 pBuffer += firstPageLen;
340 }else if(i == pageNum - 1){ //尾页写入长度为lastPageLen
341 if(FLASH_PageWrite(addr, pBuffer, lastPageLen)){
342 goto write_fail;
343 }
344 addr += lastPageLen;
345 pBuffer += lastPageLen;
346 }else{ //除首页和尾页外写入长度为FLASH_PAGE_SIZE
347 if(FLASH_PageWrite(addr, pBuffer, FLASH_PAGE_SIZE)){
348 goto write_fail;
349 }
350 addr += FLASH_PAGE_SIZE;
351 pBuffer += FLASH_PAGE_SIZE;
352 }
353 }
354
355 return 0;
356
357 write_fail:
358 return 1;
359 }