空调装在墙上,接入的是普通市电,环境也远没有数据中心那么可控。用户可能随手拔掉插头,家里断路器可能因为过载跳闸,电网波动也可能造成瞬间断电。主控板一旦在固件写入中途断电,轻则启动异常,重则整机罢工。售后人员赶到现场前,设备如果没有任何自愈能力,体验和成本都会失控。
空调OTA升级,本质上是嵌入式设备的高可靠固件更新工程,核心要解决三个问题:升级包写到哪个区域才不会把系统写死(AB分区),升级过程中掉电如何恢复(掉电保护),新固件启动失败如何自动回到旧版本(自动回滚)。这三个问题处理好了,批量升级才有底气放量推。
这篇文章会把一套可用于空调控制板的OTA工程方案拆开讲,包括分区表设计、Bootloader引导流程、升级状态机、启动计数回滚、固件签名校验,以及批量任务的灰度调度和上报逻辑。文章不绑定具体芯片型号,但流程和代码示例可以直接迁移到STM32、ESP32、Realtek等常见物联网平台,也适用于其他可靠性要求接近的嵌入式产品。
1. 空调OTA升级的工程难点与核心能力速览
先说结论:空调OTA的难点不在“能不能升级”,而在“升级失败后能不能自己恢复”。
手机、路由器升级失败,还有恢复模式、线刷工具甚至售后网点可以抢救。空调不一样,分体式室外机挂在户外,室内机面板就算能显示故障代码,普通用户也不会拆机去短接引脚刷机。所以空调OTA方案的底线是:任何一次升级失败,设备都必须在无人干预的情况下回到可用状态。
| 能力项 | 说明 |
|---|---|
| 升级目标 | 空调主控固件、Wi-Fi/蓝牙模组固件 |
| 分区策略 | AB双槽位分区,升级写入非活跃槽位 |
| 掉电保护 | 升级状态多副本记录、写后校验、上电恢复 |
| 自动回滚 | Bootloader启动计数 + 新固件自检确认 + 槽位切换 |
| 安全机制 | 固件加签验签、防回滚版本计数 |
| 批量升级 | 灰度发布、任务下发、进度上报、失败暂停 |
| 芯片平台 | STM32、ESP32、Realtek等常见嵌入式平台 |
| 升级方式 | 网络下载升级包,重启引导切换分区 |
这套方案里,AB分区、掉电保护、自动回滚是一个“铁三角”。没有AB分区,掉电恢复只能靠串口重刷,谈不上远程自愈;没有掉电保护,AB分区切换时如果状态标记写了一半,系统会分不清该启动哪个分区;没有自动回滚,就算分区切过去了,新固件自己起不来,设备照样变砖。三个机制互相依赖,少一个都不扎实。
2. 适用场景与系统边界
空调OTA方案适合以下场景:需要远程修复控制逻辑缺陷、优化能效算法、调整传感器阈值的家用空调和轻商设备;安装量大、售后人力有限、设备无法快速到场维修的产品线;以及用户无法自行完成刷机操作、必须依赖设备自恢复能力的场景。
不适合或需要额外设计的情况也要提前想清楚。空调的压缩机驱动、风机控制直接关联强电,升级过程中如果功率器件发生误动作,可能造成安全隐患。所以升级包下载阶段和镜像写入阶段,要保证主控板不执行功率相关的控制动作,或者在升级前强制进入待机状态。另外,网络覆盖差的设备在传输大镜像时容易中断,需要考虑断点续传、镜像分包校验和弱网下的重试策略,而不是简单地把升级包一次性拉下来。
从使用边界来看,OTA升级只能解决固件逻辑问题,解决不了硬件损坏。如果设备本身电源板老化、通信模块故障,再完整的回滚机制也没办法让设备恢复。方案设计时要给云端升级任务设置合理的超时和放弃机制,避免设备长时间卡在升级流程里反复下载、反复失败,白白消耗流量和Flash寿命。
3. 空调OTA升级整体架构与状态机设计
空调OTA的整体架构采用“Bootloader + 双App分区”的方式,核心组件划分如下:
| 组件 | 职责 |
|---|---|
| Bootloader | 启动引导、升级状态判断、启动计数、回滚决策 |
| Slot A / Slot B | 两个可独立启动的固件分区 |
| Misc / 状态区 | 保存升级状态、启动计数、活跃标记 |
| Data | 保存配置参数、用户设定、故障记录 |
升级状态机是整个工程的骨架,所有业务逻辑都围绕状态流转展开。状态划分得越清楚,掉电恢复和异常处理就越容易写。
typedef enum { OTA_IDLE = 0, OTA_DOWNLOADING, OTA_VERIFYING, OTA_WRITING_SLOT, OTA_SETTING_ACTIVE, OTA_WAIT_REBOOT, OTA_NEW_FW_CONFIRM, OTA_ROLLBACK, OTA_FAILED } ota_state_t;完整的状态流转过程是:设备从云端下载升级包,下载完成后先做哈希校验,校验通过后写入非活跃分区,写入完成再次校验,然后修改活跃标记并重启。重启后Bootloader引导新分区启动,新固件完成硬件自检和业务自检后,向云端上报启动成功。云端确认成功后,设备把本次启动标记为永久生效。如果新固件连续多次启动失败,Bootloader自动切回旧分区。
这里要重点注意一个细节:写入非活跃分区和修改活跃标记必须是两个独立步骤,并且修改标记放在最后。旧版固件的镜像数据始终保留在另一个槽位里,直到新固件被确认成功为止。这是AB分区能够做到“升级失败不致命”的根本原因。
4. AB分区方案:双槽位设计与活跃标记
AB分区的核心思想是让系统同时存在两套可引导的固件,一次升级只动其中一套,另一套始终作为兜底。以下是一份MTD风格的分区表示例:
0x00000000 bootloader 256KB 0x00040000 misc 64KB 0x00050000 slot_a 2MB 0x00250000 slot_b 2MB 0x00450000 data 1MB实际项目里,slot_a和slot_b的大小要根据固件镜像大小规划,建议至少留出最大镜像体积的1.3到1.5倍余量,给后续功能迭代留出空间。misc分区存放升级状态和活跃槽位标记,这个分区不需要很大,但它的可靠性直接影响整个升级方案,所以通常要采用双副本或三副本交叉备份。
活跃标记推荐使用两个变量组合:active_slot指定当前启动槽位,slot_status记录每个槽位的状态。写标记时不要直接覆盖旧值,而是先写新值到备份区,确认写入成功后同步主标记区,再校验一次。这样即使标记写入中途掉电,Bootloader也能通过备份区推断出正确的槽位。
AB分区升级具体操作流程如下:
- 下载升级包到临时存储区,计算镜像MD5或SHA256。
- 与升级包元数据中的哈希比对,不一致则丢弃。
- 锁定非活跃分区,如果是Slot B就写Slot B,逐扇区擦除并写入。
- 写完后从非活跃分区逐块读回校验,确认数据完整。
- 修改misc分区中的活跃标记,将非活跃分区的状态置为“待确认”。
- 系统重启,Bootloader按新标记引导目标分区。
这套流程最核心的工程经验是:校验通过前,绝不修改活跃标记。只要遵守这条顺序,升级过程中任何一次异常断电,系统重启后都还是启动旧分区,设备不会变砖。AB分区表面上看起来是“多烧了一份Flash空间”,实际买的是“可远程恢复的容错能力”。
5. 掉电保护设计:升级时序与恢复流程
掉电保护不是一个单一函数,而是一整套从硬件到软件的容错设计。空调场景下,掉电风险比其他家电更突出:插头可能被随意拔掉,装修中的临时插座可能接触不良,电网波动也可能造成短暂的电压跌落。方案设计必须假设掉电可能发生在升级流程的任何一步。
misc分区中的升级状态记录建议使用双副本结构,并加上CRC校验:
typedef struct { uint32_t magic; uint32_t step; uint32_t active_slot; uint32_t write_seq; uint32_t crc32; } ota_state_t;step标志当前升级进行到哪个阶段,write_seq是写入序号,每次状态更新时自增。写入时交替使用两片区域,这样就算当前区域写到一半掉电,Bootloader还可以读取另一区域的完整记录。上电后Bootloader会比较两区域的序号和CRC,取序号更新且CRC校验通过的一份作为有效状态。
恢复流程按“状态机 + 分区完整性”双重判断来处理:
- 上电后Bootloader读取misc分区有效状态。
- 如果检测到升级状态残留,则根据step判断升级进行到哪一步。
- step停留在“下载中”或“校验中”,直接清状态,走正常启动旧分区。
- step停留在“写入中”,先检查两个槽位的镜像完整性,哪个分区完整就启动哪个。
- step停留在“切换中”,优先引导按标记指向的新分区,如果新分区校验失败,回退旧分区。
空调还有一个特殊性:掉电恢复后,主控重新上电时不能立刻驱动压缩机启动,必须经过完整的待机检测和延时逻辑,否则可能造成压缩机带压启动损坏。所以OTA状态恢复模块和上电控制逻辑要分层。升级恢复只负责“系统能跑起来”,压缩机能不能启动由应用层按原有安全逻辑判断。
Flash写入方面,掉电很容易造成扇区半写。常规做法是先擦后写,写完一个扇区就读回校验,发现异常则重新擦写。如果镜像体积较大,还要考虑擦写耗时和Flash寿命。升级过程要合理设置看门狗超时,避免一次性长时间擦写导致看门狗复位,同时也要防止看门狗失效后升级卡死得不到恢复。
6. 自动回滚机制:启动计数与槽位切换
AB分区解决了“升级包写坏了”的问题,自动回滚则解决“新固件启动不起来”的问题。两者经常被混在一起讲,但实际是两个不同层面的可靠性手段。
自动回滚机制的实现思路是:Bootloader维护一组启动计数,每次切换到新的槽位时计数加1,新固件自检成功后清零。连续计数达到阈值后,Bootloader判断新固件异常,自动切回旧槽位。
void bootloader_check(void) { ota_state_t st = read_ota_state(); if (st.step == OTA_SETTING_ACTIVE) { st.boot_count++; save_ota_state(&st); if (st.boot_count >= BOOT_FAIL_THRESHOLD) { rollback_to_old_slot(&st); } } if (boot_ok_flag_from_app() == true) { st.boot_count = 0; st.step = OTA_IDLE; save_ota_state(&st); } }关键参数有三个:启动计数阈值、自检窗口时间、回滚目标槽位。计数阈值通常设为3次,太少容易把偶发硬件故障误判为固件失败,太多会让用户反复等待多次重启才恢复。自检窗口时间要结合空调的实际启动流程来定,空调从主控上电到压缩机可以正常启动,中间涉及到外机通信、传感器采样、延时保护逻辑,可能长达数分钟。如果窗口设得太短,新固件还没完成自检就被判失败,属于误杀。
新固件的自检确认建议通过三个层级完成:第一层是Bootloader引导是否成功,只要固件入口执行就算;第二层是App启动后完成硬件初始化和关键外设自检,上报启动成功;第三层是业务层确认压缩机控制、传感器采集、通信联网都正常工作。只有第三层确认通过,才把启动计数清零并标记为permanent。
自动回滚执行时要注意防止“回滚颠簸”。新固件回滚到旧固件后,如果云端再次下发同一个版本的升级包,设备又会升级,然后再次回滚,形成死循环。所以设备回滚后要上报本次回滚原因和版本信息,云端灰度策略要把回滚率过高的升级任务自动暂停。设备侧也可以加一个回滚次数字段,短时间内同一版本回滚超过一定次数,就不再接受该版本升级。
7. 固件加签验签与防回滚
空调OTA的安全等级虽然没有汽车电子那么高,但既然能联网、能远程下发固件,就必须假设升级通道可能被篡改。“加签验签”在汽车嵌入式软件OTA里已经是标配,家电方向也在逐步对齐这套思路。
固件包建议采用“元信息 + 镜像 + 哈希 + 签名”的格式:
firmware_package ├── manifest.json ├── firmware.bin ├── firmware.md5 └── firmware.sigmanifest.json中记录固件版本、硬件平台、分区目标、兼容性约束。签名使用私钥在构建阶段生成,Bootloader内置公钥,启动和升级阶段验签。签名命令可以使用OpenSSL生成:
# 生成签名,实际项目中私钥应保存在离线构建服务器 openssl dgst -sha256 -sign firmware_private.pem -out firmware.sig firmware.bin # 验签 openssl dgst -sha256 -verify firmware_public.pem -signature firmware.sig firmware.binBootloader验签流程建议放在写入非活跃分区之前和切换活跃标记之前各执行一次。第一次验签确保下载的镜像没有被篡改,第二次验签确保写入Flash的数据可读取且没有被破坏。验签的私钥管理要特别注意,开发阶段密钥和生产阶段密钥必须分开,私钥一旦泄露,攻击者就可以构造合法签名的恶意固件,所有安全机制形同虚设。
防回滚方面,固件包中要携带版本号,设备的misc分区保存当前生效固件版本。Bootloader在升级校验时判断新版本号是否不低于当前版本号。但防回滚策略要留一个运维后门,实际项目中经常遇到需要定向降级的情况,比如新固件在特定批次硬件上出现兼容性问题。可以设计一个白名单机制,只允许指定设备在指定时间段内降级到指定版本。
密钥和校验环节还有一个容易忽略的点:芯片Flash内容可能被完整读出复制。如果产品有防抄板要求,固件可以再做一层加密,但加密与签名要分开处理。签名保证固件来源可信,加密保证固件内容保密。空调产品通常不涉及核心算法泄露风险,可以先做验签,不做镜像加密,节省启动时间。
8. 批量升级任务与远程运维与进度上报
批量任务如果一次性把几十万台空调全部下发升级包,会出现两个问题:一是升级CDN或服务器带宽被打满,下载速度降低,升级失败率上升;二是如果固件存在缺陷,问题会在极短时间内大面积爆发。所以批量升级必须做灰度调度。
推荐的分批策略是:第一批选择1%左右的内测设备,观察24小时升级成功率和回滚率;确认稳定后扩大到10%到50%;最后再放量到全部设备。每一批放量前,云端都要重新统计上一批的升级数据,任何异常指标都要能自动暂停任务。
云端下发改批次升级任务的接口参数可以参考以下结构:
{ "device_id": "AC_2024_XXXX", "target_version": "2.3.1", "firmware_url": "https://example.com/fw/AC_2.3.1.bin", "firmware_md5": "7c4d0c9f3b1e42ad6e0a9b8d2e4f5a61", "firmware_size": 262144, "strategy": { "max_failure_rate": 0.02, "timeout_seconds": 7200, "allowed_time_window": "02:00-05:00" } }设备侧升级完成后,上报结果建议包含这些字段:device_id、current_version、result_code、error_message、signal_strength、total_download_bytes、upgrade_duration_seconds。只要能按这个格式上报,云端就可以实时生成升级成功率曲线。批量任务卡住时,还能通过error_message快速区分是网络下载失败、镜像校验失败、写入失败还是启动失败。
批量升级还有几个实际运维细节很容易踩坑。设备离线时云端积压的任务,在设备上线瞬间可能集中触发,导致同一时间大量设备同时下载。云端要做任务窗口控制,随机打散每个设备的任务下发时间。弱网环境下,大镜像要支持分包下载,每一包单独校验,失败只重传损坏的分包,不需要整个镜像重下。另外,升级包CDN的防盗链要提前考虑,虽然验签能挡住无效包,但被恶意刷流量也会消耗成本。
9. 常见问题与排查方法
空调OTA工程测试阶段最容易遇到的问题集中在以下几类,按现象、原因、排查方式、解决方案整理成表格:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 升级后设备完全无响应 | Bootloader或分区表被破坏,或写入期间掉电导致关键区域损坏 | 检查Bootloader是否可运行,读取分区表内容 | 保留独立的恢复升级入口,Bootloader升级单独灰度,避免频繁改动 |
| 升级后一直启动旧版本 | 活跃标记未正确写入,或Bootloader判定新分区不完整 | 读取misc分区状态,比对双副本CRC和write_seq | 检查切换标记的写入时机,确认写完后先校验再改状态 |
| 设备反复重启循环 | 新固件自检失败,启动计数触发回滚,但回滚后又收到同版本升级包 | 查看设备日志和Bootloader日志,核对回滚原因 | 云端设置回滚率暂停阈值,设备侧增加同版本回滚次数限制 |
| 固件校验失败 | 下载过程中镜像损坏,或Flash写入后读回数据不一致 | 比对下载完成后的哈希和目标分区读回数据的哈希 | 升级包增加分包校验,下载失败自动重试,写入失败重新擦写 |
| 升级时掉电后无法恢复 | 状态记录区损坏,或双副本都没有写入完整数据 | 检查misc分区内容和CRC,确认状态记录是否采用双副本 | 状态记录采用双副本交叉写入,每次写入后立即同步备份区 |
| 批量升级成功率低 | 设备网络质量差、下载并发过高、CDN带宽不够 | 按error_message分类统计失败原因 | 增加弱网断点续传机制,控制并发,错峰下发 |
| 新固件功能正常但验签失败 | 公钥不匹配或固件包签名生成方式错误 | 用OpenSSL验签命令检查签名文件和公钥 | 规范签名流程,使用同一套构建脚本,开发密钥与生产密钥严格分离 |
| 压缩机在升级后异常启动 | 升级完成后的上电流程没有执行原有的安全延时逻辑 | 查看上电时序日志和压缩机启动条件判断 | 升级恢复流程和应用层上电控制解耦,恢复后必须执行完整的待机检测流程 |
排查这类问题要养成一个习惯:所有升级流程的关键节点都打日志,并且日志要持久化到Data区或独立的日志分区。空调设备出现故障后,运维人员往往无法第一时间到现场,只能依靠设备主动上报的日志和数据定位问题。日志字段里至少要包含时间戳、升级状态、当前槽位、启动计数、关键函数返回值。
10. 最佳实践与合规建议
空调OTA升级在工程落地阶段,有一些从项目中沉淀下来的建议可以直接采纳。
第一,实验室模拟掉电测试必须成为标配。升级流程中不同阶段的掉电要分别测试,包括下载时断电、写入分区时断电、切换标记时断电、启动自检时断电。而且要在不同扇区擦写位置反复测试,不能只测一次。掉电测试工具可以用可控继电器或电子负载,随机切断设备电源,然后自动上电观察恢复结果。
第二,保留一套最小可运行系统。为了防止极端情况下两个分区都不可用,Flash规划中可以考虑预留一个独立的恢复分区,或者至少保证Bootloader具备从串口、SD卡或U盘恢复升级的能力。恢复功能不常用,但不能没有。
第三,升级任务下发前要确保用户知情和授权。空调属于家庭设备,静默升级虽然技术上可行,但用户可能正在使用空调,升级过程如果影响制冷或制热,体验会很差。合理的做法是在App端提示用户有固件更新,由用户选择立即升级或稍后提醒,或者将升级任务默认安排在用户不使用空调的时段。
第四,升级过程要保护用户配置。空调设备中有用户设定的温度、模式、定时、情景模式等数据,这些数据存放在Data分区,升级时不能因为分区切换而丢失。AB分区升级只切换固件槽位,Data分区必须与固件槽位解耦。如果Data分区的格式在升级后有变更,升级包中要包含迁移逻辑,不能直接清空。
第五,设备上报的数据要最小化。OTA升级只需要版本号、结果码、失败原因、信号强度、升级耗时等必要字段,不需要采集用户的使用习惯、室内温湿度曲线等非必要数据。采集哪些字段、保存多久、谁可以访问,都应在产品设计阶段确定。如果涉及用户个人数据的处理,要按当地法律法规要求履行告知和授权流程。
第六,固件发布前要做效果复核。OTA可以远程更新固件,意味着开发者有很高的修改自由度,但这也意味着一个低级错误可能影响所有设备。建议构建一套完整的自动化发布流水线:编译触发、静态检查、单元测试、固件签名、内测灰度、批量放量、效果监控、失败暂停,每个环节都有明确负责人和回滚按钮。
11. 总结与下一步
空调OTA升级真正值得花精力做的不是下载和写入那部分,而是异常路径的兜底设计。AB分区让你有一个永远不会被轻易写死的备用系统,掉电保护让任何一次异常断电都有恢复路线,自动回滚让新固件启动失败时设备能自己回到可用版本。三者配合,批量升级才敢放心推。
如果要从零开始做一套空调OTA,第一步应该先验证掉电中断恢复:手动在升级过程的不同阶段切断电源,确认设备在每种情况下都能自动恢复。这是整个方案中最容易出现隐藏缺陷的环节。状态标记的写入时机、双副本的一致性、启动计数的阈值设置,都会在掉电测试中暴露问题。把这部分跑稳了,再去看灰度调度、加签验签和批量上报,就会顺畅很多。
最容易踩的坑集中在三个地方:切换标记的写入时机、回滚颠簸造成新旧固件循环切换、以及批量任务集中下发造成的瞬时带宽峰值。前两个属于设备侧逻辑,最后一个属于云端调度策略,都需要在测试阶段用故障注入和批量仿真提前验证。
后续可以继续扩展的方向包括:差分升级减少下载流量、远程诊断日志的按需拉取、升级包增量恢复机制、以及多设备联动场景下的升级窗口协同。空调之外,这套方案同样适用于其他高可靠要求的产品形态,核心思路是一致的:把升级风险前置到分区设计和恢复逻辑里,而不是依赖升级永远成功。建议收藏备用,做项目时直接照着排一遍。