STM32之FLASH驱动

本文介绍如何使用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 }

 

 

源码下载:(不包括工程文件和库文件)

https://files.cnblogs.com/files/greatpumpkin/SPI_soft.rar

posted @ 2020-09-29 08:05  心灵航帆  阅读(2873)  评论(0编辑  收藏  举报