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