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区。
我通常的做法是:
- Application启动后,主动向Bootloader报告「我活得好好的」。
- Bootloader设置一个看门狗定时器,比如30秒。
- 如果30秒内没收到Application的心跳,Bootloader就认为新固件有问题,自动切回旧分区。
关键点:这个「健康报告」机制一定要在Application的早期初始化阶段就做。别等到业务逻辑都跑起来了才报告,万一初始化到一半就卡死了,看门狗都来不及救你。
2.4 实际项目中的一些坑
嗯,说了这么多理论,我分享几个实际踩过的坑:
- 坑一:Bootloader太大。我一开始把Bootloader写了200KB,结果发现Flash第一个扇区只有64KB。后来硬着头皮把Bootloader拆成两段,第一段做最小启动,第二段做升级逻辑。麻烦是麻烦了点,但稳定。
- 坑二:分区地址写死。早期项目我把分区地址硬编码在代码里,后来换了颗Flash容量更大的芯片,所有地址都要改一遍。现在我都是用宏定义+链接脚本自动计算地址,换芯片改一个宏就行。
- 坑三:升级过程中断电。这个其实无解,只能靠双备份和校验来兜底。但有一个细节:下载新固件时,建议先擦除备用区,再逐块写入。不要边擦边写,万一擦到一半断电,备用区就废了。
2.5 小结
这一章的内容其实就三个核心点:
- Bootloader要小、要稳,只做启动和升级两件事。
- 分区要提前规划好,注意扇区对齐和大小预留。
- 双备份系统是OTA的保命符,切换策略和回滚机制缺一不可。
下一章我们会深入Bootloader的具体实现,包括怎么跳转到Application、怎么处理中断向量表重映射。这些细节搞不定,你的固件可能连启动都启动不了。
好,今天就到这儿。有问题咱们群里聊。