简介:本资源是一套基于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 的 AT+MQTT 指令集在 STM32 上建立 MQTT 连接:为什么必须用 AT+MQTTSTART 而非透传模式
EC20 支持两种 MQTT 接入方式:透传模式(AT+CIPSTART + 手动拼接 MQTT 报文)和原生 MQTT 指令模式(AT+MQTTxxx)。前者需开发者完全理解 MQTT 协议二进制格式,易因字节序、剩余长度编码错误导致连接被 ONENET 拒绝;后者由模组内部协议栈处理 CONNECT/SUBSCRIBE/PUBLISH 封装,STM32 只需发送结构化 AT 指令,大幅降低出错概率。ONENET 官方文档明确要求设备使用 MQTT 3.1.1 协议,且必须携带 ClientID、Username(即 APIKey)、Password(为空字符串)三元组进行鉴权——这正是 AT+MQTTSTART 指令的输入参数来源。
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 必须为 APIKey,Password 必须为空(不能省略),ClientID 格式为
deviceid${设备ID}(注意是$符号而非{}),例如deviceid58927341。Topic 则固定为/devices/${设备ID}/tsl(用于物模型数据上报)或/devices/${设备ID}/datapoints(用于原始数据点)。本方案采用后者,因其结构简单、无需定义 TSL 模型。
2.2 STM32 端初始化 EC20:AT 指令交互框架与关键超时设置
在 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→ 等待OKAT+CGATT?→ 确认附着网络(返回+CGATT: 1)AT+CSQ→ 检查信号强度(+CSQ: 25,0表示良好)AT+MQTTSTART=0,"tcp://183.230.40.39:6002",120→ 启动 MQTT 客户端(IP 为 ONENET 公共 MQTT 地址,端口 6002)
注意:
AT+MQTTSTART的第三个参数是 KeepAlive 时间(秒),ONENET 要求 ≥60 秒,设为 120 是为应对 4G 网络抖动。若返回ERROR,常见原因是 SIM 卡未注册成功或 DNS 解析失败,此时需先执行AT+CGDCONT=1,"IP","CMNET"设置 PDP 上下文。
2.3 构造并发送 MQTT CONNECT 指令:ClientID/Username/Password 的硬编码陷阱
EC20 的AT+MQTTCONNECT指令格式为:
AT+MQTTCONNECT="ClientID","Username","Password",KeepAlive,WillFlag,WillTopic,WillQoS,WillMessage,CleanSession对应 ONENET 要求填入:
- ClientID:
deviceid58927341(设备 ID 前加deviceid$) - Username:
a1b2c3d4e5f67890(APIKey) - Password:
""(空字符串,不可省略引号) - CleanSession:
1(ONENET 要求清理会话)
实际发送代码:
char connect_cmd[128]; sprintf(connect_cmd, "AT+MQTTCONNECT=\"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中每个对象必须含at(ISO8601 时间戳)和value(数值)。常见错误是直接发送{"temperature":25.3,"humidity":60.1},这会导致 ONENET 丢弃整条消息。
3.1 DHT22 传感器驱动与防抖采样策略
DHT22 为单总线器件,STM32 需模拟时序读取。HAL 库下推荐使用 GPIO 输入输出切换方式(非定时器捕获),关键点在于:
- 初始化时将 DATA 引脚设为推挽输出,拉低 18ms 启动信号
- 然后切为浮空输入,等待 80us 响应脉冲
- 后续 40 位数据每位以 50us 低电平起始,高电平持续时间决定 0 或 1(27~28us 为 0,70us 为 1)
为提升稳定性,采样需满足:
- 每次读取前延时 2s(DHT22 最小采样间隔)
- 连续 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 通过 AT+MQTTPUB 发布数据:QoS 选择与 Topic 动态拼接
EC20 的AT+MQTTPUB指令格式为:
AT+MQTTPUB="Topic","Payload",QoS,RetailFlag- Topic:
/devices/58927341/datapoints(设备 ID 替换为实际值) - QoS:ONENET 要求 QoS=0(最多一次),设为 1 会导致连接中断
- RetailFlag:设为 0(非保留消息)
动态拼接 Topic 的 C 代码:
char topic[64]; sprintf(topic, "/devices/%s/datapoints", device_id); char pub_cmd[128]; sprintf(pub_cmd, "AT+MQTTPUB=\"%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 基于 AT+MQTTSTAT 的连接状态轮询与自动恢复
EC20 提供AT+MQTTSTAT查询当前 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("AT+MQTTSTAT\r\n", "+MQTTSTAT:", 1000)) { sscanf((char*)rx_buffer, "+MQTTSTAT: %hhu", ¤t_state); if (current_state == 3 && last_state == 1) { // 从连接态变为断开 MQTT_Reconnect(); } last_state = current_state; } }重连逻辑需重走完整流程:AT+MQTTSTOP→AT+MQTTSTART→AT+MQTTCONNECT,而非仅重发 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 服务器地址 | AT+MQTTSTART=0,"tcp://183.230.40.39:6002",120 | IP183.230.40.39,端口6002 | 误用183.230.40.40(测试地址)或1883(非 TLS 端口) |
| ClientID | AT+MQTTCONNECT="deviceid$58927341",... | 必须含deviceid$前缀 | 写成deviceid58927341(缺$)或58927341(缺前缀) |
| Username | AT+MQTTCONNECT=...,"a1b2c3d4e5f67890",... | APIKey 字符串 | 使用 MasterKey 或设备 ID 代替 APIKey |
| Topic | AT+MQTTPUB="/devices/58927341/datapoints",... | /devices/{deviceID}/datapoints | 误写为/v1.6/devices/...(HTTP API 路径)或/mqtt(无效路径) |
| QoS | AT+MQTTPUB=...,0,0 | 必须为0 | 设为1导致连接被重置 |
5. 进阶技巧:用 AT+MQTTRECV 实现下行指令接收与温湿度阈值远程配置
ONENET 支持向设备下发指令,例如远程修改温湿度报警阈值。EC20 的AT+MQTTRECV指令可获取订阅 Topic 的消息,需提前AT+MQTTSUB订阅/devices/${设备ID}/cmds主题。
5.1 订阅命令主题并解析 JSON 指令
首先订阅命令 Topic:
char sub_cmd[64]; sprintf(sub_cmd, "AT+MQTTSUB=\"/devices/%s/cmds\",1\r\n", device_id); AT_SendCommand(sub_cmd, "OK", AT_TIMEOUT_MS);在主循环中轮询接收:
if (AT_SendCommand("AT+MQTTRECV\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 串口日志应打印收到的指令内容,且
AT+MQTTRECV返回非空 payload
注意:ONENET 的命令下发有 5 分钟有效期,超时未被设备接收则失效。EC20 的
AT+MQTTRECV是轮询式,非中断式,因此需保证主循环执行频率 ≥1Hz。
5.3 温湿度数据在 ONENET 折线图中的呈现优化
ONENET 控制台「数据可视化」中创建折线图时,若发现数据点稀疏或时间轴错乱,检查两点:
- 数据点密度:默认每分钟最多显示 100 个点,若每 10 秒上报一次,则需在图表设置中将「时间粒度」调为「10秒」或「30秒」
- 时间戳一致性:若使用
at字段自定义时间,务必确保 STM32 时钟与 NTP 同步(通过 EC20 的AT+CCLK获取),否则折线图出现时间跳跃。推荐策略:Payload 中省略at字段,依赖 ONENET 服务端打戳,确保所有数据点时间连续。
本文还有配套的精品资源,点击获取