在嵌入式项目中,为了让设备在断电后某些关键参数不丢失,比如设备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不够大,这么一定义,很有可能会把栈里的其他有用数据给清掉,程序意外跑飞是很有可能的事。