基于 RT-Thread 的 OTA 升级中 Bootloader 与 App 分区校验的容错设计
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式 OTA 升级中,分区校验是保证固件完整性和系统可靠性的核心环节。本文基于 RT-Thread 平台,深入剖析 Bootloader 与 App 分区校验的容错设计,涵盖 CRC 校验、版本回退、异常恢复等机制,并给出可落地的代码示例与配置步骤,帮助开发者构建健壮的升级流程。
# 基于 RT-Thread 的 OTA 升级中 Bootloader 与 App 分区校验的容错设计
## 1. 为什么需要分区校验?
OTA(Over-The-Air)升级是物联网设备的关键能力,但升级过程可能因断电、传输错误或写入异常导致固件损坏。若缺乏校验机制,设备将变砖。因此,Bootloader 在跳转 App 前必须验证 App 分区的完整性,同时 App 在运行中也要校验自身或备份分区,以支持回滚。RT-Thread 提供了灵活的 Flash 分区管理(FAL)和软件包(如 OTA 组件),但容错设计仍需开发者根据硬件和应用场景定制。
## 2. 容错设计核心原则
- **双分区备份**:至少保留两个 App 分区(A/B 槽),当前运行一个,另一个用于接收新固件。
- **校验机制**:使用 CRC32 或 SHA256 对固件镜像进行校验,确保数据完整。
- **状态标记**:在 Flash 中记录升级状态(如待升级、升级完成、校验失败),便于 Bootloader 决策。
- **回滚策略**:若新固件启动失败(如看门狗超时),自动回退到旧版本。
- **异常处理**:任何校验失败或超时均需安全退出,不破坏现有固件。
## 3. 基于 RT-Thread 的实现方案
### 3.1 硬件与软件环境
- MCU:STM32F407(Cortex-M4,2MB Flash)
- RT-Thread 版本:4.1.0
- 使用 FAL 管理 Flash 分区,配置如下(rtconfig.h 或 fal_cfg.h):
```c
// fal_cfg.h
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WROD, "bootloader", "onchip_flash", 0, 128*1024, 0}, \
{FAL_PART_MAGIC_WROD, "app", "onchip_flash", 128*1024, 1024*1024, 0}, \
{FAL_PART_MAGIC_WROD, "app_backup", "onchip_flash", 1152*1024, 1024*1024, 0}, \
{FAL_PART_MAGIC_WROD, "download", "onchip_flash", 2176*1024, 512*1024, 0}, \
}
```
### 3.2 Bootloader 设计
Bootloader 负责启动流程,核心逻辑:
1. 检查升级标志(如某个 Flash 地址的魔数)。
2. 若存在待升级固件,校验其 CRC;若通过,则擦除 app 分区并写入新固件,更新标志。
3. 校验 app 分区当前固件(CRC 或版本号),若失败则尝试从 backup 分区恢复。
4. 跳转至 App 入口。
关键代码示例(基于 RT-Thread 的 FAL 和 CRC 软件包):
```c
#include
#include
#include
#define APP_PART_NAME "app"
#define BACKUP_PART_NAME "app_backup"
#define DOWNLOAD_PART_NAME "download"
#define FLAG_ADDR 0x08000000 // 假设在 bootloader 分区末尾存储标志
static int check_partition_crc(const char* part_name) {
struct fal_part *part = fal_part_find(part_name);
if (!part) return -1;
// 读取分区数据,计算 CRC(此处简化,实际需分段读取)
rt_uint32_t crc = 0;
rt_uint8_t buf[1024];
rt_uint32_t offset = 0;
rt_uint32_t len = part->len;
while (offset < len) {
rt_uint32_t read_len = (len - offset) > sizeof(buf) ? sizeof(buf) : (len - offset);
if (fal_part_read(part, offset, buf, read_len) != read_len) return -1;
crc = crc32_update(crc, buf, read_len);
offset += read_len;
}
// 假设固件末尾 4 字节存储 CRC 值
rt_uint32_t stored_crc;
fal_part_read(part, len - 4, &stored_crc, 4);
return (crc == stored_crc) ? 0 : -1;
}
void bootloader_main(void) {
// 检查升级标志(例如 0xA5A5 表示有待升级固件)
if (*(volatile rt_uint32_t *)FLAG_ADDR == 0xA5A5) {
if (check_partition_crc(DOWNLOAD_PART_NAME) == 0) {
// 擦除 app 并写入新固件
fal_part_erase_all(fal_part_find(APP_PART_NAME));
// 复制 download 分区到 app 分区(省略具体复制代码)
// 更新标志为 0
*(volatile rt_uint32_t *)FLAG_ADDR = 0;
} else {
// 下载固件损坏,清除标志,继续启动旧版本
*(volatile rt_uint32_t *)FLAG_ADDR = 0;
}
}
// 校验 app 分区,若失败尝试从 backup 恢复
if (check_partition_crc(APP_PART_NAME) != 0) {
if (check_partition_crc(BACKUP_PART_NAME) == 0) {
// 恢复备份
fal_part_erase_all(fal_part_find(APP_PART_NAME));
// 复制 backup 到 app(省略)
} else {
// 双分区都坏,进入紧急模式(如串口等待)
rt_kprintf("Firmware corrupt!\n");
while(1);
}
}
// 跳转到 App(需获取 app 分区起始地址)
void (*app_entry)(void) = (void (*)(void))(fal_part_find(APP_PART_NAME)->offset + 0x08000000);
app_entry();
}
```
### 3.3 App 端设计
App 端负责接收新固件并触发升级,同时需具备自校验和回滚触发机制。
- **升级流程**:
1. 从网络或外设接收固件,写入 download 分区。
2. 计算 CRC 并附加到固件末尾。
3. 设置升级标志(如写入 bootloader 分区特定地址)。
4. 软件复位,进入 Bootloader。
- **自校验与回滚**:App 启动后,启动一个看门狗(如 30 秒),若系统正常运行则喂狗,并清除回滚标志;若未及时喂狗,看门狗复位后 Bootloader 检测到回滚标志,自动从 backup 恢复。
代码示例(App 端触发升级):
```c
#include
#include
#include
#define DOWNLOAD_PART_NAME "download"
#define FLAG_ADDR 0x08000000 // 与 Bootloader 一致
extern void system_reset(void);
void ota_upgrade(const rt_uint8_t *fw_data, rt_uint32_t fw_len) {
struct fal_part *dl_part = fal_part_find(DOWNLOAD_PART_NAME);
if (!dl_part || fw_len > dl_part->len - 4) {
rt_kprintf("Invalid firmware size\n");
return;
}
// 写入固件数据
fal_part_erase_all(dl_part);
fal_part_write(dl_part, 0, fw_data, fw_len);
// 计算 CRC 并写入末尾
rt_uint32_t crc = crc32_calc(fw_data, fw_len);
fal_part_write(dl_part, fw_len, &crc, 4);
// 设置升级标志
*(volatile rt_uint32_t *)FLAG_ADDR = 0xA5A5;
// 复位
system_reset();
}
```
## 4. 注意事项
- **Flash 磨损均衡**:频繁擦写同一分区会缩短 Flash 寿命,建议使用磨损均衡算法或限制升级次数。
- **原子操作**:设置升级标志时,确保写入操作是原子的(如使用 Flash 编程的原子性),避免半写状态。
- **看门狗**:在 Bootloader 中也要启用看门狗,防止升级过程卡死。
- **版本兼容**:App 和 Bootloader 的通信协议(如标志地址)需固定,升级 Bootloader 时需谨慎。
- **测试覆盖**:模拟断电、传输错误、校验失败等场景,验证回滚机制。
## 5. 总结
通过合理的分区划分、CRC 校验、状态标志和回滚机制,基于 RT-Thread 的 OTA 升级可以做到高可靠性。本文提供的设计思路和代码示例可直接应用于实际项目,但需根据具体硬件调整 Flash 地址和分区大小。容错设计的本质是“假设一切都会出错”,并提前准备应对方案,这样才能保证设备在恶劣环境下依然稳定运行。