简介:这是一份基于STM32F103单片机驱动EC800-4G模块,通过MQTT协议上传温湿度数据至OneNET平台的完整例程,适合正在做嵌入式物联网项目或课程设计的开发者参考。代码采用Keil标准库编写,当前工程基于STM32F103,若使用同系列其他型号,只需调整芯片型号与Flash容量即可复用,降低了二次开发门槛。压缩包共234个文件,以C源文件、H头文件、编译过程文件(O/CRF)及KEIL工程配置(uvprojx/uvoptx)为主,同时包含编译生成的HEX/AXF烧录文件与BMP接线示意图,整体约6.97MB。目前已有291人学习下载,工程可直接参考使用。代码内对单片机与模块的接线做了明确注释,并附有清除编译残留的批处理脚本,便于快速构建工程;同时提供了MQTT连接、在线状态判断等关键环节的实现思路,可在此基础上接入其他传感器,适合作为物联网项目的基础框架。
1. 这套STM32F103+EC800-4G+MQTT上云工程,到底解决什么问题
做过设备上云的人都知道,把单片机的数据送到云平台,最卡壳的不是采集,而是“通信链路怎么打通”。很多套件给的例程要么是WiFi方案的,要么只给你一段裸代码,拿到手里根本不知道哪根线接哪。这份资源给的是STM32F103 + EC800-4G + MQTT + ONENET 的完整工程,从串口AT指令拨号到TCP建链,再到MQTT报文组帧、温湿度数据上送,一条链路全部打通。它适合正在做农业大棚、机房环境监控、设备远程监测的开发者,尤其是需要用4G模块做点对点远程传输,脱离WiFi依赖的场子。EC800-4G是国产Cat.1模组,不需要自己写协议栈,通过串口AT指令就能完成网络注册和TCP连接,真正麻烦的是参数对不对、时序稳不稳。这份资源把这两块都做了。
2. 硬件与工程搭建:最小系统、5V转3.3V与St-Link调试三道关
很多人在Keil工程里一编译就想去点Download,结果下载完板子没反应,回头查硬件一堆坑。这里把拿到资源后的硬件准备和调试链路梳理一遍。
2.1 stm32f103最小系统:跑起来的最低物料清单
STM32F103C8T6是这份工程的默认目标,也是市面上最常见、最便宜的F103芯片。最小系统的搭建并不复杂,但每一条引脚处理都有讲究,缺一个元件轻则下载不了,重则程序跑飞。
| 元件 | 参数/接法 | 作用 |
|---|---|---|
| 8MHz无源晶振 | 并联2个20pF负载电容,连接OSC_IN/OSC_OUT | 提供HSE时钟源 |
| BOOT0 | 下拉10kΩ电阻到GND | 确保从主Flash启动 |
| NRST | 上拉10kΩ,并联100nF到GND | 防止复位引脚悬空误触发 |
| VDD | 每个VDD脚加100nF去耦电容,电源入口加10uF钽电容 | 保证MCU供电稳定 |
| VDDA | 串联10Ω后接3.3V | 隔离模拟域噪声 |
别看元件少,BOOT0如果不拉低,程序下进去完全跑不起来,这是新手第一个玄学问题。晶振的两个负载电容推荐用20pF,位置尽量靠近OSC_IN和OSC_OUT,走线不要太长,否则起振不稳会导致定时器时间漂移。NRST的上拉电阻一定要接,STM32F103的复位脚中断触发阈值比较灵敏,悬空时静电干扰就可能造成复位。
2.2 stm32f103 5V转3.3V电路:LDO选型与共地血泪经验
系统供电如果从USB的5V进来,需要转成3.3V给MCU和模块用。常见做法是用AMS1117-3.3,价格便宜、电路简单,输入输出端各加10uF电容就能稳定工作。注意AMS1117在高压差、大电流下发热明显,如果板子功耗不大(MCU+DHT11+几个LED),完全够用;如果还要给EC800-4G模块供电,就不要从这颗LDO上取电,模块的峰值电流接近2A,1117根本扛不住,电压一被拉低,模块就会不断重启,表现为“串口完全收不到AT返回”。
EC800-4G的VBAT供电必须单独走电源,最稳妥的方式是直接接5V输入或外接稳压源,在模块VBAT引脚附近加470uF电解电容和100nF陶瓷电容。这里有一条共地血泪经验:MCU的GND和模块的GND必须连在一起,否则串口通信就是乱码或者完全没反应。很多人给模块单独供电之后,觉得电源“隔离”了挺好,实际上数字信号的参考电压就是GND,地不共,逻辑电平就对不上,这是排查串口问题时的第一优先级。
2.3 串口打印与定时器3:调试的两只眼睛
这份工程里串口1用于printf调试输出,串口2接EC800-4G模块,串口分配是标准的PA9/PA10和PA2/PA3,不用改硬件就能直接跑。先把串口1的调试输出打通,后面所有AT交互和MQTT报文才能看得见。
// usart1_printf.c 重定向fputc到串口1 int fputc(int ch, FILE *f) { while ((USART1->SR & USART_SR_TXE) == 0); // 等待发送寄存器空 USART1->DR = (uint8_t)ch; return ch; } // 初始化串口1和串口2,均为115200 8N1 void Debug_UART_Init(void) { USART_InitTypeDef usart; GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA9 TX设置为复用推挽输出 gpio.GPIO_Pin = GPIO_Pin_9; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); // PA10 RX设置为浮空输入 gpio.GPIO_Pin = GPIO_Pin_10; gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &gpio); // 串口1参数:115200 8N1 usart.USART_BaudRate = 115200; usart.USART_WordLength = USART_WordLength_8b; usart.USART_StopBits = USART_StopBits_1; usart.USART_Parity = USART_Parity_No; usart.USART_Mode = USART_Mode_TX | USART_Mode_RX; USART_Init(USART1, &usart); USART_Cmd(USART1, ENABLE); }重定向fputc之后,随便在代码里写printf("temp: %.1f\r\n", temp);,调试信息就会从串口1出来。串口2的初始化和串口1基本一样,只是引脚换到PA2/PA3,注意RX引脚要配置成浮空输入,TX配置成复用推挽输出。定时器3在工程里承担了采集时基,我一般会在CubeMX里把TIM3配成1Hz更新中断,DHT11每秒钟采样一次,LED翻转指示系统运行状态。这样看程序是否卡死,瞄一眼LED就行,比看电池快。
2.4 Keil MDK工程搭建与St-Link调试全流程
拿到资源里的keil工程,第一件事是确认自己的MDK版本和芯片Pack是否匹配。STM32F1系列的Device Family Pack需要单独安装,MDK5装好默认不带旧芯片的器件支持,必须手动安装ST官方提供的Keil.STM32F1xx_DFP.pack,选择Pack版本时可以优先选当前MDK版本推荐的,版本太新或太旧都不影响编译,但影响器件选型和调试器兼容性。
St-Link调试器的接线只有四根线:SWDIO、SWCLK、GND、3.3V,不需要接复位脚。在Keil里打开Options for Target,Debug页选择ST-Link Debugger,然后点Settings,把Port选成SW,Max Clock建议降到4MHz,兼容性最好。Flash Download页勾选Reset and Run,意思就是烧录完成后自动复位运行,不然每次下载完还得手动按复位键,很影响调试节奏。
工程编译链接后点Download,如果提示No Target Connected,先检查St-Link的USB线是不是只有电源线没有数据线,这种线最坑;再排查SWDIO和SWCLK两个引脚是否焊接虚连。如果连上了但下载后程序不跑,就回到BOOT0检查,跳帽或电阻拉低后重新复位一次。串口1能看到printf输出,就说明STM32F103的最小系统和调试链路全部打通了,下一步才能让EC800-4G正常工作。
3. EC800-4G拨号:SIM卡检测、网络注册与TCP链路建立
EC800-4G模块上电后默认是自动入网的,但你不知道它到底有没有注册上网络,也不知道APN配没配对。这一章按“检测SIM卡—查信号—注册网络—激活PDP—建TCP”的顺序,把整个拨号过程拆成可验证的步骤。
3.1 为什么选EC800-4G而不是ESP8266或W5500
资源选EC800-4G这块Cat.1模组是有原因的。ESP8266方案成本最低,但依赖WiFi路由,现场没有无线网络就全盘抓瞎;W5500是纯有线以太网方案,稳定性好,但需要布网线,设备一旦移动就要改接线。EC800-4G属于Cat.1模块,插上SIM卡就能通过运营商的公网接入物联网,信号覆盖广,移动场景也能用,数据走运营商网络,也比WiFi更适合工业环境。选它做这个工程的通信核心,是兼顾成本、稳定性和接入便捷度的结果。
3.2 EC800-4G上电与AT指令通道:先确认模块还活着
模块接线确认无误后通电,注意不要直接上电立刻发AT。EC800系列模块开机需要2到3秒的初始化时间,刚上电就发指令大概率得不到响应。正确做法是等2.5秒后发送AT命令,收到OK就说明串口链路和模块启动都正常。
// ec800_at_send.c 向模块发送AT指令并等待响应 uint8_t EC800_SendAT(char *cmd, char *expect, uint16_t timeout_ms) { uint8_t buf[256] = {0}; uint16_t len = 0, timer = 0; USART2_ClearRxBuffer(); // 清空串口2接收缓冲区 USART2_SendString(cmd); // 发送AT指令,记得带\r\n while (timer < timeout_ms) { if (USART2_RxBuffer_Available()) { buf[len++] = USART2_GetChar(); if (strstr(buf, expect) != NULL) // 找到期望的关键字 return 1; } delay_ms(1); timer++; } return 0; // 超时未收到期望响应 } // 上电后依次检测模块状态 void EC800_PowerOn_Check(void) { delay_ms(2500); // 模块上电自检 if (EC800_SendAT("AT\r\n", "OK", 2000)) printf("EC800 module ready\r\n"); else printf("EC800 no response\r\n"); // 检查供电和波特率 }这里最关键的一点是USART2_RxBuffer_Available的判断。串口接收要用中断或DMA方式持续往缓冲区放数据,不能在一个while循环里用阻塞等待串口接收标志,因为AT响应没有固定长度,阻塞式接收很容易丢字节。检查expect用strstr函数匹配子串而不是strcmp比较整个字符串,原因是AT指令的响应后面带\r\n,COM开头等前缀不同,子串匹配更可靠。timeout_ms一般给2000到3000毫秒,AT指令本身响应很快,慢的是网络相关指令,比如AT+CGACT可能要跑到5秒,要单独加大超时。
3.3 网络注册与SIM卡状态检查
模块活着之后,要做三件事:插卡是否识别、信号强度是否达标、网络是否注册成功。
- AT+CPIN? 排查SIM卡状态,返回READY表示SIM卡识别正常,返回ERROR或NO SIM就要检查卡有没有插好、触点是不是氧化了
- AT+CSQ 查信号强度,返回格式是CSQ: 20,0,第一个数字0到31,大于10基本可用,小于5基本处于断网边缘
- AT+CEREG? 查网络注册状态,返回CEREG: 0,1表示已注册到网络,如果返回0,2表示正在搜索网络,0,3表示注册被拒绝
这三个指令在EC800_SendAT里分别跑一遍,把结果printf出来。信号强度如果稳定在10以上,一般就能继续往下走。很多人在这卡很久,原因是SIM卡用的是普通手机卡,插到模组里能识别,但模组没有正确激活数据业务。没关系,下一步把APN设置好就能解决。
网络状态检查里有个常见翻车场景:MCU复位后程序里立刻执行AT+CPIN?,这时候EC800模块因为供电重新上电还在自检,指令根本发不进去。解决方法是程序启动时先做一次“模块握手”循环,发AT,超时后再发,重试三次,每次间隔2秒,直到返回OK再继续。这套重试逻辑在后续所有AT流程里都适用,尤其是网络相关指令,一定不能只发一次。
3.4 APN设置与PDP激活:移动、联通、电信物联网卡的分水岭
运营商对不同SIM卡分配的APN不一样,很多情况下模块默认的APN并不正确,导致网络显示已注册但PDP无法激活。这时需要手动设置APN。中国移动的设备卡通常用cmiot,电信设备卡是ctnet,联通设备卡一般是uninet。注意网上有一些很老的教程让你设置cmnet,那是手机卡公网APN,用在设备卡上反而激活失败。
// ec800_net.c 设置APN并激活PDP上下文 // 移动物联网卡示例 USART2_SendString("AT+CGDCONT=1,\"IP\",\"cmiot\"\r\n"); // 返回OK后查询确认 USART2_SendString("AT+CGDCONT?\r\n"); // 激活PDP上下文 USART2_SendString("AT+QIACT=1\r\n"); // 查询PDP激活状态,返回QIACT: 1,1表示激活成功 USART2_SendString("AT+QIACT?\r\n");激活PDP之后,模块才真正拿到了内网IP地址,这个状态下才能走TCP或MQTT连接。判断激活是否生效,看AT+QIACT?的返回值,第二个参数是0说明激活失败,是1说明激活成功。激活失败时先查CSQ信号是否够强,再确认APN有没有写错。还有一个很多人忽略的点:SIM卡对应的流量套餐必须开通物联网卡的数据服务,有些卡刚发货时后台默认是“未激活”状态,卡虽然识别但数据业务完全不可用,逻辑上就不是代码能解决的问题,只能去运营商后台开卡。
3.5 建立TCP连接到ONENET:AT+QIOPEN的参数踩坑
PDP激活成功后,就能用EC800的AT+QIOPEN指令建立TCP连接。ONENET平台的明文MQTT接入地址是183.230.40.96,端口6002,注意这里的端口不是8883,8883是证书加密端口,裸TCP连过去会失败或一直超时。
// 建立TCP连接到ONENET MQTT明文端口 char cmd[100]; sprintf(cmd, "AT+QIOPEN=1,0,\"TCP\",\"183.230.40.96\",6002,0,0\r\n"); USART2_SendString(cmd); // 等待异步返回结果,成功会收到: CONNECT OK // 失败会收到: +QIOPEN: 0,ERROR 或超时AT+QIOPEN执行后,模块会异步返回结果,不像AT指令那样直接回OK。连接成功会返回CONNECT OK,失败返回+QIOPEN: 0,ERROR。这一步如果失败,优先检查PDP是否还处于激活状态,模块空闲时会自动释放PDP,建议在建立TCP前重新执行AT+QIACT=1。另外AT+QIOPEN里的6个参数分别代表连接索引、socket类型、协议、目标IP、端口、访问模式、保活标志,照抄即可,不需要研究每个参数,但连接索引从1开始,后面MQTT过程中的所有发送、关闭操作都要指向这个索引号。
TCP链路打通之后,串口2收到的数据就是从ONENET平台返回的MQTT协议报文了,下一步才是MQTT层的接入和工作。
4. MQTT上云ONENET的避坑记录:鉴权参数、报文组帧与心跳
这一整章是踩坑重灾区。TCP通了不代表MQTT能上云,ONENET平台对MQTT的接入参数、Topic格式和报文格式都有自己的一套要求,任何一个字符不对,平台只会给你一个无情的关闭连接。
4.1 ONENET产品与设备创建:先把三个值记到手
在ONENET平台创建一个产品,协议类型选择MQTT,创建完成后能得到产品ID。接着在产品下添加设备,设备名称是设备的唯一标识,自定义一个,比如stm32_dev01。设备添加后平台会生成一个鉴权信息,这个值需要自己在添加设备时填,建议填一串私有的字符串,不要跟设备名重复。这三个值分别对应MQTT报文里的clientID、username和password。
ONENET平台要求这三个参数在CONNECT报文里的位置非常固定:clientID填产品ID下划线设备名,格式是“产品ID_设备名”;username就是产品ID;password就是设备的鉴权信息。很多人拿着平台生成的APIKey往password里填,结果全部连接失败,因为APIKey是平台对API接口的访问秘钥,跟MQTT设备鉴权完全是两回事。这三个值必须在代码里填到mqtt_client.c的头文件配置区,工程里这份资源已经留出了对应位置。
| 平台参数 | MQTT报文位置 | 示例值 |
|---|---|---|
| 产品ID | clientID和username | 481xxxxx |
| 设备名 | clientID的一部分 | stm32_dev01(组合后481xxxxx_stm32_dev01) |
| 鉴权信息 | password | abc123xyz(自定义字符串) |
设备创建后不需要记APIKey,只需要记住产品ID、设备名、鉴权信息这三个字段。如果设备接入时一直报“鉴权失败”,去平台对比一下设备名称的大小写,MQTT的clientID区分大小写,平台生成的设备名是什么,代码里就必须原样填什么。
4.2 MQTT报文黑匣子:CONNECT、PUBLISH、SUBSCRIBE结构
MQTT协议报文说穿了就是固定头加上可变头和负载。固定头的第一个字节是报文类型和标志位,第二个字节是剩余长度。剩余长度小于128时直接用一个字节表示,对CONNECT报文来说,剩余长度等于可变头和负载的总字节数。这就绕开了“MQTT黑匣子”的玄学焦虑。
// mqtt_pkt.c 构造MQTT CONNECT报文 uint16_t mqtt_build_connect(uint8_t *buf, char *client_id, char *username, char *password) { uint16_t i = 0; uint16_t remaining_len; // 可变头:协议名 MQTT,级别4,标志位C2,心跳60秒 uint8_t vheader[] = {0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0xC2, 0x00, 0x3C}; uint16_t client_len = strlen(client_id); uint16_t user_len = strlen(username); uint16_t pass_len = strlen(password); remaining_len = 10 + 2 + client_len + 2 + user_len + 2 + pass_len; buf[i++] = 0x10; // CONNECT报文类型 buf[i++] = (uint8_t)remaining_len; // 剩余长度,<128直接用1字节 memcpy(&buf[i], vheader, 10); i += 10; buf[i++] = 0x00; buf[i++] = (uint8_t)client_len; memcpy(&buf[i], client_id, client_len); i += client_len; buf[i++] = 0x00; buf[i++] = (uint8_t)user_len; memcpy(&buf[i], username, user_len); i += user_len; buf[i++] = 0x00; buf[i++] = (uint8_t)pass_len; memcpy(&buf[i], password, pass_len); i += pass_len; return i; // 返回整个报文长度,供AT+QISEND的len参数使用 }CONNECT报文里的标志位0xC2,二进制是1100 0010,其中bit7为0,表示当前连接不允许继续保留会话;bit1为1,表示开启Clean Session,设备每次上线都不继承历史会话状态。对于低功耗或长连接的数据上报场景,Clean Session打开是最省心的。心跳周期放在可变头的最后两个字节,单位是秒,0x003C即60秒,OneNET平台要求的保活时间必须在50到180秒之间,太短会频繁断开重连,太长又会让平台误判设备离线。
PUBLISH报文比CONNECT简单得多,固定头用0x30(QoS0、不保留),剩余长度等于2加Topic长度加负载长度,Topic在前,负载在后。SUBSCRIBE报文的固定头是0x82,剩余长度后面跟报文ID和Topic列表。这三个报文组好之后,用EC800的透传模式通过AT+QISEND把字节流直接发到TCP服务器上,不用考虑分包,模块底层会处理TCP传输。
4.3 数据上报:把DHT11读数塞进PUBLISH帧
ONENET的MQTT数据上报,Topic路径是$sys/产品ID/设备名/dp/post/json,负载是一个JSON格式的键值对。DHT11读到的温湿度是浮点数,需要先格式化成字符串再填进报文。
// mqtt_publish.c 发布温湿度数据 uint8_t mqtt_publish_temp_humi(uint8_t *buf, char *topic, float temp, float humi) { uint16_t i = 0; char payload[64]; int payload_len; // 组装JSON负载:{"temp":23.5,"humi":60.0} payload_len = sprintf(payload, "{\"temp\":%.1f,\"humi\":%.1f}", temp, humi); uint16_t topic_len = strlen(topic); buf[i++] = 0x30; // PUBLISH, QoS0 buf[i++] = (uint8_t)(2 + topic_len + payload_len); // 剩余长度 buf[i++] = 0x00; buf[i++] = (uint8_t)topic_len; memcpy(&buf[i], topic, topic_len); i += topic_len; memcpy(&buf[i], payload, payload_len); i += payload_len; // 将buf整体通过EC800_TransparentSend发到TCP连接 EC800_TransparentSend(buf, i); return i; }这里有几个容易翻车的点。第一个是JSON字符串的字段名,ONENET平台的数据流名称就是JSON里冒号前面的key,如果要展示成平台仪表盘的“温度”“湿度”数据流,key必须和平台数据流名称保持一致,比如平台数据流叫temp,JSON里就写temp,不能写temperature。第二个是浮点格式化用%.1f保留一位小数,DHT11本身的精度就是整数,保留一位小数属于经验上的友好处理,平台上显示23.5比23更好看。第三个是Topic路径里的设备名必须和CONNECT报文中的clientID里的设备名完全一致,这个错位会导致平台返回设备不存在,数据直接丢弃。
设备离线重连时,平台会发送一个订阅请求,要求设备重新订阅命令下行Topic。如果设备端的循环逻辑里没有对重连后重新SUBSCRIBE做处理,平台命令就下不来,设备只会“在线”而收不到任何指令。这个场景的排查要从平台端看设备上下线日志,而不是盯着串口输出。
4.4 避坑记录:MQTT接入中高频踩坑汇总
把开发这类工程遇到过的真实问题整理成一份排查清单,按现象、原因、解决的顺序写在一起,以后对着这张表查问题,能省一半抓头发的时间。
现象1:CONNECT报文发出去后,平台立刻关闭连接,设备反复重连。原因大概率是CONNECT报文里的clientID、username、password三项参数中有一个与平台配置不一致,尤其容易填错password,把APIKey填成了鉴权信息。解决:到平台把产品ID、设备名、鉴权信息三个值重新核对一遍,直接改代码里的配置区后重新编译下载。如果设备名前带空格或过了小写,也要一并修正。
现象2:TCP已经CONNECT OK,但MQTT报文似乎发不出去,串口看不到平台响应。原因是EC800的AT指令模式与透明传输模式没有切换,或者发送时机不对,模块处于AT指令模式时对收到的字节流不做TCP转发。解决:确认使用了AT+QISEND发送数据,发送完成后立即执行AT+QISEND=0的“数据发送结束”指令,模块才能把缓冲区的数据真正推到服务器;发送结束指令没发,数据一直停在模块缓冲区,平台当然什么都收不到。
现象3:平台设备显示在线,但数据流一直没有新数据。原因是PUBLISH报文的Topic路径写错,比较常见的是少写了$sys前缀,或者设备名写成了产品名。解决:每次上线后用平台的API调试工具查看最近数据,如果返回设备不存在,检查Topic字符串;如果返回请求成功但没有数据点,检查JSON里的key是否和平台数据流名称一致。
现象4:设备上报周期变长后平台判定离线,但串口上看程序还在跑。原因是MQTT的心跳超时或连接被平台主动清理。设备的保活时间设置超过平台上限,或实际TCP连接长时间空闲被NAT踢掉。解决:把CONNECT报文里的心跳时间设为平台允许范围内的值,一般60秒最为稳妥;同时打开EC800模块的TCP保活设置,在AT+QIOPEN参数里把最后一位设成2,表示启用KeepAlive机制,让模块自己在TCP层维持心跳,避免被NAT设备清理。
现象5:重连后平台能上线但命令下发收不到。原因是设备重新连接后没有重新订阅命令Topic,平台的命令下行依赖设备订阅关系,连接断开后订阅关系自动清除。解决:在MQTT连接成功的流程后面,无条件执行一次SUBSCRIBE指令,把命令下行Topic重新订阅一遍。
5. 上云后的进阶玩法:可视化曲线、指令下发与手动验证习惯
数据成功上送ONENET后,这套工程才真正具备了产品雏形。ONENET控制台自带可视化能力,不需要自己写前端,创建应用、拖几个控件,数据就能变成图表。进入平台的可视化页面,添加一个“曲线图”控件,数据流选择temp和humi,刷新周期设置成10秒,很快就能看到曲线在实时滚动。整个配置过程十五分钟以内就能完成,比从零写仪表盘省太多事。
指令下发是设备从“上传数据”升级到“远程控制”的关键一步。设备端要订阅$sys/产品ID/设备名/cmd/request/+这个Topic,平台下发指令时,设备RA能收到格式为“cmd/request/指令ID”的响应。收到后解析JSON,比如下发一个继电器控制指令,设备端的逻辑可以是这样:先识别指令ID,再提取控制字段,执行后提交一份“cmd/response/指令ID”的确认数据。这个机制在工程里已经预留了处理函数,可以把串口2收到的指令直接透传给RS485总线,接电表、PLC、变频器都通用,相当于让STM32F103成为一个4G网关的“下载端”。
调试时最值得养成的习惯是“先手动再自动”。每次烧录程序前,先用PC的串口调试助手连到EC800模块的串口,按顺序手动执行:AT检查模块、AT+QIACT激活PDP、AT+QIOPEN建TCP、手动粘贴组好的CONNECT报文,确认平台设备在线后再粘贴PUBLISH报文看数据是否上送。这一遍手动流程跑通,再烧单片机程序联调,能极大缩短排错时间。
手动验证这套流程,血泪教训是烧录后串口2没有任何输出时,第一时间先去量模块的电源和GND,而不是去翻代码。从那以后我每次做4G上云项目,都强制自己走一遍“手动拨号—手动MQTT—自动循环”的三段式调试,这份资源里的工程文件已经把上面所有流程都包含在了源码里,改一下产品ID和设备配置就能跑起来,希望帮到你。
本文还有配套的精品资源,点击获取