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. 快速开始
包含头文件及 NOR Flash 端口:
#include "eeprom_emulation.h" #include "hpm_nor_flash.h"
配置 NOR Flash 与 EEPROM 模拟上下文(见下方使用示例)。
调用 nor_flash_init() 后调用 e2p_config()。
使用 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_addr、 erase_size、 sector_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. 注意事项
sector_cnt 必须是偶数
组件需要两个半区实现掉电保护。
单次写入大小限制
单个变量长度不应超过 E2P_FLUSH_BUF_SIZE 大数据应拆分为多个变量存储。
版本号变更
修改 version 字段后,组件会自动擦除并重新初始化。
临界区保护
E2P_CRITICAL_ENTER / E2P_CRITICAL_EXIT 宏应尽可能短。 避免在临界区内调用耗时操作。
Flash 擦除时间
擦除操作通常耗时较长。 避免在实时性要求高的代码路径中调用 flush。