ARTICLE DETAIL

资讯详情

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

SIM7600 4G MQTT上云:STM32接入ONENET温湿度定位方案

SIM7600 4G MQTT上云:STM32接入ONENET温湿度定位方案 简介面向嵌入式物联网开发者这是一套基于KEIL标准库的STM32F103工程例程实现SIM7600CE-4G模块经MQTT协议上传GPS定位与DHT11温湿度数据至ONENET平台适用于多协议物联网项目开发。压缩包内含234个文件、约15.91MB以.h/.c源代码为主包含工程配置、BAT脚本、HEX固件和调试APK覆盖从阅读代码到烧录验证的完整链路。目前已有124人学习下载。例程代码注释详细接线定义清晰可依据芯片型号调整FLASH容量并提示J-Link/ST-Link选择同时结合APK助手可快速验证上报数据为接入其他传感器或扩展云平台场景留下参考是一份可动手改造的实战资料。1. 4G模组 MQTT 上云一套能直接复用的 STM32F103 与 SIM7600CE-4G 接入方案做设备联网改造最怕的不是代码难写而是数据出了厂区就丢。STM32F103 采集 DHT11 温湿度和 GPS 定位需要稳定上报到 ONENET 平台我试过 Wi-Fi 模块、也试过 HTTP 轮询最后在无 Wi-Fi 覆盖的移动场景里选定的方案是 SIM7600CE-4G 走 MQTT——一个模组同时解决 4G 联网和 GNSS 定位AT 指令集里自带 MQTT 协议栈MCU 端连 MQTT 客户端源码都不用移植。这篇直接把硬件接线、AT 指令流程、ONENET 多协议接入参数、MQTT 报文和实际踩坑全部展开适合正准备把样机接进 ONENET、不想在协议栈上耗一周的嵌入式工程师。2. 硬件接线与 DHT11 读取上拉电阻、时序采样和校验码2.1 最小系统与 SIM7600CE-4G 的接线分配样机用的是一块 STM32F103C8T6 最小系统板SIM7600CE-4G 模块单独供电。很多新手在这里第一脚就踩坑SIM7600 的峰值电流能到 2A如果直接从 STM32 开发板的 3.3V 或者 USB 口取电上电瞬间电压跌落直接导致模块反复重启表现为主机发 AT 完全无响应。我一般用一个 5V/2A 以上的适配器给模块供电MCU 和模块之间只连 TTL 串口线和地线共地之后再谈通信。串口分配上USART1 给 SIM7600 用工作在 115200 8N1PA9 接模块 RXDPA10 接模块 TXDUSART2 留作调试打印DHT11 接 PB0。如果后续要加 OTA 或者其他传感器建议把 USART3 也预留出来不然排线一绕改一处接口就要动好几块地方。信号MCU 引脚模块/传感器引脚说明SIM7600 TXDPA10USART1_RXSIM7600_TXD模组发MCU 收SIM7600 RXDPA9USART1_TXSIM7600_RXDMCU 发模组收DHT11 DATAPB0DATA需要 4.7kΩ 上拉到 3.3V模块电源外部 5V/2AVCC不能走 MCU 的 LDO需要注意的是DHT11 的数据引脚必须接上拉电阻我用的 4.7kΩ 到 3.3V。如果省掉这个电阻总线在空闲时处于浮空状态读回来的数据经常是 0xFF 或者校验错误而且这种问题用万用表测还不一定能马上看出来。2.2 DHT11 单总线时序与读取代码DHT11 是单总线协议通信过程分两步主机先拉低 DATA 至少 18ms 作为起始信号然后释放总线DHT11 检测到起始信号后拉低 80us 响应再拉高 80us之后开始输出 40 位数据。数据位以 50us 低电平开始高电平持续 26~28us 表示逻辑 0持续 70us 表示逻辑 1。所以采样点应该落在高电平开始后的 40us 左右。// dht11.cGPIO 模式切换按标准库封装 #define DHT11_PORT GPIOB #define DHT11_PIN GPIO_Pin_0 static void DelayUs(uint32_t us) { // SystemCoreClock72MHz每循环约4周期粗略延时可用 uint32_t ticks us * 18; while (ticks--) { __NOP(); } } uint8_t DHT11_ReadData(uint8_t *humi, uint8_t *temp) { uint8_t data[5] {0}; uint8_t i, j; // 主机拉低起始信号至少18ms GPIO_SetMode(DHT11_PORT, DHT11_PIN, GPIO_MODE_OUTPUT); GPIO_WriteBit(DHT11_PORT, DHT11_PIN, Bit_RESET); DelayUs(20000); GPIO_WriteBit(DHT11_PORT, DHT11_PIN, Bit_SET); DelayUs(30); // 切输入等待DHT11响应 GPIO_SetMode(DHT11_PORT, DHT11_PIN, GPIO_MODE_INPUT); if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) SET) return 1; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) RESET); // 等待80us低电平结束 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) SET); // 等待80us高电平结束 for (j 0; j 5; j) { for (i 0; i 8; i) { while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) RESET); // 跳过50us低电平 DelayUs(40); // 延时40us后采样 if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) SET) data[j] | (uint8_t)(0x80 i); // 仍是高电平 - 该位为1 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) SET); // 等待数据位结束 } } // 校验前4字节累加低8位 第5字节 if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) return 2; *humi data[0]; *temp data[2]; return 0; }代码里两个地方是调通的关键。第一是DelayUs(40)要落在数据位的高电平区间中间延时太短会把 0 误判成 1太长会把 1 读成 0所以这里不能用HAL_Delay这种毫秒级函数必须用微秒延时。第二是 GPIO 模式切换我在起始信号前切输出、响应阶段切输入如果只用推挽输出读引脚读到的永远是高电平。标准库下封装GPIO_SetMode需要改 CRH/CRL 寄存器换成 HAL 库的话就是GPIO_InitTypeDef重新初始化逻辑完全一致。2.3 校验和与读值异常的判断DHT11 固定输出 40 位数据湿度整数、湿度小数、温度整数、温度小数、校验和。校验和是前四个字节相加后取低 8 位如果对不上直接丢弃这一帧不要拿脏数据去上报。我在实际项目中哪怕校验通过也会加一个简单滤波连续三次读到同一温度才更新缓存避免平台上的曲线出现毛刺。这里有个反直觉的现象DHT11 温度整数是data[2]不是data[0]。第一次写代码的人经常会照着湿度的方式取data[1]结果温度显示成两位数小数。另外如果读值一直是 0xFF优先查上拉电阻和接线长度数据线超过 20cm 建议换成屏蔽线或者降低采样频率这是 DHT11 这个价位的传感器绕不过去的物理限制。3. SIM7600CE-4G 联网与 GPS 解析AT 指令流程和 NMEA 数据提取3.1 模块上电检测与串口初始化SIM7600 上电后第一件事是等模块开机常见做法是检查串口是否收到RDY或者直接发AT看有没有OK。模块冷启动要 2~3 秒如果程序一上电就连续发 AT大概率前几条会被吞掉。我一般开机后延时 3 秒再循环发 5 次AT每次间隔 500ms收到OK才继续往下走。// 发送AT指令并等待预期应答timeout单位ms uint8_t AT_SendCmd(const char *cmd, const char *expect, uint16_t timeout) { RingBuf_Clear(sim7600_rx); USART_SendString(USART1, cmd); uint32_t start HAL_GetTick(); while (HAL_GetTick() - start timeout) { if (RingBuf_Find(sim7600_rx, expect)) return 1; } return 0; } // 上电初始化流程 void SIM7600_Init(void) { HAL_Delay(3000); for (uint8_t i 0; i 5; i) { if (AT_SendCmd(AT\r\n, OK, 1000)) break; } AT_SendCmd(ATE0\r\n, OK, 500); // 关闭回显 AT_SendCmd(ATIPR115200\r\n, OK, 500); }串口这边我强烈建议用环形缓冲区接中断接收而不是在主循环里轮询接收标志。SIM7600 的 AT 响应里像CGNSINF这种带大量参数的返回值长度能到 120 字节以上如果中断里只存一个字节就退出主循环再慢吞吞地读很容易触发 USART 溢出丢数据就是从这里开始的。环形缓冲区的写法后面避坑章节会再展开。3.2 网络注册、APN 拨号与信号排查SIM7600 联网不是插卡就行需要三步注册网络、设置 APN、激活 PDP 上下文。常用指令按下表顺序执行每一步都要等OK而不是直接发下一条。AT 指令作用常见返回ATCSQ查询信号强度CSQ: 23,0第一段越高越好ATCREG?查询网络注册状态0,1表示已注册本地网ATCGDCONT1,IP,cmnet设置 APNOKATCGACT1,1激活 PDP 上下文OKAPN 要跟 SIM 卡运营商匹配移动一般用cmnet联通用wonet电信用ctnet。如果用的是物联网卡APN 很多是卡商自定义的比如cmiot或厂商提供的专用 APN填错之后ATCGACT1,1会返回ERROR信号满格也上不了网。排查顺序有讲究先ATCSQ看信号再ATCREG?看注册最后再查 APN。我遇到过信号 20 以上但死活拨号失败的情况最后查到是 SIM 卡座触点氧化重新插拔一次就好了这类问题用指令查不出来只能靠经验判断。3.3 GNSS 定位开启与经纬度解析SIM7600CE 自带 GNSS 接收器不需要外挂 GPS 模块。开启定位就两条指令ATCGNSPWR1开启电源ATCGNSINF查询定位结果。返回格式是CGNSINF: 运行状态,定位状态,UTC时间,纬度,经度,海拔,速度,航向注意前两个字段都是1才代表定位成功第一个1表示 GNSS 模块工作正常第二个1表示已经锁定卫星。// 从AT应答缓冲中解析CGNSINF void GPS_ParseCGNSINF(char *buf, float *lat, float *lon) { char *p strstr(buf, CGNSINF:); if (!p) return; p 10; // 跳过CGNSINF: int run_state atoi(p); // 字段1GNSS运行状态 while (*p *p ! ,) p; p; int fix_state atoi(p); // 字段2定位状态 if (run_state ! 1 || fix_state ! 1) return; // 跳过UTC时间字段到达纬度 for (int i 0; i 3; i) { while (*p *p ! ,) p; p; } *lat atof(p); // 字段4纬度单位度 while (*p *p ! ,) p; p; *lon atof(p); // 字段5经度单位度 }这段解析有个隐蔽问题strstr找到的位置可能落在缓冲区的中间如果主循环在串口数据还没收完时就调了解析函数可能只拿到半行CGNSINF。我一般用标志位法——在串口中断里检测到\n才置位数据就绪标志主循环发现标志置位后再从环形缓冲区整行取出解析这样能保证拿到的是一条完整响应。SIM7600 的 GNSS 是冷启动 60 秒左右首次开机在窗边等一分多钟是正常的这时候不要反复重启模块只会让冷启动时间重新计算。4. 多协议方式接入 ONENET内置 AT 指令与手工 MQTT 报文两种路线4.1 多协议接入参数从哪来ONENET 的“多协议接入”指的是同一个产品下的设备可以选择 MQTT、HTTP、TCP 等不同协议接入平台侧已经封装好了协议解析设备端只需要按约定格式上传数据。控制台里创建产品时协议选 MQTT然后添加设备拿到三样东西产品 ID、设备 ID、设备 APIKey。这三样就是 MQTT 连接的凭证。MQTT 连接时参数映射关系是这样的clientId 填产品 IDusername 填设备 IDpassword 填 APIKey。服务器地址和端口不是猜的在 ONENET 控制台的“多协议接入”页面里选择 MQTT平台会直接给出接入地址和端口旧版多协议接入通常用的是 1883 端口。如果连接不上先检查这几项有没有填反我在现场看到过把设备 ID 填到 clientId、产品 ID 填到 username 的错误平台返回的鉴权错误能排查半天。4.2 路线一SIM7600 内置 MQTT 指令上报Quectel 在 SIM7600 固件里实现了完整的 MQTT 客户端通过ATCMQTT系列指令操作。这套方案的好处是 MCU 只负责出数据和收回调TCP 连接、心跳、重连都是模组管的代码量最少。ATCMQTTSTART # 启动MQTT服务 ATCMQTTACCQ0,stm32f103_dev01 # 申请客户端标识第二个参数是clientId ATCMQTTSSLCFG0,0 # 关闭SSL多协议接入非加密场景 ATCMQTTCONNECT0,服务器地址,1883,1,60,设备ID,APIKey # 参数clientIdx,服务器,端口,重连标志,心跳,username,password ATCMQTTTOPIC0,3,$dp # 设置发布主题3就是$dp的长度 ATCMQTTPAYLOAD0,105 # 声明后续payload长度收到后发送JSON数据 ATCMQTTPUB0,1,1 # 发布clientIdx,QoS,保留标志这里$dp是 ONENET 多协议接入 MQTT 方式下专门用于上传数据点的主题payload 是 JSON 格式的数据流后面 4.4 节会给完整格式。ATCMQTTPUB的 QoS 参数我建议填 1MQTT 的 QoS 1 能保证消息“至少一次”送达代价是平台可能收到重复消息但 ONENET 侧对数据点本身有覆盖逻辑实际应用中这个重复是可以接受的。要注意这套指令在不同固件版本里参数顺序略有差异我手上的模组版本在ATCMQTTCONNECT里还多一个keepalive下的可选参数所以上线前先用ATCMQTTSTART?查一下固件支持的指令格式别照抄。4.3 路线二STM32 手工拼接 MQTT 报文的实现如果模组固件里没有CMQTT指令一些定制版本会砍掉只能退回到老办法用 TCP 透传 自己拼 MQTT 报文。这路线多写点代码但对固件没有依赖可移植性更强。// 构造MQTT CONNECT报文返回总长度 uint16_t MQTT_BuildConnect(uint8_t *buf, const char *clientId, const char *username, const char *password) { uint16_t i 0; buf[i] 0x10; // CONNECT固定头 buf[i] 0x00; // 剩余长度占位稍后回填 buf[i] 0x00; buf[i] 0x04;// 协议名长度 memcpy(buf[i], MQTT, 4); i 4; buf[i] 0x04; // 协议级别 MQTT 3.1.1 buf[i] 0xC2; // 用户名密码CleanSession buf[i] 0x00; buf[i] 0x3C;// KeepAlive 60秒 uint8_t cl strlen(clientId); // clientId2字节长度前缀 buf[i] 0x00; buf[i] cl; memcpy(buf[i], clientId, cl); i cl; uint8_t un strlen(username); buf[i] 0x00; buf[i] un; memcpy(buf[i], username, un); i un; uint8_t pw strlen(password); buf[i] 0x00; buf[i] pw; memcpy(buf[i], password, pw); i pw; buf[1] (uint8_t)(i - 2); // 回填剩余长度 return i; } // 构造PUBLISH报文QoS0 uint16_t MQTT_BuildPublish(uint8_t *buf, const char *topic, const char *payload) { uint16_t i 0; uint16_t tlen strlen(topic); uint16_t plen strlen(payload); buf[i] 0x30; // PUBLISH Qos0 buf[i] 0x00; // 剩余长度占位 buf[i] (uint8_t)(tlen 8); // topic长度大端 buf[i] (uint8_t)(tlen 0xFF); memcpy(buf[i], topic, tlen); i tlen; memcpy(buf[i], payload, plen); i plen; buf[1] (uint8_t)(i - 2); // 回填剩余长度 return i; }报文里几个细节值得说。CONNECT 的固定头固定是0x10连接标志0xC2的二进制是1100 0010其中 bit7 表示有用户名、bit6 表示有密码、bit1 表示 CleanSession1这三个位必须正确否则平台直接断开。剩余长度字段在 MQTT 协议里是变长编码这里因为 clientId、username、password 都比较短单字节就能放下如果以后接入长 topic 或者大 payload剩余长度超过 127 就要按协议拆成多字节编码这是最容易漏的地方。发送时先用ATCIPSTARTTCP,前面拿到的服务器地址,1883建立 TCP 连接返回CONNECT OK后用ATCIPSEND进入透传模式把上面拼好的字节数组发过去再发0x1A退出透传。连接建立后还要收一帧服务器的 CONNACK0x20 0x02 0x00 0x00收到才算 MQTT 层握手成功然后再发 PUBLISH 数据。4.4 数据点格式温湿度与 GPS 定位的 JSON 上报不管用哪种路线ONENET 多协议接入对上传数据点的 MQTT payload 格式是统一的。发布到$dp主题的数据按数据流datastream组织每个数据流对应平台上创建的一个数据流名称{ datastreams: [ { id: temp, datapoints: [{ value: 25.6 }] }, { id: humi, datapoints: [{ value: 48.2 }] }, { id: loc, datapoints: [{ value: { lon: 116.397470, lat: 39.908823 } }] } ] }温湿度直接传数值GPS 定位的 value 必须是一个对象包含lon和lat两个键平台才能在地图组件里画出位置。这里有个坑ONENET 对数据流类型有隐式判断如果同一数据流既传纯数字又传对象平台可能把类型锁定为数值导致定位数据插入失败。我一般拆两个数据流loc专门放经纬度对象不要在它下面传其他格式。JSON 字符串里不要带换行和多余空格模组 AT 指令缓冲区对长度有限制严格遵守格式能省很多事。5. 踩坑与排查从串口丢包到 MQTT 连接被拒的五条记录这一章里全是实际联调时踩过的坑每条都按现象、原因、解决写清楚。如果你正好卡在某一步直接对着找。5.1 串口中断丢数据AT 响应被截断现象SIM7600 返回的CGNSINF响应偶尔只有前半段或者ATCMQTTCONNECT之后模块返回的CMQTTCONNECT: 0,0收不全导致主循环匹配不到关键字。原因中断服务程序里没有及时把接收寄存器里的数据搬走USART 溢出标志 ORE 置位后后续数据直接丢弃。尤其是 SIM7600 的 GNSS 返回字符串长度超过 120 字节而 STM32F103 的接收寄存器只有 1 字节一帧数据来临时如果主循环刚好在解析上一条指令中断和主循环之间没有缓冲就会出问题。解决中断里只做一件事——把字节写入环形缓冲区总共几十个周期主循环再慢慢解析。同时处理 ORE 标志否则溢出标志不清除串口会一直处于错误状态。void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE)) { RingBuf_Write(sim7600_rx, USART_ReceiveData(USART1)); } if (USART_GetITStatus(USART1, USART_IT_ORE)) { USART_ReceiveData(USART1); // 读DR清除溢出标志 } }从那以后我写所有 AT 指令通信程序都先画一张“谁写缓冲区、谁解析缓冲区”的图中断里绝不调strstr、printf这类耗时函数。5.2 SIM7600 一直不上线ATCREG? 停在 0,1现象ATCSQ返回信号有 20 以上但ATCREG?返回CREG: 0,1已注册但未漫游实际上 0,1 表示注册上本地网络问题往往在下一步接着ATCGACT1,1返回ERRORPDP 上下文激活不了。原因APN 填的不对。我遇到过一张移动物联网卡卡商提供的 APN 不是cmnet而是专用的m2m.iot用通用 APN 拨号必然失败。还有一种情况是 SIM 卡 PIN 码锁住了模块返回SIM PIN required。解决先ATCPIN?查 SIM 状态返回READY才正常再确认卡商给的 APN用ATCGDCONT1,IP,正确APN重设然后ATCGACT0,1先停用再激活。排查顺序一定是从物理层到网络层SIM 卡 → 信号 → APN → PDP跳过任何一层排查都会事倍功半。5.3 DHT11 读值固定 0xFF换块板子又好了现象DHT11 返回校验错误或者湿度一直是 255程序在别的开发板上正常复制到自己的最小系统板上就不行。原因最小系统板的 PB0 没有外部上拉电阻。DHT11 数据引脚是开漏输出必须外部上拉才能输出高电平。我的板子上焊了 4.7kΩ 电阻后立刻正常之前是恰恰省了这个电阻。解决数据线上加 4.7kΩ 上拉到 3.3V。另外如果用的是杜邦线连接 DHT11线长超过 20cm 或者弯曲半径过小寄生电容会拉慢信号边沿导致时序采样点偏移。我后来直接把 DHT11 焊在小板上用最短的线引到主控再没出过时序问题。5.4 MQTT CONNECT 回复 0x05连接被强制关闭现象手工拼接 MQTT 报文后TCP 连接建立成功但模组收到服务器的 CONNACK 是0x05连接被拒绝鉴权失败。原因clientId、username、password 三者的映射填错了。ONENET 多协议接入 MQTT 方式要求 clientId产品 ID、username设备 ID、passwordAPIKey。我把设备 ID 填进了 clientId产品 ID 填进了 username平台鉴权直接拒绝。另一个常见问题是 password 用了设备注册时自动生成的一串字符而不是创建产品时生成的 APIKey二者不是一回事。解决回到控制台重新核对三项参数我后来把产品 ID、设备 ID、APIKey 直接写成宏定义放在一个头文件里联调时打开串口助手打印实际发送的报文与平台页面逐字符核对。这种问题查串口抓包最快别靠猜。5.5 GPS 定位状态一直是 0模块显示工作但锁不到卫星现象ATCGNSINF返回的第一个字段是 1GNSS 模块工作正常但第二个字段一直是 0未定位。产品在室内桌面测试等了五分钟也没变化。原因GPS 信号穿墙能力弱室内基本收不到卫星尤其是金属外壳或靠近窗户但朝北的房间信号强度衰减更明显。另一个可能是 GNSS 天线没接有源天线SIM7600 的 GNSS 天线需要供电如果天线是那种无源陶瓷片板上 LNA 没启用灵敏度会差很多。解决把设备拿到室外开阔处或者在窗台边等 60 秒以上确认接了有源 GNSS 天线并且天线的供电引脚ANT_GPS 旁边的 VCC连接正确。验证方法是用ATCGNSSINFO查看可见卫星数和信噪比能看到 5 颗以上的卫星数据才能判断模块本身没问题。6. 链路验证与进阶调优可视化面板、自检顺序和低功耗方向6.1 用 ONENET 可视化面板验证整条链路代码写完不是终点验证才是。ONENET 控制台自带可视化应用功能把 temp、humi 数据流绑定到折线图把 loc 绑定到地图组件平台端每一分钟能刷新出新的数据点说明从 DHT11 到模组再到平台整条链路是通的。我习惯让设备连续跑 30 分钟看曲线有没有断档断档超过 5 分钟就回去查模组的心跳设置。6.2 一次标准的上电自检顺序我给自己定了一套固定联调顺序每次重新上电都强制走一遍避免漏检DHT11 读值 → AT 握手 → CSQ 信号 → CREG 注册 → CGACT 拨号 → 模块内部 MQTT CONNECT 状态查询 → 发布一帧 test 数据 → 平台可视化面板确认。每一步失败就停在对应环节比如 CGACT 失败了就不要往下试 MQTT先解决网络问题再继续这样可以快速定位是新引入的 bug 还是环境问题。6.3 低功耗与断线重连的进阶方向项目转入量产阶段还可以做两件事一是用 SIM7600 的ATCFUN0进入飞行模式降低功耗定时唤醒后重新联网上报二是在 MCU 端设计断线重连状态机MQTT 连接断开时先检查网络注册状态再重新走ATCMQTTSTART流程不要直接反复 CONNECT否则容易把模块的协议栈状态机打乱。这套方案我从样机一直带到现场试运行中间最大的经验就是所有“看起来是代码问题”的故障最后发现都是供电、共地、APN 这类硬件和配置问题。从那以后我每次联调都先花五分钟做一遍本章 6.2 的顺序检查再动手改代码省掉的调试时间远大于这五分钟。希望帮到你。本文还有配套的精品资源点击获取
返回列表