• 正文
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

老板說(shuō),單片機(jī),F(xiàn)lash模擬EEPROM,16字節(jié),算法輪詢存儲(chǔ)給我做到100萬(wàn)次的存儲(chǔ)次數(shù)

05/09 12:30
378
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

單片機(jī)開(kāi)發(fā)中,數(shù)據(jù)存儲(chǔ)是一個(gè)繞不開(kāi)的話題。EEPROM因其非易失性存儲(chǔ)特性,常用于保存配置參數(shù)等數(shù)據(jù)。

然而,EEPROM的擦寫(xiě)次數(shù)通常有限,以STM32為例,STM32L0、STM32L4自帶的EEPROM一般10萬(wàn)次左右,而很多單片機(jī)并不內(nèi)置EEPROM,這時(shí)候,利用單片機(jī)的FLASH存儲(chǔ)器來(lái)模擬EEPROM就成為了一個(gè)高性價(jià)比的解決方案,但,F(xiàn)LASH的擦寫(xiě)次數(shù)一般在1萬(wàn)次左右,這個(gè)我們可以通過(guò)ST的官方數(shù)據(jù)手冊(cè)看到:

1萬(wàn)次,在很多場(chǎng)景下并不夠。

今天,老宇哥跟大家一起探討,如何用單片機(jī)FLASH模擬EEPROM,并且通過(guò)算法優(yōu)化實(shí)現(xiàn)高達(dá)100萬(wàn)次以上的存儲(chǔ)次數(shù)!

我們都知道,獨(dú)立的EEPROM芯片是可以直接寫(xiě)字節(jié)的,即使覆蓋寫(xiě)也無(wú)須擦除,單片機(jī)的FLASH不同,STM32必須按頁(yè)來(lái)擦除。

手頭剛好有個(gè)STM32G071RBT6的開(kāi)發(fā)板,就以這個(gè)芯片做測(cè)試,后續(xù)可以很方便的移植到其它芯片上。

要求是,程序一共有16個(gè)字節(jié)的內(nèi)容需要斷電保存,每改變其中一個(gè)字節(jié)就需要保存一次,做到100萬(wàn)次的一個(gè)存儲(chǔ)次數(shù)。

我們的核心存儲(chǔ)算法是輪詢存儲(chǔ),STM32G071RBT6的FLASH一共128KB,從0x08000000到0x0801FFFF,一共64頁(yè),每頁(yè)2KB。

我們的數(shù)據(jù)就存儲(chǔ)到最后一頁(yè),也就是0x0801F800到0x0801FFFF。

Flash主要是擦寫(xiě)次數(shù)受限,所以我們的思想是,一共16個(gè)字節(jié),第一次就寫(xiě)入前16個(gè)字節(jié),然后更新寫(xiě)入地址索引到第17個(gè)字節(jié),下一次就寫(xiě)入17到32個(gè)字節(jié),繼續(xù)更新寫(xiě)入地址索引到33,以此類推。

這里還需要做的一個(gè)就是重新上電的時(shí)候,需要找到最新的索引地址,也就是如果已經(jīng)寫(xiě)入了兩次,需要自動(dòng)找到索引地址為第33個(gè)字節(jié),這個(gè)也是核心。

故,一頁(yè)寫(xiě)滿可以存儲(chǔ)2048/16=128次,寫(xiě)滿一頁(yè)擦除一次,也就是理論存儲(chǔ)次數(shù)能達(dá)到128 × 1萬(wàn)次/頁(yè) = 128萬(wàn)次。

下面上代碼,頭文件flash.h

#ifndef FLASH__H
#define FLASH__H

#include "stm32g0xx_hal.h"
#include <string.h>

// FLASH配置
#define FLASH_BASE_ADDR 0x0801F800 // FLASH最后一頁(yè)起始地址 (128KB - 2KB)
#define PAGE_SIZE 2048 ? ? ? ? ? ? // STM32G071頁(yè)面大小為2KB
#define STATE_SIZE 16 ? ? ? ? ? ? // 結(jié)構(gòu)體大小(填充到24字節(jié))

typedef struct {
? ? unsigned int ?color; ? ? ? ? ? ? ? ? ?// 顏色
? ? unsigned int ?seconds; ? ? ? ? ? ? ? ?// 秒數(shù)
? ? unsigned char mode; ? ? ? ? ? ? ? ? ? // 模式
? ? unsigned char number; ? ? ? ? ? ? ? ? // 序號(hào)
? ? unsigned char padinng[5]; ? ? ? ? ? ? // 預(yù)留5
?unsigned char checksum; ? ? ? ? ? ? ? // 1字節(jié),校驗(yàn)和
}dataState;

extern dataState old_state;
extern dataState current_state;
void printState(dataState *state);
HAL_StatusTypeDef flash_program(unsigned int addr, unsigned char* data, unsigned int len);
void read_flash(unsigned int addr, unsigned char* data, unsigned int len);
void init_flash_addr(void);
void save_state(dataState* state);
void get_state(dataState* state);
void update_state(dataState* state);

#endif

頭文件中定義了一個(gè)結(jié)構(gòu)體,簡(jiǎn)單幾個(gè)宏定義與函數(shù)聲明,這里的結(jié)構(gòu)體我們?cè)黾恿艘粋€(gè)字節(jié)的校驗(yàn)。

接下來(lái)看flash.c

// 初始化:查找最新有效數(shù)據(jù)
void init_flash_addr(void) {
? ? dataState temp_state;
? ? uint32_t addr = FLASH_BASE_ADDR;
? ? uint32_t last_valid_addr = FLASH_BASE_ADDR;
? ? int found_valid_data = 0;

? ??while?(addr < FLASH_BASE_ADDR + PAGE_SIZE) {
? ? ? ? read_flash(addr, (uint8_t*)&temp_state, STATE_SIZE);

? ? ? ? // 檢查是否全0xFF
? ? ? ? uint8_t all_ff[STATE_SIZE];
? ? ? ? memset(all_ff, 0xFF, STATE_SIZE);
? ? ? ? int is_all_ff = (memcmp(&temp_state, all_ff, STATE_SIZE) == 0);

? ? ? ??if?(!is_all_ff && temp_state.checksum == calculate_checksum(&temp_state)) {
? ? ? ? ? ? last_valid_addr = addr;
? ? ? ? ? ? found_valid_data = 1;
? ? ? ? ? ? memcpy(&current_state, &temp_state, STATE_SIZE); ? ? ? ?
? ? ? ? }?else?{ ? ??
? ? ? ? ??break;
? ? ? ? }
? ? ? ? addr += STATE_SIZE;
? ? }

? ? flash_addr = last_valid_addr + (found_valid_data ? STATE_SIZE : 0);

? ??if(found_valid_data)
? ? ? ?printf("init first,found valid data,last_valid_addr:%Xrn",last_valid_addr);
? ??else
? ? ? ?printf("init first,it is all ff,it is the first datarn");
? ? ?
? ??if?(flash_addr > FLASH_BASE_ADDR + PAGE_SIZE) ? ? {
? ? ? ??printf("init erase page 2KBrn");
? ? ? ? erase_page(FLASH_BASE_ADDR);
? ? ? ? flash_addr = FLASH_BASE_ADDR;
? ? }

? ??if?(!found_valid_data) {
? ? ? ??printf("not found valid datarn");
? ? ? ? current_state.color = 100;
? ? ? ? current_state.seconds = 200;
? ? ? ? current_state.mode = 1;
? ? ? ? current_state.number = 1;
? ? ? ? current_state.checksum = calculate_checksum(&current_state);
? ? ? ? __disable_irq();?
? ? ? ? flash_program(FLASH_BASE_ADDR, (uint8_t*)&current_state, STATE_SIZE);
? ? ? ? __enable_irq();?
? ? ? ? flash_addr = FLASH_BASE_ADDR + STATE_SIZE; ? ? ??
? ? }
? ? printState(&current_state);
}

第一步,先從第一個(gè)地址讀取第一個(gè)16字節(jié),然后判斷是不是全部等于0xFF,如果第一次是就證明是第一次,下一步flash_addr就不需要增加STATE_SIZE,寫(xiě)入地址索引就是FLASH_BASE_ADDR。

第二步,如果不全是0xFF并且校驗(yàn)字節(jié)通過(guò),證明這是一組有效數(shù)據(jù),我們先將此數(shù)據(jù)更新到current_state,但是這里還不能證明是最后一組有效數(shù)據(jù),因?yàn)樽詈笠唤M有效數(shù)據(jù)才是我們要找到的數(shù)據(jù)。

就繼續(xù)檢查下一組數(shù)據(jù),直到檢查到一組數(shù)據(jù)是全0xFF,證明上一組數(shù)據(jù)就是最后一組有效數(shù)據(jù),就跳出,此時(shí)我們也就找到了最后一組有效數(shù)據(jù)的起始地址。

接著就此地址增加STATE_SIZE就是最新可以存儲(chǔ)數(shù)據(jù)的地址索引了。

如果flash_addr超出了空間,需要復(fù)位擦除一下,正常應(yīng)該不會(huì)到這一步。

第三步,如果沒(méi)找到有效數(shù)據(jù),證明是第一次,就寫(xiě)入默認(rèn)數(shù)值并保存,更新索引。

以上,上電的時(shí)候最新的寫(xiě)地址索引就找好了。

接下來(lái)是保存數(shù)據(jù)save_state函數(shù):

// 保存狀態(tài)到FLASH

void save_state(dataState* state) {
? ? dataState last_state;

? ??if?(flash_addr > FLASH_BASE_ADDR) {
? ? ? ? read_flash(flash_addr - STATE_SIZE, (uint8_t*)&last_state, STATE_SIZE);
? ? ? ??if?(memcmp(&last_state, state, STATE_SIZE) == 0) {
? ? ? ? ??printf("數(shù)據(jù)沒(méi)有變化,直接返回");
? ? ? ? ??return;?
? ? ? ? }
? ? }
? ? ?__disable_irq();?
? ??if?(flash_addr + STATE_SIZE > FLASH_BASE_ADDR + PAGE_SIZE) {
? ? ?printf("erase page 2KBrn");
? ? ? ? erase_page(FLASH_BASE_ADDR);
? ? ? ? flash_addr = FLASH_BASE_ADDR; ??
? ? }
? ??
? ? state->checksum = calculate_checksum(state);
? ? flash_program(flash_addr, (uint8_t*)state, STATE_SIZE);
? ? flash_addr += STATE_SIZE;?
? ? ?__enable_irq();?
}

函數(shù)就比較簡(jiǎn)單了,首先將要保存的數(shù)據(jù)與最新存儲(chǔ)的數(shù)據(jù)做對(duì)比,如果沒(méi)變化,就不操作;如果地址超出范圍了,就先擦除整個(gè)頁(yè),更新寫(xiě)索引到FLASH_BASE_ADDR,接著保存數(shù)據(jù)到當(dāng)下最新寫(xiě)地址索引即可。

重要的就是這兩個(gè)函數(shù)了,其它函數(shù)都很普通沒(méi)必要解釋。

實(shí)際測(cè)試數(shù)據(jù):

剛下在進(jìn)去代碼,最后一頁(yè)全部為0XFF,然后按下5次按鍵,number數(shù)據(jù)每次加1存儲(chǔ)。

下面這張是按了128次,存儲(chǔ)了128次的結(jié)果,整個(gè)頁(yè)都寫(xiě)滿了。

最后一張是寫(xiě)滿之后再按一次,F(xiàn)lash進(jìn)行了擦除,并保存在第一組位置中。

整個(gè)代碼邏輯有它的應(yīng)用場(chǎng)景,也可能有一些bug,非常歡迎大家指正,代碼整體老宇哥會(huì)上傳到GitHub,歡迎大家留言Star!

工程源代碼地址:https://github.com/chiphome/flashMultipleErase

現(xiàn)在很多獨(dú)立的EEPROM芯片都性價(jià)比很高了,直接IIC協(xié)議進(jìn)行讀寫(xiě),可以按字節(jié)直接修改,輕松達(dá)到100W次的擦寫(xiě)次數(shù),具體大家根據(jù)項(xiàng)目的應(yīng)用場(chǎng)景,不同的要求高度進(jìn)行選擇。

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫(xiě)文章/發(fā)需求
立即登錄

最全電子漫畫(huà)收集達(dá)人,漫畫(huà)控必選!用文字和圖片帶你領(lǐng)略電子世界之美。 由曉宇哥哥操刀的芯片之家公眾號(hào),提供45萬(wàn)個(gè)Symbol和3D封裝庫(kù)免費(fèi)下載,定期分享軟硬件、物聯(lián)網(wǎng)類技術(shù)知識(shí)外,還精心整理大量參考設(shè)計(jì)和文檔資源,電路圖和源代碼資料供下載。 立即打開(kāi)“芯片之家 ”,感受電子與藝術(shù)的完美結(jié)合。