个人网站作品,推荐小蚁人网站建设,简历制作免费模板网站,计算机培训班哪些好目录 0. 个人简介 授权须知1. Flash 和 EEROM 基本情况2. 场景要求3. 软件设计思路4. 代码展示4.1 flash.h4.2 flash.c 0. 个人简介 授权须知 #x1f4cb; 个人简介 #x1f496; 作者简介#xff1a;大家好#xff0c;我是喜欢记录零碎知识点的菜鸟… 目录 0. 个人简介 授权须知1. Flash 和 EEROM 基本情况2. 场景要求3. 软件设计思路4. 代码展示4.1 flash.h4.2 flash.c 0. 个人简介 授权须知 个人简介 作者简介大家好我是喜欢记录零碎知识点的菜鸟打工人。 个人主页欢迎访问我的博客主页… https://blog.csdn.net/qq_39217004?spm1010.2135.3001.5343 支持我点赞收藏⭐️留言 系列专栏嵌入式Linux开发 格言写文档啊不是写文章重要的还是直白 转载文章禁止声明原创不允许直接二次转载转载请根据原文链接联系作者 若无需改版在文首清楚标注作者及来源/原文链接并删除【原创声明】即可直接转载。 但对于未注明转载来源/原文链接的文章我将保留追述的权利。 https://blog.csdn.net/qq_39217004?spm1010.2135.3001.5343 作者积跬步、至千里 场景分析 在嵌入式开发中EEPROM 因其非易失性存储特性常用于保存配置参数等数据。EEPROM 的擦写次数一般在10万次左右。
单片机一般通过 IIC 接口外加一个 EEROM 存储芯片
但是有些小项目为了节省成本或者以前维护的一些项目只能把 flash 划分出一段空间当数据存储空间来用。
这样的缺点是 升级固件时如果固件变大了一旦覆盖了原来划分的数据存储区域那数据就没了。Flash 的擦写次数一般都是在 1万次 左右这个次数可是远小于 EEROM 1万次在很多场景下并不够。 因此本文参考 老板说单片机Flash模拟EEPROM16字节算法轮询存储给我做到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);#endif4.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(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:%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(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);
}第一步先从第一个地址读取第一个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接着保存数据到当下最新写地址索引即可。