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