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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM