简介这份资源面向嵌入式与物联网开发者聚焦STM32与ESP8266组合下的在线OTA升级方案借助腾讯云物联网平台完成固件远程分发与更新。内容围绕设备上线、固件分发、下载校验、存储管理、烧录更新与状态反馈等关键环节展开适合已具备单片机基础、希望掌握远程固件升级技能的工程师与学习者参考。压缩包共288个文件约6.64MB以C源文件与头文件为核心配合工程配置、编译中间产物、烧录镜像及少量图片与网页说明完整呈现BootLoader与主程序双工程结构便于直接编译验证与二次开发。目前已有5980人学习下载可作为物联网设备可维护性设计的实操范本帮助读者理清云端交互、串口通信与Flash分区烧录的落地思路。1. STM32ESP8266 在线 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 链接脚本里应用程序的起始地址一致。如果用的是 STM32F103C8T6Flash 只有 64KBBootloader 占 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 接入安信可固件可能用ATMQTTUSERCFG原厂固件可能用ATMQTTCONN。这个不确认后面连不上云平台会浪费很多时间。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转成 binfromelf --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_OFFSETF4 系列除了改这个还要在main开头调用SCB-VTOR APP_ADDR。改完重新编译 APP重新生成 bin重新走一遍 OTA。4.2 ESP8266 连不上腾讯云物联网现象AT 指令返回ERROR或者CONNECT FAILMQTT 连接建立不起来。原因三元组填错、MQTT 主题拼错、或者 ESP8266 的固件不支持腾讯云的 MQTT 接入方式。腾讯云物联网的 MQTT 接入需要用到clientId、username、password这些值不是直接填三元组而是要按照腾讯云的规则拼接。比如clientId通常是ProductID DeviceNameusername是DeviceName ; ProductIDpassword是 HMAC-SHA1 加密后的值。解决用腾讯云提供的在线生成工具算一遍 MQTT 参数和代码里的拼接逻辑比对。如果 ESP8266 固件不支持ATMQTTCONN考虑换一个支持 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 跑 APPB Bank 收新固件收完校验通过再切换。5. 进阶技巧差分升级与升级成功率验证5.1 差分升级的落地思路全量升级每次传整个 bin对串口和流量都是浪费。差分升级只传新旧固件的差异部分STM32 端需要有一个算法把差分数据和旧固件合并成新固件。常见做法是用开源差分算法比如bsdiff或hdiffpatch在 PC 端生成差分包STM32 端跑合并逻辑。合并需要额外的 RAM 做缓冲区通常至少要和差分包一样大。腾讯云物联网平台支持上传差分包但差分包需要你自己生成。流程是拿旧版本 bin 和新版本 bin用hdiffpatch生成 patch 文件上传到平台平台下发 patchSTM32 接收后和本地旧固件合并合并结果写入 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 往后挪。这个检查花不了五分钟但能省掉一次拆机救砖的麻烦。希望帮到你。本文还有配套的精品资源点击获取