
简介本资源是一套基于STM32F与EC20 4G模块实现温湿度数据上云的完整嵌入式物联网开发工程面向嵌入式初学者、物联网开发者及高校电子类课程实践者解决STM32端MQTT协议接入ONENET云平台的核心技术难点。压缩包含229个文件以64个.h头文件和52个.c源码为主涵盖STM32CubeMX生成的底层驱动、EC20串口AT指令封装、MQTT客户端基于Paho或轻量级实现、DHT/SHT系列传感器采集逻辑、JSON数据组包及ONENET主题配置等关键模块另有23个.o编译目标、22个.crf依赖文件及Keil工程配置文件uvprojx/uvoptx/bat脚本等总大小4.82MB结构规范便于理解固件构建流程与通信链路分层设计。目前已有351人学习下载提供可直接编译运行的完整工程框架、典型错误处理机制示例及ONENET平台对接配置说明助读者快速掌握从硬件连接、固件开发到云端数据落地的全链路实践能力。1. STM32F EC20 模组通过 MQTT 向 ONENET 上报温湿度不是“连上就行”而是要过三关AT 指令时序容错、MQTT 报文格式合规、ONENET 设备鉴权精准很多工程师拿到 STM32F103C8T6或类似主控 合宙 EC20 4G 模组后第一反应是“抄个 AT 指令发过去”结果卡在 CONNECT 返回 ERROR、SUBSCRIBE 超时、PUBLISH 后平台无数据。根本原因在于ONENET 对 MQTT 连接要求严格——它不接受非标准 ClientID、不校验但会静默丢弃非法 Topic、对 Payload 的 JSON 结构有硬性字段约束而 EC20 在弱信号下 AT 响应延迟波动大STM32 若未做指令超时重试与响应缓冲解析极易把MQTTSUB: 0,1误判为失败。本方案面向已焊接好 DHT22/AM2301 温湿度传感器、EC20 已完成供电与 SIM 卡注册的硬件环境聚焦「从裸机固件到平台可见数据」的完整链路覆盖 AT 指令集选型依据、MQTT 连接参数生成逻辑、ONENET 设备创建与 APIKey 绑定规则、以及 STM32 端基于 HAL 库的阻塞式超时机制实现。适合嵌入式初学者按步骤复现也提供熟手关注的 AT 指令响应状态机设计要点与 ONENET 数据点时间戳校准技巧。2. 用 EC20 的 ATMQTT 指令集在 STM32 上建立 MQTT 连接为什么必须用 ATMQTTSTART 而非透传模式EC20 支持两种 MQTT 接入方式透传模式ATCIPSTART 手动拼接 MQTT 报文和原生 MQTT 指令模式ATMQTTxxx。前者需开发者完全理解 MQTT 协议二进制格式易因字节序、剩余长度编码错误导致连接被 ONENET 拒绝后者由模组内部协议栈处理 CONNECT/SUBSCRIBE/PUBLISH 封装STM32 只需发送结构化 AT 指令大幅降低出错概率。ONENET 官方文档明确要求设备使用 MQTT 3.1.1 协议且必须携带 ClientID、Username即 APIKey、Password为空字符串三元组进行鉴权——这正是 ATMQTTSTART 指令的输入参数来源。2.1 ONENET 平台侧准备创建设备、获取 APIKey 与构造标准 Topic在 ONENET 物联网平台onenet.com控制台完成以下操作新建产品 → 选择「MQTT」接入方式 → 填写产品名称如stm32_ec20_th进入该产品 → 「设备管理」→ 「添加设备」→ 设备名称填sensor_001→ 保存后记录设备 ID如58927341和设备鉴权信息即 MasterKey进入「APIKey 管理」→ 「新建 APIKey」→ 权限选择「设备级」→ 关联设备选sensor_001→ 生成后复制APIKey 字符串如a1b2c3d4e5f67890提示ONENET 的 MQTT 连接 Username 必须为 APIKeyPassword 必须为空不能省略ClientID 格式为deviceid${设备ID}注意是$符号而非{}例如deviceid58927341。Topic 则固定为/devices/${设备ID}/tsl用于物模型数据上报或/devices/${设备ID}/datapoints用于原始数据点。本方案采用后者因其结构简单、无需定义 TSL 模型。2.2 STM32 端初始化 EC20AT 指令交互框架与关键超时设置在 STM32F103C8T6 的 HAL 库工程中需配置 UART2假设 EC20 接在 PA2/PA3并实现带超时的 AT 指令收发函数。核心是避免死等模组响应// at_command.c #define AT_TIMEOUT_MS 3000 #define AT_RETRY_MAX 3 uint8_t AT_SendCommand(const char* cmd, const char* expect, uint32_t timeout_ms) { HAL_UART_Transmit(huart2, (uint8_t*)cmd, strlen(cmd), 100); HAL_UART_Transmit(huart2, (uint8_t*)\r\n, 2, 100); uint32_t start HAL_GetTick(); while (HAL_GetTick() - start timeout_ms) { if (strstr((char*)rx_buffer, expect)) { // rx_buffer 为全局接收缓存 return 1; } HAL_Delay(10); } return 0; }初始化流程必须按顺序执行且每步失败需重试AT→ 等待OKATCGATT?→ 确认附着网络返回CGATT: 1ATCSQ→ 检查信号强度CSQ: 25,0表示良好ATMQTTSTART0,tcp://183.230.40.39:6002,120→ 启动 MQTT 客户端IP 为 ONENET 公共 MQTT 地址端口 6002注意ATMQTTSTART的第三个参数是 KeepAlive 时间秒ONENET 要求 ≥60 秒设为 120 是为应对 4G 网络抖动。若返回ERROR常见原因是 SIM 卡未注册成功或 DNS 解析失败此时需先执行ATCGDCONT1,IP,CMNET设置 PDP 上下文。2.3 构造并发送 MQTT CONNECT 指令ClientID/Username/Password 的硬编码陷阱EC20 的ATMQTTCONNECT指令格式为ATMQTTCONNECTClientID,Username,Password,KeepAlive,WillFlag,WillTopic,WillQoS,WillMessage,CleanSession对应 ONENET 要求填入ClientIDdeviceid58927341设备 ID 前加deviceid$Usernamea1b2c3d4e5f67890APIKeyPassword空字符串不可省略引号CleanSession1ONENET 要求清理会话实际发送代码char connect_cmd[128]; sprintf(connect_cmd, ATMQTTCONNECT\deviceid$%s\,\%s\,\\,120,0,\\,\\,\\,1\r\n, device_id, api_key); // device_id 和 api_key 为全局变量 if (!AT_SendCommand(connect_cmd, OK, AT_TIMEOUT_MS)) { // 处理连接失败检查 APIKey 是否过期、设备是否离线 }提示ONENET 对 ClientID 中的$符号敏感若误写为deviceid{58927341}或deviceid58927341无$CONNECT 会静默失败且无明确错误码。建议在串口调试助手中手动发送该指令验证响应。3. 采集温湿度并封装为 ONENET 兼容的 JSON 格式DHT22 读取与 MQTT Payload 构建ONENET 的/devices/${设备ID}/datapointsTopic 接收标准 JSON 格式数据其顶层必须包含datastreams数组每个元素含id数据流标识和datapoints时间序列数组而datapoints中每个对象必须含atISO8601 时间戳和value数值。常见错误是直接发送{temperature:25.3,humidity:60.1}这会导致 ONENET 丢弃整条消息。3.1 DHT22 传感器驱动与防抖采样策略DHT22 为单总线器件STM32 需模拟时序读取。HAL 库下推荐使用 GPIO 输入输出切换方式非定时器捕获关键点在于初始化时将 DATA 引脚设为推挽输出拉低 18ms 启动信号然后切为浮空输入等待 80us 响应脉冲后续 40 位数据每位以 50us 低电平起始高电平持续时间决定 0 或 127~28us 为 070us 为 1为提升稳定性采样需满足每次读取前延时 2sDHT22 最小采样间隔连续 3 次读取剔除最大最小值后取均值若校验和错误立即重试最多 3 次// dht22.c typedef struct { float temperature; float humidity; } DHT22_Data; uint8_t DHT22_ReadData(DHT22_Data* data) { uint8_t buf[5] {0}; if (DHT22_StartSignal() ! HAL_OK) return 0; if (DHT22_ReadBytes(buf) ! HAL_OK) return 0; if (buf[4] ! (buf[0]buf[1]buf[2]buf[3])) return 0; // 校验 >{ datastreams: [ { id: temperature, datapoints: [{value: 25.3}] }, { id: humidity, datapoints: [{value: 60.1}] } ] }在 STM32 上用 sprintf 构建需预估 buffer 大小char payload[256]; float temp, humi; DHT22_ReadData(sensor_data); temp sensor_data.temperature; humi sensor_data.humidity; sprintf(payload, {\datastreams\:[ {\id\:\temperature\,\datapoints\:[{\value\:%.1f}]}, {\id\:\humidity\,\datapoints\:[{\value\:%.1f}]} ]}, temp, humi);注意sprintf易造成栈溢出生产环境应改用snprintf并检查返回值。此处payload长度上限为 256 字节足够容纳双精度浮点数%.1f输出如25.3占 4 字节。3.3 通过 ATMQTTPUB 发布数据QoS 选择与 Topic 动态拼接EC20 的ATMQTTPUB指令格式为ATMQTTPUBTopic,Payload,QoS,RetailFlagTopic/devices/58927341/datapoints设备 ID 替换为实际值QoSONENET 要求 QoS0最多一次设为 1 会导致连接中断RetailFlag设为 0非保留消息动态拼接 Topic 的 C 代码char topic[64]; sprintf(topic, /devices/%s/datapoints, device_id); char pub_cmd[128]; sprintf(pub_cmd, ATMQTTPUB\%s\,\%s\,0,0\r\n, topic, payload); if (!AT_SendCommand(pub_cmd, OK, AT_TIMEOUT_MS)) { // 记录失败日志可能原因包括 MQTT 连接已断开、payload 超长1024 字节 }4. EC20 MQTT 连接稳定性优化AT 指令状态机设计与 ONENET 数据验证方法EC20 在 4G 信号波动时会出现 MQTT 连接意外断开但模组不会主动通知 STM32。若不检测后续 PUBLISH 将失败且无提示。必须实现连接状态心跳与自动重连机制。4.1 基于 ATMQTTSTAT 的连接状态轮询与自动恢复EC20 提供ATMQTTSTAT查询当前 MQTT 状态返回值含义如下MQTTSTAT: 0未启动MQTTSTAT: 1已连接MQTTSTAT: 2正在连接MQTTSTAT: 3连接断开在主循环中每 30 秒轮询一次void MQTT_CheckConnection(void) { static uint8_t last_state 0; char stat_resp[32]; if (AT_SendCommand(ATMQTTSTAT\r\n, MQTTSTAT:, 1000)) { sscanf((char*)rx_buffer, MQTTSTAT: %hhu, current_state); if (current_state 3 last_state 1) { // 从连接态变为断开 MQTT_Reconnect(); } last_state current_state; } }重连逻辑需重走完整流程ATMQTTSTOP→ATMQTTSTART→ATMQTTCONNECT而非仅重发 CONNECT。4.2 ONENET 平台侧数据验证三步定位上报失败环节当 STM32 日志显示 PUBLISH OK 但 ONENET 控制台无数据时按以下顺序排查检查设备在线状态控制台「设备管理」中设备状态是否为「在线」。若显示「离线」说明 MQTT CONNECT 未成功或 KeepAlive 超时。查看 MQTT 订阅状态ONENET 不强制 SUBSCRIBE但需确认设备是否已绑定到产品。进入「产品管理」→「设备列表」→ 点击设备 → 「数据流」页签若无temperature/humidity数据流说明首次 PUBLISH 未触发自动创建需确保 Payload JSON 结构正确。启用 ONENET 日志追踪在控制台「运维监控」→「设备日志」中选择目标设备设置时间范围筛选MQTT类型日志。正常日志含MQTT_PUBLISH_SUCCESS失败则有MQTT_PUBLISH_ERROR_INVALID_JSON或MQTT_PUBLISH_ERROR_TOPIC_NOT_FOUND。提示ONENET 的数据流datastream是懒创建的——首次向某id如temperature发送数据时自动建立。若 Payload 中id拼写错误如temperatre该数据流将永远不存在后续同名数据仍被丢弃。4.3 关键参数速查表EC20 AT 指令与 ONENET 要求对照参数项EC20 AT 指令ONENET 要求常见错误MQTT 服务器地址ATMQTTSTART0,tcp://183.230.40.39:6002,120IP183.230.40.39端口6002误用183.230.40.40测试地址或1883非 TLS 端口ClientIDATMQTTCONNECTdeviceid$58927341,...必须含deviceid$前缀写成deviceid58927341缺$或58927341缺前缀UsernameATMQTTCONNECT...,a1b2c3d4e5f67890,...APIKey 字符串使用 MasterKey 或设备 ID 代替 APIKeyTopicATMQTTPUB/devices/58927341/datapoints,.../devices/{deviceID}/datapoints误写为/v1.6/devices/...HTTP API 路径或/mqtt无效路径QoSATMQTTPUB...,0,0必须为0设为1导致连接被重置5. 进阶技巧用 ATMQTTRECV 实现下行指令接收与温湿度阈值远程配置ONENET 支持向设备下发指令例如远程修改温湿度报警阈值。EC20 的ATMQTTRECV指令可获取订阅 Topic 的消息需提前ATMQTTSUB订阅/devices/${设备ID}/cmds主题。5.1 订阅命令主题并解析 JSON 指令首先订阅命令 Topicchar sub_cmd[64]; sprintf(sub_cmd, ATMQTTSUB\/devices/%s/cmds\,1\r\n, device_id); AT_SendCommand(sub_cmd, OK, AT_TIMEOUT_MS);在主循环中轮询接收if (AT_SendCommand(ATMQTTRECV\r\n, MQTTRECV:, 500)) { // rx_buffer 形如MQTTRECV: /devices/58927341/cmds,{\cmd\:\set_threshold\,\temp_max\:30.0} char* payload_start strstr((char*)rx_buffer, \{); if (payload_start) { parse_command(payload_start 1); // 自定义 JSON 解析函数 } }典型指令 JSON 示例{cmd:set_threshold,temp_max:30.0,humi_min:40.0}解析后更新本地阈值变量并触发 LED 报警逻辑。5.2 ONENET 控制台手动下发指令验证流程控制台进入设备详情页 → 「设备调试」→ 「下发指令」Topic 填/devices/58927341/cmds与订阅 Topic 一致Payload 填 JSON 字符串如上例点击「下发」STM32 串口日志应打印收到的指令内容且ATMQTTRECV返回非空 payload注意ONENET 的命令下发有 5 分钟有效期超时未被设备接收则失效。EC20 的ATMQTTRECV是轮询式非中断式因此需保证主循环执行频率 ≥1Hz。5.3 温湿度数据在 ONENET 折线图中的呈现优化ONENET 控制台「数据可视化」中创建折线图时若发现数据点稀疏或时间轴错乱检查两点数据点密度默认每分钟最多显示 100 个点若每 10 秒上报一次则需在图表设置中将「时间粒度」调为「10秒」或「30秒」时间戳一致性若使用at字段自定义时间务必确保 STM32 时钟与 NTP 同步通过 EC20 的ATCCLK获取否则折线图出现时间跳跃。推荐策略Payload 中省略at字段依赖 ONENET 服务端打戳确保所有数据点时间连续。本文还有配套的精品资源点击获取