Flash存取資料的另一種思路
在嵌入式專案中,為了讓裝置在斷電後某些關鍵引數不丟失,比如裝置ID,網路配置,外設配置等。我們會將這些關鍵的引數儲存到片內的Flash中。一般的做法都是在Flash劃分一塊空間做儲存引數用,並且裡面有一個空間儲存一個標誌,這個標誌指示了Flash中是否儲存了有效的引數。在第一次燒錄程式時,Flash空間內沒有存過引數,上電後這個標誌讀出來就是0XFF,此時程式就會將預設引數寫入到Flash中,並且把標誌位設定為某個特定值寫入Flash中,這樣下次上電再從Flash中讀標誌就會發現不是0XFF,如果是特定值,說明FLASH中引數有效,我們就會把FLASH中的引數讀出來,拷貝給RAM中的全域性變數。當然,為了更可靠,我們一般還會加入CRC校驗。
#define PARA_FLASH_ADDR 0X80003000 typde struct{ uint8_t flag; uint32_t id; uint8_t baud; uint16t crc; }stPara_t; stPara_t gPara = {0}; uint8_t Flash_ParamRead(stPara_t* para) { FlashRead((uint8_t*)para,PARA_FLASH_ADDR,sizeof(stPara_t)); return 1; } uint8_t Flash_ParamWrite(stPara_t* para) { FlashWrite((uint8_t*)para,PARA_FLASH_ADDR,sizeof(stPara_t)); return 1; }
存取引數的時候
//取引數
Flash_ParamRead(&gPara);
//存引數
Flash_ParamWrite(&gPara);
我一般的常規操作都是像上面這樣。但是最近我遇到了另外一個方法。
#define STORAGE_ROM_BYTES (1024) #define STORAGE_MAX_BYTES (STORAGE_ROM_BYTES-4) typedef struct { unsigned short Head; unsigned char Data[STORAGE_MAX_BYTES]; unsigned short Crc; }stFlash, *stptrFlash; static const stParaConfig* ParaConfigPtr = NULL; eFlash_Err FlashGetParaRomPtr(unsigned int addr, void** data) { stptrFlash promtr = NULL; eFlash_Err err = FlashError; promtr = (stptrFlash)addr; if (promtr->Head == FLASH_HEAD) { if (crc_16_modbus(promtr->Data, STORAGE_MAX_BYTES) == promtr->Crc) { (*data) = promtr->Data; err = FlashNomal; } } if (err != FlashNomal) { if (promtr->Head == STORAGE_HEAD_FALSH_DEFAULT) { err = FlashFirstStart; } else { err = FlashError; } } return err; } eFlash_Err FlashParaSet(unsigned int addr, const void* data, int datalen) { eFlash_Err err = FlashError; stFlash stg = { 0,{0},0 }; unsigned char *data_tmp = NULL; err = FlashGetParaRomPtr(addr, (void**)&data_tmp); switch (err) { case FlashFirstStart: { //如果是first Start ,寫入初始值 stg.Head = STORAGE_HEAD; memset(stg.Data, 0, STORAGE_MAX_BYTES); memcpy(stg.Data, data, datalen); stg.Crc = crc_16_modbus(stg.Data, STORAGE_MAX_BYTES); Flash_WriteData(addr, (unsigned char*)&stg, STORAGE_ROM_BYTES); }break; case FlashNomal: { stg.Head = STORAGE_HEAD; memcpy(stg.Data, data, datalen); stg.Crc = crc_16_modbus(stg.Data, STORAGE_MAX_BYTES); Flash_WriteData(addr, (unsigned char*)&stg, STORAGE_ROM_BYTES); }break; case CfgDtFlashError: { //如果是first Start ,寫入初始值 stg.Head = STORAGE_HEAD; memset(stg.Data, 0, STORAGE_MAX_BYTES); memcpy(stg.Data, data, datalen); stg.Crc = crc_16_modbus(stg.Data, STORAGE_MAX_BYTES); Flash_WriteData(addr, (unsigned char*)&stg, STORAGE_ROM_BYTES); }break; default:return err; } return err; } int ParaConfigInit(void) { stParaConfig* pactr = NULL; eFlash_Err err = FlashError; stParaConfig cfg; ParaConfigPtr = NULL; memset(&cfg, 0, sizeof(stParaConfig)); err = FlashGetParaRomPtr(PARA_DATA_STORAGE_ADDR, (void**)&pactr); switch (err) { case FlashFirstStart: { FlashParaSet(PARA_DATA_STORAGE_ADDR, (const void*)&cfg,sizeof(stParaConfig)); ParaConfigPtr = &ParaConfigrationDef; rc = 1; } break; case FlashNomal: { ParaConfigPtr = pactr; rc = 1; } break; default: break; } return rc; }
全域性的引數定義是這句,居然只是個指標。
static const stParaConfig* ParaConfigPtr = NULL;
從Flash中把引數讀出來,呼叫的是ParaConfigInit函式,在這個函式中,定義了全域性引數資料型別的指標,然後把這個指標的地址傳遞給ParaConfigInit。
而在ParaConfigInit函式中,操作也是比較特別。首先定義了一個stFlash型別的結構體指標,然後就直接把引數儲存在Flash中的地址強轉成這個stFlash型別的指標賦值給它。然後就直接取值,看標誌位,如果標誌位對的話。注意了,直接把data的地址賦值給傳入的指標。
回到ParaConfigInit函式,讀到引數後,跳轉到FlashNomal分支。直接把剛剛獲得的指標賦值給全域性引數指標,引數讀取就完成了。需要用到全域性引數的時候就直接解引用,像這樣ParaConfigPtr ->id;
這是什麼操作呢,相當於是把引數的FLASH地址賦值給全域性引數指標,然後每次要用到全域性引數的時候,MCU就自動去flash中讀引數。這波操作也是6了。上面我一般的做法,是把FLash中的引數拷貝到RAM,用的時候都是用RAM中的那個引數。而下面這段程式碼,則沒有拷貝這一步,直接是讀引數的時候讓MCU去FLASH中讀。
好處顯而易見,這個引數一直在FLASH中,沒拷貝出來,不會佔用RAM的空間。壞處則是,無法對引數做直接的修改,因為全域性引數指向的地址是FLASH,對FLASH的讀,可以像對RAM的讀那樣操作,但是寫一般都是要遵循一定的步驟,可不像RAM那樣直接寫的。
關於這段程式,有一個地方我是難以苟同的,那就是stFlash這個結構體了,這個結構體的大小看得出來設計者是把它定位一個扇區的大小。在儲存引數的函式FlashParaSet中,直接定義了一個stFlash型別的結構體變數,然後把引數拷貝到這個變數的data欄位,把這個變數的地址傳遞給Flash_WriteData把整個變數寫入Flash。為什麼說這裡不好,因為一個stFlash結構體變數有可能很大,FLASH一個扇區的大小呢,假如MCU的RAM不夠大,這麼一定義,很有可能會把棧裡的其他有用資料給清掉,程式意外跑飛是很有可能的事。