1. 背景
在 Bootloader 设计中,经常需要保存一些 掉电不丢失的关键参数,比如:
当前正在运行的 APP 选择(APP1 / APP2)
固件升级标志
设备的配置参数(校准值、序列号等)
这些数据如果仅存在 SRAM 中(比如放在 .noinit 段的 reset_flag),在 断电后就会丢失,因此需要放到 Flash 的固定区域。
我们把这块区域称为 ENV 区。
2. ENV 与 Flash 的关系
在硬件上,ENV 区其实就是 Flash 的一段固定地址空间。
例如在我的项目中:
Bootloader 占用:0x08000000 ~ 0x08005FFF
APP1 占用:0x08006000 ~ 0x08011FFF
APP2 占用:0x08012000 ~ 0x0801DFFF
ENV 区: 0x0801E000 ~ 0x0801FFFF (大小 8KB)
可以看到,ENV 区和 APP 一样,本质上就是 Flash 的一部分,只不过我们约定它专门用来存放配置数据。
3. Flash 基础操作
在此之前,我已经封装好了 Flash 的基本操作函数(见上篇博客):
remo_flash_erase_sector(addr) 擦除一个扇区(页)
remo_flash_write_words(addr, length, buf) 按字写数据
remo_flash_read_bytes(addr, length, buf) 读取数据
这些 API 面向硬件驱动,能保证我可以对任意地址的 Flash 进行擦写和读取。
4. 为什么还需要 ENV 封装
如果直接用上面的 Flash API 来操作 ENV,每次都要写一堆逻辑:
先读出数据结构
校验 Magic 字和 CRC
如果无效,就写默认值
修改参数后,要重新计算 CRC
擦除扇区,再写回
这样做既繁琐,又容易遗漏步骤。
于是我在此基础上,增加了一层 ENV 管理模块(env.c / env.h),专门负责 ENV 区的参数读写。
5. ENV 管理模块设计
ENV 数据结构
typedef struct {
uint32_t magic; // 魔术字,区分有效/无效
uint32_t version; // 数据版本号
uint32_t app_select; // 上次选择的 APP
uint32_t upgrade_flag; // 升级标志
uint32_t reserved[8]; // 预留参数
uint32_t crc32; // 数据校验
} env_data_t;
magic 用来判断是否是有效 ENV(比如 0xA55AA55A)
crc32 用来保证数据完整性
提供的 API
env_load() 从 Flash 读取 ENV,并校验
env_save() 保存数据到 ENV 区,自动更新 CRC
env_init_default() 初始化默认 ENV
env_get() 获取内存副本指针
这样,Bootloader 或 APP 就可以很轻松地操作 ENV:
// 加载 ENV
if (env_load(NULL) != 0) {
env_init_default(); // 自动写入默认值
}
env_data_t *env = env_get();
// 修改参数
env->app_select = 1; // 下次启动 APP2
env_save(env);
6. 对比总结
直接用 Flash API(硬件层)
优点:灵活,可以对任意地址操作
缺点:每次操作 ENV 都要写一堆重复代码,容易出错
用 ENV 管理模块(逻辑层)
优点:
封装了 CRC 校验和 Magic 检查
自动管理擦写流程
接口简洁,调用更安全
缺点:只能操作约定的 ENV 区,不够通用(但对 Bootloader 足够了)
7. 总结
ENV 区其实就是 Flash 中的一块区域,只是我们在软件层面给它定义了一个结构体,并且通过 ENV 模块来集中管理。
这种 两层封装 的好处是:
下层(remo_flash_plat.c):只负责硬件的通用擦写
上层(env.c):负责具体的数据结构和业务逻辑
这样不仅逻辑清晰,还能避免代码重复,提高可靠性。
————————————————
版权声明:本文为CSDN博主「磨十三」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2401_84382970/article/details/151069397
|
|