STM32+Air724UG+阿里云实现4G Cat.1 OTA远程升级实战指南
发布时间:2026/9/29 19:19:18来源:尧图网络
写这篇文章的冲动来源于后台接连收到好几条基于STM32的毕业设计关键词留言而其中近半都指向OTA远程升级。恰好我手头刚完成一个基于STM32F407Air724UG接入阿里云物联网平台的量产级项目OTA这块从方案设计到联调上线踩过的坑和梳理出来的经验都不少干脆系统地整理一篇实战笔记。先说结论STM32Air724UG这套组合在4G Cat.1网络下做OTA远程升级是完全可行的成熟方案。Air724UG负责4G通信和MQTT协议栈STM32作为主控负责业务逻辑和固件更新阿里云物联网平台提供设备接入和OTA任务管理能力。整个链路打通后我在实际项目里把128KB的固件包完整升级时间控制在20秒左右断点续传、异常回滚这些关键场景也都验证过。这篇文章会把架构设计、Bootloader分区、固件分包逻辑、阿里云Topic对接细节以及排查过的坑一次性讲清楚适合正在做同类项目、或者准备把OTA纳入产品规划的工程师参考。1. 为什么是Air724UG 阿里云物联网平台这套组合1.1 选型之前先想清楚你要解决什么很多人一提到远程升级第一反应就是ESP8266或ESP32走Wi-Fi方案再配个小服务器或者MQTT Broker。这个方案在实验室环境确实好用但放到真实的工控、农业、能源类项目里Wi-Fi的短板非常明显现场没有可靠的无线网络、路由器重启后设备失联、跨网段访问要折腾端口映射。我遇到过不止一个客户的产品部署在郊区配电房或农业大棚里Wi-Fi连不上运维人员只能开车到现场烧录程序。在做技术选型时关键问题是你要在什么网络环境下干活。如果产品要跑在公网、要跨地域管理、要随时能被云端唤醒升级蜂窝网络几乎是唯一不需要依赖现场基础设施的选择。Air724UG作为一款Cat.1模组定位恰好卡在NB-IoT和高速LTE之间下行速率理论10Mbps实际跑固件下载足够而且全网通、功耗可控、价格合理。相比NB-IoTCat.1在带宽和时延上都更适合OTA这种突发流量场景。1.2 Air724UG为何比Wi-Fi方案更契合现场需求Air724UG是合宙推出的一款四模Cat.1模组内置MQTT、HTTP、TCP/UDP等协议栈主控通过串口AT指令即可完成所有网络操作。对我这种习惯用MCU做主控的开发者来说它的最大价值在于网络部分不用自己写协议栈也基本不需要在STM32上跑RTOSlwIP这类重型组件业务代码的复杂度因此大幅下降。对比一下Wi-Fi方案和Cat.1方案的差异对比维度Wi-Fi方案ESP8266/ESP32Cat.1方案Air724UG网络依赖性依赖现场路由器与联网配置插SIM卡即用无需现场配置覆盖范围受限于Wi-Fi信号基站覆盖全国范围协议处理需自行处理TCP/TLS/HTTP模组内置协议栈AT指令调用设备管理需自行对接公网服务器可直连阿里云/腾讯云等平台现场运维断网需到场处理远程可查状态可OTA修复另外在物联网平台的选型上我见过不少团队自己搭EMQX或者用开源IoT平台但真正落地时设备管理、物模型、OTA任务下发这些功能模块全都要自己实现开发量远超想象。阿里云物联网平台把这些能力做成了开箱即用的服务固件管理、升级包校验、升级任务灰度发布都有现成的控制台界面和设备端的对接协议也是标准化的Alink JSON整体接入成本低很多。1.3 阿里云平台在OTA这件事上省了哪些事在没用阿里云物联网平台之前我设想过一套自己搭的OTA方案自建HTTP文件服务器 设备定时轮询 版本比对 手动触发下载。这套方案链路长、问题多服务器要维护HTTPS证书、版本管理要自己写后台、升级状态无法统一监控、设备重启后升级状态机容易错乱。而阿里云物联网平台的OTA服务把这些环节都封装好了固件上传和管理平台侧管理固件版本支持差分包和整包两种升级模式。升级任务定义可以指定升级设备范围、灰度比例、升级时间窗口。设备端状态同步平台自动维护设备当前版本号升级进度和设备上报的步骤信息可以在控制台实时查看。消息通道复用OTA指令直接通过设备已建立的MQTT长连接下发不需要额外开端口。对开发者的意义就是你只需要关心STM32端如何安全地把固件写好以及Air724UG如何把平台下发的指令解析成下载动作。平台与设备之间的认证、消息路由、任务状态管理这些脏活累活平台侧已经解决。2. OTA升级的整体架构与运行时序2.1 一张文字版架构图四个端点之间的分工很多刚接触这个项目的朋友容易把OTA理解成把文件从云端发到设备上。实际上OTA是一条至少涉及四个端点的链路每个端点各司其职阿里云物联网平台负责设备认证、OTA任务管理、固件存储和消息路由。Air724UG模组作为4G通信底座通过MQTT协议与平台保持长连接同时作为HTTP客户端从固件下载地址拉取固件数据。STM32主控既是业务逻辑的执行者也是OTA的执行者。它解析MQTT下发的指令控制固件分包接收与存储并在合适时机完成从下载区到App区的搬运和跳转。用户/运维后台通过阿里云控制台创建升级任务监控升级进度。从网络拓扑看STM32与Air724UG之间是串口连接典型的AT指令通信Air724UG与阿里云之间是4G移动网络链路非常干净。这种设计的好处是固件下载不占用MCU的网络协议栈资源也不容易因为MCU侧的内存不足导致数据丢失。2.2 一次完整OTA升级走过的7个步骤我在项目里定义了一条比较标准的升级时序每一步都有对应的设备和平台动作设备上电STM32初始化完成后通过串口向Air724UG发送AT指令建立MQTT连接连接成功后订阅OTA升级相关Topic。设备主动上报当前固件版本号到平台。阿里云平台记录该设备的当前版本。运维人员在平台创建OTA升级任务指定目标版本和升级设备范围。平台通过OTA下行的Topic向设备推送升级通知消息里包含固件下载地址、固件大小、版本号、校验值等信息。STM32收到升级通知后先校验目标版本是否比当前版本新然后切换工作状态通过Air724UG发起HTTP GET请求下载固件包。固件分包写入外部Flash或内部Flash下载缓存区每包校验CRC后记录进度。全部下载完成后进行整体校验。校验通过后STM32在下载区写入升级完成标志并复位Bootloader启动后检测到标志将固件从下载区复制到App区跳转执行新程序App启动后向平台上报新版本号流程结束。你会发现下载和安装实际上是分成两步的下载到缓存区是软操作固件还在备份区随时可以丢弃回滚而从缓存区复制到App区才是真正动刀的动作。这个设计在后面讲断点续传和异常回滚时会体现出价值。2.3 为什么指令走MQTT、固件走HTTP这是整个OTA架构里最容易被忽略、也最值得理解的一个设计决策。阿里云物联网平台的设备接入通道是MQTTOTA升级通知也走MQTT下发这一点没有争议。但固件本身的传输我没有选择通过MQTT逐包推送而是用了HTTP下载。原因有三点第一MQTT协议本身是为低带宽、高可靠的控制消息设计的固件包动辄几十上百KB如果拆分成的消息数量太多会占用大量Topic消息配额在弱网环境下还会因为消息堆积导致控制通道拥堵影响设备心跳和命令响应。第二HTTP协议天然支持断点续传Range头字段可以直接指定从哪个字节开始下载这对4G网络环境下的不稳定传输非常友好。而MQTT要实现断点续传需要自己设计消息序号和确认机制复杂度高且不标准。第三阿里云平台提供的固件下载地址本质上是对象存储URL配合HTTP Range请求可以灵活控制下载粒度也支持下载进度实时监控。Air724UG内置的HTTP客户端通过AT指令就能发起GET请求收到数据后通过URC主动上报消息送给STM32。实际项目中我使用的是类似ATHTTPGETurl,timeout,offset,length的方式控制下载位置每下载一包数据STM32就校验一次并记录偏移量效果很理想。3. STM32端存储布局与Bootloader设计3.1 分区规划决定了后面所有的代码结构OTA不是把新固件写进Flash就完事Bootloader、App、下载缓存、参数区怎么划分直接决定了升级的安全性。以我使用的STM32F407VET6为例这颗芯片有512KB内部Flash结合一颗W25Q64外部Flash8MB做下载缓存规划如下区域存储介质地址范围大小用途Bootloader区内部Flash0x08000000 - 0x08007FFF32KB启动引导、升级执行、回滚控制App区内部Flash0x08008000 - 0x0807FFFF480KB业务固件运行区参数区内部Flash0x08080000 - 0x08080FFF4KB存储升级标志、版本号、续传位图、启动次数下载缓存区1外部FlashW25Q64 偏移0x000000最大1MB固件包下载临时存储下载缓存区2外部FlashW25Q64 偏移0x100000最大1MB上一版本固件备份用于回滚内部Flash不放下载缓存是因为STM32F407剩余空间不足以放一份完整的第二固件外部Flash则不存在空间压力。把上一版本固件同时备份到下载缓存区2是为了支持升级失败自动回滚Bootloader发现新固件校验失败时可以从这个备份区恢复旧固件而不是让设备变砖。参数区是整个升级逻辑的中枢它保存的关键信息包括typedef struct { uint32_t magic; // 参数区魔法数 0xOTA5A5A uint32_t update_flag; // 0x00: 无升级任务, 0x01: 固件已下载待安装 uint8_t new_version[16]; // 目标版本号 uint8_t cur_version[16]; // 当前版本号 uint32_t total_size; // 固件总大小 uint32_t downloaded_bytes; // 已下载字节数断点续传用 uint8_t boot_count; // 升级后启动次数用于回滚检测 } upgrade_param_t;3.2 Bootloader怎么安全地跳到AppBootloader的职责可以概括为检查参数区的升级标志决定是直接跳转App还是先执行固件搬运。跳转逻辑看起来就几行代码但细节处理不好很容易出现跳过去就死机的局面。跳转代码的核心如下#define APP_ADDR 0x08008000 void jump_to_app(void) { uint32_t app_sp *(volatile uint32_t *)APP_ADDR; uint32_t app_pc *(volatile uint32_t *)(APP_ADDR 4); // 安全检查栈顶指针必须落在SRAM范围内 // STM32F407的SRAM是0x20000000 - 0x2001FFFF if ((app_sp 0xFFE00000) ! 0x20000000) { // 栈指针异常说明App区没有有效程序不能跳转 return; } __disable_irq(); // 跳转前关全局中断 SCB-VTOR APP_ADDR; // 重映射中断向量表到App区 __set_MSP(app_sp); // 设置主栈指针 void (*app_entry)(void) (void (*)(void))app_pc; app_entry(); // 跳转到App的Reset_Handler }这里有几个容易踩的坑一是跳转前必须关闭所有已使能的中断和外设否则中断回调进入Bootloader的地址空间直接HardFault二是SCB-VTOR在部分Cortex-M系列上需要按256字节对齐App区偏移地址一定不能随意定义三是栈指针检查不能省略如果App区是空白的读出来的app_sp可能是0xFFFFFFFF或任意值直接跳转必死。跳转成功后Bootloader的生命周期就结束了。但App自身必须在初始化早期就把中断向量表重映射到自己所在的地址否则中断向量仍然指向Bootloader的向量表任何中断都会触发异常。3.3 App侧必须处理的中断向量表重映射这是跳转成功但App跑起来像半残废的头号原因。App在main()函数最开头必须执行SCB-VTOR APP_ADDR;如果用的是HAL库很多初始化流程在 main 的HAL_Init()里已经执行了但HAL_Init()本身不会帮你重映射向量表需要手动放在它之前。我曾经在项目里把这一行放到了外设初始化之后结果串口、定时器全部乱套排查了整整半天才发现是向量表问题。另外如果App涉及OTA功能本身还需要预留一个接收升级指令的入口。我的做法是在App中注册MQTT消息处理函数当收到OTA通知时App将接收到的固件信息写入参数区然后触发软件复位进入Bootloader流程。4. 固件分包、断点续传与完整性校验4.1 分包的大小不是随便定的固件下载最忌讳一把梭。4G网络并不稳定如果下载过程中TCP连接断开从头再来既耗时又浪费流量。分包下载是标准解法HTTP Range每次只拉取一小段数据逐包校验、逐包记录进度。分包大小的选择有两个约束一是Air724UG的AT指令数据通道每次能承载的数据量二是STM32接收数据的缓冲区和外部Flash的页大小。我实测下来Air724UG通过URC方式每包上报的数据量在1460字节左右受限于MQTT/HTTP的TCP分段所以我把包大小定为1024字节给协议头和缓冲区留足余量。每个数据包设计成如下格式typedef struct { uint32_t seq; // 包序号从0开始 uint16_t len; // 本包数据长度通常1024最后一包可能不足 uint8_t data[1024]; uint32_t crc32; // 本包数据的CRC32 } fw_packet_t;STM32收到完整一包后先校验CRC通过则将该包写入外部Flash并将downloaded_bytes增加1024同时更新参数区的序列号。如果CRC失败则请求重传当前包不影响后续包的下载。4.2 续传思路一张位图解决一半问题断点续传最朴素的实现是记录已下载多少字节但这样粒度太粗。假设下载到60%时网络断了重连后你只知道下载到第600个包左右如果第590个包实际上没写成功比如Final ACK丢失续传后固件就是坏的。我在参数区里维护了一张位图把固件按1024字节分成N个块每块对应位图中的一个bit// 假设固件最大1MB1024字节一块最多1024块位图128字节 uint8_t bitmap[128];每成功写入一个包就把对应的bit置1。续传时先扫描位图从第一个为0的bit开始继续下载。位图同时保存在外部Flash和内部参数区每次更新后立即擦写内部参数区擦写耗时约20ms可接受确保掉电时位图不会丢失太多进度。按照这个方案哪怕设备在下载最后1500字节时突然断电重启后也只需要重新下载这2KB数据而不是从头拉取整个固件包。4.3 校验通过之后再动刀从下载区写到App区固件全部下载完成后还需要做一次整体校验。下载阶段每个包单独CRC通过不代表整个固件是正确的比如固件顺序错乱所以我会在固件尾部写一个结束标记包含整体CRC32、固件大小和版本信息。整体校验通过后Bootloader进入安装阶段。这个阶段最危险的场景是搬运到一半掉电App区既有部分新固件又有部分旧固件设备彻底无法启动。为了规避这个问题我设计了两个安全层第一搬运过程使用先擦后写、按页搬运策略。STM32F407的Flash按扇区擦除16KB/扇区我从0x08008000开始每次擦除一个扇区后立即写入该扇区对应的新固件内容。因为下载区完整即使搬运到一半掉电下次上电Bootloader检测到新固件未搬运完会重新从下载区再次搬运。第二搬运完成后不立即清升级标志而是在App启动并成功上报版本后才清除。App每次启动会递增boot_count如果连续3次启动都没有上报新版本Bootloader判定新固件异常从下载缓存区2恢复旧固件并跳转。这套机制的完整工作状态如下阶段参数区 update_flag参数区 boot_count设备行为空闲0x000正常启动App已下载待安装0x010Bootloader搬运固件到App区已安装待确认0x021App启动上报版本未确认确认成功0x000升级流程结束连续启动异常0x013Bootloader执行回滚5. Air724UG接入阿里云的关键对接细节5.1 AT指令跑MQTT固件选型和三元组Air724UG有两个开发路线一是AT指令MCU通过串口下发AT指令控制模组二是合宙的Lua二次开发直接在模组里跑业务逻辑。在做STM32主控 模组透传架构时AT指令是更自然的选择逻辑清晰、便于调试。模组固件必须选择带MQTT协议栈的版本我使用的是合宙官方的AirM2M_AT_V系列固件。设备上电后按以下顺序初始化模组ATE0 # 关闭回显 ATCGMM # 查询模组型号 ATCSQ # 查询信号质量 ATCEREG? # 查询网络注册状态返回1或5为已注册 ATCGDCONT1,IP,CMIOT # 设置APN这里以物联网卡为例 ATMCONFAaliyun_mqtt,1883,productKey.deviceName.deviceSecret,0,120 # 配置MQTT ATMCONN # 连接MQTT服务器特别注意ATMCONFA的第四参数是连接保持时间单位秒我配置为120秒。这个值不能太长否则弱网环境下平台会因为长期收不到心跳而判定设备离线也不能太短否则模组频繁发送心跳会增加功耗和流量消耗。设备接入阿里云采用一机一密认证方式三元组ProductKey、DeviceName、DeviceSecret在创建产品后从控制台获取固件中不要硬编码在源码里建议放到参数区便于量产时独立烧录。5.2 阿里云OTA升级Topic与消息格式阿里云物联网平台的OTA服务有自己专属的Topic分类和设备属性、服务调用分开。需要订阅和发布的Topic如下角色Topic用途设备发布/ota/device/inform/${productKey}/${deviceName}上报当前固件版本设备订阅/ota/device/upgrade/${productKey}/${deviceName}接收OTA升级指令设备发布/ota/device/progress/${productKey}/${deviceName}上报升级进度与状态设备上电连接成功后的第一条MQTT消息就是上报当前版本{ id: 1, params: { version: 1.0.1 } }平台收到版本上报后才会在控制台显示设备的当前版本。如果跳过这一步创建升级任务时会发现设备不在升级目标列表里。之后当运维人员创建升级任务时平台通过/ota/device/upgrade下发指令消息格式如下{ id: 123, params: { version: 1.0.2, size: 131072, url: https://iot-ota.oss-cn-shanghai.aliyuncs.com/xxx.bin, sign: e10adc3949ba59abbe56e057f20f883e, signMethod: Md5 } }这里最关键的是url字段它是指向固件文件的对象存储地址设备拿到它之后就开始HTTP下载流程。sign是对固件文件计算出的MD5值下载完本地整体校验用。5.3 设备端上报版本和进度别把时序搞反OTA升级过程中设备端需要按约定时序向平台上报进度否则平台控制台的升级任务会一直显示升级中直到超时。设备端的状态上报应遵循以下顺序// 下载开始前 { id: 1, params: { step: 1, desc: start download, version: 1.0.2, progress: 5 } } // 下载完成校验通过后 { id: 2, params: { step: 2, desc: download success, version: 1.0.2, progress: 80 } } // 安装完成App启动后 { id: 3, params: { step: 3, desc: upgrade success, version: 1.0.2, progress: 100 } }step的取值范围是1-3分别对应下载中、下载完成、升级完成。如果某个步骤卡住不上报平台默认在48小时后超时将任务标记为失败。所以设备端务必要在关键节点及时上报状态否则后台看到的进度永远是0%。需要提醒的是升级完成的上报必须由新固件发出也就是App启动后、业务逻辑运行前立刻上报。如果新固件存在初始化卡死的问题平台会一直等不到最终确认这种case我们在回滚策略里做了兜底依靠boot_count机制回到旧版本。6. 实测表现与高频踩坑记录6.1 下载地址是HTTPS怎么办Air724UG的AT指令HTTP客户端对HTTPS的支持比较有限它虽然能发起HTTPS GET请求但对服务器的证书链、TLS版本有一定要求实测中经常出现ATHTTPGET返回错误或者连接中途断开。阿里云的固件下载地址默认是HTTPS刚开始联调时我在这就卡了好几天。最终的解决方案是按优先级尝试这三种方法推荐做法在阿里云物联网平台的固件管理里把固件下载配置为使用免费OSS并开启公读时可拿到一个HTTP的临时下载地址。如果业务允许直接使用HTTP地址下载。如果必须走HTTPS阿里云OSS支持自定义绑定域名并配置HTTP但需要额外备案流程不适合快速验证。在Air724UG侧部分新版本AT固件增强了对TLS的支持升级到最新模组固件后再试HTTPS。我实际上线用的是方案1平台生成下载URL后我在设备的OTA指令处理逻辑里做了字符串替换把https://替换成http://前提是固件本身不含敏感数据。如果对安全性要求高建议对固件包做AES加密下载后解密再写入Flash这样即使链接被截获也无法直接提取固件。6.2 升级时看门狗反复复位设备中Windog独立看门狗本来是为了防止业务死机但升级耗时较长时反而成了猪队友。我第一次实测OTA时固件下载到一半设备突然重启日志显示是看门狗复位。原因是我在主循环里喂狗但下载固件时串口接收是阻塞在中断里处理的长时间不喂狗导致溢出复位。解决思路有两个升级期间把看门狗超时时间拉长或者把喂狗操作放到下载状态机里每收到一个有效数据包就喂一次。哪个方案更稳妥取决于你的看门狗设计。我最终选择在升级状态机里喂狗这样即使下载中途因为网络问题卡住设备也不会被看门狗误杀而是依赖HTTP超时机制来失败重连。6.3 跳转成功但App跑起来像半残废这个坑在3.2和3.3里已经铺垫过。跳转最快的坑就是向量表没有重映射。补充一个排查技巧如果跳转后App的串口能打印日志但定时器中断不触发、延时函数卡死十有八九是SCB-VTOR设置时机太晚或者没有设置。用Keil调试时查看SCB-VTOR的地址是否等于App的起始地址即可确认。另外还有一个隐蔽问题Bootloader里初始化过的外设跳转前没有彻底Deinit。比如Bootloader里初始化了某个串口用于打印日志跳转时这个串口的中断仍处于使能状态一旦收到数据就会跳进Bootloader的中断服务函数导致HardFault。我踩过一次之后养成了习惯跳转前完成外设复位至少要把已开启的中断全部关闭并把外设寄存器恢复到复位值。6.4 网络抖动导致下载中断别只盯着重试弱网环境下HTTP下载中断非常正常断点续传位图已经解决重头下载的问题但还有一个容易忽略的细节CPU处理下载和MQTT消息是并行的当HTTP下载占用大量串口带宽时MQTT的心跳和下行消息可能会出现延迟。如果心跳延迟超过平台的keepalive时间设备会被判定离线。我的做法是在OTA下载期间把MQTT的keepalive从120秒临时缩短到60秒并保证模组在下载数据的同时仍能及时处理MQTT心跳PING报文。Air724UG的AT固件在HTTP GET过程中仍然处理MQTT协议栈理论上不会丢心跳但实测发现如果MCU侧一次性从串口缓冲区读走大量数据会延迟URC消息的处理。所以STM32的串口DMA缓冲要开大一些中断服务函数里尽量只搬运数据不要做耗时的Flash写操作。6.5 异常回滚的一次真实演练有一次为了测试回滚策略我在新固件里故意制造了一个启动即死机的bug然后通过平台发起升级。设备的表现完全符合预期新固件搬运完成跳转后App在初始化阶段卡死无法上报版本Bootloader里的boot_count连续3次递增后回滚逻辑生效从备份区恢复旧固件设备在4次上电后回到了正常版本。整个过程不需要人工到场后台能看到设备恢复到旧版本并上报成功。这套回滚策略是OTA可靠性的最后一道防线。很多教程只讲升级成功的路径对升级失败怎么办避而不谈但真实产品里失败才是常态。固件本身有bug、下载链路不稳定、安装过程中掉电每一种异常都需要在方案设计阶段就考虑进去而不是等量产后再补。做了几个OTA项目之后我最大的体会是远程升级不是一个功能模块而是一套完整的可靠性工程体系。它涉及通信协议、存储管理、启动流程、异常恢复任何一个环节处理不到位最后都会演变成升级失败→设备变砖→必须返厂的运维事故。回到标题里的三个关键词——STM32、Air724UG、阿里云物联网平台每一层都有它独特的技术纵深STM32侧要处理好Flash生命周期和中断迁移Air724UG侧要摸透AT指令和网络状态机阿里云平台侧要理解Topic流转和OTA任务状态。这篇实战指南把这些点按一条真实的升级链路串起来希望能帮你少走一些我已经趟过的弯路。
网站建设高端定制企业官网