ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

空调OTA升级方案:AB分区、掉电保护与自动回滚实战

空调OTA升级方案:AB分区、掉电保护与自动回滚实战 空调这种设备看起来不像手机那么复杂但真要把OTA升级做到“用户无感、出问题还能自动回来”难度一点都不小。我这两年帮客户调过好几套空调主控板项目几乎每次涉及OTA最后翻车的点都集中在三个地方一是固件分区安排得太随意升级包没地方落或者Bootloader没保护二是升级过程中断电Flash写坏一半设备直接变砖三是新固件能写进去但启动就崩溃没有任何自动恢复机制。这篇文章就把这三个问题放到一个完整的空调OTA工程方案里从分区设计、掉电保护、自动回滚到签名校验和实际排查顺序全部拆一遍。无论你是做STM32、ESP32还是其他MCU方案只要空调主控带联网模块这套思路都适用。最核心的目标只有一条升级失败不可怕怕的是失败之后设备起不来。1. 先说清楚空调OTA升级为什么比手机升级更难1.1 空调设备的运行环境与升级窗口手机OTA升级时用户至少有稳定的Wi-Fi、充电宝、流量和充裕的时间窗口。空调不一样它的主控板多数还是MCU方案Flash存储通常只有几MB内存可能从几十KB到几百KB算力和网络带宽都有限。升级过程中可能遇到的问题包括用户在空调面板上直接断电Wi-Fi模块断网低功耗模式把外设关闭升级还没完成看门狗已经复位MCU。我常见到的空调联网方案里还有一种分工模式值得注意。一种是Wi-Fi或蓝牙模块充当通信桥梁负责连接云端把升级包通过串口转发给主控MCU由主控MCU去写自己的Flash。另一种是MCU本身直接带网络协议栈或者挂在Modbus、RS485等工业总线上。前者的重点工作在串口通信的流控和分包后者的重点工作在协议栈稳定性和带宽限制。不管哪种架构升级过程都不能简单理解成“把新固件写进Flash”。它更像一个受控的、分阶段的事务每一步都要有明确状态掉电之后加电能恢复到上一个安全点。否则一次普通的升级动作就可能让设备长期处于不可用状态。1.2 升级失败带来的真实代价手机可以进入Recovery模式重新刷机用户最多有点烦。空调如果升级失败设备可能表现为开不了机、压缩机不动作、面板无显示或者反复重启。这些故障是用户直接能感知的不是“新功能没生效”那么简单。这类设备在用户家里维修成本和体验代价都很高。如果是中央空调或工程项目里的设备问题更大售后工程师上门以后可能只是为了刷一次固件或者更换主控板单次成本高等待周期长品牌方还会背上“质量差”的标签。所以空调OTA工程方案里的第一优先级不是“升级速度”而是“恢复能力”。一个升级动作可以慢一点但不能把设备变砖。1.3 三类机制必须作为一个整体设计AB分区解决的是“升级写入时不让当前系统受影响”的问题。掉电保护解决的是“写入过程中电断了还能回来”的问题。自动回滚解决的是“新固件本身是坏的还能回到旧固件”的问题。只做AB分区不处理掉电新分区可能写一半就损坏。只做掉电保护不处理回滚新固件挂掉时只能干等人工介入。只做回滚没有AB分区的备份回滚目标可能早就被覆盖了。实际项目里有一种很典型的翻车场景分区、掉电保护都做了但没做启动成功确认。新固件也能写进去也能跳转结果初始化逻辑里有一个死循环看门狗不断复位系统永远困在新槽位用户看到的只是空调反复重启。这个问题不靠自动回滚机制根本解不了。所以这三点必须同步设计而不是哪个问题先出现再补哪个。2. 先定分区方案AB分区是整套机制的地基2.1 AB分区解决什么问题AB分区简单说就是把固件存放区做成两个大小相同的槽位一个叫Slot A一个叫Slot B。当前运行的固件在其中一个槽位升级时把新的固件写入另一个槽位。写入完成后通过标志位或引导参数让Bootloader下次从新槽位启动。如果新槽位启动失败Bootloader还可以切回旧槽位。这个方案最大的价值是整个升级过程中始终有一个完整可用的固件。哪怕新固件写入失败设备仍然可以用旧固件正常开机。不要在只有一份App分区的情况下谈可靠OTA。比如你当前固件在0x08000000新固件直接往里覆盖一旦写入过程中掉电或数据包缺失整个设备就没有可用的App了。AB分区不是锦上添花是安全底线。2.2 最小分区表示例与存储规划下面是一个比较典型的最小分区布局适合主控Flash在2MB到4MB的空调控制器分区大小用途bootloader64KB启动引导、密钥校验、回滚决策app_a1MB运行固件Aapp_b1MB运行固件B升级写入目标factory512KB出厂固件极端恢复使用param32KB用户参数、故障记录、配置项flag16KB升级状态标志、启动计数log128KB运行日志、升级日志实际大小要根据你的固件体积调整。唯一的原则是Bootloader、App、参数、日志、标志位不共用分区。常见的问题就是把参数区和固件区放一起升级时不小心把用户设置覆盖了。分区地址要在链接脚本和Bootloader里保持一致。很多启动异常问题追到根因就是链接脚本里App的起始地址和Bootloader跳转地址对不上。2.3 为什么Bootloader必须独立有些项目图省事把Bootloader和App合在一起每次升级连引导程序一起覆盖。这样做风险很大一旦Bootloader写入失败或升级包校验有问题整个设备就失去了恢复能力。Bootloader应该是独立分区承担三件事校验升级包、选择启动槽位、执行回滚判断。它本身要做得足够小升级频率很低甚至在出厂后不再更新。如果后面因为方案调整必须更新Bootloader也要单独走一次窄窗口升级比如本地串口刷写并且必须保证整个升级过程不中断。把Bootloader做小的另一个好处是它能放在Flash的最前面上电后立即运行不依赖外部存储。它只需要访问Flag区和分区表不需要完整驱动无线模块、文件系统或设备协议栈所以逻辑越简单越可靠。2.4 分区切换与启动计数的设计每次设备上电Bootloader需要决定从哪个槽位启动。这里的核心变量有三个当前槽位ID、新固件启动是否成功、启动失败计数。常见做法是Bootloader读Flag区取出槽位ID和启动计数。如果上次运行成功计数清零按当前槽位启动。如果上次运行异常计数加一当计数超过阈值时强制切换或回滚到另一个槽位。下面是一个简化版的参考逻辑uint32_t try_count read_startup_count(); if (check_app_success_flag()) { clear_startup_count(); } else { try_count; write_startup_count(try_count); } if (try_count MAX_BOOT_TRIES) { switch_to_other_slot(); clear_startup_count(); } jump_to_selected_slot();这个机制的实现并不复杂但要注意时序启动计数应该在Bootloader里增加而不能只在App里记录。因为App可能连启动都没完成就根本没有机会写日志。3. 掉电保护OTA升级里最容易翻车的变量3.1 掉电对Flash写入的影响OTA升级过程中绝大多数“变砖”案例都跟掉电有关。MCU内置Flash有擦除和写入的物理限制写入一个Page之前要先擦除而擦除是一个比较耗时的操作。如果掉电刚好发生在擦除或写入过程中这个区域的数据可能变成不确定状态。更要命的是升级过程中如果固件写到一半槽位里的数据已经破坏了但标志位又没有更新Bootloader无法判断这个槽位是完整的还是损坏的。所以掉电保护不能只靠硬件稳定必须靠状态机。掉电还有一个隐蔽问题供电恢复后电源可能存在反复跌落和回升。如果MCU在电压不稳时就开始跑OTA逻辑很容易造成二次破坏。所以上电检测也很重要。3.2 状态标志位的设计和写入顺序我建议把升级状态定义成一组枚举值每个关键阶段开始前先写标志位再执行真正动作。typedef enum { STATE_IDLE 0, STATE_DOWNLOADING, STATE_VERIFYING, STATE_WRITING, STATE_SWITCH, STATE_ACTIVATED, } ota_state_t;状态含义STATE_IDLE无升级任务STATE_DOWNLOADING正在下载升级包STATE_VERIFYING正在校验固件包STATE_WRITING正在写入目标槽位STATE_SWITCH新固件写入完成待切换STATE_ACTIVATED新固件启动并确认成功写入顺序很关键。先写STATE_WRITING再开始写Flash。先写STATE_SWITCH再更新启动槽位。任何一步掉电重启后Bootloader都能看到上一个明确状态决定是继续、重试还是回滚。标志位本身也要做冗余。比较保险的方式是保存两份以上副本并附带CRC或校验值。如果一份副本被掉电写坏Bootloader还能从另一份恢复判断。不要只在一个字节里记状态那样很容易出现“写了一半”的混乱。3.3 升级动作分阶段执行升级动作不要一次性做完。我一般拆成下面几个阶段下载升级包到临时存储区边下载边做分片校验。对完整升级包做整体CRC或哈希校验。擦除目标槽位。按块写入固件。写完后回读关键区域做校验。更新启动切换标志。每一小步都允许被中断。中断之后加电恢复逻辑负责决定下一步。不要让“下载、校验、擦除、写入、切换”全部挤在一个大循环里否则一个中断点就会导致整个系统状态不可控。3.4 掉电后的恢复流程掉电恢复的推荐顺序是上电后Bootloader先读标志区判断当前处于哪个状态。如果处于STATE_IDLE或STATE_ACTIVATED正常启动。如果处于STATE_DOWNLOADING或STATE_VERIFYING删除临时升级包恢复空闲状态继续用旧固件启动。如果处于STATE_WRITING说明目标槽位可能写坏了需要判断目标槽位是否还有效。如果没有回滚到另一个槽位。如果处于STATE_SWITCH说明写入完成但还没确认启动成功可以按新槽位启动然后走自动回滚判断。这个流程的关键是任何时候都要保证有一个槽位是完整的。就算当前槽位损坏只要另一个槽位还是好的设备就有救。3.5 硬件配合掉电检测与储能电容软件再完善也需要硬件给一点缓冲时间。建议在电源输入端增加掉电检测电路当检测到供电下降低于阈值时给MCU一个中断信号。有了这个信号MCU可以快速停止正在写Flash的任务保存关键参数然后进入低功耗挂起。如果你用的是带无线模块的方案还要考虑无线模块断电时是否会影响主控最好让主控和无线模块分开供电或者用一个大一点的储能电容撑过最后几次写操作。注意掉电中断处理程序里不要做复杂逻辑。真正要做的只有两件事停止对Flash的写入保存最小状态。4. 自动回滚让失败的新固件自动让位4.1 判断新固件是否启动成功的标准自动回滚的前提是Bootloader必须知道“新固件到底算不算跑起来了”。这里不能简单理解成“跳转到新固件没死就成功”。我建议采用更保守的判断方式新固件启动后应该在规定时间内完成外设初始化和服务注册。初始化完成后主动调用一个接口把“运行成功”标志写入标志区。Bootloader在下次上电时看到这个标志才把启动失败计数清零。另一种做法是看门狗。如果App卡死看门狗复位系统再次进入Bootloader启动计数数字不减少。连续复位几次后Bootloader直接判失败。4.2 启动计数与看门狗的组合策略推荐组合是启动计数加看门狗加运行成功标志。说具体一点Bootloader每次从新槽位启动先把启动计数加1再跳转到App。App启动完成后如果业务运行正常主循环里会持续喂看门狗同时在某一个业务稳定点写入运行成功标志。当计数达到阈值时Bootloader认为这个槽位反复启动失败自动回滚到另一个槽位。阈值一般建议设置为3次。太少容易误判太多会延长用户感知的故障时间。看门狗的超时时间也要仔细算。既要能覆盖App启动初期的慢操作比如Wi-Fi模块初始化、文件系统挂载、传感器校准又不能太长否则App卡死时用户要等很久才能看到设备恢复。4.3 回滚触发条件与重试策略回滚触发条件通常包括这些启动失败计数超过阈值。新固件校验失败。新固件启动后主动上报系统状态异常。升级包在下载阶段已经损坏且无法重新获取。回滚完成后设备应该进入稳定状态不要继续自动尝试升级同一个版本。自动重试策略要有上限一般建议连续失败3次后就停住把升级状态上报到后台等待人工指令或后台重新下发。反复自动升级同一个坏固件不仅浪费流量还会反复打断用户的正常使用。设备能自己降下来但能不能再次升上去应该由后台决定。4.4 回滚后的业务状态与日志回滚不是只把固件切回去那么简单。切换槽位后设备还需要恢复用户习惯设置、故障记录等业务数据。所以升级前一定要把参数区单独保护起来不要在升级动作里随意覆盖。日志也特别重要。每次升级开始、标志位切换、启动计数增加、回滚触发都要写入日志区。故障上报排查时如果没有日志工程师几乎等于盲猜。日志区建议使用环形缓冲区记录最近几次升级的关键事件。事件里至少包含时间戳、当前状态、槽位ID、启动计数、错误码。这样远程拉日志时能直接看出来是哪一步出了问题。5. 升级包完整性签名、校验、版本号与传输控制5.1 升级包结构与密钥体系空调OTA不像手机App那样可以靠高频率更新来弥补问题。它是嵌入式设备一旦跑起来可能几年不换固件包必须在一开始就设计好完整性保护。升级包一般由三部分组成升级包头、固件数据、签名信息。包头里放魔数、固件版本号、目标分区、固件大小、CRC32或SHA256值。签名信息用非对称密钥生成私钥放在生产或后台服务器公钥内置在Bootloader里。设备端在写入固件之前要做两步检查先做CRC或哈希校验保证数据没有损坏再做签名验签保证固件来自可信来源。不要只做CRCCRC只能发现随机错误不能防恶意篡改。5.2 下载与传输的可靠性空调设备的网络环境往往不稳定。Wi-Fi信号弱、路由器重启、运营商网络波动都可能导致下载中断。所以下载过程必须支持断点续传和分片校验。比较实用的方案是分片下载每片大小根据内存来定比如256KB或512KB。每下载完一片先计算哈希值与后台返回的校验值比对成功后继续下载下一片。全部下载完成后再对整个包做一次整体校验。这样即使中途断网也只要重传失败的那一片。如果设备本身没有大容量外部Flash下载时可以把每一片直接写入目标槽位但要在每个片区之前放一个校验头。这样掉电后至少能知道哪个片区是完整可用的后续可以断点续写。5.3 版本号管理与降级策略后台下发升级包时一定要带上完整的版本信息比如大版本号、小版本号、编译时间、Commit号。设备端在收到升级指令后先判断版本是否高于当前版本。是否允许降级要结合你的场景决定。我建议默认禁止降级但保留一个“售后强制刷机”通道。这个通道只在本地串口或专用工具下开放不需要出现在OTA流程里。如果版本号管理混乱会出现一种很尴尬的情况设备上报的是新版本后台认为它没有升级于是反复下发同一个升级包设备又反复执行升级。这个问题的根因往往不是设备端而是后台版本比对逻辑不严格。5.4 校验时机与写入后校验很多项目只在下载阶段校验一次就认为万事大吉。实际上从下载完成到真正写入Flash之间还有很长时间这个过程中标志位闪存、看门狗复位、其他任务抢占都有可能导致数据变化。稳妥的做法是写入过程中边写边校验。每写完一块回读一次对比哈希值一致后再写下一块。全部写完后再对整个槽位做一次最终校验。只有最终校验通过才允许更新切换标志。注意校验失败不一定非得立刻重写。可以先保留现场日志再决定是删除升级包重新下载还是回滚旧槽位。不要在原因不明的情况下反复擦写FlashFlash寿命和稳定性都有限。6. 从下发到激活的完整流程先串链路再谈优化6.1 完整步骤我建议把空调OTA整条链路固定成下面这12步不要随意跳过后台生成新固件包计算版本号、哈希值完成签名。设备上报当前版本后台判定是否可升级。设备接收升级指令进入OTA准备状态。下载升级包到临时区域分片校验。校验签名和整体哈希。备份参数区关键数据。写STATE_WRITING标志。擦除目标槽位按块写入新固件。写入后回读校验。写STATE_SWITCH标志更新启动槽位。复位启动到新槽位。新固件运行成功写STATE_ACTIVATED标志并上报后台。每一步都要有超时处理和失败分支。比如步骤4下载超时可以选择重试或退出步骤8写入超时需要判断Flash是否真的擦除成功再决定是否继续。6.2 最小验证清单联调阶段不要直接拿生产环境跑我一般先用一个最小清单只升级一次验证新固件能否正常启动。掉电时刻分别覆盖下载中、校验中、擦除中、写入中、切换标志更新后。模拟启动失败验证自动回滚是否触发。连续升级两次验证版本号是否被正确判断。确认参数区数据在升级前后不变。这5项全部通过以后再考虑批量设备和线上灰度升级。很多项目跳过掉电测试只在干净环境里升级一次就上线结果真实用户一旦在升级瞬间关掉空调问题立刻暴露。6.3 推荐参数参考表下面是一些常见参数的参考值实际取值要根据你的硬件和业务调整参数建议值或思路启动失败计数阈值3次看门狗超时15s到30s下载分片大小256KB或512KB升级总超时根据固件大小建议留1.5倍余量连续失败熔断次数3次后停止自动升级状态标志位副本2份以上日志保留次数至少最近3次升级日志参数不能照搬要基于实际测试调整。比如看门狗超时设置太短App正常启动过程中可能会被误复位设置太长故障恢复又会变慢。7. 排查链路与真实项目中的避坑点7.1 升级后反复重启现象设备跳转到新固件后不停复位看门狗一直在触发。排查顺序先看启动计数有没有递增。如果递增说明Bootloader每次都能跳转到新固件但App没完成运行成功确认。看App初始化过程是否卡在某个外设访问上比如Wi-Fi模块未响应、I2C总线挂死。看日志里最后一条操作是什么。很多情况是App启动初期就访问了外部Flash或无线模块而这些模块还没有准备好。不要一上来就怀疑Bootloader分区选错。多数反复重启其实是新固件业务逻辑问题。7.2 回滚不生效现象新固件启动失败但设备没有回到旧固件而是一直卡在失败状态。常见原因有三个启动计数没有写进标志区或者标志区没有冗余掉电后丢失。Bootloader在跳转前就把计数清零了导致一直无法达到阈值。回滚目标槽位本身也已经损坏可能是回滚逻辑里没有校验旧槽位完整性。排查时先看标志区数值再看Bootloader里计数递增的位置最后确认两个槽位的CRC状态。7.3 升级包校验通过但启动异常这种问题最容易让人掉以轻心。整体哈希一致CRC也通过但新固件启动还是异常。排查方向有两个一是升级包生成时是否和实际编译产物一致比如固件bin文件里包含偏移地址信息而打包工具没有按正确地址生成二是写入顺序是否正确芯片Flash写入时先写哪个Bank、是否需要解锁、是否保持看门狗暂停都会影响最终效果。这类问题不能只在模拟环境里验证最好直接在外设齐全的真机上跑一遍完整升级。真机上能看到电源时序、晶振启动、外设复位等模拟环境发现不了的问题。7.4 批量升级的管理建议当设备数量多了以后OTA就不再只是嵌入式问题。后台要关注灰度比例、失败率、版本分布和回滚率。建议控制同一批次升级的设备数量先放5%到10%的设备升级观察24小时后再扩大范围。同时后台要能远程查看每个设备当前的槽位、启动计数和升级状态否则出问题时你连“哪些设备已经回滚”都不知道。另外升级日志要设计成可远程拉取。空调设备一断电就唤醒、一断网就恢复如果日志只在本地那远程排查会非常痛苦。日志里至少要包含设备序列号、当前固件版本、目标版本、上次升级时间、升级结果、回滚状态。这样批量设备出问题时后台能快速筛选出异常设备而不是一台一台去现场接串口。空调OTA这种需求看起来只是“把固件通过网络刷进去”真正落地时牵涉到的工程细节非常密。把AB分区、掉电保护、自动回滚和签名校验从最早的分区表阶段就一起规划后面遇到的坑会少很多。最怕的是先把升级功能做出来出了问题再补安全机制那时候分区已经被定死Flash空间也不够改起来代价非常大。
返回列表