本文介绍如何使用STM32标准外设库驱动FLASH,本例程驱动的FLASH为W25Q64。
本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。
FLASH存储器又称为闪存,为可重复擦写的存储器,容量比EEPROM大的多。
FLASH在写入数据时只能把1改成0,而0无法直接改成1,因此要写入数据时,必须先执行擦除操作,而一次擦除操作无法仅擦除一个字节,必须将一整块区域的数据全部改成1。因此FLASH操作的特性是擦除时必须一次擦除一整块区域;写入时可以按字节或按块写入;读取则不受限制,可以读取一个字节和任意多个字节。
FLASH可分为NOR FLASH和NAND FLASH,两者特性有所区别,NOR FLASH读取速度快、可以按字节读写,但容量相同的情况下价格较高,而NAND FLASH读取速度较慢,只能按块为单位读写,但容量相同的情况下价格较低。一般NOR FLASH适用于存储程序代码,NAND FLASH适用于存储大数据量存储。
一般常用的NOR FLASH为Winbond公司的W25Qxx系列,常用容量从16M到256Mbit不等,换算成字节为2M到32MBytes,可以根据项目需求和价格综合考虑选型。
以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个字节。
W25Q64厂商号为0xEF,FLASH型号为0x4017,可以读取这些信息判定FLASH是否正常。
该表中的第一列为指令名,第二列为指令编码,第三至第 N 列的具体内容根据指令的不同而有不同的含义。其中带括号的字节参数,方向为 FLASH 向主机传输,即命令响应,不带括号的则为主机向 FLASH 传输。表中“A0~A23”指 FLASH 芯片内部存储器组织的地址;“M0~M7”为厂商号(MANUFACTURER ID);“ID0-ID15”为 FLASH 芯片的ID;“dummy”指该处可为任意数据;“D0~D7”为 FLASH 内部存储矩阵的内容。
使用读取命令(指令编码为03h),发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回内部存储的数据,读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。
在向 FLASH 写入数据或者擦除前,首先要使能写操作,通过发送“Write Enable”命令使FLASH可写,写入或者擦除完毕之后,FLASH自动进入写禁用状态,无需单独发送写禁用命令,当FLASH为写禁用状态时,任何写入或擦除操作无效,这样可避免误写入或误擦除。
由于FLASH写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认 FLASH 芯片“空闲”时才能进行再次写入。为了表示自己的工作状态,FLASH 芯片定义了一个状态寄存器,这个状态寄存器的第 0 位为“BUSY”,当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在进行“擦除”或“数据写入”的操作。利用指令表中的“Read Status Register”指令可以获取 FLASH 状态寄存器的内容,只要向 FLASH 芯片发送了读状态寄存器的指令,FLASH 芯片就会持续向主机返回最新的状态寄存器内容,直到收到 SPI通讯的停止信号。因此可以通过查看该位,直到该位为0时,即可对FLASH进行擦除或者写操作。如果刚写完数据就执行读操作,也需要等待。
FLASH写入数据之前需要先擦除,擦除可分为扇区擦除(Sector Erase)、块擦除(Block Erase)和整片擦除(Chip Erase)。指令编码分别为20h、D8h,而整片擦除支持2个命令,即2个命令均可使用,为C7h和60h。要实现擦除操作时先发送指令编码,扇区擦除和块擦除需要继续发送要擦除区域的地址,而整片擦除无需发送地址。要执行擦除操作之前需要确保FLASH处于写使能状态,可通过发送写使能命令实现。
使用页写入命令(指令编码为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
原文:https://www.cnblogs.com/greatpumpkin/p/13747892.html