1. 程式人生 > >Flash存取資料的另一種思路

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不夠大,這麼一定義,很有可能會把棧裡的其他有用資料給清掉,程式意外跑飛是很有可能的事。