摘要:无线升级(OTA)固件包 200KB,传输到一半突然断电,重启后进不了程序?不是网络不稳,而是 缺少断点记录(Checkpoint)和双分区回滚(Rollback)机制。本文解析工业级 OTA 的完整实现思路。


一、反面教材(裸奔式 OTA)

场景:收到一包就直接往 Application 区写。

// 收到第一包
FLASH_Erase(APP_ADDR);
// 每收到一包
FLASH_Write(APP_ADDR + offset, data);
// 写完校验
if (CRC_OK) Set_Boot_Flag(BOOT_APP);

痛点:

  • 断电变砖:写到一半断电,Flash 里是残缺固件,重启直接 HardFault。

  • 无回滚:新固件有 Bug,启动失败,只能上门拆机重刷。

  • 重传浪费:断了要从 0 重传,低功耗设备受不了。


二、进阶写法(双分区 + 断点 + 回滚)

核心思想:永远不在运行分区上直接写;记录进度;新固件跑不起来自动回退。

1. Flash 分区规划(铁律)

+-------------------+ 0x08000000
| Bootloader (32KB) |
+-------------------+
| App_A (Active)    |  <- 当前运行
+-------------------+
| App_B (Download)  |  <- OTA 写入区
+-------------------+
| Param / OTA_State |  <- 记录版本/进度/CRC
+-------------------+

2. OTA 状态结构体(断电记忆)

typedef struct {
    uint32_t magic;        // 0xAA55AA55 表示有效
    uint32_t total_size;
    uint32_t written_offset;// 断点位置
    uint32_t crc_received;
    uint8_t  state;        // 0:空闲 1:下载中 2:校验OK 3:激活待重启
    uint16_t crc_header;
} OTA_State_t;

写入策略:

  • 每接收并写入 N 包(或 1KB 对齐),更新 written_offsetcrc_received,回写 Flash。

  • 这样断电重启后,Bootloader 可读出 written_offset,通知上位机从断点续传

3. 断点续传流程

[设备重启]
  -> Bootloader 读 OTA_State
  -> 若 state == DOWNLOADING:
       上报断点 offset 给上位机
       上位机发送 offset 之后的数据
  -> 继续写入 App_B

4. 校验与激活(原子操作)

// 全部写完
if (Calc_CRC(APP_B_ADDR, total_size) == crc_expected) {
    ota_state.state = OTA_STATE_VERIFIED;
    Save_OTA_State(&ota_state);
    
    // 切换启动分区(改 Option Bytes 或 Boot Flag)
    Set_Boot_Partition(BOOT_APP_B);
    NVIC_SystemReset();
}

5. 回滚机制(Watchdog + 标记位)

新固件第一次启动必须"自证清白":

// App_B main()
int main(void)
{
    if (Is_First_Boot()) {
        // 执行关键外设自检
        if (Peripheral_Self_Test_OK()) {
            Clear_First_Boot_Flag();
            Start_IWDG();  // 开启独立看门狗
        } else {
            Request_Rollback_To_App_A(); // 回滚
            NVIC_SystemReset();
        }
    }
    // ...
}
  • 若新固件 没喂狗 / 启动失败 / 自检不过​ → 看门狗复位或主动回滚 → Bootloader 切回 App_A。


三、进阶收益

维度

裸奔 OTA

工业级 OTA

断电安全

变砖

断点续传

新固件有 Bug

变砖

自动回滚 App_A

流量

全量重传

只传剩余包

量产信心


四、工程级最佳实践

  1. Bootloader 绝不升级

    • Bootloader 区只读,OTA 只换 App 分区,防死锁。

  2. 签名校验(可选进阶)

    • 除 CRC 外,用 RSA/ECDSA 验签,防恶意固件。

  3. 版本号记录

    • Param 区存 fw_version,便于云端追溯。

  4. 最小 OTA State 写入次数

    • 每 4KB 或每帧更新一次,避免频繁擦写 Param 区。


五、总结 Checklist

  • [ ] 是否采用双 App 分区(Active / Download)?

  • [ ] 是否记录了断点 written_offset可供续传?

  • [ ] 新固件是否有首次启动自检 + 回滚逻辑?

  • [ ] Bootloader 是否只切换启动标志,不写 App 内容?


六、写在最后(关注我,少走弯路)

我是 gqqsherry666,一个拒绝调包、专注工程落地的嵌入式架构师。

OTA 是产品上线后的第一道鬼门关。

没有断点续传和回滚,一次升级失败就可能让你失去所有用户信任。

关注我的专栏《嵌入式系统架构实战》,系列正式完结。


原创文章,转载请注明出处。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐