STM32 使用 Flash 存儲數(shù)據(jù)時(shí)的一種管理辦法
以上三點(diǎn)對于實(shí)際使用時(shí)的影響,首先,寫數(shù)據(jù)必須以 16bit 為單位,很多 32bit 長度的值就不能直接使用類似 A = B 的賦值語句的方法去操作了,可以統(tǒng)一轉(zhuǎn)化為指向 16bit 無符號整形值的指針來處理。舉例,有一個(gè) 32bit 長度的 float 變量 v_float,要存入地址為 (FLASH_ADDRESS)的 flash中:
#define FLASH_ADDRESS (0x0803F800)
if(FLASH->CR &= FLASH_CR_LOCK) //解鎖flash
{
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);
while(FLASH->CR & FLASH_CR_LOCK);
}
while(FLASH->SR &= FLASH_SR_BSY);
FLASH->CR |= FLASH_CR_PG; //功能選擇,寫flash
{
*((unsigned short *)(FLASH_ADDRESS) + 0 ) = *((unsigned short *)(&v_float) + 0 );
*((unsigned short *)(FLASH_ADDRESS) + 1 ) = *((unsigned short *)(&v_float) + 1 );
}
FLASH->CR &= ~FLASH_CR_PG;
FLASH->CR |= FLASH_CR_LOCK; //鎖flash
如果需要讀取寫入到 flash 中的浮點(diǎn)數(shù)到 ram 中的變量 a_float,使用如下語句:
a_float = *((float *) FLASH_ADDRESS);
這樣,寫入和讀取就完成了,下面是擦除,按照參考手冊的流程來就可以了:
if(FLASH->CR &= FLASH_CR_LOCK) //解鎖flash
{
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);
while(FLASH->CR & FLASH_CR_LOCK);
}
while(FLASH->SR &= FLASH_SR_BSY); //Wait until flash not busy
FLASH->CR |= FLASH_CR_PER; //功能選擇,擦除頁
FLASH->AR = FLASH_ADDRESS; //寫入flash地址
FLASH->CR |= FLASH_CR_STRT; //開始擦除
while(FLASH->SR &= FLASH_SR_BSY); //等待
if(FLASH->SR &= FLASH_SR_EOP)
{
FLASH->SR |= FLASH_SR_EOP; //重置EOP
}
FLASH->CR &= ~FLASH_CR_PER;
FLASH->CR |= FLASH_CR_LOCK; //鎖flash
以上代碼會擦除 FLASH_ADDRESS 所在的整個(gè) flash 頁。
實(shí)現(xiàn)少量數(shù)據(jù)的讀寫和擦除操作以后,下一步要開始組織 flash 中存儲的數(shù)據(jù),這樣以后閱讀和修改都更為方便,我使用類似 stm32 官方寄存器配置文件的方式,用結(jié)構(gòu)體來組織。假設(shè)我要將某個(gè)人的個(gè)人信息儲存在 flash 中地址為(FLASH_ADDRESS)的位置,包括:姓名、性別、身高、體重,代碼如下:
typedef struct //構(gòu)造結(jié)構(gòu)體
{
unsigned char name[16];
unsigned char male;
float height;
float weight;
}Personal_Information_TypeDef;
#define FLASH_ADDRESS (0x0803F800) //flash地址
#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )
以上代碼在起始地址為(FLASH_ADDRESS)的 flash 中,定義了用于儲存?zhèn)€人信息的結(jié)構(gòu)體指針 Personal_Data,也就是說 Personal_Data 這個(gè)指針的值就是 FLASH_ADDRESS,只不過除了這個(gè)指針的值以外,我們還定義了這個(gè)指針?biāo)赶虻臄?shù)據(jù)的結(jié)構(gòu)。要讀出這些儲存于 flash 中的值,使用讀取結(jié)構(gòu)體指針的方式:
Temp_variable = Personal_Data -> male;
寫 flash 的時(shí)候,因?yàn)槊看沃荒軐?16bit,所以除了 short 類型以外,類似 int 和 float 這種 32bit 的數(shù)據(jù),都要取地址強(qiáng)制轉(zhuǎn)化為 16bit 類型后再取值最后寫入,方法類似一開始的 float 類型數(shù)據(jù)寫入 flash 的操作。我為了操作方便,在 ram 中建立了一個(gè)和 flash 內(nèi)結(jié)構(gòu)相同的結(jié)構(gòu)體,每次需要寫入 flash 的時(shí)候,就將 ram 中結(jié)構(gòu)體的所有值全部寫入 flash:
typedef struct //構(gòu)造結(jié)構(gòu)體
{
unsigned char name[16];
unsigned char male;
float height;
float weight;
}Personal_Information_TypeDef;
#define Length (5) //結(jié)構(gòu)體總長(16bit單位)
#define FLASH_ADDRESS (0x0803F800) //flash地址
#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )
Personal_Information_TypeDef Personal_Data_Mirror; //ram中的結(jié)構(gòu)體
/* 寫入過程 */
(unsigned short *)WriteAddress = (unsigned short *)Personal_Data;
(unsigned short *)ReadAddress = (unsigned short *)(&Personal_Data_Mirror);
if(FLASH->CR &= FLASH_CR_LOCK) //解鎖flash
{
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);
FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);
while(FLASH->CR & FLASH_CR_LOCK);
}
FLASH->CR |= FLASH_CR_PG; //功能選擇,寫入
while(Length > 0)
{
*WriteAddress = *ReadAddress; //Ram to Flash program, 16bit each
while(FLASH->SR &= FLASH_SR_BSY);
WriteAddress += 1;
ReadAddress += 1;
Length -= 1;
}
FLASH->CR &= ~FLASH_CR_PG; //Clear PG bit
FLASH->CR |= FLASH_CR_LOCK; //Lock flash
以上的寫入過程之前,必須確保要寫入的 flash 位置首先擦除過,或者說要保證要寫入數(shù)據(jù)的地方的值為0xFFFFFFFF,否則無法寫入,硬件會有標(biāo)志位來報(bào)錯(cuò)。
這樣以結(jié)構(gòu)體為單位擦寫 flash 的好處是,如果需要修改要儲存的數(shù)據(jù)數(shù)量或類型的話,只需要修改結(jié)構(gòu)體定義就可以了,而且用結(jié)構(gòu)體來管理變量,程序的可讀性較好。
最后就是 flash 的擦寫次數(shù)問題了,最少10k次的擦寫壽命,對于某些需要頻繁更新的內(nèi)容還是太少了,比EEPROM 通常的 100k 少了一個(gè)數(shù)量級,而且即使是改動(dòng)一個(gè)變量,也必須首先擦除整個(gè) flash 塊,更加速了 flash 的消耗。但是 stm32 的 flash 容量還是不錯(cuò)的,動(dòng)輒 256Kbytes,所以我們可以用容量換壽命,
具體思路就是不要在同一個(gè)地址重復(fù)擦寫,寫的時(shí)候不停的變換地址,寫滿以后再擦除。比如,需要儲存的結(jié)構(gòu)體長度為 20x16bit,那么一個(gè) 2Kbytes 的 flash 頁就可以儲存 50 個(gè)相同的結(jié)構(gòu)體,那么執(zhí)行完 50 次寫操作以后才需要執(zhí)行一次擦除操作,flash 的使用壽命隨之大為延長。還是以儲存一個(gè)結(jié)構(gòu)體為例說明如何實(shí)現(xiàn)這種儲存方式,首先定義結(jié)構(gòu)體,除了你需要儲存的數(shù)據(jù)以外,還要額外增加一個(gè)變量,用于識別你當(dāng)前讀寫的 flash 地址:
typedef struct //構(gòu)造結(jié)構(gòu)體
{
unsigned char flag; //用于識別當(dāng)前地址的標(biāo)記
unsigned char name[16];
unsigned char male;
float height;
float weight;
}Personal_Information_TypeDef;
#define Length (6) //結(jié)構(gòu)體總長(16bit單位)
#define FLASH_ADDRESS (0x0803F800) //flash地址
#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )
Personal_Information_TypeDef Personal_Data_Mirror; //ram中的結(jié)構(gòu)體
這部分除了結(jié)構(gòu)體中增加了一個(gè)標(biāo)記(flag)變量以外,其它部分相同,但是思想上,我們其實(shí)是在 flash 中定義了一個(gè)結(jié)構(gòu)體數(shù)組,只不過沒有使用通常的[]來遍歷數(shù)組變量,取而代之的是直接使用指針來操作。每次寫入時(shí),將 flag 變量固定寫為 0x00。需要讀取 flash 數(shù)據(jù)時(shí),就可以根據(jù)標(biāo)記變量 flag 的值找到最新的 flash 數(shù)據(jù)地址:
#define FLASH_ADDRESS_MAX; //最大偏移量,防止跨區(qū)塊操作
unsigned short FlashAddress_Offset = 0; //用于儲存flash地址偏移量的臨時(shí)變量
while( (Personal_Data + FlashAddress_Offset) -> flag == 0x00)
{
FlashAddress_Offset += 1;
if( (FlashAddress_Offset + FlashAddress_Offset) > MAX_OFFSET)
{
break;
}
}
找到寫有數(shù)據(jù)的 flash 地址以后,后繼的寫操作和讀操作和單個(gè)結(jié)構(gòu)體的操作相似,寫的地址變?yōu)椋?Personal_Data + FlashAddress_Offset)
讀的地址是:
(Personal_Data + FlashAddress_Offset - 1)
具體實(shí)現(xiàn)時(shí)要注意,結(jié)構(gòu)體的長度要算好,不能出現(xiàn)兩個(gè)結(jié)構(gòu)體交叉寫入;擦除 flash 需要時(shí)間,此間最好不進(jìn)行需要讀取 flash 的操作。
評論