2. OTA升级核心概念:全量升级与差分升级、升级包版本管理、回滚机制

好,咱们进入第二章。这一章我打算把OTA升级里最核心的几个概念掰开揉碎讲清楚。说白了,你搞懂了全量升级和差分升级的区别,学会了怎么管版本号,再掌握了回滚机制,那OTA这块地基就算打牢了。

我在项目里见过不少团队,上来就写升级逻辑,结果版本号乱成一锅粥,回滚更是想都没想。最后设备变砖,只能返厂。嗯,咱们别走那条路。

2.1 全量升级 vs 差分升级

这两种方式,是OTA升级的两条腿。你得根据场景选,不能瞎用。

2.1.1 全量升级

全量升级,顾名思义,就是把整个固件镜像完整地下载到设备上,然后替换掉旧固件。我刚开始做IoT时,觉得这方法太笨了——每次都传一整包,流量不要钱吗?

但后来发现,它有个巨大的优点:简单、可靠。你想想看,整个镜像都在,校验、签名、烧写,每一步都清清楚楚。出错了?重新下载就行。

适用场景:

  • 设备存储空间充足(比如网关、工控机)
  • 网络带宽够大(Wi-Fi、以太网)
  • 升级频率低,对流量不敏感
  • 首次烧录或重大版本变更

我个人习惯:在原型验证阶段,我永远先用全量升级。等逻辑稳定了,再考虑差分。别一上来就搞复杂,容易翻车。

2.1.2 差分升级

差分升级就聪明多了。它只下载旧固件和新固件之间的“差异部分”。这个差异,我们叫它“差分包”或“补丁包”。

设备收到差分包后,在本地把旧固件和补丁合并,生成新固件,再烧进去。这个过程,说白了就是“打补丁”。

我做过一个NB-IoT的项目,每次全量升级要传2MB,设备根本扛不住。换成差分后,差分包只有200KB左右,流量省了90%。那感觉,真爽。

注意:差分升级虽然省流量,但计算开销大。设备需要在本地做合并运算,CPU和内存不够的话,容易卡死。我曾经在一个Cortex-M0的芯片上跑差分合并,结果内存溢出,直接死机。后来加了内存池管理才搞定。

两种方式怎么选?我整理了个对比表,你一看就明白:

对比维度 全量升级 差分升级
下载数据量 大(完整镜像) 小(仅差异部分)
本地计算开销 低(直接烧写) 高(需要合并运算)
存储需求 需要双备份区 需要临时存储区
可靠性 高(校验简单) 中等(合并可能出错)
适用网络 Wi-Fi、以太网 蜂窝、LoRa、NB-IoT
升级频率 低 高

2.2 升级包版本管理

版本管理这事儿,看着简单,做起来全是坑。我见过有人用日期当版本号,结果同一天发了两个包,彻底乱套。还有人用Git commit hash,长到记不住,调试时根本对不上。

我的建议是:用语义化版本号。格式是 主版本.次版本.修订号,比如 2.1.3。

  • 主版本:不兼容的API变更,或者重大架构调整
  • 次版本:向下兼容的功能新增
  • 修订号:向下兼容的问题修复

举个例子:

v1.0.0 → 初始版本
v1.1.0 → 新增了蓝牙配网功能
v1.1.1 → 修复了蓝牙断连的bug
v2.0.0 → 重构了通信协议,不兼容旧版

避坑指南:我曾经在一个项目里,把版本号写成了字符串比较。结果 "1.10.0" 被当成比 "1.9.0" 小,因为字符串比较是按字符逐位比的。后来我改成用三个uint16_t分别存主、次、修订号,再也没出过问题。

版本号在升级包里的位置也很关键。我习惯把它放在固件头的固定偏移处,这样设备在下载前就能先读版本号,决定要不要继续。省流量,也省时间。

下面这张图,是我常用的升级包结构:

升级包结构示意图 升级包结构 包头 魔数: 0xOTA1 版本: 2.1.3 包类型: 全量/差分 包大小: 1024KB 校验算法: SHA256 签名区 签名算法: ECDSA 签名长度: 64字节 签名值: 0xAB...CD 固件数据 全量: 完整固件镜像 差分: 差异补丁数据 压缩: 可选gzip/lzma 加密: 可选AES-128 设备先解析包头,校验签名,再决定是否下载固件数据

2.3 回滚机制

回滚,就是升级失败后,能退回到上一个能用的版本。这玩意儿,是OTA的最后一道防线。没有回滚的OTA,就是在裸奔。

我见过最惨的一次,某智能门锁OTA升级到一半断电了,固件只写了一半。重启后,系统既不是新版本也不是旧版本,直接变砖。用户被锁在门外,打电话骂了半小时。

从那以后,我设计的OTA方案,回滚是必选项,不是可选项。

2.3.1 双备份区方案

这是最常用的方案。说白了,就是Flash里放两个固件区:一个叫运行区,一个叫备份区。

  • 正常运行时,从运行区启动
  • 升级时,把新固件写入备份区
  • 校验通过后,切换启动标志,下次从备份区启动
  • 如果新固件启动失败,自动切回运行区

关键点:切换启动标志这一步,一定要在确认新固件能正常工作之后再做。我习惯的做法是:新固件启动后,先跑一段“自检程序”,自检通过后才把标志位写死。这叫“延迟提交”。

2.3.2 回滚触发条件

什么时候触发回滚?我总结了三种情况:

  1. 启动失败:设备连续重启超过N次(比如3次),自动回滚
  2. 看门狗超时:新固件在规定时间内没有喂狗,系统复位后回滚
  3. 用户手动触发:通过按键或远程指令强制回滚

注意:回滚次数也要有限制。我曾经见过一个设备,因为硬件故障导致每次启动都失败,结果它不停地来回滚、升级、再回滚,陷入了死循环。后来我加了“回滚计数器”,连续回滚超过3次就停止自动升级,只保留当前版本。

2.3.3 回滚流程

下面这张图,是双备份区回滚的完整流程:

双备份区回滚流程图 双备份区回滚流程 正常启动(运行区) 收到升级包 写入备份区 校验签名和完整性 校验成功 → 切换启动标志 校验失败 → 保留旧版本

2.3.4 回滚策略对比

除了双备份区,还有几种回滚策略。我整理了一下:

策略 存储开销 可靠性 实现复杂度 适用场景
双备份区 高(2倍Flash) 高 低 存储充足的设备
单备份区+差分包 中(1.2倍Flash) 中 高 存储受限的设备
远程回滚 低(仅当前版本) 低(依赖网络) 中 网络稳定的设备
无回滚 最低 最低 最低 不推荐,除非你不在乎变砖

我的建议:只要Flash够用,就用双备份区。这是最省心的方案。如果Flash实在紧张,可以考虑单备份区+差分包,但一定要把合并逻辑测透。远程回滚我一般不推荐,因为设备如果网络都连不上,你拿什么回滚?