ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32接入阿里云MQTT:从ESP8266到微信小程序的全链路实现

STM32接入阿里云MQTT:从ESP8266到微信小程序的全链路实现 简介基于MQTT协议实现STM32数据上云与微信小程序远程控制是一套可直接运行的嵌入式物联网项目方案适合毕业设计、课程设计、工程实训及学科竞赛等场景。压缩包共262个文件约7.94MB以STM32F10x标准库C源码53个.c和头文件55个.h为主并包含工程文件、编译产物、说明文档及微信小程序交互代码覆盖USART、ADC、定时器、I2C、CAN等常见外设驱动便于二次开发。已有477人学习下载。资料提供完整源码与工程配置经测试可编译烧录同时对不熟悉硬件画板的小白可用面包板加杜邦线连接外设模块快速复刻。既可用作单片机与物联网结合的练手项目也可在MQTT通信框架上扩展更多传感器或远程控制功能适合作为云端远程控制原型的基础工程。1. 为什么 STM32 设备上阿里云不直接用 TCP 协议而走 MQTT先说结论在毕设和竞赛这类短周期项目里自定义 TCP 协议是时间开销最大的选择。你要自己处理粘包拆包、心跳保活、断线重连、请求应答对应这些工作做完至少一周没了。而 MQTT 协议把这些问题全部变成标准行为阿里云物联网平台已经实现了 broker 端闭环设备端只需要关注三件事算对签名、拼好 JSON、发布到正确 topic。这套资源就是沿这条链路搭的——STM32 采集数据ESP8266 经 MQTT 上抛到阿里云微信小程序订阅和发布完成远程控制工程文件用 Keil5 打开就能编译。从课程设计到毕业设计这条链路都是最稳妥的快速原型方案拿到源码后换掉密钥和引脚定义就能复刻成自己的项目。2. 接入链路拆解MQTT Broker 参数、三要素签名与物模型 Topic2.1 三层架构中每层关注的协议边界把整个系统拆开其实是四段设备围绕一张 MQTT broker 转。STM32 负责采集和控制ESP8266 负责把串口数据变成 MQTT 报文阿里云物联网平台负责认证转发微信小程序负责可视化展示与远程下发。这里有一个经常被忽略的设计点MQTT 的发布订阅模型天然地解耦了生产者和消费者STM32 上报数据时并不知道小程序在不在线小程序下发指令时也不需要知道 STM32 是否醒着这一切由 broker 的 topic 订阅关系在中间维持。因此代码层面真正硬编码的东西只有三样阿里云实例地址、设备三要素、三条 topic。对应到工程里就是宏定义和初始化串口时的数据结构。毕设答辩被问到的概率最大的问题就是“topic 为什么这么设计”回答思路是自上而下一条链路物模型的属性对应 topic 的 property操作属性就是 set属性变化就是 post。2.2 连接参数与 HMAC 签名计算阿里云物联网平台为每个设备预分配了三个唯一标识productKey、deviceName、deviceSecret。MQTT 接入时认证用的是“动态签名”而不是静态密码——你必须用 deviceSecret 计算出一个一次性密码这个密码里隐含了时间戳和设备身份。连接参数列表如下参数拼接规则示例MQTT broker 地址${productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.coma1xxxxx.iot-as-mqtt.cn-shanghai.aliyuncs.com端口1883TCP/ 443WebSocket1883clientId${productKey}.${deviceName}securemode2,signmethodhmacsha1username${deviceName}${productKey}dev01a1xxxxxpasswordHMACSHA1(deviceSecret, 拼接串)32 位 hex 字符串clientId 里的 securemode 可选 2 或 32 对应无加密 TCP3 对应 TLS 加密。在设备端和 ESP8266 做联调时先用 securemode2链路通了再切 3不然加密握手失败会浪费很多排错时间。password 的计算过程是整个接入方案的核心拼接串格式必须严格按下面的顺序import hmac, hashlib, time product_key a1xxxxxxxxx device_name stm32_dev01 device_secret 29f5a1c2cf0b6ff0d8c7d7c6c6a9bc95 timestamp str(int(time.time() * 1000)) client_id {}|securemode2,signmethodhmacsha1|.format( product_key . device_name) content clientId{}deviceName{}productKey{}timestamp{}.format( client_id, device_name, product_key, timestamp) password hmac.new(device_secret.encode(), content.encode(), hashlib.sha1).hexdigest() print(clientId:, client_id) print(username:, device_name product_key) print(password:, password)这段脚本用于在烧录固件前验证密码是否算得对。如果烧进 STM32 后连不上把这个脚本算出来的三要素填进 MQTTX 客户端连接一下能连上就说明是单片机端的问题连不上就说明签名参数有误。我遇到过比较多的情况是 timestamp 位数不对阿里云要求毫秒级时间戳秒级时间戳会导致签名不一致重启后密码就只能用一分钟。2.3 属性上报/下发的 Topic 结构与 JSON 物模型在阿里云控制台的产品定义里你新建的属性会生成对应的 topic 路径。设备不使用传统的自定义主题格式而是统一走物模型标准 topic。常用三条场景topic属性上报/sys/{productKey}/{deviceName}/thing/event/property/post属性设置云端到设备/sys/{productKey}/{deviceName}/thing/service/property/set属性设置回复/sys/{productKey}/{deviceName}/thing/service/property/set_reply上报 JSON 需要用固定外层结构包裹{ id: 1001, version: 1.0, method: thing.event.property.post, params: { Temperature: 26.5, Humidity: 60.3, Power: 1 } }params 里的字段名必须和物模型 identifier 完全一致。很多同学报错不是协议问题而是大小写或下划线写错云端那边会直接提示“数据格式错误”并在日志中心丢弃。设置类指令的格式类似但 method 变为 thing.service.property.set设备解析完必须在 15 秒内回复 set_reply否则控制台会标红超时。2.4 Topic 订阅与断线重连的联动STM32 端的 MQTT 订阅动作要和连接成功回调绑定而不是只写在主循环开头。我自己的工程是封装了一个 mqtt_connect() 函数里面先完成连接、订阅、在线状态置位三步。断线后触发重连重连成功会再次调用订阅流程这样小程序端才不至于在设备重启后收不到任何回复。这个细节在资源里的 stm32f10x_tim.c 和 stm32f10x_usart.c 之间的配合上体现得比较明显定时器中断负责周期上报串口中断负责接收 AT 事件帧。3. STM32 端实现标准外设库、AT 指令上抛与控制指令解析3.1 工程结构与外设对应关系资源里的 Keil 工程使用标准外设库涉及 stm32f10x_adc.c、stm32f10x_usart.c、stm32f10x_tim.c、stm32f10x_i2c.c、stm32f10x_can.c 等文件。打开工程后先在 stm32f10x_conf.h 里检查用到的外设宏是否打开标准库默认很多是用不到的。实际接线USART1_TX/PA9 接 ESP8266 的 RXDUSART1_RX/PA10 接 ESP8266 的 TXDGND 共地3.3V 供电注意 ESP8266 峰值电流可能到 300mA建议单独供电而不是直接从板载稳压取电。ADC 采集用 PA0 通道 0控制继电器用 PB1、PB2。3.2 ESP8266 AT 指令序列与 MQTT 连接ESP8266 固件版本建议至少 2.0 以上太低不支持 MQTT AT 指令。STM32 的启动流程是先发AT\r\n试探返回 OK 后依次配置模式、连接 WiFi、设置 MQTT 参数、建立连接。完整序列static const char *boot_cmd[] { AT\r\n, ATCWMODE1\r\n, // 1Station模式 ATCWJAP\MyWiFi\,\12345678\\r\n, // 连接无线网络 ATMQTTUSERCFG0,1,\NULL\,\dev01\,\password\,0,0,\\\r\n, ATMQTTCONN0,\a1xxxxx.iot-as-mqtt.cn-shanghai.aliyuncs.com\,1883,1\r\n, ATMQTTSUB0,\/sys/a1xxxxx/dev01/thing/service/property/set\,1\r\n };ATMQTTUSERCFG 的第三个参数是 clientID第七第八个参数是用户名密码的开关位如果用三要素签名方式这里直接填上一步计算好的 clientId、username、password。ATMQTTSUB 在每次重连后都要重新执行所以这段序列不能只跑一次建议封装成 mqtt_reconnect()在 MQTT 连接异常断开时重新调用。3.3 ADC 采样与属性上抛属性上抛的代码放在主循环的定时器轮询里比如每 5 秒采样一次 ADC 并发布uint16_t adc_value adc_read_channel(ADC_Channel_0); float temperature (float)adc_value * 3.3f / 4096.0f * 100.0f; char json[96]; sprintf(json, {\id\:\%d\,\version\:\1.0\, \params\:{\Temperature\:%.1f,\Humidity\:%.1f}, \method\:\thing.event.property.post\}, msg_id, temperature, humidity); char cmd[192]; sprintf(cmd, ATMQTTPUB0,\/sys/a1xxxxx/dev01/thing/event/property/post\,\%s\,0,0\r\n, json); uart1_send(cmd);代码里最后两个 0 分别是 QoS 和 retain 标志。QoS 0 适用于周期性上报丢了下一帧补上即可控制指令涉及状态变化建议用 QoS 1。retain 保持 false否则 broker 会把最后一帧数据缓存新设备上线立即收到过期数据。3.4 property/set 指令解析与状态回复ESP8266 接收到消息后会在串口输出以 MQTTSUBRECV 开头的事件帧STM32 中断里把它缓存到环形缓冲区主循环里解析// 例如 // MQTTSUBRECV:0,/sys/.../property/set,26,{method:thing.service.property.set,...} if (strstr(rx_line, \Power\:1)) { GPIO_SetBits(GPIOC, GPIO_Pin_13); // 开继电器 } else if (strstr(rx_line, \Power\:0)) { GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 关继电器 } // 收到set后需要主动回一条reply否则控制台显示超时 uart1_send(ATMQTTPUB0,\/sys/a1xxxxx/dev01/thing/service/property/set_reply\, \{\\\code\\\:200,\\\data\\\:{}}\,1,0\r\n);这里用子串匹配解析简单场景是可接受的但一旦物模型里出现多个开关量最好引入 cJSON 解析。子串匹配的问题是Power:10会把Power:1误判成开。回复报文里 code 固定为 200data 可以是空对象重点是必须带 id 字段对应回消息归属。4. 微信小程序端接入MQTT over WebSocket 与双向控制4.1 为什么小程序不能直接连 1883微信小程序底层网络能力基于 wx.connectSocket它只允许建立 WebSocket 连接而原生 MQTT 是 TCP 长连接在小程序环境里直接使用 mqtt.js 连接 1883 必然失败。阿里云物联网平台对这种情况提供了 MQTT over WebSocket 的桥接方案broker 地址不变、端口改为 443、路径固定在 /mqtt并在握手阶段自动识别 WebSocket 子协议为 mqtt。这样开发者不需要自建协议转换网关只需要把端口和路径换掉剩下的 MQTT 报文照常编解码。实际校验的时候注意wxs:// 前缀是平台针对微信小程序的特殊适配用 wss:// 在部分老版本基础库上会握手失败。4.2 mqtt.js 连接与 npm 构建4.2.1 构建 npm 前置步骤小程序端使用 mqtt.js 需要先在项目根目录执行 npm init 并安装依赖然后在微信开发者工具里点击“工具 → 构建 npm”构建完成之后才能 import。不执行构建这一步直接 require 会报module not found。4.2.2 连接代码与参数说明import mqtt from mqtt; const productKey a1xxxxx; const deviceName stm32_dev01; const client mqtt.connect( wxs://${productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.com/mqtt, { clientId: ${productKey}.${deviceName}|securemode3,signmethodhmacsha1|, username: ${deviceName}${productKey}, password: HMAC生成的字符串, connectTimeout: 8000 } ); client.on(connect, () { // 设备上报的属性小程序订阅后实时刷新页面 client.subscribe(/sys/${productKey}/${deviceName}/thing/event/property/post); });如果 mqtt.js 版本较老还需要手动设置transport: wss。建议把 mqtt.js 版本锁在 4.3.8这个版本对微信小程序的适配比较稳定3.x 版本的 connect 回调和 4.x 存在显著差异踩过坑之后再也没升过。connectTimeout 设 8 秒是折中值太短弱网环境频繁掉线太长用户等待无反馈。4.3 页面数据刷新与控制指令发布页面里 onLoad 时保存 client 实例onMessage 里解析 JSON 后调用 this.setData 更新温度显示。控制开关的代码// 按钮点击 sendControl(cmd) { const topic /sys/${productKey}/${deviceName}/thing/service/property/set; const payload { id: String(Date.now()), version: 1.0, method: thing.service.property.set, params: { Power: cmd ? 1 : 0 } }; this.client.publish(topic, JSON.stringify(payload), { qos: 1 }); }publish 的 QoS 为 1 时如果 30 秒内没有收到 broker 的 pubackmqtt.js 会主动重发。重发场景下STM32 端可能已经执行过一次控制就会产生重复动作。因此在设备端做好幂等处理同一个 id 的指令如果已经执行过直接忽略只回 set_reply 不重复操作外设。微信小程序的用户界面还需要处理 APP 切入后台再回前台时 WebSocket 可能断开的情况onShow 里检测 client.connected 状态决定是否重连。有个容易被忽略的点微信开发者工具里一切正常安卓真机上也正常但 iOS 上连不上大部分情况是没把域名加进 socket 合法域名列表。4.4 合法域名注册表与实例状态注意微信公众平台对 socket 合法域名的校验比 request 更严格域名必须经过 ICP 备案且不能带端口。开发阶段可以勾选“不校验合法域名”但真机预览时必须把wss://a1xxxxx.iot-as-mqtt.cn-shanghai.aliyuncs.com加入 socket 合法域名列表。发布审核时小程序涉及 IoT 远程控制需要选择合适类目最好在页面醒目位置注明“教学演示用途”。另外有一个今年特别容易踩的外部限制阿里云物联网平台已经不再支持新购公共实例只对已开通的账号提供存量服务。如果账号下没有可用实例可以先在本地用 EMQX 或 MQTTX 把协议逻辑跑通等有可用实例或改用企业认证时再切换地址和密钥。这种方案不影响整体架构改的只是 broker 地址和签名参数。5. 调试三板斧签名校验、MQTTX 交叉验证与 MQTT 返回码对照5.1 先用独立脚本卡住签名参数把第 2 章的 Python 签名脚本保存为 sign.py每次修改 deviceSecret 后先用它输出三要素。再在桌面端装一个 MQTTX用脚本生成的结果新建连接地址填 broker端口 1883。能连上就说明签名正确把三要素原样填到 STM32 工程宏定义里再出问题就从串口和网络两个方向排查。5.2 MQTTX 的两种模拟角色一种是把 MQTTX 当云端模拟器STM32 开发板正常运行MQTTX 订阅 property/post topic验证传感器数据是否按预期上报另一种是把 MQTTX 当设备模拟器小程序端正常连接MQTTX 发布 property/set topic验证小程序收到的状态变化是否符合预期。这种方法能做到协议层全链路交叉验证比直接烧录整套工程再找问题快得多。5.3 连接失败常见返回码返回码含义处理建议4用户名或密码错误签名过期/拼接错误核对 productKey、deviceName、timestamp5未授权或连接被拒确认设备状态为“已激活”且实例可用7未知错误多为网络ping broker 域名确认 DNS 解析正常8域名解析失败检查 ATCIPDOMAIN 是否能解析调试时在 STM32 串口日志里把 MQTTSUBRECV 前缀和 AT 响应帧全部打印出来出现 MQTTDISCONNECTED 时记录是模块主动断开还是服务器断开。服务器断开普遍是心跳间隔超过 120 秒ESP8266 端把 MQTT keepalive 设成 60 秒内就能稳定保住长连接。本文还有配套的精品资源点击获取
返回列表