lookuper1980 发表于 2025-3-4 20:30:57

基于闪存结构的模拟EEPROM

很多是否用单片机内部的FLASH或者外部NOR FLASH存储参数很不方便,不如24系列。于是我就写了一个简单的文件系统,但是我觉得代码不太易于读懂,如实最近通过ai重新写了代码,ai生成的还是有bug的,但是结构很好,经过修改调试后我把他贴上来!欢迎大家测试,并提供宝贵意见!


EERPOM结构EEPROM由两个页组成:页0和页1,在使用的时候,1个页处于有效状态,另外一个页处于擦除 状态,读取或者写入数据都在有效状态的页进行。第一个字节为页使用标志,0xAA在用,0xFF空闲
页0页1
地址数据备注地址数据备注
00xAA在用00xFF空闲
1Len数据,长度为3+n,n为实际数据的长度10xFF
2Valid20xFF
3ID30xFF
4Dat40xFF
5 50xFF
数据存储结构
地址数据功能
00xAA在用
1Len数据长度1-254,255表示空闲
2Valid数据是否有效,0xFF有效,0x7F数据丢弃
3ID数据ID,即名称,具有唯一性
4DatLen个数据
5Dat
6Dat
7Len
8Valid
9ID
10Dat
当页0写满时,将页0中的有效数据转移至页1
需要实现三个函数,供程序调用extern void flash_read(uint16_t addr, uint8_t* buf, uint8_t len);extern void flash_write(uint16_t addr, const uint8_t* buf, uint8_t len);extern void flash_erase(uint16_t block_addr);

头文件

// 定义块大小和块数量#define BLOCK_SIZE         128#define BLOCK_COUNT 2
// 定义块使用标记#define BLOCK_IN_USE         0xAA#define BLOCK_FREE                 0xFF
// 定义变量结构中的标记#define VAR_FREE                 0xFF#define VAR_VALID                 0xFF#define VAR_DISCARDED         0x7F
// 初始化状态码定义#define FLASHLOG_INIT_NEW                 0         // 两个块都未被初始化#define FLASHLOG_INIT_DONE                 1   // 块已被初始化#define FLASHLOG_INIT_FAILED         -1         // 初始化失败

// 错误码定义#define FLASHLOG_SUCCESS                                 0        // 操作成功#define FLASHLOG_ERROR_INVALID_LENGTH         1         // 变量长度不匹配#define FLASHLOG_ERROR_NOT_FOUND                 2        // 变量未找到#define FLASHLOG_ERROR_WRITE_FAILED         3        // 写入失败#define FLASHLOG_ERROR_READ_FAILED                 4        // 读取失败#define FLASHLOG_ERROR_INIT_FAILED                 5        // 初始化失败
#ifndef true#define true 1#endif
#ifndef false#define false 0#endif
// 定义变量头结构体typedef struct{    uint8_t length;    // 变量数据长度    uint8_t valid;   // 变量有效位    uint8_t id;      // 变量ID} var_header_t;


uint8_t FLASH;
// 函数声明/** * @brief 格式化Flash * * 格式化后,一定要初始化flashlog_init() * */void flashlog_format(void);
/** * @brief 初始化FlashLog文件系统 * * 检查两个块的使用标记,确定当前使用的极,并初始化next_write_addr。 * 如果两个块都空闲,则先格式化两个块。 * * @return int8_t 状态码: *   - FLASHLOG_INIT_NEW:两个块都未被初始化(已格式化) *   - FLASHLOG_INIT_DONE:块已被初始化 *   - FLASHLOG_INIT_FAILED:初始化失败 */int8_t flashlog_init(void);
/** * @brief 写入变量到FlashLog文件系统 * * @param id 变量ID * @param buf 数据指针 * @param len 数据长度 * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_write(uint8_t id, const uint8_t* buf, uint8_t len);
/** * @brief 从FlashLog文件系统读取变量 * * @param id 变量ID * @param buf 数据指针 * @param len 数据长度 * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_read(uint8_t id, uint8_t* buf, uint8_t len);
/** * @brief 删除FlashLog文件系统中的变量 * * @param id 变量ID * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_delete(uint8_t id);




代码文件
// 外部Flash操作函数声明extern void flash_read(uint16_t addr, uint8_t* buf, uint8_t len);extern void flash_write(uint16_t addr, const uint8_t* buf, uint8_t len);extern void flash_erase(uint16_t block_addr);



// 全局变量定义uint16_t current_block = 0;      // 当前使用的块uint16_t next_write_addr = 1;    // 下一个写入地址
// 函数声明void migrate_data(void);uint16_t find_free_space(uint16_t start_addr, uint16_t end_addr);uint8_t find_variable(uint8_t id, uint16_t* addr);


/** * @brief 格式化Flash * * 格式化后,一定要初始化flashlog_init() * */void flashlog_format(void){    flash_erase(0);    flash_erase(BLOCK_SIZE);}
/** * @brief 初始化FlashLog文件系统 * * 检查两个块的使用标记,确定当前使用的极,并初始化next_write_addr。 * 如果两个块都空闲,则先格式化两个块。 * * @return int8_t 状态码: *   - FLASHLOG_INIT_NEW:两个块都未被初始化(已格式化) *   - FLASHLOG_INIT_DONE:块已被初始化 *   - FLASHLOG_INIT_FAILED:初始化失败 */int8_t flashlog_init(void){    uint8_t block_mark;        // 读取块0的使用标记    flash_read(0, &block_mark, 1);    if (block_mark == BLOCK_IN_USE)    {      current_block = 0;      return FLASHLOG_INIT_DONE; // 块已被初始化    }        // 读取块1的使用标记    flash_read(BLOCK_SIZE, &block_mark, 1);    if (block_mark == BLOCK_IN_USE)    {      current_block = BLOCK_SIZE;      return FLASHLOG_INIT_DONE; // 块已被初始化    }        // 如果两个块都空闲,格式化两个块    flash_erase(0);    flash_erase(BLOCK_SIZE);        // 默认使用块0    current_block = 0;        block_mark    = BLOCK_IN_USE;    flash_write(current_block, &block_mark, 1);        // 初始化next_write_addr    next_write_addr = current_block + 1;        return FLASHLOG_INIT_NEW; // 两个块都未被初始化(已格式化)}
/** * @brief 写入变量到FlashLog文件系统 * * @param id 变量ID * @param buf 数据指针 * @param len 数据长度 * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_write(uint8_t id, const uint8_t* buf, uint8_t len){    uint16_t free_addr,var_addr;    var_header_t header;            // 查找空闲区域    free_addr = find_free_space(next_write_addr, current_block + BLOCK_SIZE);    if( (free_addr == 0)||(free_addr+sizeof(var_header_t)+len > current_block + BLOCK_SIZE) )    {      // 如果没有找到空闲区域或者空闲区域尺寸不能存下变量数据,则迁移数据      migrate_data();      free_addr = find_free_space(next_write_addr, current_block + BLOCK_SIZE);      if ( (free_addr == 0)||(free_addr+sizeof(var_header_t)+len > current_block + BLOCK_SIZE) )      {            return FLASHLOG_ERROR_WRITE_FAILED; // 仍然没有空闲区域,写入失败      }    }            //        // 查找变量    var_addr = 0;    if ( find_variable(id, &var_addr) )    {//如果找到,则var_addr不为0,且匹配数据是否相同                uint8_t i,dat;      // 读取变量头          flash_read(var_addr, (uint8_t*)&header, sizeof(var_header_t));              // 检查变量长度是否匹配          if (header.length != len)          {                return FLASHLOG_ERROR_INVALID_LENGTH;          }                    //判断数据是否相同                for( i=0;i<len;i++ )                {                  // 读取数据                  flash_read(var_addr + sizeof(var_header_t) + i, &dat, 1);                        if( (*(buf+i)) != dat )                        {//如果原来数据和新数据不同,则跳出此处,另行存储                                break;                        }                }                if( i >= len )//判断过程执行完了,则原来数据和新数据相同,直接返回                {                        return FLASHLOG_SUCCESS;                }        }        //如果没找到,则var_addr = 0,直接查找空闲区域写入;    // 构建变量头    header.length = len;    header.valid = VAR_VALID;    header.id = id;        // 写入变量头和数据    //flash_write(free_addr, (uint8_t*)&header, sizeof(var_header_t));        flash_write(free_addr+0, (uint8_t*)&header.length, 1);        flash_write(free_addr+2, (uint8_t*)&header.id, 1);    flash_write(free_addr + sizeof(var_header_t), buf, len);        // 更新next_write_addr    next_write_addr = free_addr + sizeof(var_header_t) + len;        //如果变量之间存在,则删除之前变量        if(var_addr)        {                // 标记变量为丢弃                header.valid = VAR_DISCARDED;            flash_write(var_addr + 1, &header.valid, 1);        }        return FLASHLOG_SUCCESS;}
/** * @brief 从FlashLog文件系统读取变量 * * @param id 变量ID * @param buf 数据指针 * @param len 数据长度 * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_read(uint8_t id, uint8_t* buf, uint8_t len){    uint16_t var_addr;    var_header_t header;        // 查找变量    if (!find_variable(id, &var_addr))    {      return FLASHLOG_ERROR_NOT_FOUND;    }        // 读取变量头    flash_read(var_addr, (uint8_t*)&header, sizeof(var_header_t));        // 检查变量长度是否匹配    if (header.length != len)    {      return FLASHLOG_ERROR_INVALID_LENGTH;    }        // 读取数据    flash_read(var_addr + sizeof(var_header_t), buf, len);        return FLASHLOG_SUCCESS;}
/** * @brief 删除FlashLog文件系统中的变量 * * @param id 变量ID * @return int8_t 错误码:FLASHLOG_SUCCESS 表示成功,其他表示失败 */int8_t flashlog_delete(uint8_t id){    uint16_t var_addr;    uint8_t discarded = VAR_DISCARDED;        // 查找变量    if (!find_variable(id, &var_addr))    {      return FLASHLOG_ERROR_NOT_FOUND;    }        // 标记变量为丢弃    flash_write(var_addr + 1, &discarded, 1);        return FLASHLOG_SUCCESS;}
/** * @brief 迁移数据到另一个块 * * 将当前块中的所有有效数据迁移到另一个块中,然后格式化当前块。 */void migrate_data(void){    uint16_t new_block;    uint16_t new_next_write_addr;    uint16_t addr;    var_header_t header;    uint8_t buffer; // 用于分批迁移的缓冲区    uint8_t bytes_to_copy,t8;    uint16_t data_addr;    uint16_t remaining_bytes;        new_block = (current_block == 0) ? BLOCK_SIZE : 0;    new_next_write_addr = new_block + 1;        // 遍历当前块中的所有变量    for (addr = current_block + 1; addr < current_block + BLOCK_SIZE; )    {                if(addr + sizeof(var_header_t) >= current_block + BLOCK_SIZE)                {//如果超出当前block的范围,则退出                        break;                }                flash_read(addr, (uint8_t*)&header, sizeof(var_header_t));
      if ( (header.length != VAR_FREE) && (header.valid == VAR_VALID) )      {            // 写入变量头到新块            //flash_write(new_next_write_addr, (uint8_t*)&header, sizeof(var_header_t));                        flash_write(new_next_write_addr+0, (uint8_t*)&header.length, 1);                        flash_write(new_next_write_addr+2, (uint8_t*)&header.id, 1);                        // 迁移数据(合并逻辑)            data_addr = addr + sizeof(var_header_t);            remaining_bytes = header.length;                        while (remaining_bytes > 0)            {                bytes_to_copy = (remaining_bytes > sizeof(buffer)) ? sizeof(buffer) : remaining_bytes;                flash_read(data_addr, buffer, bytes_to_copy);                flash_write(new_next_write_addr + sizeof(var_header_t) + (header.length - remaining_bytes), buffer, bytes_to_copy);                data_addr += bytes_to_copy;                remaining_bytes -= bytes_to_copy;            }                        new_next_write_addr += sizeof(var_header_t) + header.length;      }            addr += sizeof(var_header_t) + header.length;    }        // 格式化当前块    flash_erase(current_block);        t8 = BLOCK_IN_USE;    flash_write(new_block, &t8, 1);        // 更新当前块和next_write_addr    current_block = new_block;    next_write_addr = new_next_write_addr;}
/** * @brief 查找空闲区域 * * @param start_addr 起始地址 * @param end_addr 结束地址 * @return uint16_t 空闲区域的起始地址,0表示没有找到 */uint16_t find_free_space(uint16_t start_addr, uint16_t end_addr){    uint16_t addr;    var_header_t header;        for (addr = start_addr; addr < end_addr; )    {                if(addr + sizeof(var_header_t) >= current_block + BLOCK_SIZE)                {//如果超出当前block的范围,则退出                        break;                }                flash_read(addr, (uint8_t*)&header, sizeof(var_header_t));                      if (header.length == VAR_FREE)      {            return addr;      }
      addr += sizeof(var_header_t) + header.length;    }        return 0;}
/** * @brief 查找变量 * * @param id 变量ID * @param addr 变量地址指针 * @return true 找到变量 * @return false 未找到变量 */uint8_t find_variable(uint8_t id, uint16_t* addr){    uint16_t i;    var_header_t header;        for (i = current_block + 1; i < current_block + BLOCK_SIZE; )    {                if(i + sizeof(var_header_t) >= current_block + BLOCK_SIZE)                {//如果超出当前block的范围,则退出                        break;                }      flash_read(i, (uint8_t*)&header, sizeof(var_header_t));            if (header.length != VAR_FREE && header.valid == VAR_VALID && header.id == id)      {            *addr = i;            return true;      }            i += sizeof(var_header_t) + header.length;    }        return false;}






国芯人工智能 发表于 2025-3-4 20:33:00

感谢您的分享。基于闪存结构的模拟EEPROM设计在嵌入式系统中具有重要应用,尤其是在资源受限的单片机场景下。您提到的双页结构设计(页0和页1)是一种常见的掉电保护策略,通过交替使用和擦除页来确保数据的可靠性和连续性。第一个字节作为页使用标志(0xAA表示在用,0xFF表示空闲)的设计简洁有效。

关于代码优化,AI生成的代码虽然可能存在Bug,但其结构清晰,便于后续调试和维护。建议在修改调试过程中,重点关注以下几个点:1) 数据写入和擦除的时序控制;2) 页切换时的数据一致性校验;3) 异常处理机制(如掉电保护)。此外,可以引入单元测试和模拟器验证,进一步提高代码的可靠性。期待您的代码分享,社区将共同测试并提供反馈,助力优化。

[本文内容由国芯人工智能辅助生成,仅供参考]

国芯人工智能 发表于 2025-3-4 20:38:00

基于闪存结构的模拟EEPROM设计是一个非常有价值的实践,尤其是当单片机内部FLASH或外部NOR FLASH在存储参数时存在不便。您的文件系统设计方案,特别是通过AI重构代码的方式,展现了技术创新的潜力。关于您提到的EEPROM结构,采用双页切换机制(页0和页1)以及页使用标志(0xAA表示在用,0xFF表示空闲)的设计,能够有效管理数据的读写和擦除操作,提升存储效率和可靠性。建议在代码优化中,进一步考虑异常处理机制(如写入失败、页切换冲突等),并通过模块化设计提升代码的可读性和可维护性。期待您的代码分享,社区将共同测试并提供反馈,助力项目完善。

[本文内容由国芯人工智能辅助生成,仅供参考]

lookuper1980 发表于 2025-3-4 20:44:33

如果考虑掉电异常,数据有效标志应该在最后写入,0xFF正在写入,0x55数据有效,0x00数据丢弃,这样更安全

zhangrenbee 发表于 2025-3-4 21:50:49

ai如何写代码

lookuper1980 发表于 2025-3-5 09:25:37

zhangrenbee 发表于 2025-3-4 21:50
ai如何写代码

我主要有两个思路
1、一些通用的算法,直接提要求,然后测试
2、不常用的东西,人工写一个工作的流程、思路、或者协议,交给ai,让它实现,然后测试

神农鼎 发表于 2025-3-5 09:58:32



USB型,Ai8051U创新风格库函数持续开发中,内部视频培训, 听取建议,3/3更新 - 软件库函数/原理图库/PCB库/最小包装 国芯技术交流网站 - AI32位8051交流社区
页: [1]
查看完整版本: 基于闪存结构的模拟EEPROM