
简介这份资源是面向物联网初学者与嵌入式开发者的MQTT连接腾讯云实战示例包围绕STM32微控制器与ESP8266 Wi-Fi模块演示如何借助MQTT协议接入腾讯云物联网平台解决设备端从注册、配置到订阅发布消息的完整链路问题。压缩包共119个文件、约2.96MB以Keil工程文件为主包含uvprojx工程配置、c与h源码、crf与o编译中间文件、axf与hex可执行产物以及map、lst等链接映射信息便于直接打开工程编译烧录。源码中可见aliyun_mqtt、esp8266、ds18b20、usart、timer等模块覆盖通信驱动、传感器采集与定时任务能帮助读者理解MQTT客户端参数设置、心跳与断线重连机制。目前已有1657人学习下载适合作为课程设计或小型IoT项目的参考底稿对照代码快速搭建联网实验环境。1. 从一块 STM32 和一片 ESP8266 说起这个示例到底能跑通什么手里有一块 STM32 最小系统板一片 ESP8266 模块想连腾讯云物联网平台结果卡在 AT 指令收发、MQTT 报文拼装、三元组签名这几步上——这是很多做嵌入式物联网入门的开发者都会遇到的场景。这个压缩包给的就是一条已经跑通的链路STM32 通过串口驱动 ESP8266 上网再用 MQTT 协议接入腾讯云物联网平台完成设备注册、属性上报和下行控制。它适合两类人一类是刚接触物联网平台、想先看到数据在云端跳动的嵌入式新手另一类是手里有现成硬件、想拿一份可对照的通信代码做二次开发的老手。整个示例不依赖复杂框架核心就是串口 AT 指令 MQTT 报文代码量不大但把设备侧接入云端的完整流程走了一遍。2. 技术栈与目录结构先看清这套代码由哪几块拼起来2.1 硬件与软件栈的选型逻辑这套示例的选型非常典型主控用 STM32联网模块用 ESP8266云平台用腾讯云物联网通信。三者分工明确——STM32 负责业务逻辑和传感器数据采集ESP8266 负责把串口收到的数据转成 TCP/IP 报文发出去腾讯云负责设备管理、消息路由和数据存储。为什么不用 STM32 直接跑 MQTT因为大多数 STM32 型号没有内置网络外设加一片 ESP8266 是最低成本的联网方案AT 指令虽然原始但胜在稳定、资料多、调试直观。软件栈上STM32 侧一般用 HAL 库或标准库驱动串口ESP8266 侧用 AT 指令集完成 Wi-Fi 连接和 TCP 透传MQTT 报文则由 STM32 自己拼装。常见做法是把 MQTT 的 CONNECT、PUBLISH、SUBSCRIBE 报文按协议格式手工组包通过串口发给 ESP8266 的透传模式。这样做的好处是不依赖任何第三方 MQTT 库代码可控代价是要自己处理报文长度、剩余长度编码和心跳保活。2.2 目录结构拆解压缩包解压后目录大致是这样的结构不同版本可能略有差异但核心文件不会少MQTT连接腾讯云示例/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── usart.h │ │ └── gpio.h │ └── Src/ │ ├── main.c │ ├── usart.c │ └── gpio.c ├── Drivers/ │ ├── STM32F1xx_HAL_Driver/ │ └── CMSIS/ ├── Middlewares/ │ └── mqtt/ │ ├── mqtt_client.c │ └── mqtt_client.h ├── ESP8266/ │ ├── esp8266.c │ └── esp8266.h └── README.mdCore是 STM32CubeMX 生成的工程骨架Drivers是 HAL 库和 CMSISMiddlewares/mqtt放的是 MQTT 报文组包和解析逻辑ESP8266目录封装了 AT 指令的发送与响应解析。这个分层很清晰底层驱动、网络模块、协议层各管一摊改哪一层都不会牵动其他层。2.3 关键文件职责说明文件职责改动频率main.c初始化外设、主循环、调用连接与上报高usart.c串口初始化、中断接收低esp8266.cAT 指令封装、Wi-Fi 连接、TCP 建链中mqtt_client.cMQTT 报文组包、解析、心跳中main.h引脚定义、全局宏低新手最容易犯的错是一上来就改mqtt_client.c其实先跑通esp8266.c里的 AT 指令流程更重要——网络不通MQTT 报文拼得再对也发不出去。3. 从上电到连云一次完整的连接流程拆解3.1 串口与 ESP8266 的初始化STM32 和 ESP8266 之间靠串口通信通常用 USART2 或 USART3波特率默认 115200。初始化分两步先配 STM32 的串口外设再发 AT 指令确认 ESP8266 在线。下面是一段典型的串口初始化代码/* usart.c 片段串口2初始化用于与ESP8266通信 */ void MX_USART2_UART_Init(void) { huart2.Instance USART2; huart2.Init.BaudRate 115200; // ESP8266默认波特率 huart2.Init.WordLength UART_WORDLENGTH_8B; huart2.Init.StopBits UART_STOPBITS_1; huart2.Init.Parity UART_PARITY_NONE; huart2.Init.Mode UART_MODE_TX_RX; huart2.Init.HwFlowCtl UART_HWCONTROL_NONE; huart2.Init.OverSampling UART_OVERSAMPLING_16; if (HAL_UART_Init(huart2) ! HAL_OK) { Error_Handler(); } }这段代码没什么玄学但有两个参数值得注意BaudRate必须和 ESP8266 固件的默认波特率一致常见是 115200部分老固件是 9600HwFlowCtl设为无硬件流控因为大多数 ESP8266 模块只引出了 TX/RX 两根线。初始化完成后主循环里先发AT\r\n如果收到OK说明模块活着可以继续往下走。3.2 AT 指令连接 Wi-Fi 与 TCP 建链ESP8266 的联网流程是一串 AT 指令先ATCWMODE1设为 Station 模式再ATCWJAPssid,password连路由器最后ATCIPSTARTTCP,mqtt.qcloud.com,1883建立 TCP 连接。封装成函数后大概是这样/* esp8266.c 片段连接Wi-Fi并建立TCP链路 */ uint8_t ESP8266_ConnectWiFi(const char *ssid, const char *pwd) { char cmd[128]; /* 设为Station模式 */ if (ESP8266_SendCmd(ATCWMODE1\r\n, OK, 2000) ! 0) return 1; /* 连接热点超时给10秒 */ snprintf(cmd, sizeof(cmd), ATCWJAP\%s\,\%s\\r\n, ssid, pwd); if (ESP8266_SendCmd(cmd, OK, 10000) ! 0) return 2; /* 建立TCP连接指向腾讯云MQTT接入地址 */ if (ESP8266_SendCmd(ATCIPSTART\TCP\,\mqtt.qcloud.com\,1883\r\n, CONNECT, 5000) ! 0) return 3; return 0; }ESP8266_SendCmd是自定义的发送并等待响应函数内部用串口中断接收数据匹配到期望字符串就返回成功。这里的关键参数是超时时间连 Wi-Fi 给 10 秒因为路由器握手和 DHCP 分配 IP 需要时间TCP 建链给 5 秒超时多半是网络不通或地址写错。返回值用不同数字区分失败阶段调试时看返回值就能定位是哪一步挂了。3.3 MQTT CONNECT 报文组包与三元组签名TCP 通了之后STM32 要自己拼 MQTT 的 CONNECT 报文。腾讯云物联网平台要求用三元组产品 ID、设备名、设备密钥做 HMAC-SHA1 签名拼进 ClientID、Username、Password 字段。报文结构是固定头 可变头 载荷下面是一段简化的组包逻辑/* mqtt_client.c 片段组MQTT CONNECT报文 */ uint16_t MQTT_BuildConnect(uint8_t *buf, const char *clientId, const char *username, const char *password) { uint16_t idx 0; uint16_t remainLen; /* 固定头报文类型0x10剩余长度后续填 */ buf[idx] 0x10; /* 可变头协议名MQTT、版本4、连接标志、心跳间隔 */ remainLen 10 2 strlen(clientId) 2 strlen(username) 2 strlen(password); idx MQTT_EncodeRemainLen(buf[idx], remainLen); buf[idx] 0x00; buf[idx] 0x04; buf[idx] M; buf[idx] Q; buf[idx] T; buf[idx] T; buf[idx] 0x04; // 协议版本4即MQTT 3.1.1 buf[idx] 0xC2; // 用户名密码清理会话标志 buf[idx] 0x00; buf[idx] 0x3C; // 心跳60秒 /* 载荷ClientID、Username、Password每个前面带2字节长度 */ idx MQTT_WriteString(buf[idx], clientId); idx MQTT_WriteString(buf[idx], username); idx MQTT_WriteString(buf[idx], password); return idx; }0xC2这个连接标志位拆开看bit7 是用户名标志bit6 是密码标志bit1 是清理会话组合起来就是 0xC2。心跳间隔 60 秒意味着设备要每 60 秒内发一次 PINGREQ否则平台会断开。MQTT_EncodeRemainLen负责把剩余长度按 MQTT 协议的可变字节编码写进去剩余长度超过 127 时要用两个字节这个细节漏了会导致平台直接拒连。3.4 属性上报与下行消息解析连上之后设备通过 PUBLISH 报文往平台上报属性主题一般是$thing/up/property/{productId}/{deviceName}。平台下发的控制消息走$thing/down/property/...STM32 在串口接收中断里解析。上报的代码大致是/* 上报温度属性payload为JSON字符串 */ void MQTT_ReportTemperature(float temp) { char payload[64]; uint8_t packet[128]; uint16_t len; snprintf(payload, sizeof(payload), {\method\:\report\,\params\:{\temperature\:%.1f}}, temp); len MQTT_BuildPublish(packet, $thing/up/property/xxx/dev1, payload); ESP8266_SendRaw(packet, len); // 透传模式直接发原始字节 }注意ESP8266_SendRaw走的是透传模式发送前要先发ATCIPSENDlen等模块回后再发数据。JSON 里的method字段固定为reportparams里放属性名和值属性名必须和平台物模型里定义的一致否则平台会丢弃这条消息。4. 避坑与排查这几处翻车点我替你踩过了4.1 现象AT 指令一直返回 ERROR 或超时原因通常是波特率不匹配或接线反了。ESP8266 的 TX 要接 STM32 的 RXRX 接 TX交叉接线是最容易忽略的一步。另外部分模块出厂固件波特率是 9600代码里写 115200 就会一直超时。解决办法是先用手动串口工具以 9600 和 115200 各试一次确认模块响应的波特率后再改MX_USART2_UART_Init里的参数。4.2 现象TCP 能连上但 MQTT CONNECT 发出去没回应多半是三元组签名算错了。腾讯云的 Password 字段是HMAC-SHA1(设备密钥, 签名内容)再 Base64签名内容里包含时间戳和随机数时间戳偏差超过几分钟平台就会拒绝。排查时先把 ClientID、Username、Password 打印到串口用平台提供的在线工具对一遍确认签名一致再查报文。另一个常见原因是剩余长度编码写错报文长度超过 127 时只写了一个字节平台解析直接失败。4.3 现象连上几分钟后自动掉线心跳没发或者发晚了。MQTT 的 keepalive 是 60 秒设备必须在 1.5 倍时间内发出 PINGREQ否则平台判定离线。很多示例代码在主循环里用HAL_Delay做其他事情一延迟就把心跳耽误了。解决办法是把心跳放到定时器中断里每 30 秒置一个标志位主循环检测到标志就发 PINGREQ不依赖主循环的节奏。4.4 现象上报数据平台收不到但串口显示发送成功先查主题拼写。上报主题里的产品 ID 和设备名必须和平台上的完全一致大小写敏感。再查 JSON 格式params里的属性名要和物模型定义对上多一个空格或少一个引号都会被平台静默丢弃。最后确认透传模式下ATCIPSEND的长度参数和实际发送字节数一致长度写多了会截断写少了会粘包。4.5 现象ESP8266 偶尔死机需要断电重启供电不足是头号嫌疑。ESP8266 在发射瞬间电流能到 300mA 以上如果 STM32 板子的 3.3V 稳压芯片带不动模块就会复位或死机。解决办法是给 ESP8266 单独供电或者在电源脚并一个 100uF 以上的电解电容。软件上也可以加看门狗检测到模块无响应就发ATRST复位比整机断电温和。5. 二次开发改造把示例改成你自己的设备5.1 换产品与设备改三处就能复用拿到这份代码做二次开发最直接的需求是换成自己的产品和设备。需要改的地方只有三处main.c里的三元组宏定义、mqtt_client.c里 CONNECT 报文的 ClientID/Username/Password 拼装、以及上报主题里的产品 ID 和设备名。建议把这三处抽到一个cloud_config.h里集中管理/* cloud_config.h集中管理云端接入参数 */ #define PRODUCT_ID 你的产品ID #define DEVICE_NAME 你的设备名 #define DEVICE_SECRET 你的设备密钥 #define MQTT_HOST mqtt.qcloud.com #define MQTT_PORT 1883 #define TOPIC_UP_FMT $thing/up/property/%s/%s #define TOPIC_DOWN_FMT $thing/down/property/%s/%s这样换设备只改一个头文件不用在多个源文件里翻找。签名函数里的时间戳建议用 STM32 的 RTC 或者联网后从平台获取别用编译时间硬编码否则过几天签名就失效了。5.2 增加多属性上报与下行控制示例通常只上报一个温度属性实际项目里往往要上报多个传感器数据。改造思路是把 JSON 拼装抽成独立函数用参数控制上报哪些字段/* 多属性上报按位掩码选择要上报的字段 */ void MQTT_ReportMulti(uint8_t mask, float temp, float humi, uint16_t lux) { char payload[128]; int n snprintf(payload, sizeof(payload), {\method\:\report\,\params\:{); if (mask 0x01) n snprintf(payload n, sizeof(payload) - n, \temperature\:%.1f,, temp); if (mask 0x02) n snprintf(payload n, sizeof(payload) - n, \humidity\:%.1f,, humi); if (mask 0x04) n snprintf(payload n, sizeof(payload) - n, \lux\:%u,, lux); /* 去掉末尾逗号再闭合JSON */ if (payload[n - 1] ,) n--; snprintf(payload n, sizeof(payload) - n, }}); MQTT_Publish($thing/up/property/xxx/dev1, payload); }下行控制则在串口接收中断里解析平台下发的 JSON提取params里的字段映射到 GPIO 或 PWM 输出。解析 JSON 不建议自己写字符串查找容易在嵌套结构上翻车可以引入一个轻量的 cJSON几百行代码移植成本很低。5.3 验证方法与调试习惯改完之后怎么确认没改坏我的习惯是分三层验证第一层用串口助手看 AT 指令交互确认 Wi-Fi 和 TCP 都通第二层用平台的控制台看设备是否在线、上报数据是否出现在物模型里第三层用平台的下行消息测试功能发一条控制指令看设备端是否响应。三层都过了再上真实传感器。还有一个技巧在ESP8266_SendCmd里加一个调试开关打开时把发送和接收的原始字节全部打印到调试串口。MQTT 报文是二进制直接看串口助手是乱码但对照协议文档逐字节看能快速定位是固定头错了还是载荷长度错了。从那以后我每次改 MQTT 组包代码都强制先跑一遍这个原始字节打印确认报文结构和协议文档对得上再往下走省了很多来回烧录的时间。希望帮到你。本文还有配套的精品资源点击获取