一、Flash簡介
快閃存儲器(flash memory),是一種電子式可清除程序化只讀存儲器的形式,允許在操作中被多次擦或寫的存儲器。它是一種非易失性存儲器,即斷電數據也不會丟失。
二、STM32F1的Flash
STM32F103ZET6的Flash大小為512KB,屬于大容量產品。在中文參考手冊中給出了大容量產品的Flash模塊組織結構圖
大容量產品Flsh模塊組織結構圖
系統存儲器中存儲的是啟動程序代碼。啟動程序就是串口下載的代碼。當Boot0接VCC,Boot1接GND時,運行的就是系統存儲器中的代碼。系統存儲器中存儲的啟動代碼,是ST公司在芯片出廠時就已經下載好的,用戶無法修改。選擇字節是用來配置寫保護和杜保護功能。
在執行閃存寫操作時,任何對閃存的讀操作都會被鎖住。只有對閃存的寫操作結束后,讀操作才能夠正常執行。也就是說,在對閃存進行寫操作或者擦除操作時,無法對閃存進行讀操作。
三、Flash操作步驟
? 解鎖和鎖定
? 寫/擦除操作
? 獲取Flash狀態
? 等待操作完成
? 讀取Flash指定地址數據
四、程序設計
操作內部Flash時,最小單位是半字(16位)。
44.1 讀取數據
讀取數據用的是指針的方式,在之前博主的文章中有關于如何利用指針在指定地址讀寫數據的操作。 ```c /*
*============================================================================== *函數名稱:Med_Flash_ReadHalfWord *函數功能:讀取指定地址的半字(16位數據) *輸入參數:faddr:讀取地址 *返回值:對應讀取地址數據 *備 注:對內部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數 *============================================================================== */ vu16 Med_Flash_ReadHalfWord (u32 faddr) { return (vu16)faddr; }
```c
/*
*==============================================================================
*函數名稱:Med_Flash_Read
*函數功能:從指定地址開始讀出指定長度的數據
*輸入參數:ReadAddr:讀取起始地址;pBuffer:數據指針;
NumToRead:讀取(半字)數
*返回值:無
*備 注:對內部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數
*==============================================================================
*/
void Med_Flash_Read (u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i = 0;i < NumToRead;i ++)
{
pBuffer[i] = Med_Flash_ReadHalfWord(ReadAddr); // 讀取2個字節.
ReadAddr += 2; // 偏移2個字節.
}
}
4.2 寫入數據(不檢查)
這里的不檢查,是指在寫入之前,不檢查寫入地址是否可寫。
/*
*==============================================================================
*函數名稱:Med_Flash_Write_NoCheck
*函數功能:不檢查的寫入
*輸入參數:WriteAddr:寫入起始地址;pBuffer:數據指針;
NumToWrite:寫入(半字)數
*返回值:無
*備 注:對內部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數
*==============================================================================
*/
void Med_Flash_Write_NoCheck (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i = 0;i < NumToWrite;i ++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
WriteAddr += 2; // 地址增加2.
}
}
4.3 寫入數據(檢查)
/*
*==============================================================================
*函數名稱:Med_Flash_Read
*函數功能:從指定地址開始寫入指定長度的數據
*輸入參數:WriteAddr:寫入起始地址;pBuffer:數據指針;
NumToRead:寫入(半字)數
*返回值:無
*備 注:對內部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數
*==============================================================================
*/
// 根據中文參考手冊,大容量產品的每一頁是2K字節
#if STM32_FLASH_SIZE < 256
#define STM32_SECTOR_SIZE 1024 // 字節
#else
#define STM32_SECTOR_SIZE 2048
#endif
// 一個扇區的內存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];
void Med_Flash_Write (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; // 扇區地址
u16 secoff; // 扇區內偏移地址(16位字計算)
u16 secremain; // 扇區內剩余地址(16位計算)
u16 i;
u32 offaddr; // 去掉0X08000000后的地址
// 判斷寫入地址是否在合法范圍內
if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
{
return; // 非法地址
}
FLASH_Unlock(); // 解鎖
offaddr = WriteAddr - STM32_FLASH_BASE; // 實際偏移地址
secpos = offaddr / STM32_SECTOR_SIZE; // 扇區地址
secoff = (offaddr % STM32_SECTOR_SIZE) / 2; // 在扇區內的偏移(2個字節為基本單位)
secremain = STM32_SECTOR_SIZE / 2 - secoff; // 扇區剩余空間大小
if (NumToWrite <= secremain)
{
secremain = NumToWrite; // 不大于該扇區范圍
}
while (1)
{
// 讀出整個扇區的內容
Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
// 校驗數據
for (i = 0;i < secremain;i ++)
{
// 需要擦除
if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
{
break;
}
}
// 需要擦除
if (i < secremain)
{
FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE); // 擦除這個扇區
// 復制
for (i = 0;i < secremain;i ++)
{
STM32_FLASH_BUF[i + secoff] = pBuffer[i];
}
// 寫入整個扇區
Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
}
else
{
// 寫已經擦除了的,直接寫入扇區剩余區間
Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
}
if (NumToWrite == secremain)
{
break; // 寫入結束了
}
// 寫入未結束
else
{
secpos ++; // 扇區地址增1
secoff=0; // 偏移位置為0
pBuffer+=secremain; // 指針偏移
WriteAddr+=secremain; // 寫地址偏移
NumToWrite-=secremain; // 字節(16位)數遞減
if (NumToWrite >(STM32_SECTOR_SIZE/2))
{
secremain=STM32_SECTOR_SIZE/2; // 下一個扇區還是寫不完
}
else
{
secremain=NumToWrite; // 下一個扇區可以寫完了
}
}
}
FLASH_Lock(); // 上鎖
}
宏定義如下
// STM32的Flash容量,單位為KB
#define STM32_FLASH_SIZE 512
// FLASH主存儲塊起始地址
#define STM32_FLASH_BASE 0x08000000
上面的讀取數據和不檢查的寫入都比較簡單,因此并沒有再做分析。這里分析一下帶檢查的寫入的程序設計思路。
- ? 首先用一小段條件編譯來區分一下大容量產品和其他產品。因為大容量產品的一頁(一個扇區)是2K字節,中小容量產品的一頁是1K字節。定一個了一個數組,數組大小是一個扇區的大小。
// 根據中文參考手冊,大容量產品的每一頁是2K字節
#if STM32_FLASH_SIZE < 256
#define STM32_SECTOR_SIZE 1024 // 字節
#else
#define STM32_SECTOR_SIZE 2048
#endif
// 一個扇區的內存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];
大容量產品,一個扇區2K字節,除以2是因為在對內部Flash操作時,最小單位是半字。
- ? 接下來,判斷要寫入的地址是否合法,也就是是否在主存儲塊地址范圍內。
// 判斷寫入地址是否在合法范圍內
if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
{
return; // 非法地址
}
- ? 如果要寫入的地址合法,那么解鎖后計算一些參數值。
offaddr = WriteAddr - STM32_FLASH_BASE; // 實際偏移地址
實際偏移地址 ,指的是要寫入的地址與主存儲塊基地址(0x0800 0000)的差值。
secpos = offaddr / STM32_SECTOR_SIZE; // 扇區地址
扇區地址指的是要寫入的地址所在扇區前面的扇區數。由于所有的參數都不是浮點型,因此在做除法時,小數位都是0。最終除出來的結果就是當前扇區前面的扇區數。
secoff = (offaddr % STM32_SECTOR_SIZE) / 2; // 在扇區內的偏移(2個字節為基本單位)
在扇區內的偏移指的是要寫入的地址與其所在扇區首地址的差值。用要寫入的地址取余每一個扇區的字節數,余數就是偏移地址。但是由于操作內部Flash時的最小單位是半字,因此要除以2。
secremain = STM32_SECTOR_SIZE / 2 - secoff; // 扇區剩余空間大小
扇區內剩余空間大小只需要用扇區總的空間大小減去偏移地址即可得到。但是需要注意的是,單位都是半字。這里的剩余空間大小,并不是真正的剩余空間大小。而是指寫入地址后面的扇區大小。這里不太好理解,畫一個圖表示一下
。
扇區內剩余空間大小示意圖
正是因為這里的扇區剩余空間大小并不是指真正的剩余空間大小。在剩余空間內,也可能存在已經寫入數據的地址。所以后面需要進行判斷,來確定是否需要擦除。
- ? 判斷在寫入地址所在扇區能否將寫入內容全部寫入完成
if (NumToWrite <= secremain)
{
secremain = NumToWrite; // 不大于該扇區范圍
}
如果可以,直接將要寫入的半字數賦值給當前扇區剩余空間大小。如果當前扇區剩余空間大小可以容納要寫入的半字數,那么只需要寫入一次即可,在后續判斷是否寫完時,直接通過,while循環只執行一次。
- ? 讀出整個扇區內容,判斷是否需要擦除
// 讀出整個扇區的內容
Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
// 校驗數據
for (i = 0;i < secremain;i ++)
{
// 需要擦除
if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
{
break;
}
}
要對內部Flash某個地址寫入數據時,需要確保該地址數值為0xFFFF。
判斷方法就是從扇區內的偏移開始,利用for循環判斷讀出地扇區剩余空間內,是否存在已經被寫入內容的地址。for循環找到i的值,i加上在扇區內的偏移加1之后的空間,才是真正的扇區剩余空間大小。
for循環結束后,判斷是否需要進行擦除
// 需要擦除
if (i < secremain)
{
FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE); // 擦除這個扇區
// 復制
for (i = 0;i < secremain;i ++)
{
STM32_FLASH_BUF[i + secoff] = pBuffer[i];
}
// 寫入整個扇區
Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
}
else
{
// 寫已經擦除了的,直接寫入扇區剩余區間
Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
}
擦除時,最小單元為一個扇區。在大容量產品中,也就是2048字節。
? 最后,將需要寫入的數據,寫入到對應位置。如果是需要擦除的情況,寫入時是先將原來的內容提取出來,然后在后面填充上需要寫入的內容,擦除整個扇區之后再一起寫入。如果是不需要擦除的情況,直接寫入即可。
五、注意事項
在操作Flash時,注意不要對代碼區內容進行擦寫。如果擦寫的地址在代碼區,會導致程序運行異常。那么如何確保我們操作的地址不是在代碼區?這就需要我們知道我們的代碼所占的內存是多少。在Keil5編譯完成后,會顯示下面的內容
keil5編譯后提示
- ? Code 程序所占用的內存大小(存放在Flash中)
- ? RO-data 程序定義的常量所占內存大小(存放在Flash中)
- ? RW-data 已被初始化的全局變量所占內存大小(在程序初始化的時候,RW-data會從FLASH中拷貝到RAM中)
ZI-data 未被初始化的全局變量所占內存大小(存放在RAM中)
最后,計算程序代碼所占Flash空間。flash = Code + RO-data + RW-data
。
-
RAM
+關注
關注
8文章
1367瀏覽量
114531 -
STM32
+關注
關注
2266文章
10871瀏覽量
354806 -
FLASH閃存
+關注
關注
0文章
7瀏覽量
7603 -
快閃存儲器
+關注
關注
0文章
15瀏覽量
11158 -
for循環
+關注
關注
0文章
61瀏覽量
2493
發布評論請先 登錄
相關推薦
評論