【FLASH】STM32内部Flash模拟EEPROM磨损均衡算法–存储设备擦写均衡自带掉电保护接口-如何在同等存储空间下增加FLASH寿命呢?往下看-STM32F334实现FLASH擦写均衡
STM32内部Flash的写寿命大约是1万次,假如我们在其Flash中存储数据,每天100次写操作,100天后Flash就无法继续可靠使用了;外部FLASH,比如说W25Q32,擦写次数也只有十万次,在高频率读写下也支撑不了多久, 本文采取了一种非常简单的方法,将Flash的使用寿命无限延长,取决于你为它分配的存储区大小。
主要思想就是将FLASH 分配一块区域给我们的管理机,然后用索引的方式累积写FLASH,中途不进行擦写,在存满整个分区时进行统一擦写,读取根据ID进行读取,并且加上了数据校验,异常回调。主要用于存储系统配置,运行记录等。支持多个存储管理机管理不同的区域。
FLASH存储模型如下:

管理者内存结构如下:
typedef struct{
unsigned int PageSize; //页大小
unsigned int Pages; //写的总页数
unsigned int StartAddr; //起始地址
unsigned int DriftAddr; //偏移地址需要大于四字节
unsigned int Number; //总共有效条数
unsigned char * Buffer; //数据缓冲区地址
unsigned int ReadAddr; //读地址,用于节省查找时间提高效率
unsigned int WriteAddr; //写地址,用于节省时间提高查找空区效率
void (* Error_Handle)(FERROR err, unsigned char * src, unsigned int len);
}FlashConfig;
PageSize:用户为该管理机提供的存储分区业大小。同时也最小擦除单位。
Pages:存储分区页大小。
StartAddr:存储分区起始地址。
DriftAddr:需要存储数据的数据宽度
Number:需要存储的数据条数,也就是最大ID,这里如果你相同类型的数据有多条有效数据,就通过ID来进行区分。打个比方:周一到周五的菜单,每天固定三个菜,要进行存储的话,就需要提供五组数据,那么日期就是ID,用来区分周几。
Buffer:这个是你数据的内存首地址,这个在后面不同的接口会有介绍
ReadAddr:读地址,由于查找最后一块有效数据地址随着分区的大小,查找时间会相应的变长,所以在系统上电初始化的时候将最后一块有效数据块首地址记录,之后就不用再从子分区0查找了,提高系统执行效率,这个保留在这,暂未实现,后面的版本会加入。
WriteAddr:写地址,与上面一样。
Error_Handle:错误回调,如未实现,请赋值为NULL
存储器接口:
void open_service()
{
//这里可提供线程安全防止中断flash操作,由close_service释放安全锁
eeplog("%s", "open service\n");
}
void close_service()
{
eeplog("%s", "close service\n");
}
/**
* @brief 写储存器接口。
* @param addr:写首地址
sd:写入的数据首地址
len:写入的长度
* @retval None
*/
void write_flash(unsigned int addr, const unsigned char * sd, unsigned int len){
unsigned short * pt = (unsigned short *)sd;
HAL_FLASH_Unlock();
for(int i = 0; i < len/2; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr+i*2, *(pt+i));
}
HAL_FLASH_Lock();
}
/**
* @brief 擦除储存器接口
* @param addr:擦除地址
* @param 擦写长度,暂时未用到,保留,接口默认擦除一页,
* @retval None
*/
void erase_flash(unsigned int addr, unsigned int len){
uint32_t PageError = 0;
FLASH_EraseInitTypeDef pEraseInit;
pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
pEraseInit.PageAddress = addr;
pEraseInit.NbPages = 1;
HAL_FLASH_Unlock();
HAL_StatusTypeDef s = HAL_FLASHEx_Erase(&pEraseInit, &PageError);
HAL_FLASH_Lock();
}
/**
* @brief 读储存器接口.
* @param addr:读起始地址
sd:读数据缓存区首地址
len:读的长度
* @retval None
*/
void read_flash(unsigned int addr, unsigned char * sd, unsigned int len){
unsigned short * pt = (unsigned short *)sd;
for(int i = 0; i < len / 2; i++){
pt[i] = *((__IO uint16_t*)(addr+i*2));
}
}
erase_flash:这里的长度其实无效,这里需要提供的是擦除一页的接口,这里也许会有人说NAND Flash,最小擦除单位为1块,也就是64页,怎么办呢?这里你就需要在配置管理类的时候,将页数量配置为1,然后页大小不超过一块的大小就可以。
下面是全部代码:
eeprom.c
/********************************************************************************* *Copyright(C) - *FileName: eeprom.c *Author: 我不是阿沸 *Version: 6.1.0 *Date: 2023.08.20 *Description: 用于嵌入式系统保存数据至存储器,兼容大多数存储器,经内部算法优化,擦写寿命可达:正常寿命*100倍,这个取决于你分配的空间大小,空间越大寿命越长。 *Others: 互斥需自己实现 *History: 1.Date:2023/04/03 Author:我不是阿沸 Modification:增加自定义互斥接口,由用户实现 2.Date:2023/05/16 Author:我不是阿沸 Modification:修改相关接口实现方式 3.Date:2023/07/18 Author:我不是阿沸 Modification:修改读写数据实现方式 4.Date:2023/08/20 Author:我不是阿沸 Modification:删除冗余成分 4.Date:2023/010/24 Author:我不是阿沸 Modification:增加查找分区地址记录,只需上电查找一次,提高效率 5.其他修改 **********************************************************************************/#include "eeprom.h"void open_service(){ //HAL_FLASH_Unlock(); //这里可提供线程安全防止中断flash操作,由close_service释放安全锁 eeplog("%s", "open service\n");}void close_service(){ eeplog("%s", "close service\n"); //HAL_FLASH_Lock();}//ProgramGroup programGroups[10];/** * @brief 写储存器接口。 * @param addr:写首地址 sd:写入的数据首地址 len:写入的长度 * @retval None */void write_flash(epmu32 addr, const epmu8 * sd, epmu32 len){ //注意,这里传入的sd必须是偶数地址,不然在转换为16位时会出现字节对齐的情况 uint64_t * pt = (uint64_t *)sd; HAL_FLASH_Unlock(); //rt_kprintf("\nlen:%d\n", len); Flash_If_Write((uint8_t*)sd, addr, len);// for(int i = 0; i < len/8; i++)// {// HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr+i*8, *(pt+i));// } HAL_FLASH_Lock();} /** * @brief 擦除储存器接口 * @param addr:擦除地址 * @retval None */void erase_flash(epmu32 addr, epmu32 len){ epmu32 PageError = 0; //FLASH_EraseInitTypeDef pEraseInit; HAL_FLASH_Unlock(); Flash_If_Erase(addr); HAL_FLASH_Lock();} /** * @brief 读储存器接口. * @param addr:读起始地址 @param sd:读数据缓存区首地址 @param len:读的长度 * @retval None */void read_flash(epmu32 addr, epmu8 * sd, epmu32 len){ uint64_t * pt = (uint64_t *)sd;// for(int i = 0; i < len / 8; i++){// pt[i] = *((__IO uint64_t*)(addr+i*8));// } Flash_If_Read(sd, addr, len);}/** * @brief 校验数据接口 * @param src:需要校验的数据首地址 len:要校验的长度 * @retval epmbool:返回校验成功或者失败 */epmbool check_flash(epmu8 *src, epmu32 len){ eeplog("start check data\n"); epmu32 src_ckeck = epm_crc32(src, len -4); epmu32 dec_ckeck = epmu32_buffer_max((src+ len - 4)); if(src_ckeck != dec_ckeck) { eeplog("data check error!\n"); return EPMFALSE; } eeplog("data check success!\n"); return EPMTRUE;}/** * @brief 判断第一块存储区是否进行初始化 * @param flg:分区信息 * @retval epmbool:true:存储区已初始化 false:未初始化 */epmbool isinit_first_block(FlashConfig flg){ epmu8 ch[10]; read_flash(flg.start_addr, ch, 8); int i = 0; for(; i < 8; i++){ if(ch[i] != 0xA5) break; } if(i != 8) return EPMFALSE; return EPMTRUE; }/** * @brief 第一块存储区初始化,写入初始化信息 * @param flg:分区信息 * @retval epmbool:true:存储区初始化成功 false:分区损坏 */epmbool first_block_init(FlashConfig flg){ eeplog("Is check epm init?\n"); epmu8 ch[10]; memset(ch, 0xA5, 8); eeplog("Sector not initialized, about to initialize...\n"); for(int m = 0; m < flg.pages; m++){ erase_flash(flg.start_addr+m*flg.page_size, flg.page_size); } write_flash(flg.start_addr, ch, 8); read_flash(flg.start_addr, ch, 8); for(int i = 0; i < 8; i++){ if(ch[i] != 0xA5) { eeplog("Sector initialization default!!!!!!!!!!!!!!!\n"); return EPMFALSE; } } eeplog("Sector initialization successful\n"); return EPMTRUE;}/** * @brief 校验flash读出的数据是否为空 * @param data:需要校验的数据首地址 len:数据的长度 * @retval epmbool:未被写入过数据返回true */epmbool is_eeprom_null(FlashConfig flg, epmu8 * data, int len){ int n = 0; eeplog("Is flash empty?\n"); for(n = 0; n < len; n++){ if(data[n] != 0xff){ eeplog("flash isn`t empty\n"); return EPMFALSE; } } eeplog("flash is empty\n"); return EPMTRUE;}/** * @brief CRC32校验 * @param data:需要校验的数据首地址 len:数据的长度 * @retval CRC32校验值 */#define POLY 0xEDB88320ULepmu32 epm_crc32(epmu8 * data, epmu32 datalen){ const uint8_t *bytes = data; uint32_t crc = 0xFFFFFFFFUL; // 循环处理每个字节 for (size_t i = 0; i < datalen; i++) { crc ^= bytes[i]; // 把当前字节与 crc 的低 8 位进行异或操作 // 处理当前字节的 8 位,每次处理一位 for (int j = 0; j > 1) ^ POLY; } else { // 否则,只右移一个比特位 crc >>= 1; } } } return ~crc; // 取反操作得到最终结果}/** * @brief 初始化分区信息 * @param 分区信息指针 * @param 起始地址 * @param 偏移地址,子存储区大小 * @param 有效数据条数 * @param 分区占用存储器页数 * @param 存储器叶大小 * @param 数据缓存区指针 * @param 错误回调接口 * @retval none */void eeprom_init(FlashConfig * flg, epmu32 start_addr, epmu8 drift_addr, epmu16 number, epmu16 pages, epmu32 page_size, epmu8 * buffer, error_handle error){ flg->find_flag = EPMFALSE; flg->find_addr = start_addr; flg->start_addr = start_addr; flg->drift_addr = drift_addr; flg->number = number; flg->pages = pages; flg->page_size = page_size; flg->buffer = buffer; flg->error = error;}/** * @brief 顺序查找flash空区 * @param flg:分区信息 * @retval int:返回空区首地址 */epmu32 epm_seq_search_addr(FlashConfig * flg){ int findCount = flg->page_size * flg->pages / (flg->drift_addr+4); epmu32 start_addr = flg->start_addr; epmu32 find_addr = start_addr; epmu8 data[200]; int i; open_service(); eeplog("epy find start!\n"); //这里注意,首地址为初始化校验区,无有效数据 if(flg->find_flag == EPMFALSE) { epmbool bl = isinit_first_block(*flg); if(bl == EPMFALSE) { close_service(); flg->find_flag = EPMTRUE; flg->find_addr = start_addr; return start_addr; } for(i = 1; i drift_addr+4), data, flg->drift_addr+4); if(is_eeprom_null(*flg, data, flg->drift_addr+4) == EPMTRUE){ find_addr = start_addr + i*(flg->drift_addr+4); eeplog("epy find end, return emp addr!\n"); break; } } if(i == findCount) { find_addr = start_addr + findCount*(flg->drift_addr+4); } else { } flg->find_addr = find_addr; flg->find_flag = EPMTRUE; } close_service(); return flg->find_addr; }/** * @brief 写入数据 * @param flg:分区信息 data:需要写入的数据 * @retval int:返回对应的空区首地址 */FERROR epm_write_data(FlashConfig * flg, const epmu8 * data){ epmu8 ch[200] = {0}; epmu8 chb[200] = {0}; epmu32 addr = flg->start_addr; int findCount = flg->page_size * flg->pages / (flg->drift_addr+4); addr = epm_seq_search_addr(flg); if(flg->find_addr >= flg->start_addr + findCount* (flg->drift_addr+4)) { flg->find_addr = flg->start_addr; addr = flg->start_addr; } open_service(); if(addr == flg->start_addr){ epmbool b = first_block_init(*flg); if(b == EPMFALSE){ flg->error(InitError, NULL, 0); close_service(); return InitError; } for(int i = 0; i number; i++){ memcpy(ch, flg->buffer+i*flg->drift_addr, flg->drift_addr); epmu32 src_ckeck = epm_crc32(ch, flg->drift_addr); buffer_epmu32_max(src_ckeck, (ch+flg->drift_addr)); write_flash(addr +(i+1)*(flg->drift_addr+4), ch, flg->drift_addr+4); //这里可适当加入回读错误处理 } flg->find_addr = addr + (flg->drift_addr+4)*(flg->number+1); eeplog("addr:%08X\n", addr); close_service(); return Success; } memcpy(ch, data, flg->drift_addr); epmu32 src_ckeck = epm_crc32(ch, flg->drift_addr); buffer_epmu32_max(src_ckeck, (ch+flg->drift_addr)); write_flash(addr, ch, flg->drift_addr+4); read_flash(addr, chb, flg->drift_addr+4); flg->find_addr += (flg->drift_addr+4); if(flg->find_addr >= flg->start_addr + findCount* (flg->drift_addr+4)) { flg->find_addr = flg->start_addr; } int m = 0; for( ; m drift_addr+4; m++){ if(chb[m] != ch[m]){ break; } } if(flg->drift_addr+4 == m) { close_service(); eeplog("%s", "data write succse"); return Success; } else { eeplog("%s", "data write error, sd have bad block"); } close_service(); eeplog("%s", "data write error, sd have bad block"); return WriteError; } /** * @brief 读出数据 * @param flg:分区信息 data:需要写入的数据 * @retval NONE */FERROR epm_read_data(FlashConfig *flg, epmu8 * data){ epmu32 addr = epm_seq_search_addr(flg); epmu8 ch[200]; if(flg->drift_addr+4 >= 200) return IndexError; if(addr == flg->start_addr){ flg->error(Empty, NULL, 0); return Empty; } else{ addr -= (flg->drift_addr+4); open_service(); read_flash(addr, ch, flg->drift_addr+4); close_service(); if(check_flash(ch, flg->drift_addr+4) == EPMFALSE){ flg->error(ReadError, NULL, 0); return ReadError; } memcpy(data, ch, flg->drift_addr); } return Success;}/** * @brief 查询全部数据 ,并且通过ID写入对应的缓冲区 * @param flg:分区信息 * @retval void */void epm_select_all_data(FlashConfig * flg){ int findCount = (flg->page_size * flg->pages) / (flg->drift_addr+4); epmu32 start_addr = flg->start_addr; int i, n; epmu8 ch[200] = {0}; if(flg->drift_addr+4 >= 200) return; open_service(); epmbool b = isinit_first_block(*flg); if(b == EPMFALSE){ flg->error(InitError, NULL, 0); close_service(); return; } for(i = 1; i drift_addr+4), ch, flg->drift_addr+4); if(is_eeprom_null(*flg, ch, flg->drift_addr+4) == EPMTRUE) { break; } if(check_flash(ch, flg->drift_addr+4) == EPMFALSE){ if(flg->error != NULL) { flg->error(CheckError, ch, flg->drift_addr); } continue; } int id = *((epmu16*)ch); if(id >= flg->number) id = flg->number - 1; for(n = 0; n drift_addr; n++){ flg->buffer[id*flg->drift_addr+n] = ch[n]; } } close_service(); return;}/** * @brief 通过索引查询多条数据数据 * @param flg:分区信息 start:开始条数,这里换算成地址为:flg->drift_addr* start num:查寻条数 * @retval int:返回对应的空区首地址 */void epm_select_data(FlashConfig * flg, epmu32 start, epmu32 num){ epmu32 start_addr = flg->start_addr; epmu8 ch[200] = {0}; if(flg->drift_addr+ 4 >= 200) return; open_service(); for(int i = 1; i < num && i number; i++){ read_flash(start_addr + (i+start)*(flg->drift_addr+4), ch, flg->drift_addr+4); if(check_flash(ch, flg->drift_addr+4) == EPMFALSE){ flg->error(CheckError, ch, flg->drift_addr); continue; } for(int n = 0; n drift_addr; n++){ flg->buffer[(i-1)*flg->drift_addr+n] = ch[n]; } } close_service(); return;}eeprom.h:
/*********************************************************************************
*Copyright(C) -
*FileName: eeprom.h
*Author: 我不是阿沸
*Version: 6.1.0
*Date: 2023.08.20
*Description: 用于嵌入式系统保存数据至存储器,兼容大多数存储器,经内部算法优化,擦写寿命可达:正常寿命*100倍,这个取决于你分配的空间大小,空间越大寿命越长。
*Others: 互斥需自己实现
*History:
1.Date:2023/04/03
Author:我不是阿沸
Modification:增加自定义互斥接口,由用户实现
2.Date:2023/05/16
Author:我不是阿沸
Modification:修改相关接口实现方式
3.Date:2023/07/18
Author:我不是阿沸
Modification:修改读写数据实现方式
4.Date:2023/08/20
Author:我不是阿沸
Modification:删除冗余成分
4.Date:2023/010/24
Author:我不是阿沸
Modification:增加查找分区地址记录,只需上电查找一次,提高效率
5.其他修改
**********************************************************************************/
#ifndef __EEPROMS_H
#define __EEPROMS_H
#include
#include "if_flash.h"
#include
#include
//#define __EEPDEBUG
#ifdef __EEPDEBUG
#define eeplog(format, ...) \
rt_kprintf("[%s:%d->%s]:"format, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#else
#define eeplog(format, ...)
#endif
#define FLASH_PAGE_STEP FLASH_PAGE_SIZE
#define START_ADDRESS (uint32_t)0x08000000
#define epmu32_buffer_min(x) (epmu32)(*((x+3))<<24 | *((x)+2)<<16 | *((x)+1)<<8 | *(x))
#define epmu32_buffer_max(x) (epmu32)(*(x)<<24 | *((x)+1)<<16 | *((x)+2)<<8 | *((x)+3))
#define epmu16_buffer_min(x) (epmu16)(*((x)+1)<<8 | *(x))
#define epmu16_buffer_max(x) (epmu16)(*(x)<>8; (buf)[2] = (x)>>16; (buf)[3] = (x)>>24;}
#define buffer_epmu32_max(x, buf) {(buf)[3] = (x); (buf)[2] = (x)>>8; (buf)[1] = (x)>>16; (buf)[0] = (x)>>24;}
#define buffer_epmu16_min(x, buf) {(buf)[0] = (x); (buf)[1] = (x)>>8;}
#define buffer_epmu16_max(x, buf) {(buf)[1] = (x); (buf)[0] = (x)>>8;}
typedef unsigned int epmu32;
typedef unsigned short epmu16;
typedef unsigned char epmu8;
typedef enum flash_error{
Success,
WriteError,
ReadError,
IndexError,
CheckError,
InitError,
Empty
}FERROR;
typedef enum{
EPMFALSE,
EPMTRUE
}epmbool;
typedef void (* error_handle)(FERROR err, epmu8 * src, epmu32 len);
typedef struct{
epmu32 page_size; //页大小
epmu16 pages; //写的总页数
epmu32 start_addr; //起始地址
epmu8 drift_addr; //偏移地址需要大于四字节
epmu32 number; //总共有效条数
epmu8 * buffer; //数据缓冲区地址
epmu32 find_addr;
epmbool find_flag;
error_handle error;
}FlashConfig;
void eeprom_init(FlashConfig * flg, epmu32 start_addr, epmu8 drift_addr, epmu16 number, epmu16 pages, epmu32 page_size, epmu8 * buffer, error_handle error);
epmu32 epm_seq_search_addr(FlashConfig * flg);
void epm_select_data(FlashConfig * flg, epmu32 start, epmu32 num);
epmu32 epm_crc32(epmu8 * data, epmu32 datalen);
epmbool check_flash(epmu8 *src, epmu32 len);
void erase_flash(epmu32 addr, epmu32 len);
void write_flash(epmu32 addr, const epmu8 * sd, epmu32 len);
void read_flash(epmu32 addr, epmu8 * sd, epmu32 len);
FERROR epm_read_data(FlashConfig *flg, epmu8 * data);
FERROR epm_write_data(FlashConfig * flg, const epmu8 * data);
void epm_select_all_data(FlashConfig * flg);
#endif
main.c
#include "stdio.h"
#include "eeprom.h"
typedef struct
{
char id;
char depth; //宽度
short cmp;
short angle;
short temp; //温度
short count; //执行次数
short width; //间隔时间
short lc ; //lc开关值
unsigned int freq;
int expand;
int outi;
int kp1;
int ki1;
int kd1;
int power;
int kp2;
int ki2;
int kd2;
int outv;
int kp3;
int ki3;
int kd3;
int y1;
int y2;
int y3;
int ts;
int xx;
}Preset;
Preset preset;
FlashConfig PresetCfg;
void preset_config_ehandle(FERROR err, unsigned char * src, unsigned int len)
{
eeplog("preset_config_ehandle error:%d\n", err);
}
void printf_preset(Preset p)
{
eeplog("freq:%d,cmp:%d,angle:%d,expand:%d,temp:%d,count:%d,width:%d,depth:%d", p.freq, p.cmp, p.angle, p.expand, p.temp, p.count, p.width, p.depth);
}
epmbool write_preset(void)
{
//printf_preset(preset);
epm_write_data(&PresetCfg, (unsigned char *)&preset);
return EPMTRUE;
}
/**
* @brief read_preset 读取预设组
* @param
* @retval bool:读取是否成功
*/
epmbool read_preset(void)
{
epm_read_data(&PresetCfg, (unsigned char * )&preset);
return EPMTRUE;
}
epmbool preset_config_init(void){
eeplog("size:%d\n", sizeof(Preset));
eeprom_init(&PresetCfg, 59*FLASH_PAGE_STEP + START_ADDRESS, sizeof(Preset), 1, 4,
FLASH_PAGE_STEP, (unsigned char * )&preset, preset_config_ehandle);
read_preset();
printf_preset(preset);
return EPMTRUE;
}
如有疑问可以评论区留言,或者联系QQ:2227273007,另外附上eeprom的源文件:
(7条消息) STM32存储设备磨损均衡算法与数据结构资源-CSDN文库
本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/665ccb3d7e.html
