STM32+ESP8266 在线OTA升级:腾讯云物联网差分方案实战
简介:这份资源面向嵌入式与物联网开发者,聚焦STM32与ESP8266组合下的在线OTA升级方案,借助腾讯云物联网平台完成固件远程分发与更新。内容围绕设备上线、固件分发、下载校验、存储管理、烧录更新与状态反馈等关键环节展开,适合已具备单片机基础、希望掌握远程固件升级技能的工程师与学习者参考。压缩包共288个文件,约6.64MB,以C源文件与头文件为核心,配合工程配置、编译中间产物、烧录镜像及少量图片与网页说明,完整呈现BootLoader与主程序双工程结构,便于直接编译验证与二次开发。目前已有5980人学习下载,可作为物联网设备可维护性设计的实操范本,帮助读者理清云端交互、串口通信与Flash分区烧录的落地思路。
1. STM32+ESP8266 在线 OTA 升级:从串口烧录到云端差分的落地路径
手里有一块 STM32 板子,接上 ESP8266,想让它像手机一样在联网状态下自己拉取固件、自己完成升级,而不是每次抱着下载器插排线——这个需求在物联网设备量产之后会变得非常现实。这份资源包围绕的正是这条链路:STM32 作为主控跑应用逻辑,ESP8266 负责联网,腾讯云物联网平台作为固件托管与版本下发的中枢,最终实现设备端在线 OTA。它解决的不是“能不能联网”这种入门问题,而是“设备已经装在现场了,怎么把新固件安全地推上去”的工程问题。适合已经能跑通 STM32 串口收发、对 ESP8266 AT 指令有基本概念、准备把 demo 推进到可维护阶段的嵌入式开发者。如果你还在纠结 GPIO 怎么配,这份资源会有点超前;但如果你已经卡在“固件怎么分片、怎么校验、怎么回滚”上,它值得拆开看。
2. OTA 链路拆解:STM32 与 ESP8266 的分工边界
2.1 为什么不是 STM32 直接联网
STM32 本身不带 Wi-Fi 协议栈,硬要跑 TCP/IP 加 TLS 会把 Flash 和 RAM 吃得很紧,尤其在没有外部 SDRAM 的型号上,LwIP 加 mbedTLS 的占用会让应用层几乎没空间。常见做法是让 ESP8266 承担网络传输,STM32 只负责固件接收、校验和写入。这样分工的好处是:网络协议栈的复杂度被隔离在 ESP8266 内部,STM32 端只需要处理串口帧和 Flash 操作,实时性和确定性都更好。代价是串口带宽成为瓶颈,ESP8266 的 AT 固件默认波特率下,实际有效吞吐往往只有几十 KB/s,所以固件包不能太大,差分升级或分片策略就变得关键。
这份资源里,ESP8266 的角色是“透传通道 + 云端会话保持”,STM32 通过 AT 指令与它交互,拿到的是腾讯云物联网平台下发的固件分片数据。腾讯云物联网的 OTA 服务支持全量与差分两种模式,差分包体积通常只有全量包的 10% 到 30%,对串口传输来说这是很实际的优化。资源包里的代码应该已经封装了 AT 指令的收发解析,但具体指令序列和超时处理需要你根据自己手上的 ESP8266 固件版本微调。
2.2 腾讯云物联网 OTA 的版本模型
腾讯云物联网平台的 OTA 升级不是“传个 bin 就完事”,它有一套版本管理逻辑:你需要在控制台创建固件,指定版本号,上传固件文件,然后创建升级任务,把任务推给指定设备或设备分组。设备端要做的第一件事是上报当前固件版本,平台根据版本差异决定是否下发升级通知。这个版本号必须和 STM32 内部记录的版本一致,否则会出现“平台认为你该升级,设备却觉得自己已经是最新”的错位。
资源包里应该包含版本号存储和读取的逻辑,通常放在 Flash 的某个固定扇区,或者用备份寄存器保存。常见做法是在 Flash 末尾划一个 2KB 的配置区,存版本字符串、固件长度、CRC 校验值。升级前先读这个区,把版本号通过 ESP8266 上报给平台;升级完成后写入新版本号,再重启。这个流程听起来简单,但版本号格式不统一、字节序搞反、Flash 写入前忘记擦除,都是血泪经验里高频出现的翻车点。
2.3 串口帧格式与分片接收
STM32 和 ESP8266 之间的串口通信不能只靠裸 AT 指令,因为固件数据是二进制流,而 AT 指令是文本协议,混在一起会出乱子。常见做法是让 ESP8266 进入透传模式,STM32 端定义一个简单的帧头帧尾协议,比如0xAA 0x55开头,后面跟长度、序号、数据和 CRC。这样即使串口上混入了 AT 的响应文本,也能通过帧头过滤掉。
分片接收时,每片数据写入 Flash 之前要先校验 CRC,校验不过就请求重传。腾讯云物联网的 OTA 分片大小可以配置,常见是 1KB 或 2KB 一片。STM32 端需要维护一个接收缓冲区,缓冲区大小至少是分片大小的两倍,防止处理不及时丢数据。下面是一个简化的分片处理逻辑:
// 假设串口中断收到数据,放入 ring_buffer // 主循环中解析帧 typedef struct { uint8_t header[2]; // 0xAA 0x55 uint16_t seq; // 分片序号 uint16_t len; // 数据长度 uint8_t data[2048]; // 数据载荷 uint32_t crc; // 整帧 CRC32 } ota_frame_t; void ota_process_frame(uint8_t *buf, uint16_t size) { ota_frame_t *frame = (ota_frame_t *)buf; if (frame->header[0] != 0xAA || frame->header[1] != 0x55) { return; // 帧头不对,丢弃 } uint32_t calc_crc = crc32(buf, size - 4); if (calc_crc != frame->crc) { ota_request_retry(frame->seq); // CRC 失败,请求重传 return; } // 写入 Flash 对应偏移 flash_write(OTA_APP_ADDR + frame->seq * 2048, frame->data, frame->len); ota_ack(frame->seq); // 回复 ACK }这段代码的关键参数是OTA_APP_ADDR,它必须和你的 STM32 链接脚本里应用程序的起始地址一致。如果用的是 STM32F103C8T6,Flash 只有 64KB,Bootloader 占 8KB 的话,APP 起始地址就是0x08002000。这个地址写错,升级完设备直接变砖,只能上 SWD 救回来。
2.4 Bootloader 与 APP 的跳转约定
OTA 能跑通的前提是 Bootloader 和 APP 之间有明确的跳转约定。Bootloader 负责判断“要不要升级”,APP 负责“正常干活并响应升级请求”。常见做法是:上电后 Bootloader 先检查一个升级标志位,如果标志位有效,就留在 Bootloader 里等待固件传输;如果无效,就跳转到 APP。APP 收到升级通知后,先擦除标志位所在扇区,写入升级标志,然后软复位,让 Bootloader 接管。
这个标志位通常放在 Flash 的某个固定地址,或者用 RTC 备份寄存器。用 Flash 的好处是掉电不丢,坏处是擦写次数有限;用备份寄存器的好处是快,坏处是掉电可能丢。我一般会两个都用:备份寄存器做快速判断,Flash 做最终确认。跳转前要关中断、设栈指针、重定向向量表,这三步少一步都可能跑飞。资源包里应该有对应的jump_to_app函数,但你需要确认它用的栈顶地址和你的 APP 编译输出一致。
3. 从零跑通一次 OTA:环境、编译与云端配置
3.1 开发环境与工具链确认
这份资源包大概率是 Keil MDK 工程,因为 STM32 加 ESP8266 的组合在 Keil 下最省事。你需要确认几件事:Keil 版本至少能打开工程不报错,STM32 的器件包已经安装,串口驱动正常。如果用的是 STM32CubeMX 生成的工程,还要确认 HAL 库版本和资源包里的代码兼容。常见翻车点是 HAL 库版本不一致导致HAL_UART_Receive_IT的行为差异,比如老版本里回调函数里必须重新调用接收,新版本里有些封装已经帮你做了。
ESP8266 这边,你需要确认它的 AT 固件版本。资源包里的 AT 指令序列如果是基于安信可的 AT 固件写的,而你手上是乐鑫原厂固件,指令集可能有细微差别。比如连接腾讯云物联网的 MQTT 接入,安信可固件可能用AT+MQTTUSERCFG,原厂固件可能用AT+MQTTCONN。这个不确认,后面连不上云平台会浪费很多时间。
3.2 腾讯云物联网控制台配置
登录腾讯云物联网控制台,创建一个产品,品类选“自定义”,通信方式选“Wi-Fi”。然后在产品下创建设备,拿到三元组:ProductID、DeviceName、DeviceSecret。这三个值要填到 STM32 端的配置文件里,通常是一个iot_config.h或者类似的头文件。
接下来配置 OTA 服务。在控制台找到“固件升级”,创建固件,版本号填一个比当前设备版本高的值,比如设备当前是1.0.0,新固件填1.0.1。上传编译好的 bin 文件,注意这个 bin 必须是 APP 的 bin,不是整个工程的 hex。Keil 默认输出 axf,你需要用fromelf转成 bin:
fromelf --bin --output app.bin Objects\your_project.axf这条命令在 Keil 的安装目录下,Objects是你的编译输出目录。转出来的app.bin大小要小于你给 APP 划分的 Flash 空间,否则升级到一半写不进去,设备就卡在 Bootloader 里了。
3.3 设备端版本上报与升级触发
设备上电后,STM32 先读本地版本号,通过 ESP8266 发 MQTT 消息上报给腾讯云物联网。平台收到版本号后,如果发现有更高版本的固件任务,会下发升级通知。这个通知通常是一个 JSON,里面包含固件大小、版本号、分片大小、校验方式。STM32 解析这个 JSON,确认无误后回复“开始升级”,然后平台开始推送分片。
下面是一个简化的 MQTT 上报和通知解析流程:
// 上报版本号 void ota_report_version(void) { char payload[128]; snprintf(payload, sizeof(payload), "{\"version\":\"%s\"}", get_local_version()); mqtt_publish(OTA_VERSION_TOPIC, payload); } // 解析升级通知 void ota_handle_notify(char *json) { cJSON *root = cJSON_Parse(json); cJSON *size = cJSON_GetObjectItem(root, "file_size"); cJSON *version = cJSON_GetObjectItem(root, "version"); if (size && version) { ota_start(size->valueint, version->valuestring); } cJSON_Delete(root); }这里用到了 cJSON 库,资源包里应该已经包含。如果没有,你需要自己移植一个,注意堆栈大小,cJSON 解析大 JSON 时比较吃 RAM。OTA_VERSION_TOPIC是腾讯云物联网定义的版本上报主题,具体字符串在控制台的设备详情里能看到,不要自己猜。
3.4 固件写入与校验
分片数据写入 Flash 时,要注意 STM32 的 Flash 写入粒度。F1 系列按半字写,F4 系列可以按字节写,但擦除必须按扇区。如果你在 Bootloader 里擦除了 APP 区域,写入时直接按分片偏移写就行;如果没擦,写入会失败。常见做法是收到第一个分片时擦除整个 APP 区域,但这样如果升级中断,APP 就没了。更稳妥的做法是边写边擦,或者用双 Bank 交替升级。资源包如果是单 Bank 方案,你需要在升级前确保电量充足,或者加一个外部 Flash 做缓存。
写入完成后,STM32 要计算整个固件的 CRC 或 SHA256,和平台下发的校验值比对。比对通过,写入新版本号,清除升级标志,软复位;比对失败,保持旧版本,上报失败原因。这个校验步骤不能省,串口传输在电磁环境差的现场很容易出现位翻转,没有校验的升级就是在赌运气。
4. 避坑与排查:OTA 升级中最容易翻车的五个点
4.1 升级后设备无响应,串口无输出
现象:OTA 流程走完,设备重启后串口没有任何打印,像是死机了。
原因:APP 的起始地址和 Bootloader 跳转地址不一致,或者向量表没有重定向。STM32 的 APP 如果不在默认的0x08000000,必须在system_stm32fxxx.c里修改VECT_TAB_OFFSET,否则中断向量表还是指向旧地址,一进中断就跑飞。
解决:确认 Bootloader 里的APP_ADDR和 APP 工程里的VECT_TAB_OFFSET一致。F1 系列改VECT_TAB_OFFSET,F4 系列除了改这个,还要在main开头调用SCB->VTOR = APP_ADDR。改完重新编译 APP,重新生成 bin,重新走一遍 OTA。
4.2 ESP8266 连不上腾讯云物联网
现象:AT 指令返回ERROR或者CONNECT FAIL,MQTT 连接建立不起来。
原因:三元组填错、MQTT 主题拼错、或者 ESP8266 的固件不支持腾讯云的 MQTT 接入方式。腾讯云物联网的 MQTT 接入需要用到clientId、username、password,这些值不是直接填三元组,而是要按照腾讯云的规则拼接。比如clientId通常是ProductID + DeviceName,username是DeviceName + ";" + ProductID,password是 HMAC-SHA1 加密后的值。
解决:用腾讯云提供的在线生成工具算一遍 MQTT 参数,和代码里的拼接逻辑比对。如果 ESP8266 固件不支持AT+MQTTCONN,考虑换一个支持 MQTT 的 AT 固件,或者让 STM32 自己跑 MQTT 协议栈,ESP8266 只做 TCP 透传。
4.3 分片接收丢包,升级卡在中途
现象:升级进度走到一半不动了,串口打印显示某个分片 CRC 校验失败,重传几次后超时。
原因:串口波特率太高,STM32 处理不过来;或者接收缓冲区太小,分片数据溢出。ESP8266 在透传模式下,数据是连续吐出来的,如果 STM32 的串口中断优先级低,被其他中断打断,就会丢字节。
解决:降低波特率到 115200 甚至 57600,增大接收环形缓冲区到至少 4KB,提高串口中断优先级。如果用的是 HAL 库的HAL_UART_Receive_IT,注意每次接收完要重新调用,否则只能收一次。另外,分片之间的间隔不要太短,给 STM32 留出写 Flash 的时间,Flash 写入期间串口可以继续收,但缓冲区要够大。
4.4 版本号回滚,平台反复推送升级
现象:设备升级到新版本后,平台仍然认为设备是旧版本,反复下发升级任务。
原因:版本号写入 Flash 后没有正确读取,或者写入的地址和读取的地址不一致。常见的是写入时用了OTA_VERSION_ADDR,读取时用了APP_VERSION_ADDR,两个宏定义指向不同扇区。
解决:统一版本号的存储地址,写入后立即读回来验证。如果用的是字符串版本号,注意结尾的\0也要写入,否则读出来会拼接上后面的垃圾数据。建议在版本号前面加一个魔数,比如0x5A5A,读取时先校验魔数,魔数不对就认为版本号无效,走默认版本。
4.5 升级过程中断电,设备变砖
现象:OTA 进行到一半,现场断电,再来电时设备不启动,Bootloader 也进不去。
原因:单 Bank 方案在擦除 APP 区域后,如果升级未完成,APP 就是空的,Bootloader 如果设计成“无 APP 就死等”,设备就卡住了。更糟的是,如果 Bootloader 也在升级范围内,擦除后连 Bootloader 都没了。
解决:Bootloader 永远不要参与 OTA 升级,它应该是最小可运行系统,只负责跳转和接收固件。APP 区域升级前,先确保 Bootloader 里有超时机制:等待升级数据超过 30 秒没有有效帧,就跳转到备份 APP 或者进入低功耗模式。如果 Flash 空间允许,用双 Bank 方案,A Bank 跑 APP,B Bank 收新固件,收完校验通过再切换。
5. 进阶技巧:差分升级与升级成功率验证
5.1 差分升级的落地思路
全量升级每次传整个 bin,对串口和流量都是浪费。差分升级只传新旧固件的差异部分,STM32 端需要有一个算法把差分数据和旧固件合并成新固件。常见做法是用开源差分算法,比如bsdiff或hdiffpatch,在 PC 端生成差分包,STM32 端跑合并逻辑。合并需要额外的 RAM 做缓冲区,通常至少要和差分包一样大。
腾讯云物联网平台支持上传差分包,但差分包需要你自己生成。流程是:拿旧版本 bin 和新版本 bin,用hdiffpatch生成 patch 文件,上传到平台,平台下发 patch,STM32 接收后和本地旧固件合并,合并结果写入 APP 区域。合并过程中如果断电,旧固件可能被破坏,所以合并前最好把旧固件备份到外部 Flash 或者另一块 Bank。
# PC 端生成差分包 hdiffpatch -s old_app.bin new_app.bin app.patch # 查看差分包大小 ls -lh app.patch如果差分包大小超过全量包的 60%,差分升级的意义就不大了,直接传全量更省事。这个阈值可以根据你的串口速率和流量成本来定。
5.2 升级成功率的量化验证
OTA 功能做完,不能只测一次成功就完事。你需要做几组压力测试:连续升级 50 次,看成功率;在升级过程中随机断电 20 次,看设备能否恢复;在串口线上人为注入噪声,看 CRC 校验和重传机制是否生效。这些测试能暴露很多实验室里遇不到的问题。
我一般会写一个简单的测试脚本,用 Python 控制串口,模拟设备端的上报和接收,统计每次升级的耗时和重传次数。如果重传次数超过分片总数的 10%,说明串口链路质量有问题,需要检查硬件走线或者降低波特率。升级耗时也要记录,全量升级如果超过 5 分钟,用户体验就很差了,考虑换差分或者压缩固件。
5.3 一个容易被忽略的细节:Flash 对齐
STM32 的 Flash 擦除是按扇区来的,F1 系列扇区大小不一,F4 系列扇区更大。如果你在链接脚本里把 APP 起始地址设在了扇区中间,擦除时会连带擦掉前面的 Bootloader 或者后面的配置区。这个坑我在早期项目里踩过,Bootloader 被擦掉一半,设备直接不启动。
从那以后我每次改链接脚本,都强制走一遍扇区对齐检查:打开芯片参考手册的 Flash 章节,找到扇区划分表,确认APP_ADDR正好是某个扇区的起始地址。如果不是,要么调整 Bootloader 大小,要么把 APP 往后挪。这个检查花不了五分钟,但能省掉一次拆机救砖的麻烦。希望帮到你。
本文还有配套的精品资源,点击获取