找回密码
 立即注册
查看: 205|回复: 6

基于闪存结构的模拟EEPROM

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-03-04 20:30:34
已绑定手机

1

主题

4

回帖

39

积分

新手上路

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


EERPOM结构  
EEPROM由两个页组成:页0和页1,在使用的时候,1个页处于有效状态,另外一个页处于擦除 状态,读取或者写入数据都在有效状态的页进行。
第一个字节为页使用标志,0xAA在用,0xFF空闲
0
1
地址
数据
备注
地址
数据
备注
0
0xAA
在用
0
0xFF
空闲
1
Len
数据,长度为3+nn为实际数据的长度
1
0xFF
2
Valid
2
0xFF
3
ID
3
0xFF
4
Dat
4
0xFF
5
5
0xFF
数据存储结构
地址
数据
功能
0
0xAA
在用
1
Len
数据长度1-254,255表示空闲
2
Valid
数据是否有效,0xFF有效,0x7F数据丢弃
3
ID
数据ID,即名称,具有唯一性
4
Dat
Len个数据
5
Dat
6
Dat
7
Len
8
Valid
9
ID
10
Dat
当页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[BLOCK_SIZE*BLOCK_COUNT];

// 函数声明
/**
* @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[8]; // 用于分批迁移的缓冲区
    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;
}







回复

使用道具 举报 送花

3

主题

1160

回帖

1031

积分

等待验证会员

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

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

[本文内容由国芯人工智能辅助生成,仅供参考]
回复 支持 反对

使用道具 举报 送花

3

主题

1160

回帖

1031

积分

等待验证会员

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

[本文内容由国芯人工智能辅助生成,仅供参考]
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-03-04 20:30:34
已绑定手机

1

主题

4

回帖

39

积分

新手上路

积分
39
发表于 2025-3-4 20:44:33 | 显示全部楼层
如果考虑掉电异常,数据有效标志应该在最后写入,0xFF正在写入,0x55数据有效,0x00数据丢弃,这样更安全
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:135
  • 最近打卡:2025-05-01 08:09:26
已绑定手机

2

主题

82

回帖

194

积分

注册会员

积分
194
发表于 2025-3-4 21:50:49 | 显示全部楼层
ai如何写代码
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-03-04 20:30:34
已绑定手机

1

主题

4

回帖

39

积分

新手上路

积分
39
发表于 2025-3-5 09:25:37 | 显示全部楼层

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

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:54
  • 最近打卡:2025-05-01 09:07:55

717

主题

1万

回帖

1万

积分

管理员

积分
15613
发表于 2025-3-5 09:58:32 | 显示全部楼层
回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 02:33 , Processed in 0.125154 second(s), 84 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表