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

STM32入門學(xué)習(xí)筆記之外置FLASH讀寫實(shí)驗(yàn)(下)
什么是Flash閃存以及STM32使用NAND Flash

Flash閃存技術(shù)是什么?創(chuàng)世SD NAND Flash又有何獨(dú)特之處?#嵌入式開(kāi)發(fā) #存儲(chǔ)芯片 #閃存
STM32學(xué)習(xí)筆記-Flash做為存儲(chǔ)器儲(chǔ)存數(shù)據(jù)
STM32F0x HAL庫(kù)學(xué)習(xí)筆記分享
flash_erase無(wú)法無(wú)錯(cuò)誤地擦除某些閃存頁(yè)面怎么解決?
Flash閃存有哪些類型,Flash閃存分類
你真的了解Flash閃存嗎?Flash閃存具備哪些類型?

STM32學(xué)習(xí)心得三十三:FLASH閃存編程原理與實(shí)驗(yàn)

HAL庫(kù)之讀寫STM32F103內(nèi)部的FLASH空間

STM32F103學(xué)習(xí)筆記——SPI讀寫Flash(三)

STM32F103學(xué)習(xí)筆記——SPI讀寫Flash(四)

評(píng)論