用 A/B 双页冗余保存 MCU 固定配置
Flash 擦除和写入需要时间。设备在这个过程中掉电、复位或受到干扰,正在更新的页可能只写入了一部分,单页存储随后无法提供完整配置。
长期使用中还可能出现偶发位翻转。CRC32 可以发现数据已经损坏,但单页存储没有第二份数据可供恢复。A/B 双页存储把同一份结构体写入两个独立擦除页:CRC32 负责识别损坏页,另一页负责提供可用副本。
本文示例来自实际工程中的 FlashStore 模块,现已整理成独立的 HAL 无关 C 库:FlashStore A/B Partition 开源仓库。
双页冗余的职责
每个页都保存魔数、数据长度、CRC32 和 payload。A 页是优先读取的主副本,B 页用于回退。
这套实现没有代次号,也不会在两页之间轮流选择最新记录。两页保存同一份数据,目标是在一次擦除或写入失败后仍保留一份可校验副本。
先 A 后 B 的更新顺序
保存时先擦除并写入 A 页。A 页更新失败,流程立即停止,B 页中的旧值保持不变;A 页写入成功后再更新 B 页。
读取时先检查 A 页的魔数、长度和 CRC32。A 页无效时再检查 B 页。两个副本都无效,接口返回 FLASH_STORE_ERROR_NO_VALID_DATA,由应用决定加载默认值或进入故障处理。
HAL 无关接口
核心库只要求平台提供读取、擦除和整页写入三个回调。调用方再配置两个页地址、页大小和一个静态工作区:
FlashStore_Config config = {
.io = {
.read = board_flash_read,
.erase = board_flash_erase,
.program = board_flash_program,
},
.page_a_address = PAGE_A_ADDRESS,
.page_b_address = PAGE_B_ADDRESS,
.page_size = sizeof(workspace),
.workspace = workspace,
.workspace_size = sizeof(workspace),
};
FlashStore_Init(&store, &config);
FlashStore_Save(&store, key, (const uint8_t *)&settings, sizeof(settings));
FlashStore_Load(&store, key, (uint8_t *)&settings, sizeof(settings));仓库中的 examples/air001_example.c 给出了 Air001 HAL 的回调实现。移植到其他 MCU 时,只需替换这三个回调,并按芯片手册处理页大小、写入粒度和对齐要求。
使用边界
- 两个页必须从链接脚本的固件区排除,避免代码覆盖配置页。
- payload 上限是页大小减 12 字节的页头。
- 结构体字段变化需要应用层维护版本兼容。
- 静态工作区会在调用期间被改写,接口不支持并发调用。
key只提供可逆混淆,不具备密码学安全性。- 双页冗余只覆盖单页损坏;两个副本同时损坏时仍需加载默认值或从外部恢复。
该方案适合固定结构体、低频写入和代码空间敏感的 MCU。动态 KV、频繁更新、磨损均衡或时序记录仍应使用 FlashDB 等完整存储组件。两类方案的资源和维护成本对比见 FlashDB 占用约 7KB Flash 时,固定配置可考虑 A/B 双页存储。