2. 设备端升级架构设计
OTA 能否安全落地,关键不在云端推送,而在设备端是否有一套“可切换、可验证、可回退”的升级架构。这一节我们把设备端拆成四个相互咬合的部件:Bootloader、分区表、固件镜像格式、启动切换与回滚策略。它们共同回答一个问题——新固件写进去之后,设备凭什么敢启动它,又凭什么在它失败时退回旧版本。
Bootloader:升级架构的信任起点
Bootloader 是上电后最先运行的代码,职责是初始化最小硬件、读取启动标志、校验并跳转到应用固件。它必须足够小、足够稳,且自身通常不参与 OTA 更新,否则一旦刷坏就彻底变砖。工程上常把它分为两级:一级 Bootloader(BL1)固化在 ROM 或受保护扇区,只负责加载二级 Bootloader;二级 Bootloader(BL2)实现分区管理、镜像校验、启动计数与回滚决策。这样即使 BL2 升级失败,BL1 仍能恢复它。
Bootloader 需要具备的最小能力包括:读取分区表、计算镜像哈希、验证签名、维护启动标志、支持串口或 USB 恢复通道。以常见的 MCU 方案为例,BL2 可占用 64 KB Flash,应用分区各 512 KB,升级缓存分区 512 KB,参数区 8 KB 用于存放启动标志和版本信息。
分区表:把 Flash 划成有明确职责的区域
分区表定义了每块 Flash 区域的偏移、大小和用途。OTA 场景下最典型的是 A/B 双分区布局:A 分区运行当前固件,B 分区接收新固件;升级成功后切换启动目标,失败则保留原分区继续运行。相比单分区“原地覆盖”,双分区牺牲一半空间,换来的是断电安全和可回滚。
一个可参考的分区表示例如下(以 2 MB Flash 为例):
| 分区名 | 起始偏移 | 大小 | 用途 |
|---|---|---|---|
| bootloader | 0x000000 | 64 KB | BL2,含校验与切换逻辑 |
| slot_a | 0x010000 | 768 KB | 应用固件 A |
| slot_b | 0x0D0000 | 768 KB | 应用固件 B |
| params | 0x190000 | 8 KB | 启动标志、版本、重试计数 |
| factory | 0x192000 | 剩余空间 | 出厂恢复固件 |
分区表通常以固定结构体或 JSON 描述,编译时生成并烧录到固定偏移。Bootloader 与应用程序必须使用同一份分区定义,否则会出现“写对了地址、读错了分区”的隐蔽故障。
固件镜像格式:让 Bootloader 能读懂、能验证
裸 bin 文件没有元数据,Bootloader 无法判断它属于哪个版本、是否完整、是否被篡改。因此 OTA 镜像通常采用“头部 + 负载 + 尾部”的封装格式。头部包含魔数、格式版本、目标型号、固件版本、负载长度、负载哈希、签名算法与签名值;负载是实际可执行代码;尾部可放整镜像 CRC。
typedef struct {
uint32_t magic; // 0x4F544131 "OTA1"
uint16_t format_ver; // 镜像格式版本
uint16_t target_id; // 硬件型号标识
uint32_t fw_version; // 固件版本号
uint32_t payload_len; // 负载长度
uint8_t payload_hash[32]; // SHA-256
uint8_t signature[64]; // ECDSA P-256
} ota_header_t;
Bootloader 的校验顺序应为:魔数与型号匹配 → 头部哈希比对 → 签名验证 → 负载哈希比对。任何一步失败都不得切换启动目标。签名验证保证来源可信,哈希比对保证传输完整,两者缺一不可。
启动切换机制与回滚策略
启动切换依赖参数区中的一组标志:active_slot、pending_slot、boot_attempts、confirmed。典型流程是:新固件写入 B 分区并校验通过后,Bootloader 将 pending_slot 设为 B、boot_attempts 清零;下次启动时尝试引导 B,并递增尝试计数。若应用在限定次数内调用“确认接口”将 confirmed 置位,则 B 转为正式分区;若连续尝试超过阈值仍未确认,Bootloader 判定启动失败,回退到 A 分区并清除 pending 标志。
boot_attempts 阈值设为 3。应用启动后应在完成网络、外设自检后尽快确认,避免“能启动但功能异常”的固件被误判为成功。回滚策略还需考虑“确认后仍出问题”的场景。可在参数区保留上一版本槽位信息,并提供手动回滚命令;同时记录每次启动的复位原因与版本号,便于云端判断设备是否处于反复回滚状态。若 A、B 均不可用,则进入 factory 恢复分区,通过串口或本地接口重新烧录。
把上述四部分串起来看:分区表决定“写到哪里”,镜像格式决定“写进去的东西是否可信”,Bootloader 决定“启动谁”,启动标志与回滚策略决定“启动失败怎么办”。四者缺一,OTA 就只是“把文件传下去”,而不是“把设备安全地升级上去”。实际项目中,建议先用串口手动触发一次 A/B 切换与回滚,确认参数区读写、签名校验和计数逻辑都正确,再接入云端 OTA 流程。