AHT20+RM500U 5G模组接入OneNET:MQTT数据上云全流程指南
发布时间:2026/9/10 13:58:00来源:尧图网络
简介这是一份STM32单片机物联网实战项目资料面向有单片机基础、正在学习5G通信与云平台对接的开发者。工程使用移远RM500U 5G模块采集AHT20温湿度传感器数据通过MQTT协议上传至OneNET物联网平台覆盖了传感器读取、串口通信、网络注册、数据发布等完整链路。代码基于KEIL HAL库编写注释详细引脚接线在源码中均有定义方便移植到其他STM32型号也便于按需扩展不同传感器。资料包共972个文件压缩后大小约13.38MB。其中包含567个C语言源文件、242个头文件另有工程配置文件、链接脚本、编译中间文件以及最终生成的HEX固件既能对照阅读源码逻辑也可直接用于烧录验证。文件类型覆盖源码、配置和输出物目录按功能组织可快速定位模块驱动、业务代码和平台对接部分。当前已有617人学习下载。对于正在调试RM500U或类似5G模组的开发者这份工程能帮助快速跑通数据采集与上云流程通过阅读作者预留的扩展说明也能掌握接入其他传感器的改造方法减少独立摸索的时间成本。内容完整、注释友好。1. 先把链路拆开RM500U 5G 模组、AHT20 和 OneNET 各自负责什么把 5G 模组用在只产生几十字节温湿度数据的环境里第一反应是浪费但 RM500U 这个组合真正解决的是“没有网线、没有 WiFi、又要求设备长期在线”的现场问题。AHT20 是价格几块钱的温湿度传感器STM32 只用一路 I2C 和一个串口就能完成采集与转发RM500U 在中间承担了最难的部分4G/5G 拨号、TCP 承载和 MQTT 会话单片机本身甚至不需要理解 TCP/IP。OneNET 就是最后一站设备注册后拿到四个参数按平台的主题和 JSON 格式把数据抛上去设备影子、历史曲线和数据订阅全部交给平台侧。这条链路适合刚摸到 5G 模组的嵌入式工程师也适合做远程环境监测、冷链运输或配电房温湿度告警的原型验证。关键不在 RM500U 有多“快”而在如何把 AT 指令时序和平台主题对齐。2. AHT20 采集I2C 时序、状态字与温湿度换算的常见偏移2.1 AHT20 不是“一读就回”的 I2C 从机校准位和忙标志要轮询AHT20 的 I2C 地址是 0x38但这个传感器和 EEPROM 不一样上电后要先等内部校准完成每次触发测量后还要等几十毫秒直接去读只会拿到 0xFF 或旧数据。很多从 51 或 Arduino 迁移过来的程序没有做忙标志轮询结果就是第一包数据永远是野值。初始化时向 0x38 写0xBE 0x08 0x00然后读状态字bit3 为 1 表示校准完成测量时写0xAC 0x33 0x00然后等状态字 bit7 从 1 变成 0。下面是一段软件 I2C 下的读取函数#define AHT20_ADDR 0x38 uint8_t aht20_read_raw(uint8_t buf[6]) { uint8_t cmd[3] {0xAC, 0x33, 0x00}; if (!i2c_write(AHT20_ADDR, cmd, 3)) { return 0x01; /* I2C NACK先查上拉和地址 */ } /* 触发后最快也要 40ms数据手册给的上限是 80ms */ delay_ms(80); /* 读 6 字节直到状态字 bit7 变为 0 */ for (int i 0; i 5; i) { if (!i2c_read(AHT20_ADDR, buf, 6)) { return 0x02; /* 读取失败 */ } if (!(buf[0] 0x80)) { return 0x00; /* 测量完成 */ } delay_ms(5); } return 0x03; /* 超时传感器可能未初始化 */ }这段代码把“触发测量”和“等待完成”放在同一个函数里注释里标出了三个关键点。i2c_write和i2c_read是底层软件 I2C 原语7 位地址 0x38 在发送时要左移成 0x70写或 0x71读这是软件模拟和硬件 I2C 最容易不一致的地方。参数上SCL 频率不要超过 400kHz实测 100kHz 兼容性最好SDA 和 SCL 的上拉电阻用 4.7kΩ如果连接线超过 20cm 就换成 2.2kΩ。STM32F405 这类芯片的硬件 I2C 在这颗传感器上偶尔会出现忙标志卡死我一般直接开 GPIO 模拟省掉排查硬件 I2C 状态机的 time。2.2 20bit 原始值换算湿度满量程是 2^20温度要减 50AHT20 返回的 6 字节帧结构是byte0 状态字byte1~byte2 是湿度高 16 位byte3 的高 4 位是湿度低 4 位、低 4 位是温度高 4 位byte4~byte5 是温度低 16 位。这个交叉结构是换算最容易算错的地方。uint32_t raw_h ((uint32_t)buf[1] 12) | ((uint32_t)buf[2] 4) | ((uint32_t)buf[3] 4); uint32_t raw_t ((uint32_t)(buf[3] 0x0F) 16) | ((uint32_t)buf[4] 8) | (uint32_t)buf[5]; float hum ((float)raw_h) * 100.0f / 1048576.0f; float temp ((float)raw_t) * 200.0f / 1048576.0f - 50.0f;湿度的满量程是 2^20也就是 1048576不是 0x1000000更不是 1024。温度换算时先按满量程映射到 0~200再减 50 得到 -40~85 摄氏度的范围。(buf[3] 0x0F)是温度的最高 4 位如果把它漏掉温度会凭空少 16 度这种错误在数据线上很难看出来。换算之后还要加一道物理范围检查温度小于 -40 或大于 100、湿度小于 0 或大于 100 的帧直接丢弃。AHT20 在校准未完成时可能返回全 0 或全 1这些值在平台上显示为 -50 度和 0% 湿度非常容易误导后续的告警逻辑。2.3 上电首读判坏值和重试策略冷启动时 AHT20 需要 20ms 上电稳定再等 10ms 完成校准第一次触发测量往往拿到的是内部状态未就绪时的残留值。常见做法是上电后先丢弃第一次读取结果从第二次开始上传后续每次读取如果连续失败超过 3 次就重新发一次初始化命令0xBE 0x08 0x00而不是反复触发测量。static uint8_t aht20_valid(float temp, float hum) { if (temp -40.0f || temp 100.0f) return 0; if (hum 0.0f || hum 100.0f) return 0; return 1; }这个校验函数放在数据进入网络栈之前STM32 的 while 主循环里每次采集后都调用一次。AHT20 的读取周期建议放在 1~2 秒一次这个频率对温湿度场景足够也不会给 RM500U 的 MQTT 连接造成不必要的压力。注意不要在中断服务函数里做这个传感器的轮询软 I2C 的延时会让中断处理时间超过系统调度周期。3. RM500U 接入AT 指令通道、开机时序与 MQTT 连接参数3.1 从 USB 转串口开始调 RM500UPWRKEY 时序和 AT 会话RM500U 是移远的 5G Sub-6 频段模组对外提供 USB、PCIe 和调试串口STM32 项目里用的通常是 UART 通道。不要一上来就把这路串口焊死在 MCU 上先用 USB 转 TTL 小板把模组的调试串口接到 PC 上验证 AT 指令通、SIM 卡能注册、信号值正常再让 STM32 接管。模组的供电和开机时序是第一个坑。VBAT 供电范围大约 3.4V~4.4V但 5G 发射时的瞬态电流比 4G 模组明显大电源设计要按 3A 脉冲能力来留余量线径太细会导致 RM500U 在注册网络时反复重启。开机时把 PWRKEY 拉低至少 500ms 再释放等待 3~5 秒然后发AT看有没有回显。RM500U 引脚方向说明VBAT输入3.4V~4.4V按 3A 瞬态电流设计PWRKEY输入拉低 500ms 以上触发开机UART_TXD输出接 MCU 的 RXD3.3V 电平UART_RXD输入接 MCU 的 TXDGND-与 MCU 共地下面这段 AT 会话是在串口助手里确认模组初始状态的标准步骤AT OK ATCPIN? CPIN: READY ATCSQ CSQ: 22,99 ATCGATT? CGATT: 1ATCSQ返回的第一个数字是接收信号强度22 表示在 20~30 这个健康区间第二个值是误码率99 代表未知或不可用这不影响网络附着。如果信号值长期小于 10先检查天线有没有接、馈线有没有损伤而不是继续调 MQTT 参数。ATCGATT?返回 1 表示已经附着到 5G/4G 网络返回 0 时去查 APN 配置和 SIM 卡欠费情况。3.2 QMTOPEN/QMTCONN/QMTPUB让 5G 模组自己跑 MQTT 客户端RM500U 内部自带 MQTT 客户端不需要在 STM32 上移植 paho 或 lwIP 那套东西。模组通过 AT 指令建立到 OneNET broker 的连接之后直接把 JSON 报文当成字符串发给 QMTPUB 指令网络层、MQTT 层都由模组完成。这种方式比 TCP socket 直连省心得多因为掉线重连、心跳保活都由模组自己管理。ATQMTOPEN0,onenet_mqtt_broker,1883 OK ATQMTCONN0,client_id,username,password QMTCONN: 0 ATQMTPUB0,0,1,0,$sys/{pid}/{dev_name}/thing/property/post,{\id\:\1\,\params\:{\Temp\:{\value\:25.6}}} QMTPUB: 0,0,0QMTOPEN的第一个参数 0 是客户端索引单模组场景固定填 0。QMTCONN后面的三个字符串分别对应 OneNET 平台的 client_id、username 和 password这三个值不要在代码里写死建议放到配置区方便切换测试环境和正式环境。QMTPUB的参数顺序中第二个 0 是消息 ID第三个 1 是 QoS 级别第四个 0 是 retain 标志。QoS 用 1 是温湿度上报场景的合理选择QoS 0 在网络抖动时丢包平台端的曲线会缺点QoS 2 的确认流程在低数据量下没有必要还会增加模组和 broker 的报文交互。数据量只有几十字节用 QoS 1 的成本可以忽略。注意QMTOPEN打开连接时指定的 broker 地址要从 OneNET 控制台的设备接入信息里复制不要用网上旧教程里的 IP。OneNET 升级过接入体系旧版 MQTT 的接入地址、端口和主题格式都和新版物模型不同混用的表现是 TCP 能连上但 MQTT CONNACK 一直收不到。3.3 在 STM32 侧按返回结果驱动状态机STM32 通过串口和 RM500U 通信时最忌讳的做法是发完指令直接HAL_Delay(500)然后发下一条。模组在开机、搜网、连接 broker 时耗时不同固定延时要么太长拖慢逻辑要么太短导致上一条指令还没处理完。正确做法是按行读取串口数据解析出OK、ERROR或QMTOPEN:这类关键字后再切换状态。int8_t at_wait_ok(UART_HandleTypeDef *huart, uint32_t timeout_ms) { uint8_t byte; char line[64]; uint16_t len 0; uint32_t start HAL_GetTick(); do { if (HAL_UART_Receive(huart, byte, 1, 100) HAL_OK) { if (byte \n) { line[len] \0; if (strstr(line, OK)) return 0; if (strstr(line, ERROR)) return -1; len 0; } else if (len sizeof(line) - 1) { line[len] byte; } } } while (HAL_GetTick() - start timeout_ms); return -2; }这个函数的两个参数要按实际串口修改huart是 RM500U 对应的串口句柄timeout_ms根据所处阶段来传搜网阶段给 10 秒连接阶段给 5 秒。line 缓冲区的大小要能容纳QMTCONN: 0这类回显一般 64 字节够用。实际使用时我给 RM500U 开的是一张“发送后等待”的状态机每个状态都维护一个at_wait_ok的返回值。如果超时返回 -2就把错误码发到调试串口同时进入 3 秒后的重试分支。这种设计比在 while 循环里堆延时更容易定位是模组没开机、AT 指令格式错、还是网络没注册。4. OneNET 平台侧Topic 主题、设备鉴权信息与 JSON 报文4.1 控制台里取 4 个参数MQTT 地址、设备名、鉴权信息与产品 IDOneNET 平台注册完产品后真正要落到 STM32 代码里的参数只有 4 个MQTT broker 地址、MQTT 端口、设备名device name、设备的鉴权信息。当前 OneNET 的接入体系里username 通常是产品 IDpassword 是从设备详情页生成或复制的一串密钥client_id 一般填设备名具体字段名以控制台“设备信息”页为准。参数从哪拿用在哪个位置MQTT Broker 地址控制台接入说明ATQMTOPEN的服务器参数端口 1883同上ATQMTOPEN端口参数产品 ID产品详情页ATQMTCONN的 username设备名设备列表client_id设备密钥或 APIKey设备详情页ATQMTCONN的 password 或平台后台调用接口老项目里常见的onenet 生成 apikey是指平台开放 API 的调用凭证它用于你在服务器端查数据流、拉历史曲线不是给设备做 MQTT 认证用的。把 APIKey 烧进 STM32 的做法不是不行但那样权限粒度太大一旦固件被提取攻击者能拿到整个产品的管理权限。新版接入体系用设备级密钥每个设备独立吊销更适合量产。当前 OneNET 物模型接入的主题格式是$sys/{产品ID}/{设备名}/thing/property/post发布到这个主题表示上报属性数据如果你想从平台下行控制设备用的是另一个以/thing/property/set结尾的主题。这两个主题不要搞混尤其不要把属性上报的数据发到事件主题上平台会按物模型定义校验并拒绝。4.2 属性上报的 JSON 结构params 嵌套与整数传输策略OneNET 物模型属性上报的 JSON 不是平铺的 key-value而是固定外壳加嵌套 params 的结构{ id: 20240415_001, version: 1.0, params: { Temp: { value: 25.6 }, Humi: { value: 61.2 } } }id是消息标识必须每次不同我一般用累加序号加时间戳params的字段名要和平台上物模型定义的功能标识符完全一致比如你在 OneNET 产品里创建的功能点叫temperature而不是Temp那这里的 key 就只能写temperature大小写也不能错。STM32 端通过 snprintf 拼这个 JSONuint8_t payload[160]; int16_t temp_x10 (int16_t)(temp * 10); int16_t humi_x10 (int16_t)(hum * 10); int len snprintf((char *)payload, sizeof(payload), {\id\:\%u\,\version\:\1.0\,\params\:{\Temp\:{\value\:%d},\Humi\:{\value\:%d}}}, seq, temp_x10, humi_x10);这段代码里特别把浮点数放大了 10 倍转成整数再拼字符串是为了绕开 Keil 或 GCC 下%f依赖浮点格式化库的问题。很多 STM32 工程没开 MicroLib 或没有链接 printf 浮点支持snprintf遇到%f会输出空字符串整包 JSON 直接坏掉。放大十倍之后平台端拿到 256 这个整数在展示或告警时再除以 10就是 25.6 摄氏度。payload缓冲区的大小要注意当前结构体大约是 80 字节但后续如果加电池电压、信号强度、定位信息容易超过 128 字节。我一般直接开 256 字节然后传给 QMTPUB 时使用len作为长度避免把缓冲区末尾的残留字节也发上平台。5. 上抛验证与排错看重传、看 QoS、看返回值5.1 用本地 MQTT 客户端拆变量模组链路和报文格式分开验证当 OneNET 后台迟迟没有数据曲线时不要急着怀疑 RM500U先把“数据有没有从模组发出去”和“JSON 格式平台认不认”两件事拆开。用串口工具把 STM32 组好的 JSON 字符串复制出来在自己的电脑上起一个本地 mosquitto broker用 MQTTX 或 mosquitto_pub 原样发布一次mosquitto_pub -h 127.0.0.1 -p 1883 -t test/aht20 -m {id:1,version:1.0,params:{Temp:{value:25.6},Humi:{value:61.2}}}这一步验证的是报文本身可解析、主题格式合法。如果本地 broker 能正常收到问题就集中在模组侧RM500U 的 APN、SIM 卡、信号强度、QMTPUB 返回值。反过来如果本地能解析但 OneNET 后台不显示就要看平台的产品物模型里字段名、消息类型定义是否和上报 JSON 对齐。5.2 三个高频故障的定位顺序排查链路时我按三个步骤走顺序不要反。第一步看模组信号和网络附着。执行ATCSQ和ATCGATT?CSQ 低于 10 或者 CGATT 返回 0后面的 MQTT 调得再对也是白费。5G 模组对天线位置比 4G 敏感室内窗边和桌面上信号可能差 10 个 dBm先固定设备位置再往下查。第二步看 MQTT 连接返回。QMTCONN 成功会返回QMTCONN: 0如果返回非 0查看 broker 地址是否可达、端口是否写对、username 和 password 与平台设备信息是否匹配。这里有个典型错误有人把产品 ID 和 APIKey 抄反了TCP 层能通但 MQTT 握手上平台不发 CONNACK表现就是模组一直卡在等待连接。第三步看 QMTPUB 的响应。发布成功时返回QMTPUB: 0,0,0中间那个 0 是错误码如果返回负数或超时多半是 QoS 确认没有走完检查网络质量而不是重发逻辑。STM32 侧的重发不能做成“发一次 delay 一下再发一次”要按 3.3 节的状态机等模组回包否则模组内部的 MQTT 队列会被重复指令撑满。另外OneNET 平台的旧版 MQTT 项目还在不少博客里流传主题格式、broker 地址和报文外壳都与新版物模型不同。如果你抄到的代码里主题是dp开头或者 username 填的是设备 ID 而 password 填的又是 APIKey大概率是旧版接入要不要迁移取决于存量设备规模新项目一律按新版物模型接入。先把 CSQ 和 QMTPUB 返回打出来再谈数据上云这个顺序能解决掉大部分“连不上”的幻觉。本文还有配套的精品资源点击获取
网站建设高端定制企业官网