# 基于 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 地址和分区大小。容错设计的本质是“假设一切都会出错”,并提前准备应对方案,这样才能保证设备在恶劣环境下依然稳定运行。