4、Bootloader设计原理(下):双备份启动策略、看门狗喂狗机制、启动超时处理
好,咱们接着聊Bootloader。上一节我们把基础框架搭起来了,这一节要聊的,才是真正让产品在医疗现场「扛得住」的关键。
说实话,医疗设备最怕什么?最怕升级到一半断电,或者启动时卡死。病人躺在手术台上,设备起不来,那可不是闹着玩的。所以双备份、看门狗、超时处理,这三板斧必须得硬。
一、双备份启动策略:给系统上双保险
什么叫双备份?说白了,就是Flash里存两份固件。一份是当前运行的,一份是备份的。我习惯叫它们:App_A 和 App_B。
你想想看,如果只有一份固件,升级写到一半断电了,那系统就彻底变砖了。医疗设备变砖?那后果我不敢想。
1.1 双备份的布局方式
我一般这样划分Flash:
|------------------|
| Bootloader | (固定区域,不参与升级)
|------------------|
| App_A (主分区) | (当前运行)
|------------------|
| App_B (备份分区) | (备用)
|------------------|
| 配置参数区 | (存放启动计数、版本号等)
|------------------|
这里有个细节:配置参数区很重要。我会在里面存一个字节叫 boot_flag,用来标记下次该启动哪个分区。
1.2 启动流程怎么走?
我设计的流程大概是这样的:
- 上电后,Bootloader先读配置参数区的
boot_flag。 - 如果
boot_flag指向 App_A,就校验 App_A 的CRC。 - 校验通过 → 跳转到 App_A 运行。
- 校验失败 → 自动切到 App_B 启动。
- 如果 App_B 也失败 → 进入恢复模式(等待串口或USB重新烧录)。
嗯,这里要注意:每次成功启动后,要把 boot_flag 更新一下。比如这次从 App_A 启动了,就把标志改成下次优先启动 App_A。但如果 App_A 连续启动失败,就要切到 App_B。
核心原则:永远保证至少有一份可用的固件。哪怕两份都坏了,Bootloader本身不能坏。
1.3 我在项目中遇到的坑
有一次,我发现设备偶尔会莫名其妙地切到备份区启动。查了好久,最后发现是CRC校验的时机不对。我在Bootloader里校验时,用的是App_A的CRC值,但App_A在运行过程中会修改自己的代码区(比如写参数到Flash末尾),导致CRC对不上。
从那以后,我学乖了:CRC校验只校验代码段,不校验可写的参数区。或者干脆把参数区单独划出来。
二、看门狗喂狗机制:别让系统死得不明不白
看门狗这东西,说白了就是一个定时器。你不定期喂它,它就复位系统。医疗设备里,看门狗是标配,不是选配。
但问题来了:Bootloader阶段要不要喂狗?
我个人习惯:要喂,但不能乱喂。
2.1 Bootloader阶段的喂狗策略
我一般这样设计:
- Bootloader启动后,立即初始化看门狗,超时时间设为 5 秒。
- 在关键步骤(如CRC校验、Flash擦写)完成后,喂一次狗。
- 跳转到App之前,把看门狗重新配置一下(或者交给App去管)。
为什么要这样?因为Bootloader阶段如果死循环了,看门狗能把它拉回来。但如果你每行代码都喂狗,那看门狗就形同虚设了——系统卡在某个地方,你还在喂狗,它永远不会复位。
我的经验:只在「确定性」的步骤完成后喂狗。比如「CRC校验完成」、「Flash擦除完成」、「跳转准备就绪」。不要在循环里喂狗。
2.2 避坑指南:我曾经犯过的错
我曾经在一个项目里,把看门狗的喂狗操作放在了 while(1) 主循环里。结果有一次,程序卡在了一个中断服务函数里,主循环还在跑,看门狗一直被喂,系统就是不复位。最后是现场工程师发现设备死机了,但看门狗没起作用。
从那以后,我改成:喂狗只放在关键路径上,不放在循环里。而且我会在喂狗前加一个标志位检查,确保程序确实在正常执行。
三、启动超时处理:别让用户等太久
医疗设备启动,一般要求 3秒内进入主界面。如果Bootloader卡住了,用户看到的就是黑屏。你想想看,医生急着用设备,结果等了10秒还没反应,他会怎么想?
3.1 超时处理的两种场景
我一般把超时分为两种:
| 场景 | 超时时间 | 处理方式 |
|---|---|---|
| 等待用户按键(进入升级模式) | 1~2秒 | 超时后直接启动当前固件 |
| 等待外部通信(如串口OTA指令) | 5~10秒 | 超时后进入默认启动流程 |
嗯,这里要注意:等待用户按键的时间不能太长。我见过有些设计等5秒,用户以为设备坏了,直接拔电。我个人习惯:1.5秒,够用。
3.2 超时处理的代码实现
我一般用SysTick来做超时计数,不用硬件定时器(省资源)。大概这样:
// 启动超时处理示例
#define BOOT_TIMEOUT_MS 1500 // 1.5秒超时
uint32_t tick_start = get_systick_ms();
while(1) {
if (check_uart_cmd()) {
// 收到升级指令,进入升级模式
enter_ota_mode();
break;
}
if ((get_systick_ms() - tick_start) > BOOT_TIMEOUT_MS) {
// 超时,直接启动
jump_to_app();
break;
}
}
这里有个小技巧:超时时间内,要顺便喂狗。不然看门狗先超时了,系统复位,又回到这个循环,永远进不了App。
警告:不要在超时循环里做Flash擦写等耗时操作。否则超时时间会不准,而且可能影响看门狗。
四、把三者结合起来:一个完整的启动流程
好了,我们把双备份、看门狗、超时处理串起来,看看完整的Bootloader启动流程是什么样的:
- 上电复位:初始化时钟、GPIO、看门狗(超时5秒)。
- 检查启动标志:读配置参数区的
boot_flag,决定启动哪个分区。 - 等待用户按键:1.5秒超时,期间喂狗。如果收到升级指令,进入OTA模式。
- 校验固件:对目标分区做CRC校验。校验通过后喂狗。
- 校验失败:切到备份分区,重新校验。如果备份也失败,进入恢复模式。
- 跳转前准备:关闭中断、关闭外设、重置看门狗配置。
- 跳转到App:修改栈指针,跳转。
你看,每一步都有兜底。双备份兜底固件损坏,看门狗兜底程序卡死,超时处理兜底用户等待。三个机制互相配合,才能保证医疗设备在恶劣环境下也能可靠启动。
总结一句话:Bootloader设计的核心不是「怎么启动」,而是「启动失败了怎么办」。双备份、看门狗、超时处理,就是给失败准备的退路。
好,这一节就聊到这儿。下一节我们开始讲OTA升级的通信协议设计,那又是另一个有意思的话题了。