13. EEPROM Emulation Component

13.1. 概述

EEPROM 模拟组件(eeprom_emulation)在 NOR Flash 上实现类似 EEPROM 的掉电保护数据存储功能。组件通过双区域设计和状态机机制,确保在任何掉电场景下数据的安全性和一致性。

13.2. 组件架构

+--------------------------------------------------+
|            应用层(Application)                  |
|  - 通过 e2p_write/read/delete 操作数据           |
+--------------------------------------------------+
                     |
                     | API 调用
                     |
+--------------------------------------------------+
|      EEPROM 模拟核心层(Core)                   |
|  - eeprom_emulation.h / eeprom_emulation.c       |
|  - 状态管理、索引表、CRC 校验                    |
+--------------------------------------------------+
                     |
                     | 函数指针
                     |
+------------------------+-------------------------+
|                        |                         |
+-----------------------+  |   +----------------------+
|   NOR Flash 端口      |  |   |  其他 Flash 端口     |
| (port/hpm_nor_flash)  |  |   |  (可扩展)            |
+-----------------------+  |   +----------------------+
|                        |                         |
+--------------------------------------------------+
|          Flash 驱动层(Driver)                   |
|  - ROM API / XPI Driver                          |
+--------------------------------------------------+
                     |
+--------------------------------------------------+
|              硬件层(Hardware)                   |
|              XPI 外部 Flash                       |
+--------------------------------------------------+

13.3. 核心特性

  • 掉电保护

    • 双半区(Half-Area)设计,任何掉电场景均可安全恢复

    • 状态机通过 1→0 位翻转实现(无需擦除 flash)

    • 自动检测并恢复未完成的整理操作

  • 数据完整性

    • 每个条目包含 CRC32 校验

    • 读取时自动验证,损坏数据返回错误

  • 空间管理

    • 自动整理:空间不足时自动触发

    • 删除标记:e2p_delete() 标记无效,下次 flush 回收

    • 废弃数据回收:同一 block_id 的旧数据在整理时自动清理

  • 可移植性

    • 通过函数指针抽象 flash 驱动

    • 支持不同 flash 介质(NOR Flash、SPI Flash 等)

    • 端口层独立,易于扩展

  • 配置灵活

    • 可配置擦除块大小(erase_size)

    • 可配置扇区数量(sector_cnt)

    • 可配置版本号(version)变更自动擦除

13.4. Flash 区域布局

EEPROM 模拟使用 NOR Flash 的连续区域,分为两个半区(A 和 B):

+--------------------------------------------------+
|                   Half Area A                    |
|  +------------------------------------------+    |
|  | Data 区域(地址增长方向:↑)             |    |
|  |  - 用户数据                              |    |
|  |  - 数据块:block_id + data               |    |
|  +------------------------------------------+    |
|  | Info 区域(地址增长方向:↓)             |    |
|  |  - Info 描述符数组                       |    |
|  |  - 每个 Info:16 bytes                   |    |
|  |  - 尾部:Header(12 bytes)              |    |
|  +------------------------------------------+    |
+--------------------------------------------------+
|                   Half Area B                    |
|  (布局同上)                                   |
+--------------------------------------------------+
  • Data 区域:存储实际用户数据,从区域起始地址向高地址增长

  • Info 区域:存储数据块描述符,从区域尾部向低地址增长

  • Area Header:每个半区尾部 12 bytes,包含 version、magic、state

  • Info 描述符结构

typedef struct {
    uint32_t block_id;      /* 块 ID(用户自定义)*/
    uint32_t data_addr;     /* 数据在 Data 区域的地址 */
    uint16_t length;        /* 数据长度 */
    uint16_t valid_state;   /* 有效性标记 */
    uint32_t crc;          /* CRC32 校验值 */
} e2p_block_t;

13.5. 状态机与掉电保护

组件使用 4 位状态机,通过 NOR Flash 的 1→0 位翻转实现(无需擦除):

状态值

状态名称

说明

0

valid

正常工作状态

8

finish

数据复制完成,准备擦除旧区

12

write

正在向新区写入数据

14

start

整理操作开始

15

invalid

未初始化/擦除后的状态

状态转换流程

invalid(15) → start(14) → write(12) → finish(8) → valid(0)

掉电恢复策略

掉电时机

Area A 状态

Area B 状态

恢复动作

首次启动

15

15

选中 Area A,标记为 valid

整理中掉电(数据已复制)

0 (valid)

8 (finish)

擦除 Area A,切换到 B,标记 B 为 valid

整理写入中掉电

0 (valid)

12 (write)

擦除 B,重新整理

整理开始前掉电

0 (valid)

14 (start)

擦除 B,重新整理

13.6. 主要 API

13.6.1. 初始化与配置

hpm_stat_t e2p_config(e2p_t *e2p);
  • 功能:初始化 EEPROM 模拟,校验 flash 头部,恢复掉电状态,重建索引表

  • 参数: - e2p: 上下文结构体指针,需预先配置 flash 操作函数指针

  • 返回E2P_STATUS_OK 成功,错误码失败

13.6.2. 写入数据

hpm_stat_t e2p_write(uint32_t block_id, uint16_t length, const uint8_t *data);
  • 功能:写入数据,自动更新相同 block_id 的旧数据

  • 参数: - block_id: 用户自定义标识符(通过 e2p_generate_id() 生成) - length: 数据长度(字节) - data: 数据缓冲区指针

  • 返回E2P_STATUS_OK 成功,错误码失败

13.6.3. 读取数据

hpm_stat_t e2p_read(uint32_t block_id, uint16_t length, uint8_t *data);
  • 功能:读取数据,自动验证 CRC32

  • 参数: - block_id: 用户自定义标识符 - length: 缓冲区大小(可小于存储长度) - data: 输出缓冲区指针

  • 返回E2P_STATUS_OK 成功,E2P_ERROR_NOT_FOUND 未找到,E2P_ERROR_CRC 校验失败

13.6.4. 删除数据

hpm_stat_t e2p_delete(uint32_t block_id);
  • 功能:删除指定 block_id 的数据(标记为无效)

  • 参数: - block_id: 用户自定义标识符

  • 返回E2P_STATUS_OK 成功,E2P_ERROR_NOT_FOUND 未找到

13.6.5. 整理数据

hpm_stat_t e2p_flush(uint8_t flag);
  • 功能:整理数据,回收空间

  • 参数 - flag: E2P_FLUSH_TRY 仅在空间不足时整理或 E2P_FLUSH_FORCE 强制整理

  • 返回E2P_STATUS_OK 成功,错误码失败

13.6.6. 生成 ID

uint32_t e2p_generate_id(const char *name);
  • 功能:从字符串生成 32 位 block_id

  • 参数: - name: 字符串指针(前 4 个字符被打包)

  • 返回:32 位 block_id

13.6.7. 清空数据

void e2p_clear(void);
  • 功能:擦除整个存储区域(两个半区)

13.6.8. 显示状态信息

void e2p_show_info(void);
  • 功能:打印当前 EEPROM 模拟状态(当前活动区、剩余空间等)

  • 参数:无

  • 返回:无

13.7. 配置参数

核心配置结构体:

typedef struct {
    uint32_t start_addr;
    uint32_t sector_cnt;
    uint32_t erase_size;
    uint32_t version;

    hpm_stat_t (*flash_read)(uint8_t *buf, uint32_t addr, uint32_t size);
    hpm_stat_t (*flash_write)(const uint8_t *buf, uint32_t addr, uint32_t size);
    hpm_stat_t (*flash_erase)(uint32_t start_addr, uint32_t size);
} e2p_config_t;

字段名称

说明

start_addr

Flash 起始地址

sector_cnt

扇区数量(必须是偶数)

erase_size

擦除块大小(字节)

version

版本号(变更时自动擦除)

flash_read/write/erase

Flash 操作函数指针

用户配置(user_config.h):

配置

说明

E2P_DEBUG_LEVEL

调试级别(0-4)

E2P_MAX_VAR_CNT

最大变量数量(默认 100)

E2P_FLUSH_BUF_SIZE

整理缓冲区大小(默认 512)

13.8. 快速开始

  1. 包含头文件及 NOR Flash 端口:

    #include "eeprom_emulation.h"
    #include "hpm_nor_flash.h"
    
  2. 配置 NOR Flash 与 EEPROM 模拟上下文(见下方使用示例)。

  3. 调用 nor_flash_init() 后调用 e2p_config()

  4. 使用 e2p_generate_id(“NAME”) 生成 block_id,再调用 e2p_write() / e2p_read()

13.9. 使用示例

基本用法示例:

#include "eeprom_emulation.h"
#include "hpm_nor_flash.h"

static nor_flash_config_t g_nor_cfg;
static e2p_t g_e2p_ctx;

/* Flash 操作封装 */
static hpm_stat_t my_read(uint8_t *buf, uint32_t addr, uint32_t size) {
    return nor_flash_read(&g_nor_cfg, buf, addr, size);
}
static hpm_stat_t my_write(const uint8_t *buf, uint32_t addr, uint32_t size) {
    return nor_flash_write(&g_nor_cfg, buf, addr, size);
}
static hpm_stat_t my_erase(uint32_t start_addr, uint32_t size) {
    return nor_flash_erase(&g_nor_cfg, start_addr, size);
}

int main(void) {
    /* 配置 NOR Flash 端口 */
    g_nor_cfg.xpi_base = BOARD_APP_XPI_NOR_XPI_BASE;
    g_nor_cfg.base_addr = BOARD_FLASH_BASE_ADDRESS;
    g_nor_cfg.opt_header = BOARD_APP_XPI_NOR_CFG_OPT_HDR;
    g_nor_cfg.opt0 = BOARD_APP_XPI_NOR_CFG_OPT_OPT0;
    g_nor_cfg.opt1 = BOARD_APP_XPI_NOR_CFG_OPT_OPT1;

    /* 配置 EEPROM 模拟 */
    g_e2p_ctx.config.start_addr = 0x80080000;
    g_e2p_ctx.config.erase_size = 4096;
    g_e2p_ctx.config.sector_cnt = 128;
    g_e2p_ctx.config.version = 0x4553;
    g_e2p_ctx.config.flash_read = my_read;
    g_e2p_ctx.config.flash_write = my_write;
    g_e2p_ctx.config.flash_erase = my_erase;

    /* 初始化 */
    nor_flash_init(&g_nor_cfg);
    if (e2p_config(&g_e2p_ctx) != E2P_STATUS_OK) {
        printf("Init failed\n");
        return -1;
    }

    /* 写入数据 */
    uint32_t var_id = e2p_generate_id("TEST");
    const char *data = "hello,world";
    e2p_write(var_id, strlen(data), (uint8_t *)data);

    /* 读取数据 */
    uint8_t buf[32];
    if (e2p_read(var_id, sizeof(buf), buf) == E2P_STATUS_OK) {
        printf("Read: %s\n", buf);
    }

    return 0;
}

13.10. 扩展 Flash 端口

若需要支持其他 Flash 介质(如 SPI Flash、内部 Data Flash),在 port/ 目录下新增驱动文件:

/* port/my_flash.h */
typedef struct {
    /* 端口特定配置 */
} my_flash_config_t;

hpm_stat_t my_flash_init(my_flash_config_t *cfg);
hpm_stat_t my_flash_read(my_flash_config_t *cfg,
                         uint8_t *buf, uint32_t addr, uint32_t size);
hpm_stat_t my_flash_write(my_flash_config_t *cfg,
                          const uint8_t *buf, uint32_t addr, uint32_t size);
hpm_stat_t my_flash_erase(my_flash_config_t *cfg,
                          uint32_t start_addr, uint32_t size);

使用方法与 NOR Flash 端口相同,只需将函数指针注册到 e2p_config_t 即可。

13.11. 错误码

错误码

说明

E2P_STATUS_OK

0

成功

E2P_ERROR

1

通用错误

E2P_ERROR_NO_MEM

2

空间不足

E2P_ERROR_INIT_ERR

3

初始化错误

E2P_ERROR_BAD_ID

4

无效的 block_id

E2P_ERROR_BAD_ADDR

5

无效的地址

E2P_ERROR_OVERFLOW

6

索引表溢出

E2P_ERROR_CRC

7

CRC 校验失败

E2P_ERROR_NOT_FOUND

8

未找到

13.12. 最佳实践

  • 块大小:单变量长度建议小于等于 E2P_FLUSH_BUF_SIZE,大数据拆分为多个变量。

  • 整理:避免在实时性要求高的路径中调用 e2p_flush(E2P_FLUSH_FORCE);擦除耗时较长。

  • 临界区:在 E2P_CRITICAL_ENTER / E2P_CRITICAL_EXIT 内尽量缩短代码,避免耗时操作。

  • 版本号:修改配置中的 version 会触发全量擦除并重新初始化;适用于布局或格式变更时。

13.13. 故障排除

  • 初始化失败:检查 start_addrerase_sizesector_cnt 及 flash 读写擦除函数指针是否已设置。

  • E2P_ERROR_NO_MEM:执行 e2p_flush(E2P_FLUSH_FORCE) 回收空间,或减少变量数量/大小。

  • E2P_ERROR_CRC:存储数据可能损坏;重新写入该变量或检查 flash 硬件。

  • E2P_ERROR_NOT_FOUND:确认该 block 已写入且 block_id 一致(如通过 e2p_generate_id() 生成)。

13.14. 注意事项

  1. sector_cnt 必须是偶数

    组件需要两个半区实现掉电保护。

  2. 单次写入大小限制

    单个变量长度不应超过 E2P_FLUSH_BUF_SIZE 大数据应拆分为多个变量存储。

  3. 版本号变更

    修改 version 字段后,组件会自动擦除并重新初始化。

  4. 临界区保护

    E2P_CRITICAL_ENTER / E2P_CRITICAL_EXIT 宏应尽可能短。 避免在临界区内调用耗时操作。

  5. Flash 擦除时间

    擦除操作通常耗时较长。 避免在实时性要求高的代码路径中调用 flush。