项目介绍
本项目是一个基于 STM32 裸机开发的物联网边缘节点解决方案。通过串口(AT指令)驱动 4G 通信模组,利用 MQTT 协议将设备成功接入 ThingsCloud 物联网云平台。项目实现了传感器数据的定时上报、云端指令的下发解析,以及设备状态的实时同步(双向通信闭环),为低功耗、远距离的工业/农业物联网设备提供了一套稳定可靠的上云模板。
基于老师给我的板子,目前没有传感器,原理图什么都不知道。所以我通过变量通过keil5的调试功能的Witch来判断平台是不是有数据下发。
项目知识
一、串口底层驱动与数据接收机制
知识点 1:不定长网络数据包的接收(RXNE + IDLE 中断组合)
在串口通信中,如果采用传统的接收中断(RXNE),每收到一个字节就会打断一次 CPU。而 4G 模组通过 MQTT 下发的 JSON 报文长度是不固定的,无法预知包尾在哪里。
解决方案:引入串口空闲中断(IDLE)。当一帧数据发送完毕,串口总线会空闲(静默)一个字节的时间,此时触发 IDLE 中断。结合 RXNE 中断,就能实现“来一个字节存一个,总线一空闲就通知主循环处理整包”的高效机制。
代码体现
//==== USART2 中断服务函数 ==== void USART2_IRQHandler(void) { u8 Res2; // 1. 接收数据中断 if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { USART_ClearITPendingBit(USART2, USART_IT_RXNE); Res2 = USART_ReceiveData(USART2); // 只读一次! if (wifi.buff_length < BUFF_MAX - 1) { wifi.BUFF[wifi.buff_length++] = Res2; wifi.BUFF[wifi.buff_length] = '\0'; // 时刻保持字符串结尾 } } // 2. 串口空闲中断 (说明这一帧数据收完了) else if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) { USART2->SR; USART2->DR; // 清除 IDLE 标志 wifi.buff_flag = 1; // 告诉主循环:有完整数据到达了 } }
二、AT 指令通信与状态控制
知识点 2:同步请求-应答机制(超时重试)
4G 模组通过 AT 指令控制,这是一种典型的“一问一答”同步模型。单片机发送指令后,必须等待模组的响应(如 OK、CONNECT 等)。如果模组死机或断网,不能无限期等待,必须引入超时机制,超时则视为失败,交由主程序决策(如重试或报错)。
代码体现
// 发送指令并等待预期应答 uint8_t WIFI_CheckAckchar *src, char *dest, int timeout) { char *check = NULL; WIFI_BUFF_MEMSET(); // 发送前清空缓存 usart2_send_stringsrc); while (timeout > 0) // 阻塞等待 { delay_nms10); timeout -= 10; if (wifi.buff_flag == 1) // 收到一包数据 { check = strstrwifi.BUFF, dest); if (check != NULL) { WIFI_BUFF_MEMSET(); return 1; // 匹配成功 } wifi.buff_flag = 0; // 没匹配到,继续等下一包 } } return 0; // 超时失败 }
三、C 语言工程规范与编译器特性(核心踩坑)
知识点 3:Keil 长字符串解析 Bug 与解耦拼装
当 snprintf 的格式化字符串过长且跨行,内部包含大量反斜杠转义符(如 \\22、\\2C)时,Keil 的解析器容易出现类型判断混乱(报 argument of type "int" is incompatible)。
解决方案:采用“分步拼装法”。先用一个 snprintf 拼装纯业务数据,再用第二个 snprintf 将数据套入 AT 指令模板,化繁为简。
代码体现
// 1. 先拼装纯业务 JSON 数据 snprintf(json_payload, sizeof(json_payload), "{\\22temperature\\22:%d.%d\\2C\\22light\\22:%d\\2C\\22switch\\22:%s}", t_i, t_d, sim_light, sim_switch ? "true" : "false"); // 2. 再拼装完整的 AT 指令 snprintf(mqtt_ack_msg, sizeof(mqtt_ack_msg), "AT+MPUB=\"%s\",0,0,\"%s\"\r\n", MQTT_PUB_TOPIC, json_payload);
四、4G 模组底层协议与数据组装
知识点 4:合宙模组的 AT 指令转义陷阱(致命坑)
当通过合宙模组的 AT+MPUB 发送多属性 JSON 时,模组固件会优先将 JSON 内部的普通逗号 , 识别为 AT 指令自身的参数分隔符,导致 JSON 被截断,数据静默失败。必须将 JSON 内部的双引号转义为 \22,逗号转义为 \2C
代码体现
// 注意看这里的 \\22 (代表 "),以及 \\2C (代表逗号) // 这种写法是为了骗过 4G 模组底层的解析器,将标准 JSON 发出去 snprintf(json_payload, sizeof(json_payload), "{\\22temperature\\22:%d.%d\\2C\\22light\\22:%d\\2C\\22switch\\22:%s}", ...);
五、物联网业务逻辑设计
知识点 5:轻量级 JSON 特征解析
单片机 RAM 极小,不适合使用庞大的 cJSON 库。对于格式相对固定的云端下发指令(如 {"switch":true}),可以利用 C 语言的字符串查找函数 strstr 寻找键值对,并用 atoi 或 strncmp 快速提取数据。
代码体现
static char *find_value_start(char *json, const char *key) { char *p = strstr(json, key); // 找键名 if (p == NULL) return NULL; p = strchr(p, ':'); // 找冒号 if (p == NULL) return NULL; p++; // 跳过空格、引号和转义符,直达数值 while (*p == ' ' || *p == '"' || *p == '\\') p++; return p; }
知识点 6:状态闭环(State Closure)控制
当用户在云端平台点击开关,平台会通过 MQTT 下发指令。单片机解析并改变本地变量后,不能仅仅执行动作就结束了。必须立刻主动上报一次最新状态。这样云端后台才能确认设备已执行,从而将 UI 开关真正点亮,形成真正的双向通信闭环。
代码体现
// 在 mqtt_rx_Control 函数中 if (strstr(wifi.BUFF, "switch") != NULL) { // 1. 解析出 sim_switch 的新值 parse_bool_field (p_json, "switch", &sim_switch); // 2. 立即调用上报函数,把刚才的值发回给云端 WIFI_BUFF_MEMSET(); report_simulated_attributes(); return; }
项目实施
数据上报
此项目我用常量进行模拟,如下图。其次我们需要服务器IP,用户名,密码。这个在上个博客有讲这里就不多说了。

打开keil5的调试,在watch1窗口添加config_ok和sim_switch。(这两个量一个是看我是不是连上,另一个是看云端的数据有没有下发)关于数据的下发,还需要单片机去订阅 "attributes/push" 这个主题

当程序下载进去的时候,云台收到消息

从下面两张图看目前sim_switch是0x00,接下来的是属性下发。
sim_switch从0x00更改为0x01


也可以为sim_switch设置以下的看板,实现可视化。



浙公网安备 33010602011771号