N32G43x Bootloader 中 ENV 区的管理与实现
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; // 上次选择的 APPuint32_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 区,自动更新 CRCenv_init_default()
初始化默认 ENVenv_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
):负责具体的数据结构和业务逻辑
这样不仅逻辑清晰,还能避免代码重复,提高可靠性。