ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32+MQTT接入阿里云IoT:DHT11温湿度采集与物模型上报实战

STM32+MQTT接入阿里云IoT:DHT11温湿度采集与物模型上报实战 简介面向物联网与嵌入式初学者的STM32MQTT上云实战工程包完整演示了温湿度传感器数据采集、STM32外设配置、MQTT协议接入阿里云物联网平台的全过程适合用于毕设、课程设计或实际产品原型验证。压缩包共236个文件以STM32标准库的.c/.h源码、MDK编译生成的.o/.d/.crf及.hex烧录文件为主附带.uvprojx工程文件和.map/.lst等编译输出整体仅6.23MB目录结构清晰便于直接打开、重新编译或烧录验证。内容覆盖定时器、ADC、I2C、USART等常用外设模块并集成Paho MQTT库实现客户端接入、主题发布与云端数据上报从传感器驱动到底层协议栈再到阿里云平台配置均有完整工程代码供对照学习。已有4571人学习下载适合需要参考完整代码链路、快速掌握STM32上云流程的开发者。1. 从传感器到云端的完整链路STM32、MQTT 与阿里云 IoT 的取舍实际调试基于 STM32 的物联网项目时最常遇到的怪现象是串口已经打印出“MQTT connected”阿里云控制台上设备却一直显示“未激活”。问题几乎不出在 MQTT 协议本身而在阿里云物联网平台独有的设备认证规则——clientId、username、password 这三者的组合方式和你平时连公共 MQTT broker 时完全不一样。这篇笔记把 STM32F103 通过 ESP8266 接入阿里云 IoT 的整条链路拆开讲从 DHT11 单总线采集、MQTT 报文结构、阿里云三元组认证到物模型属性上报每个环节都有可抄的代码和参数对照。正在做基于 STM32 的嵌入式项目或者毕业设计的开发者直接按这条链路复现即可换传感器型号、换个云平台思路也能平移。2. STM32 温湿度采集DHT11 单总线时序与 F103 外设配置2.1 传感器选型与引脚分配温湿度传感器最常用的三款是 DHT11、AM2302DHT22和 SHT30三者的接口、精度和价格差异很大直接影响代码写法和上报频率。型号接口温度精度湿度精度采样周期适用场景DHT11单总线±2℃±5%RH1s低成本原型、室内环境监测AM2302/DHT22单总线±0.5℃±2%RH2s对温湿度精度有要求的场景SHT30I2C±0.3℃±2%RH可配置低功耗、连续采集场景DHT11 只占一根 GPIO但时序敏感读取时必须关中断SHT30 走 I2C 时序宽松但要额外移植驱动库。考虑到工程基于 STM32F10x 标准外设库选 DHT11 最省事项目里已经有 stm32f10x_gpio.c 和 stm32f10x_tim.cRCC 时钟配置也现成。引脚分配上F103 的大部分 GPIO 都支持推挽输出和上拉输入切换但注意避开 ADC 通道。假如后续要扩展 NTC 热敏电阻采集别把温湿度传感器放在 PA1、PA3 这类 ADC1 通道上否则外设冲突。推荐接线方式DHT11 DATA - STM32F103C8T6 PA6 DHT11 VCC - 3.3V DHT11 GND - GNDPA6 作为示例实际可换成任意普通 GPIO。单总线空闲时靠外部上拉电阻拉高数据线超过 20cm 时建议加 4.7kΩ 上拉到 VCC否则波形边缘变缓读出来全是乱码。打开工程第一件事是检查 stm32f10x_rcc.c 里的时钟使能GPIOA 挂在 APB2 总线上初始化代码RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出用于拉低总线发起起始信号 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure);这段代码把 PA6 配成推挽输出GPIO_Speed_50MHz 表示 IO 翻转速率单总线通信建议保持 50MHz翻转太慢会导致起始信号边缘不陡传感器误判。切换到读取模式时重新初始化 GPIO_Mode_IPU也就是上拉输入模式。2.2 DHT11 单总线时序与代码实现DHT11 一次完整数据帧是 40 bit8 bit 湿度整数、8 bit 湿度小数、8 bit 温度整数、8 bit 温度小数、8 bit 校验和校验和等于前四个字节之和的低 8 位。难点不在数据格式而在时序窗口。主机先拉低总线 18ms 以上发起起始信号释放后 DHT11 回 80us 低电平响应再拉高 80us 表示开始输出数据。每个数据位以 50us 低电平开始随后高电平持续 26~28us 表示逻辑 0持续 70us 左右表示逻辑 1。判断方法是在高电平中间采样一次读到高就是 1读到低就是 0。2.2.1 时序参数对照表信号方向最小典型最大起始信号低电平主机→DHT1118ms20ms30ms响应低电平DHT11→主机80us80us80us响应高电平DHT11→主机80us80us80us数据位“0”高电平DHT11→主机26us28us28us数据位“1”高电平DHT11→主机70us70us70us实测中 DHT11 的响应时间有时会漂到 90us所以代码里不能死等 80us必须有超时保护防止总线异常时程序卡死在 while 循环里。2.2.2 DHT11 读取与校验代码// 从 PA6 读取一次温湿度成功返回 0失败返回 -1 // buffer[0]湿度整数 buffer[1]湿度小数 buffer[2]温度整数 buffer[3]温度小数 uint8_t DHT11_ReadData(uint8_t buffer[4]) { uint8_t data[5] {0}; // 1. 主机拉低 20ms发起起始信号 DHT11_Pin_Output(); GPIO_ResetBits(GPIOA, GPIO_Pin_6); Delay_Ms(20); GPIO_SetBits(GPIOA, GPIO_Pin_6); Delay_Us(30); // 释放总线等待 DHT11 拉低响应 DHT11_Pin_Input(); // 2. 检测响应低电平超时 100us 防止卡死 uint32_t timeout 100; while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) Bit_SET timeout--) ; // 3. 读取 40 bit 数据 for (int i 0; i 40; i) { while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) Bit_RESET) ; // 等低电平结束 Delay_Us(40); // 延时到高电平中段采样 data[i / 8] (data[i / 8] 1) | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) Bit_SET); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) Bit_SET) ; // 等待位结束 } // 4. 校验和验证 if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) { return -1; } memcpy(buffer, data, 4); return 0; }逻辑说明第一步的 20ms 低电平是关键DHT11 手册要求最小 18ms太短传感器不响应第二步检测响应低电平超时 100us 防止总线异常死循环第三步是核心每个数据位以 50us 低电平开头先等低电平结束再延时 40us 落在高电平中段此时读到高电平判定为 1低电平判定为 0。参数说明Delay_Us 建议用 SysTick 或 DWT 实现不要用空循环编译器优化等级一变空循环的延时时间就完全不可控。注意 DHT11 采样周期是 1s两次读取之间至少间隔 1s否则传感器返回的数据帧不稳定。应用层调用时把读取周期放到 2s 一次留足余量也方便后续 MQTT 按固定周期上报。3. STM32 上移植 MQTT 客户端网络链路与 Paho 库裁剪3.1 网络链路选型ESP8266 AT 指令还是 W5500STM32F103 没有以太网 MAC连网通常两条路串口外接 ESP8266 WiFi 模块或者 SPI 外接 W5500 以太网控制器。ESP8266 适合部署位置不方便布线的场景TCP/IP 协议栈由模块自己完成STM32 只通过 USART 发 AT 指令开发工作量集中在字符串解析上。W5500 是硬件 TCP/IP 协议栈STM32 通过 SPI 读写 socket 缓冲区稳定性和实时性更好但必须插网线部署场景受限。对于阿里云 IoT 这种需要长时间保持 TCP 连接的场景ESP8266 是最常见搭配。要注意确认模块固件支持透传模式串口波特率设置为 115200代码里通过三级 AT 指令完成链路搭建AT 复位、ATCWMODE 设为 Station 模式并连接路由器、ATCIPSTART 建立 TCP 连接。TCP 连接建立后MQTT 报文是在这个 TCP 连接上跑的字节流ESP8266 只负责透传不解析内容。这里有一个很多新手踩过的坑ESP8266 新版固件自带 ATMQTTCONN 指令看似方便但阿里云要求 clientId 里携带逗号和等号securemode3,signmethodhmacsha1逗号会被 AT 指令解析器当成参数分隔符导致连接失败。所以稳妥的做法是只用 ATCIPSTART 建 TCPMQTT 报文在 STM32 侧自己组包数据链路尽量保持简单透明。3.2 阿里云 MQTT 连接参数与认证规则阿里云 IoT 平台的 MQTT 连接不是随便填个 broker 地址就行clientId、username、password 三个字段全部由三元组派生规则如下连接参数生成规则示例Broker 地址${productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.coma1Xxxxx.iot-as-mqtt.cn-shanghai.aliyuncs.com端口1883明文 / 8883TLS1883clientId${deviceName}.securemode3,signmethodhmacsha1,timestamp${ts}dev01.securemode3,signmethodhmacsha1,timestamp1712345678username${deviceName}${productKey}dev01a1XxxxxpasswordHMAC-SHA1(deviceSecret, 待签字符串)十六进制字符串待签字符串拼接格式是clientId${clientId}deviceName${deviceName}productKey${productKey}timestamp${timestamp}注意这里嵌入的 clientId 是带 securemode 参数的完整字符串不是只有设备名。password 拿 deviceSecret 作为 HMAC 密钥对该字符串做 SHA-1 计算结果转小写十六进制。实际工程里生成 HMAC-SHA1 签名可以在 STM32 上跑 mbedtls 的 SHA1 和 HMAC 接口也可以在 PC 上先用 Python 验证一遍再移植import hmac, hashlib def gen_password(product_key, device_name, device_secret, client_id, timestamp): content clientId%sdeviceName%sproductKey%stimestamp%s % ( client_id, device_name, product_key, timestamp) return hmac.new(device_secret.encode(), content.encode(), hashlib.sha1).hexdigest()参数说明device_secret 是创建设备时阿里云返回的三元组之一content 字符串的拼接顺序完全固定任何一个字段顺序错乱都会导致签名校验失败。建议先用这段 Python 算出预期结果再对比 STM32 端 mbedtls 算出的结果两边一致再上板。3.3 基于 Paho MQTT 嵌入式库的移植Paho MQTT 的 C 语言版本是嵌入式项目的主流选择它不依赖具体网络接口只提供报文编解码和会话状态机底层的收发函数需要自己实现。移植时只需要填充 Network 结构体里的 connect、read、write 三个函数指针。在 STM32 ESP8266 组合中这三个函数建立在 AT 指令之上。核心发送函数// 通过 USART1 把 MQTT 报文转发给 ESP8266 int mqtt_net_write(Network *n, unsigned char *buf, int len, int timeout) { for (int i 0; i len; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, buf[i]); } return len; }逻辑说明Paho 的 MQTTClient 在调用 MQTTPublish 时会把已编码的 PUBLISH 报文通过 write 函数发出。这里逐字节写入 USART1 发送寄存器TXE 标志位变为 SET 表示发送寄存器空可以写入下一个字节。参数说明timeout 参数在原库中用于控制等待时间但在串口场景下不生效保留它只是为了保持接口签名一致buf 指向 Paho 内部编码好的 MQTT 报文不要试图在外部修改其内容。read 函数要复杂一些因为 Paho 在等待 CONNACK 或 PUBACK 时是阻塞式读取read 必须严格返回指定长度的数据如果 ESP8266 返回的字节不够函数要一直等待直到超时。建议在 read 里用队列缓冲接收到的串口数据避免丢字节。Paho 移植时MQTTClient.h 里的MQTT_MAX_PACKET_SIZE宏要设为 256这决定了发送缓冲区大小默认 128 在物模型上报场景下可能不够。keepalive 参数按阿里云建议设置在 60~120 秒之间太大会被服务端主动断开太小增加无效心跳报文。4. 阿里云物联网平台配置与 JSON 数据上报4.1 创建产品与设备物模型定义要点在阿里云物联网平台控制台先创建产品再添加设备。产品节点类型选择“直连设备”连网方式选 WiFi数据格式选“ICA 标准数据格式”也就是 Alink JSON。设备品类如果选错后续物模型里找不到对应的标准功能点属性和事件都要手动定义。创建设备后平台生成 productKey、deviceName、deviceSecret 三元组设备端 MQTT 连接参数完全由这三个值派生。物模型定义两个属性Temperature 和 Humidity标识符必须是英文字母数据类型选 float读写类型选只读。这里有个容易忽略的细节物模型属性标识符和设备端 JSON 里的 key 必须大小写完全一致设备端上报Temperature物模型里就不能定义成temperature。4.2 标准物模型上报格式设备端属性上报的 Topic 格式为/sys/${productKey}/${deviceName}/thing/event/property/postpayload 必须是标准 Alink JSON{ id: 123, version: 1.0, sys: { ack: 1 }, params: { Temperature: 26.5, Humidity: 58.3 }, method: thing.event.property.post }字段说明id 是消息标识服务端会原样返回设备内自增params 里的 key 必须和物模型属性标识符一致method 固定为thing.event.property.post。sys.ack 设为 1 表示云端收到数据后返回 ACK 响应设为 0 不返回调试阶段建议开 1 确认链路通畅。4.3 STM32 端 JSON 拼装与 MQTT 发布STM32 上资源有限不需要引入 cJSON 库标准物模型上报的 JSON 结构固定直接用 sprintf 拼装更轻量char payload[128]; uint8_t sensor_data[4]; uint16_t msg_id 0; if (DHT11_ReadData(sensor_data) 0) { // sensor_data[2] 温度整数sensor_data[3] 温度小数 sprintf(payload, {\id\:\%d\,\version\:\1.0\,\sys\:{\ack\:1}, \params\:{\Temperature\:%d.%d,\Humidity\:%d.%d}, \method\:\thing.event.property.post\}, msg_id, sensor_data[2], sensor_data[3], sensor_data[0], sensor_data[1]); MQTTMessage msg; msg.qos 0; // 属性上报用 QoS0 足够 msg.retained 0; msg.payload (void *)payload; msg.payloadlen strlen(payload); MQTTPublish(client, /sys/a1Xxxxx/dev01/thing/event/property/post, msg); }逻辑说明读取传感器成功后把温湿度整数和小数部分拼进 JSON 模板。msg_id 每次上报自增方便和云端日志对应。MQTTPublish 是 Paho 的发布接口内部会把 payload 封装成 MQTT PUBLISH 报文发送。注意 sprintf 的格式里温度小数位传感器返回的是 0~9 的整数直接%d.%d拼接所以温度 26.5 会被正确拼成 26.5不会出现精度问题。云端 ACK 响应在另一个 Topic 上/sys/${productKey}/${deviceName}/thing/event/property/post_reply调试时如果发现数据发布成功但控制台看不到优先订阅这个 reply Topic服务端返回的 Code 值能区分是设备未激活、物模型不匹配还是权限错误。qos 设置为 0 的合理性在于温湿度数据周期性上报丢失个别数据不影响整体监控也不占用额外的确认带宽。如果传输的是控制指令那就必须用 QoS1避免指令丢失。5. 连接稳定性调试与 CONNACK 返回码排查5.1 CONNACK 返回码对照与定位设备上电后第一次 MQTT 连接失败原因集中在认证参数。串口打印 Paho 的 MQTTConnect 返回值0 表示连接成功非 0 对照下表定位CONNACK 返回码含义排查方向1协议版本不支持检查 MQTT 版本是否 3.1.12clientId 非法检查是否包含未转义字符3服务器不可用检查 broker 地址和端口可达性4username 或 password 错误重新核对 HMAC-SHA1 待签字符串拼接顺序5未授权检查 productKey / deviceName 是否匹配连接成功后验证上行链路重点看阿里云控制台“日志服务”里的“上行消息分析”。如果日志出现 topic 不匹配或 uri 非法绝大多数情况是 payload 里的 method 字段写错或者 params 里的 key 与物模型标识符不一致。5.2 断线重连与心跳参数边界重连策略不能激进。主循环里检测到断开就立刻重连在弱网环境下会进入“断开-重连-再断开”的死循环MQTT broker 侧会堆积大量半开连接时间久了被平台限流。常见的做法是记录上次重连时间戳间隔 10s 再发起下一次连续失败 5 次后把间隔拉长到 30s。这个策略在 ESP8266 透传模式下尤其重要因为 WiFi 断开的检测有延迟TCP 连接的异常并不是每次都能被立刻感知。心跳参数同样有边界。keepalive 设置为 60s 时要确保 ESP8266 的 TCP keepalive 不被路由器提前清理。一些家用路由器默认 TCP 空闲超时是 120s如果 MQTT heartbeat 间隔接近这个值连接会在路由器层面先断掉设备端却毫不知情。保守的做法是 keepalive 设 60s同时在 STM32 侧维护一个“最近一次 publish 时间”如果超过 30s 没有数据上报主动发一个 MQTT PINGREQ 心跳保活。最后提醒一个常被忽略的底层问题时钟频率跑偏会导致 DHT11 时序延时不准Paho 里的超时计算也跟着偏。曾经遇到过一个项目MQTT 能正常连接、数据正常上报但温度值忽高忽低排查到最后发现是 RCC 配置里 PLL 倍频数写错系统主频跑了 108MHz 而不是 72MHz所有延时都偏了 1.5 倍。遇到“数据能上云但值不合理”的故障先用逻辑分析仪看单总线波形再回头查 stm32f10x_rcc.c 里的时钟树配置这两步能排除八成的问题。本文还有配套的精品资源点击获取
返回列表