2. 固件架构基础:Bootloader与Application分区设计、双备份(A/B)系统原理

好,咱们进入第二章。这一章是整个OTA方案的基石。

你想想看,如果固件架构本身就不支持远程升级,那后面讲的所有东西都是空中楼阁。我见过不少团队,上来就急着写应用代码,结果到了要OTA的时候才发现——哎呀,Bootloader没留接口,Flash分区没规划,升级过程中断电就变砖。

嗯,这种坑我踩过不止一次。所以这一章,咱们把地基打牢。

2.1 为什么需要Bootloader?

说白了,Bootloader就是芯片上电后第一个跑的程序。它的任务很简单:决定接下来该执行哪个Application。

我习惯把Bootloader比作一个「看门大爷」。他不管楼里具体在干什么业务,他只负责开门、关门、确认进来的人对不对。

  • 正常启动:Bootloader检查Application的完整性,没问题就直接跳转。
  • 升级模式:收到升级指令,Bootloader接管控制权,把新固件写入Flash。
  • 异常恢复:Application跑飞了或者校验失败,Bootloader能兜底,不至于变砖。

核心原则:Bootloader要尽量小、尽量稳定。我见过有人把Bootloader写得比Application还复杂,结果Bootloader自己先出bug了——这就很尴尬。

2.2 分区设计:Flash怎么切?

POS机的Flash通常不大,几兆到几十兆。怎么切分区,直接决定了OTA的灵活性和可靠性。

我个人习惯把Flash至少分成三个区:

分区名称 起始地址 大小 用途
Bootloader 0x08000000 64KB 启动管理、升级入口
Application A 0x08010000 1MB 主运行固件
Application B 0x08110000 1MB 备份固件
参数区 0x08210000 128KB 配置、升级标志、日志

注意,这里的地址只是举例。实际项目中要根据芯片的Flash大小和扇区对齐来调整。我曾经在一个项目里没注意扇区对齐,结果擦除操作把隔壁分区的数据也干掉了——嗯,那晚加班到凌晨三点。

小技巧:分区大小建议按Flash的最小擦除块(Sector/Page)的整数倍来规划。比如STM32的某些系列,一个Sector是16KB,那你分区大小最好是16KB的倍数。

2.3 双备份(A/B)系统原理

双备份系统,也叫A/B系统。说白了就是:Flash里同时存两份Application固件。

为什么会这样?因为OTA升级有风险。万一升级过程中断电了、网络断了、固件包损坏了,你至少还有一份能跑的旧固件。

我给你们画个流程图,用文字描述一下:

上电 → Bootloader读取升级标志
  │
  ├─ 无升级标志 → 检查A区完整性
  │   ├─ A区OK → 跳转A区
  │   └─ A区失败 → 检查B区完整性
  │       ├─ B区OK → 跳转B区
  │       └─ B区失败 → 进入恢复模式
  │
  └─ 有升级标志 → 下载新固件到备用区
      ├─ 下载成功 → 校验 → 切换启动区
      └─ 下载失败 → 回滚到当前区

这个流程看起来简单,但实际实现时有很多细节。我重点说三个:

2.3.1 升级标志怎么存?

升级标志不能放在Application分区里。为什么?因为升级过程中Application可能会被擦除。

我习惯把升级标志放在独立的参数区,用两个字节来存:一个表示「请求升级」,一个表示「升级完成」。而且我会写三次,防止Flash写入过程中掉电导致标志位半写半不写。

注意:千万不要只用单个字节做标志位。Flash写入过程中如果掉电,那个字节可能处于「未擦除」和「已写入」之间的中间态。我遇到过,读出来是0xFF,但实际是0x7F,程序直接懵了。

2.3.2 切换策略:什么时候切?

新固件下载到备用区后,什么时候切过去?

有两种策略:

  • 立即切换:下载完校验通过,马上把启动标志指向新分区。下次重启就跑新固件。优点是快,缺点是如果新固件有严重bug,用户一重启就中招。
  • 延迟切换:下载完先不切,等用户下次主动重启或者系统空闲时再切。我比较推荐这种,尤其是POS机这种对稳定性要求高的设备。

我在一个支付终端项目里用的就是延迟切换。新固件下载完后,系统会弹个提示:「新版本已就绪,下次开机时生效」。用户确认后,才写切换标志。这样用户体验好,万一用户不想重启,也不影响当前交易。

2.3.3 回滚机制:怎么保底?

双备份最大的价值就是能回滚。

假设你切到了B区,结果B区跑起来发现有问题——比如刷卡功能异常。这时候Bootloader应该能自动回滚到A区。

我通常的做法是:

  1. Application启动后,主动向Bootloader报告「我活得好好的」。
  2. Bootloader设置一个看门狗定时器,比如30秒。
  3. 如果30秒内没收到Application的心跳,Bootloader就认为新固件有问题,自动切回旧分区。

关键点:这个「健康报告」机制一定要在Application的早期初始化阶段就做。别等到业务逻辑都跑起来了才报告,万一初始化到一半就卡死了,看门狗都来不及救你。

2.4 实际项目中的一些坑

嗯,说了这么多理论,我分享几个实际踩过的坑:

  • 坑一:Bootloader太大。我一开始把Bootloader写了200KB,结果发现Flash第一个扇区只有64KB。后来硬着头皮把Bootloader拆成两段,第一段做最小启动,第二段做升级逻辑。麻烦是麻烦了点,但稳定。
  • 坑二:分区地址写死。早期项目我把分区地址硬编码在代码里,后来换了颗Flash容量更大的芯片,所有地址都要改一遍。现在我都是用宏定义+链接脚本自动计算地址,换芯片改一个宏就行。
  • 坑三:升级过程中断电。这个其实无解,只能靠双备份和校验来兜底。但有一个细节:下载新固件时,建议先擦除备用区,再逐块写入。不要边擦边写,万一擦到一半断电,备用区就废了。

2.5 小结

这一章的内容其实就三个核心点:

  1. Bootloader要小、要稳,只做启动和升级两件事。
  2. 分区要提前规划好,注意扇区对齐和大小预留。
  3. 双备份系统是OTA的保命符,切换策略和回滚机制缺一不可。

下一章我们会深入Bootloader的具体实现,包括怎么跳转到Application、怎么处理中断向量表重映射。这些细节搞不定,你的固件可能连启动都启动不了。

好,今天就到这儿。有问题咱们群里聊。