4、在线升级核心原理:双镜像启动原理(Golden Image + Update Image),看门狗回滚机制

各位工程师朋友,今天我们来聊聊在线升级里最核心、也最让人头疼的问题——升级失败了怎么办?

你想想看,产品已经部署到现场了,OTA升级包发过去,结果写到一半断电了,或者Flash坏块了,设备直接变砖。这种场景,我在项目里遇到过不止一次。所以,双镜像启动和看门狗回滚机制,就是用来解决这个问题的。

4.1 为什么需要双镜像?

说白了,就是给自己留一条后路。

传统的单镜像方案,升级时直接覆盖原有程序。一旦写入过程中出现任何异常,系统就再也起不来了。你只能派人去现场拆机、烧录,成本极高。

双镜像的思路很简单:保留一个永远不会被破坏的“安全启动区”,也就是Golden Image。另一个区域用来放更新程序,叫Update Image。就算Update Image写坏了,系统还能从Golden Image启动,保证设备不彻底死掉。

核心原则:Golden Image 只读,Update Image 可读写。升级操作只影响 Update Image,绝不碰 Golden Image。

4.2 双镜像启动流程

我习惯把启动流程分成三个阶段:引导、校验、切换。下面这张图可以帮你快速理解整体逻辑。

双镜像启动与看门狗回滚流程图 系统上电 Bootloader 启动 Update 有效? 启动 Update Image 启动 Golden Image 看门狗超时回滚 Golden Image(安全区) Update Image(升级区) 看门狗回滚路径

流程其实不复杂,我拆开来讲。

4.2.1 第一阶段:Bootloader 引导

系统上电后,首先执行的是Bootloader。Bootloader很小,放在Flash的最开头,一般只有几十KB。它的任务只有一个:决定启动哪个镜像

Bootloader会去检查Update Image的头部信息。这个头部里通常包含:

  • 镜像有效标志:一个特定的魔数(比如0xA5A5A5A5)
  • CRC校验值:对整个镜像的校验
  • 版本号:用于判断是否是新版本
  • 镜像大小:加载时需要知道长度
我的经验:镜像头部最好再加一个“升级完成标志”。我曾经遇到过一种情况:升级包写完了,CRC也对了,但系统复位后就是起不来。后来发现是升级过程中某个外设配置没写完。加了这个标志后,只有应用层主动置位才算升级完成,否则Bootloader一律认为升级失败。

4.2.2 第二阶段:镜像校验

Bootloader拿到头部信息后,会做两件事:

  1. 检查魔数:如果魔数不对,说明Update Image区域是空的或者数据损坏,直接跳转到Golden Image。
  2. 计算CRC:对整个Update Image做CRC校验。如果CRC不对,说明镜像被破坏了,同样跳转到Golden Image。

只有两项都通过了,Bootloader才会把控制权交给Update Image。

注意:CRC算法建议用CRC32,不要用CRC8或CRC16。Flash的位翻转错误在工业现场并不罕见,CRC32的检错能力更可靠。我见过有人用累加和做校验,结果两个不同镜像的累加和居然一样,这种坑踩一次就够了。

4.2.3 第三阶段:应用启动与看门狗回滚

这里有个关键点:即使CRC校验通过了,也不能保证应用一定能正常运行

为什么?因为CRC只保证数据完整性,不保证逻辑正确性。比如你升级了一个有bug的固件,启动后系统死机了,怎么办?

这就是看门狗回滚机制发挥作用的地方。

具体做法是这样的:

  • Bootloader启动Update Image之前,先开启一个长超时看门狗(比如30秒)。
  • Update Image启动后,必须在看门狗超时之前,完成初始化并主动喂狗
  • 如果Update Image死机了,看门狗超时,系统复位。
  • 复位后Bootloader再次启动,发现Update Image的“升级完成标志”没有被置位,就知道上次启动失败了。
  • 于是Bootloader自动切换到Golden Image,并标记Update Image为无效,下次不再尝试启动它。
一句话总结:看门狗回滚的本质是“用复位来检测失败,用标志位来记住失败”。

4.3 看门狗回滚的工程实现

嗯,这里我直接给出一段伪代码,帮你理解核心逻辑。实际项目中,你根据芯片平台调整即可。

// Bootloader 主流程
void bootloader_main(void) {
    // 1. 检查 Update Image 是否有效
    if (check_update_image_valid()) {
        // 2. 清除升级完成标志
        clear_update_complete_flag();
        
        // 3. 设置看门狗超时时间为30秒
        watchdog_init(30000);
        
        // 4. 跳转到 Update Image
        jump_to_update_image();
        
        // 注意:如果Update Image正常启动,它会主动喂狗
        // 如果它死机了,看门狗会复位系统
    }
    
    // 5. 如果Update无效,直接启动Golden Image
    jump_to_golden_image();
}

// Update Image 启动后
void application_main(void) {
    // 1. 初始化硬件
    hardware_init();
    
    // 2. 喂狗,告诉Bootloader我活过来了
    watchdog_feed();
    
    // 3. 设置升级完成标志
    set_update_complete_flag();
    
    // 4. 进入主循环
    while(1) {
        // 正常业务逻辑
        // 定期喂狗
        watchdog_feed();
    }
}
避坑指南:我曾经犯过一个错误——把看门狗超时时间设得太短,只有5秒。结果Update Image初始化一个外部Flash需要8秒,每次启动都被看门狗复位。后来我把超时时间改成了30秒,并在初始化完成后立即喂狗,问题就解决了。记住:超时时间要大于最慢的初始化路径

4.4 Flash分区规划建议

双镜像启动离不开合理的Flash分区。我一般这样规划:

分区名称 起始地址 大小 说明
Bootloader 0x00000000 64KB 引导程序,只读
Golden Image 0x00010000 512KB 出厂固件,只读
Update Image 0x00090000 512KB 升级固件,可读写
参数区 0x00110000 64KB 存储标志位、配置参数

这里有个细节:参数区一定要单独划分。不要把标志位放在镜像内部,否则每次升级都会覆盖掉。参数区用来存“升级完成标志”、“回滚次数”、“当前启动镜像编号”等信息。

4.5 回滚次数限制

最后提一个容易被忽略的点:回滚次数限制

如果Update Image每次启动都失败,系统就会反复在Update和Golden之间切换。每次切换都是一次复位,如果现场设备频繁复位,用户会投诉的。

我的做法是:在参数区记录一个回滚计数器。每次从Update回滚到Golden时,计数器加1。如果连续回滚超过3次,就永久锁定在Golden Image,不再尝试启动Update Image。同时通过日志或指示灯提示用户“升级失败,请联系售后”。

核心要点回顾:
  • 双镜像:Golden Image 保底,Update Image 升级
  • Bootloader 负责校验和决策
  • 看门狗回滚:超时复位 + 标志位判断
  • 回滚次数限制:防止无限复位

好了,双镜像启动和看门狗回滚机制就讲到这里。这套方案我在多个工业产品上验证过,稳定可靠。你只要把分区规划好、标志位设计清楚,在线升级的安全性就能上一个台阶。