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

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

8小時(shí)前
216
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

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

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

1萬次,在很多場景下并不夠。

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

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

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

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

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

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

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

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

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

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

#ifndef FLASH__H
#define FLASH__H

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

// FLASH配置
#define FLASH_BASE_ADDR 0x0801F800 // FLASH最后一頁起始地址 (128KB - 2KB)
#define PAGE_SIZE 2048 ? ? ? ? ? ? // STM32G071頁面大小為2KB
#define STATE_SIZE 16 ? ? ? ? ? ? // 結(jié)構(gòu)體大?。ㄌ畛涞?4字節(jié))

typedef struct {
? ? unsigned int ?color; ? ? ? ? ? ? ? ? ?// 顏色
? ? unsigned int ?seconds; ? ? ? ? ? ? ? ?// 秒數(shù)
? ? unsigned char mode; ? ? ? ? ? ? ? ? ? // 模式
? ? unsigned char number; ? ? ? ? ? ? ? ? // 序號
? ? 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)體,簡單幾個(gè)宏定義與函數(shù)聲明,這里的結(jié)構(gòu)體我們增加了一個(gè)字節(jié)的校驗(yàn)。

接下來看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,寫入地址索引就是FLASH_BASE_ADDR。

第二步,如果不全是0xFF并且校驗(yàn)字節(jié)通過,證明這是一組有效數(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就是最新可以存儲數(shù)據(jù)的地址索引了。

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

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

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

接下來是保存數(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ù)沒有變化,直接返回");
? ? ? ? ??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ù)就比較簡單了,首先將要保存的數(shù)據(jù)與最新存儲的數(shù)據(jù)做對比,如果沒變化,就不操作;如果地址超出范圍了,就先擦除整個(gè)頁,更新寫索引到FLASH_BASE_ADDR,接著保存數(shù)據(jù)到當(dāng)下最新寫地址索引即可。

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

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

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

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

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

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

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

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

相關(guān)推薦

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

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