第二节 固件升级原理:升级流程、分区表设计、Bootloader与App分区
好,咱们进入正题。固件升级这事儿,说白了就是让设备在运行中把自己给换了。听起来有点玄乎,对吧?我刚开始做基站开发时也觉得这玩意儿挺神奇——一个正在跑的程序,怎么能把自己给覆盖掉?
嗯,这里的关键在于「分工」。我们把存储空间分成几个区域,各干各的活。就像盖房子,你不能一边住人一边拆承重墙,得先搭个临时板房,再慢慢拆旧房。
2.1 升级流程:三步走,稳得很
一个完整的固件升级流程,我习惯把它拆成三个阶段:
- 下载阶段:新固件从远程服务器拉下来,存到备用区域
- 校验阶段:检查下载的文件是否完整、是否被篡改
- 切换阶段:让Bootloader接管,把新固件搬到主运行区
你想想看,如果下载到一半断电了怎么办?如果校验不通过就启动新固件,设备变砖了怎么办?这些问题,都是我们在设计流程时必须考虑的。
核心原则:升级过程中,任何时候断电或异常,设备都必须能恢复到可运行状态。说白了,就是「不死机」。
我在项目中遇到过最典型的情况:某次基站批量升级,网络突然抖动,30%的设备只下载了60%的固件。幸好我们设计了「断点续传」和「版本回退」机制,否则那场面...嗯,你懂的。
2.2 分区表设计:存储空间的「房产证」
分区表,就是告诉芯片:哪块地址归谁管,多大面积,干什么用。我建议你把它想象成一张地图。
一个典型的基站固件分区表长这样:
分区名 起始地址 大小 用途
-----------------------------------------------
Bootloader 0x08000000 64KB 启动加载器
App_Primary 0x08010000 1MB 主运行区(当前固件)
App_Backup 0x08110000 1MB 备份区(新固件下载区)
Config 0x08210000 128KB 配置参数区
Log 0x08230000 256KB 日志存储区
Factory 0x08270000 64KB 出厂固件(救砖用)
为什么要搞两个App分区?我刚开始做时也觉得浪费空间。直到有一次升级到一半,发现新固件有严重bug...你想想看,如果没有备份区,设备就彻底废了。两个分区,一个跑、一个备,这叫「双保险」。
我的经验:分区大小最好留20%余量。固件只会越来越大,别到时候想加个功能,发现空间不够了。我曾经就因为少算了200KB,导致后续三个版本都得重新调整分区表,那叫一个折腾。
2.3 Bootloader与App分区:谁先谁后?
Bootloader,就是上电后第一个跑的程序。它的任务很简单:检查App分区是否有效,有效就跳过去执行,无效就进入升级模式。
App分区,就是咱们真正干活的地方。基站协议栈、信号处理、网络管理,全在这里。
这两个分区的关系,我打个比方:
- Bootloader = 机场的塔台,负责引导飞机(App)起飞降落
- App = 飞机本身,负责执行飞行任务
塔台不能跟着飞机一起飞走,对吧?所以Bootloader分区是只读的,升级时绝对不能碰它。一旦Bootloader坏了,设备就真成砖了。
警告:千万不要在App运行期间擦写Bootloader分区!我见过有人为了省空间,把Bootloader和App放在同一个可擦写区域,结果一次升级失败,整个设备变砖,只能拆机用编程器救。那酸爽...你懂的。
2.4 升级流程的完整时序
咱们把整个流程串起来,看看实际跑起来是什么样子:
- 设备上电 → Bootloader启动
- Bootloader检查:App分区是否有效?
- 有效 → 跳转到App,正常运行
- 无效 → 进入升级等待模式(比如亮红灯、开FTP服务)
- App运行中收到升级指令 → 下载新固件到App_Backup分区
- 下载完成 → 校验CRC/SHA256,确保文件完整
- 设置标志位 → 告诉Bootloader「下次启动用新固件」
- 重启设备 → Bootloader读取标志位,从App_Backup启动
- 新固件运行 → 如果运行正常,把App_Backup复制到App_Primary
- 如果新固件启动失败 → Bootloader自动回退到App_Primary
你看,第9步和第10步是关键。为什么要复制?因为下次升级时,App_Backup要被覆盖,如果新固件一直跑在备份区,那下次升级就没地方存了。
避坑指南:我曾经设计过一个系统,升级后直接跑在备份区,不复制到主区。结果第二次升级时,备份区被覆盖,主区还是旧版本。设备重启后,Bootloader发现主区版本太老,又跳回备份区...嗯,死循环了。从那以后,我坚持「升级完必须同步到主区」。
2.5 分区表设计的几个坑
说几个我踩过的坑,你记一下:
- 对齐问题:分区起始地址必须是擦除块大小的整数倍。比如Flash的擦除块是64KB,那分区起始地址就得是0x10000、0x20000这样。不对齐的话,擦除时会误伤邻居。
- 版本号管理:每个分区头部都要存版本号。我习惯用4字节,格式是「主版本.次版本.修订号.构建号」。别用字符串,解析起来慢。
- 校验和冗余:分区表本身也要有校验。我见过有人把分区表放在Flash开头,结果一次写操作出错,整个分区表乱掉,设备直接变砖。后来我加了两个备份分区表,一个坏了用另一个。
嗯,说到校验,我再啰嗦一句:CRC32不够用,用SHA256。CRC32只能检测随机错误,防不了人为篡改。基站设备涉及网络安全,别在这种地方省钱。
2.6 小结
固件升级这事儿,说白了就是「稳」字当头。分区表设计好了,流程设计严谨了,剩下的就是代码实现的问题。我见过太多人把精力花在「怎么让升级更快」上,结果忽略了「怎么让升级不死机」。记住:基站设备可以慢,但不能死。
下一节,咱们聊聊具体的代码实现——怎么在STM32上写一个可靠的Bootloader。到时候我会把实际项目中的代码片段拿出来,咱们一行一行地过。