【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 0xEDB88320UL
epmu32 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 < 8; j++) {
            if (crc & 1) {      // 如果 crc 的最低位为 1,则右移并与多项式除数进行异或操作
                crc = (crc >> 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 < findCount; i++){
			read_flash(start_addr + i*(flg->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 < flg->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 < flg->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 < findCount; i++){
		read_flash(start_addr + i*(flg->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 < flg->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 < flg->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 < flg->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 <stdio.h>
#include "if_flash.h"
#include <stdio.h>
#include <string.h>



//#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 | *(x+1))

#define buffer_epmu32_min(x, buf) {(buf)[0] = (x); (buf)[1] = (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文库

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值