目錄
- 0. 個人簡介 && 授權須知
- 1. Flash 和 EEROM 基本情況
- 2. 場景要求
- 3. 軟件設計思路
- 4. 代碼展示
- 4.1 flash.h
- 4.2 flash.c
0. 個人簡介 && 授權須知
📋 個人簡介
- 💖 作者簡介:大家好,我是喜歡記錄零碎知識點的菜鳥打工人。😎
- 📝 個人主頁:歡迎訪問我的博客主頁🔥…
- https://blog.csdn.net/qq_39217004?spm=1010.2135.3001.5343
- 🎉 支持我:點贊👍+收藏??+留言📝
- 📣 系列專欄:嵌入式Linux開發 🍁 🍁
- 💬格言:寫文檔啊不是寫文章,重要的還是直白!🔥
轉載文章,禁止聲明原創;不允許直接二次轉載,轉載請根據原文鏈接聯系作者
若無需改版,在文首清楚標注作者及來源/原文鏈接,并刪除【原創聲明】,即可直接轉載。
但對于未注明轉載來源/原文鏈接的文章,我將保留追述的權利。https://blog.csdn.net/qq_39217004?spm=1010.2135.3001.5343
作者:積跬步、至千里
場景分析:
在嵌入式開發中,EEPROM
因其非易失性存儲特性,常用于保存配置參數等數據。EEPROM
的擦寫次數一般在10萬次
左右。
單片機一般通過 IIC
接口外加一個 EEROM
存儲芯片
但是,有些小項目為了節省成本或者以前維護的一些項目,只能把 flash
劃分出一段空間,當數據存儲空間來用。
這樣的缺點是
- 升級固件時如果固件變大了,一旦覆蓋了原來劃分的數據存儲區域,那數據就沒了。
Flash
的擦寫次數一般都是在1萬次
左右,這個次數可是遠小于EEROM
1萬次,在很多場景下并不夠。
因此本文參考
老板說:單片機,Flash模擬EEPROM,16字節,算法輪詢存儲給我做到100萬次的存儲次數 ,
的思路,通過軟件設計,提高flash的擦寫次數。
1. Flash 和 EEROM 基本情況
- 獨立的
EEPROM
芯片是可以直接寫字節的,即使覆蓋寫也無須擦除, - 單片機的FLASH 寫入數據之前,必須
按頁來擦除
,先擦除再寫入
2. 場景要求
程序一共有15個字節的內容需要斷電保存,每改變其中一個字節就需要保存一次,做到100萬次的一個存儲次數。
單片機的 Flash 一共是 128KB
,從0x08000000----0x0801FFFF
,一共64頁,每頁2KB。
3. 軟件設計思路
以每個 page
2KB 的大小為一個節點,假設數據要存儲到【最后一個page】,設計思路如下:
- 從第一個
page
開始寫入,第一次寫前16字節,然后更新寫入地址索引到第16個字節 - 第二次從 17-31 字節寫入,然后更新寫入地址索引到第32個字節,依次循環
這樣來算,每個 page 可以存儲 2048/16 = 128 次,寫滿一頁擦除一次,理論上存儲次數能達到128 × 1萬次/頁 = 128萬次。
4. 代碼展示
4.1 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 // 結構體大小(填充到24字節)typedef struct {unsigned int color; // 顏色unsigned int seconds; // 秒數unsigned char mode; // 模式unsigned char number; // 序號unsigned char padinng[5]; // 預留5unsigned char checksum; // 1字節,校驗和
}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
4.2 flash.c
// 初始化:查找最新有效數據
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);// 檢查是否全0xFFuint8_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(¤t_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:%X\r\n",last_valid_addr);elseprintf("init first,it is all ff,it is the first data\r\n");if (flash_addr > FLASH_BASE_ADDR + PAGE_SIZE) {printf("init erase page 2KB\r\n");erase_page(FLASH_BASE_ADDR);flash_addr = FLASH_BASE_ADDR;}if (!found_valid_data) {printf("not found valid data\r\n");current_state.color = 100;current_state.seconds = 200;current_state.mode = 1;current_state.number = 1;current_state.checksum = calculate_checksum(¤t_state);__disable_irq(); flash_program(FLASH_BASE_ADDR, (uint8_t*)¤t_state, STATE_SIZE);__enable_irq(); flash_addr = FLASH_BASE_ADDR + STATE_SIZE; }printState(¤t_state);
}
- 第一步,先從第一個地址讀取第一個
16
字節,然后判斷是不是全部等于0xFF,
如果第一次是就證明是第一次,下一步flash_addr
就不需要增加STATE_SIZE
,寫入地址索引就是FLASH_BASE_ADDR
。 - 第二步,如果不全是
0xFF
并且校驗字節通過,證明這是一組有效數據,我們先將此數據更新到current_state
,但是這里還不能證明是最后一組有效數據,因為最后一組有效數據才是我們要找到的數據。 - 繼續檢查下一組數據,直到檢查到一組數據是全
0xFF
,證明上一組數據就是最后一組有效數據,就跳出,此時我們也就找到了最后一組有效數據的起始地址。 - 此地址增加
STATE_SIZE
就是最新可以存儲數據的地址索引了。 - 如果沒找到有效數據,證明是第一次,就寫入默認數值并保存,更新索引。
保存數據save_state函數:
oid 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("數據沒有變化,直接返回");return; }}__disable_irq(); if (flash_addr + STATE_SIZE > FLASH_BASE_ADDR + PAGE_SIZE) {printf("erase page 2KB\r\n");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();
}
函數就比較簡單了,首先將要保存的數據與最新存儲的數據做對比,如果沒變化,就不操作;如果地址超出范圍了,就先擦除整個頁,更新寫索引到FLASH_BASE_ADDR
,接著保存數據到當下最新寫地址索引即可。