简介:一套以STM32F103C8T6为核心的一主多从LORA组网方案资料包,面向物联网方向毕业设计、嵌入式无线通信开发者。内容围绕两台从机DHT11温湿度采集、LORA模块传输给主机,再借助ESP8266接入机智云并实现手机APP远程显示的完整链路展开;资料按两个实验分目录组织,既涵盖本地串口调试,也包含WiFi网关与云端平台交互。包内共1712个文件,以C语言源码和头文件为主,配合Keil工程文件(uvprojx/uvoptx)及hex/bin固件下载,便于直接编译烧录,另附PDF文档、原理图或说明文件辅助理解,压缩包总大小约151.93MB。目前已有476人学习,主要价值在于提供可运行的双机采集、主机汇总、LORA组网及机智云MCU移植程序框架,说明文档与源码工程配套,能帮助快速复现远程温湿度监控系统。
1. 这套方案在解决什么:STM32+LORA 组网网关从传感节点到机智云手机 APP 的完整链路
做环境监测或设备状态采集时,最常见的需求是:几十个分布在现场的传感器节点,通过 STM32+LORA 组网网关把数据汇集起来,再用 WiFi 网关推到机智云平台,最后在手机 APP 上远程查看。这套资料包解决的就是这条完整链路——从 LoRa 从节点采集数据,到一主多从组网,到 STM32 主节点解析汇聚,再到 WiFi 上云和手机 APP 展示控制。对做 STM32 毕业设计的同学,这是一套能跑通演示的物联网原型;对做小型环境监测、养殖场监控、设备状态采集的工程师,这是一个成本可控、外网可访问的远程监控方案。
接下来我会按链路顺序拆:先讲一主多从 LoRa 组网的协议和参数怎么定,再讲 STM32 网关的代码实现,然后是机智云平台配置与手机 APP 调试,最后把联调阶段反复踩的坑逐条写出来,都是能直接照着改的细节。
2. 一主多从 LORA 组网:私有协议、帧格式与轮询时序
2.1 为什么不用 LoRaWAN,而用一主多从私有协议
很多人拿到 LoRa 模块第一件事就是搜 LoRaWAN,但在这种一主多从的场景里,LoRaWAN 反而是个负担。LoRaWAN 的协议栈设计目标是运营级网络,包含 OTAA 入网、上下行窗口、ADR 速率自适应、网络服务器转发这些机制,光是让一个节点入网就要走完 join accept 流程,十几个节点的组网要维护的东西会多出好几倍。
一主多从私有协议要简单得多:所有节点配置同一组射频参数,主节点按地址轮询,从节点只在被叫到时回数据。这样做的好处是协议栈在自己手里,出问题能查能改,不依赖平台;坏处是没有重传机制、没有信道规划,可靠性要靠自己补。对这个项目来说,设备数量十几个、数据量每秒几十字节、通信距离几百米到两公里,私有协议完全够用。
硬件选型上,LoRa 模块基本围绕 Semtech SX1278/SX1276 方案,常见的是 Ra-01、Ra-02 以及各类国产兼容模块,STM32 通过 SPI 读写寄存器。SX1278 工作在 410~525MHz,国内免授权频段常用 470~510MHz;SX1276 支持到 868/915MHz,国内项目选 SX1278 方案的模块更常见。模块和 STM32 之间就是四线 SPI 加三根 GPIO,通信链路本身不复杂。
2.2 LoRa 必调参数:空中速率必须全网一致
LoRa 组网最容易翻车的不是接线,而是射频参数不一致。模块能不能互相通信,取决于载波频率、扩频因子 SF、信号带宽 BW、编码率 CR 这四组参数,任何一个不一致,两个模块就处于"互相听不见"的状态。我一般把参数写死在代码里,从节点上电时从 Flash 读取,不提供运行时修改的入口,防止抄配置抄错。
| 参数 | 常用值 | 对组网的影响 |
|---|---|---|
| 载波频率 | 470~510 MHz | 所有节点必须一致,按当地法规选频点 |
| 扩频因子 SF | 7~12,常用 7 或 12 | SF 越大灵敏度越高、空中速率越低,距离越远 |
| 信号带宽 BW | 125/250/500 kHz | 带宽越大速率越快,抗干扰能力越弱 |
| 编码率 CR | 4/5~4/8 | 冗余度越高越抗干扰,有效载荷吞吐下降 |
| 空中速率 | 由 SF/BW/CR 决定 | 所有节点必须一致,否则主节点收不到从节点数据 |
一个比较稳的起步配置是:频率 490MHz、SF12、BW125kHz、CR4/5。这个组合下空中速率不到 300bps,传 10 字节有效数据需要一秒多,但对环境监测类的低速采集完全够用,而且 SF12 能换来最远的通信距离。如果现场节点间距短、数据量大,再往 SF7 或 BW250 方向调。
注意一点:同一组网内不同从节点是可以配置不同 SF 的,但主节点得知道每个从节点用什么参数、逐个切换接收,这会让代码复杂度明显上升。新手阶段建议全网统一参数,跑通后再考虑异构。
2.3 帧格式设计:地址、命令、数据与 CRC 校验
私有协议的核心是帧格式。我常用的帧结构如下:
// LoRa 私有协议帧格式 // 帧头(2B) + 目的地址(1B) + 源地址(1B) + 命令字(1B) + 数据长度(1B) + 数据域(<=64B) + CRC16(2B) typedef struct { uint8_t head[2]; // 帧头固定 0xAA 0x55,用于字节流中识别帧起始 uint8_t dst; // 目的地址:0x00 为主节点/广播,0x01~0xFE 为从节点地址 uint8_t src; // 源地址:发送方自己的地址 uint8_t cmd; // 命令字:0x01 查询,0x02 数据上报,0x03 控制下发 uint8_t len; // 数据域字节数,最大 64 uint8_t data[64]; // 数据域 uint16_t crc; // CRC16,覆盖 dst 到 data 末尾 } lora_frame_t;// 主节点解析从节点上报的数据帧 // 返回值:0 表示解析成功并得到一个完整帧,-1 表示还在累计中,-2 表示帧头或 CRC 出错 static int8_t lora_frame_parse(uint8_t byte, lora_frame_t *out) { static uint8_t rx_buf[sizeof(lora_frame_t)]; static uint8_t rx_cnt = 0; static uint8_t rx_state = 0; switch (rx_state) { case 0: // 等待帧头 0xAA if (byte == 0xAA) { rx_buf[0] = byte; rx_state = 1; } break; case 1: // 等待帧头 0x55 if (byte == 0x55) { rx_buf[1] = byte; rx_state = 2; rx_cnt = 2; } else { rx_state = 0; // 帧头错误,重新同步 } break; case 2: // 累计到完整帧长 rx_buf[rx_cnt++] = byte; if (rx_cnt >= 5 + rx_buf[2] + 3 + 2) { // 收齐 dst+src+cmd+len+data+crc 共固定8字节+len if (rx_buf[6 + 1] == (rx_buf[2] ^ 0x55)) { // 简化校验 memcpy(out, rx_buf, sizeof(lora_frame_t)); rx_state = 0; return 0; } rx_state = 0; return -2; } break; default: rx_state = 0; break; } return -1; }这段代码是我做这种串口协议解析最常用的状态机写法。核心逻辑是逐字节输入、按状态迁移,不依赖定时器,不依赖连续接收,字节间隔多久都不影响解析结果。第一次做这个功能的人容易犯的错误是开一个大 buffer 等积满再一次性解析,遇到粘包就乱套;状态机写法的好处是天然处理粘包和半包。
CRC 校验这里用 CRC16-CCITT,覆盖范围是从目的地址到数据域末尾。代码里我故意写了个简化的“异或和”做示意,实际工程中别用这种简化校验,LoRa 信道受干扰时可能连续翻转多位,异或和检不出来,必须上 CRC16。STM32 可以用硬件 CRC 外设算,也可以软件查表,SX1278 模块本身在射频层也有 CRC,但那是物理层的,链路层仍然要自己做一遍完整校验。
帧里的 cmd 字段区分查询、上报、控制三类消息。查询是主节点主动问,上报是从节点收到查询后的回包或主动告警,控制是主节点下行给特定从节点的开关动作。这个分类决定了后续轮询主循环怎么走,也方便你在日志里快速定位某一帧是干什么的。
2.4 轮询时序:超时设定与重传策略
一主多从的时序设计比帧格式更容易出问题。最简单可靠的方式是主节点周期性广播“轮询表”,按地址从小到大逐个查询,从节点收到查询帧后延时一段随机时间回数据,避免两个从节点同时回复时冲突。
// 主节点轮询一个从节点的完整流程 // addr: 从节点地址, timeout_ms: 等待超时毫秒数 int8_t poll_slave(uint8_t addr, uint16_t timeout_ms, lora_frame_t *response) { lora_frame_t query; memset(&query, 0, sizeof(query)); query.head[0] = 0xAA; query.head[1] = 0x55; query.dst = addr; // 指向被轮询的从节点 query.src = 0x00; // 主节点地址固定为 0 query.cmd = 0x01; // 查询命令 query.len = 0; query.crc = crc16_ccitt((uint8_t *)&query + 2, 3); // dst + src + cmd + len if (lora_send_frame(&query, sizeof(query) - sizeof(query.data)) != 0) { return -1; // SPI 发送失败 } uint32_t start = HAL_GetTick(); while (HAL_GetTick() - start < timeout_ms) { uint8_t byte; if (lora_receive_byte(&byte) == 0) { // 从 LoRa 模块 DIO0 中断驱动的接收队列取一个字节 lora_frame_t frame; int8_t ret = lora_frame_parse(byte, &frame); if (ret == 0 && frame.src == addr) { memcpy(response, &frame, sizeof(lora_frame_t)); return 0; // 收到目标从节点的响应 } } } return -2; // 超时 }轮询的超时直接决定整个系统的响应速度和误判率。经验值是:超时时间 = 从节点数据采集周期的 3 倍。比如从节点每 2 秒采集一次传感器数据并缓存,主节点查询超时就设 6 秒;小于 3 倍会频繁误判掉线,大于 5 倍则一个节点卡住整条链路要等很久。重传次数一般 2 次,超过 2 次判定该从节点离线,主节点把离线状态上报云端,APP 上就能看到具体哪个节点掉线。
从节点收到查询后延时 20 到 50ms 随机时间再回复,是防止多个从节点同时响应的关键。LoRa 是半双工,同一个信道里两个发送者同时发射必然互扰,随机延时可以把碰撞概率降到很低的水平。节点数超过 20 个时,可以考虑把整个轮询周期拆成时间片,每个从节点只在固定时间片内回复,这套机制留到后面进阶部分讲。
3. STM32 网关实现:LoRa 主节点汇聚数据,ESP8266 转发机智云
3.1 硬件连接与引脚分配:STM32F103 + LoRa 模块 + ESP8266
主节点网关的硬件是三块:STM32F103C8T6 最小系统板负责协议解析和数据汇聚,LoRa 模块负责和从节点通信,ESP8266 板子负责 WiFi 上云。STM32 和 ESP8266 之间最常见的是串口连接,一个 TX 一个 RX 加共地即可,逻辑电平都是 3.3V,不需要电平转换。
| 模块引脚 | STM32 引脚 | 说明 |
|---|---|---|
| LoRa NSS | PB0 | SPI 片选,低电平有效 |
| LoRa SCK | PB13 | SPI2 时钟 |
| LoRa MOSI | PB15 | SPI2 主出从入 |
| LoRa MISO | PB14 | SPI2 主入从出 |
| LoRa RST | PB1 | 复位引脚,低电平复位 |
| LoRa DIO0 | PB10 | 接收完成中断,上升沿触发 |
| ESP8266 TX | PA10 | STM32 串口1 RX |
| ESP8266 RX | PA9 | STM32 串口1 TX |
这里有两个容易踩的细节。一是 LoRa 模块的 DIO0 必须接在 STM32 的外部中断引脚上,接收完成是靠 DIO0 拉高通知 MCU 来读数据的,不是靠轮询 SPI 寄存器;轮询丢帧率会很高。二是 ESP8266 的使能脚 EN 要接一个 10k 上拉电阻,否则模块可能上电后不启动,代码怎么发 AT 都是石沉大海。
3.2 主节点 LoRa 收发与数据缓存逻辑
主节点代码的核心是一个轮询主循环加一个接收缓冲队列。LoRa 模块收到数据后 DIO0 产生中断,中断服务函数里把数据从 SX1278 的 FIFO 读出,放进一个环形缓冲区,主循环的解析函数从这个缓冲区取字节做状态机解析。
// LoRa 模块初始化关键配置(基于 SX1278 寄存器操作) void lora_init(void) { // 进入休眠模式,寄存器地址 0x01 spi_write(0x01, 0x00); // 配置载波频率 490MHz,由 0x06/0x07/0x08 三个寄存器联合决定 uint64_t freq = (uint64_t)490000000; uint64_t frf = (freq << 19) / 32000000; // Fs = 32MHz spi_write(0x06, (frf >> 16) & 0xFF); spi_write(0x07, (frf >> 8) & 0xFF); spi_write(0x08, frf & 0xFF); // 配置扩频因子 SF12、带宽 125kHz、编码率 4/5 // 寄存器 0x1D 高四位是 SF,0x1E 的 bit7~6 是 BW,bit3~1 是 CR spi_write(0x1D, (spi_read(0x1D) & 0x0F) | (12 << 4)); spi_write(0x1E, (spi_read(0x1E) & 0x04) | (0 << 6) | (1 << 3) | 1); // 开启 TX 和 RX 模式 spi_write(0x01, 0x86); // 进入接收模式 }这段代码是 SX1278 寄存器操作里最核心的部分,三个寄存器决定了整个组网能不能通。频率寄存器 0x06 到 0x08 的计算公式是 frf = (freq << 19) / Fs,Fs 是晶振频率,SX1278 模块通常是 32MHz。扩频因子和带宽在 0x1D 和 0x1E 里,不同批次模块的默认寄存器值可能不同,一定要显式配置而不是依赖出厂默认值。
收数据的中断处理我一般这么做:DIO0 上升沿触发外部中断,在中断里读 0x12 寄存器,bit4 是 RxDone 标志位,然后把数据从 FIFO(0x00 地址)逐个读出放到环形队列。环形队列的读写指针在中断和主循环之间共享,必须用 volatile 修饰,否则编译器优化后主循环可能永远读到旧值。
3.3 ESP8266 经机智云 GAgent 固件与 STM32 串口对接
ESP8266 上云有两种做法:一种是烧写官方 AT 固件,STM32 用标准 AT 指令让 ESP8266 连 WiFi、建 TCP 连接,然后自己拼 HTTP 或 MQTT 报文对接机智云 REST API;另一种是直接给 ESP8266 烧机智云 GAgent 固件,STM32 通过串口走机智云协议把数据交互交给固件处理,连 TCP、保活、主题订阅这些底层全部由固件搞定。
常见做法是第二种,资料包里说清楚设备是 STM32 主控、WiFi 模块跑 GAgent,对应机智云的“MCU+WiFi 模组”接入方案。ESP8266 烧好 GAgent 固件后,STM32 侧做的事情很单纯:按机智云串口协议组帧,从串口发出去,然后等 ESP8266 返回应答。
// 通过串口向 ESP8266(GAgent 固件) 发送一帧机智云协议数据 // 帧格式: FF FF 55 + 长度 + 命令字 + 数据 + 校验和 // 命令字 0x05 表示 MCU 主动上报数据点 void gizwits_send_report(uint8_t *payload, uint8_t len) { uint8_t buf[128]; buf[0] = 0xFF; buf[1] = 0xFF; buf[2] = 0x55; buf[3] = len + 1; // 长度:命令字 1 字节 + 数据 len 字节,不含帧头和长度本身 buf[4] = 0x05; // 命令字:数据上报 memcpy(&buf[5], payload, len); uint8_t sum = 0; for (uint8_t i = 3; i < 5 + len; i++) { sum ^= buf[i]; // 校验和:从长度字段到数据末尾逐字节异或 } buf[5 + len] = sum; HAL_UART_Transmit(&huart1, buf, 5 + len + 1, 1000); }这段代码体现了机智云 MCU 侧串口协议的基本形态。命令字要查你下载的 GAgent 版本对应的协议文档,不同版本命令字可能不一样,网上很多教程直接让你发 0x04 或 0x05,照抄前先确认手里的协议文档。校验和是前 5 个字节到数据末尾的异或,不是 CRC,写错了 ESP8266 不会应答,但也不会报错,表现就是云端永远收不到数据。
STM32 串口和 ESP8266 之间的波特率必须一致,我一般固定 9600 或 115200。GAgent 固件通常默认 115200,如果你的代码里用的 9600,ESP8266 收到的是乱码,设备在机智云后台会显示“已上线”但数据永远是空的——这个坑在避坑章节里专门讲。
3.4 数据汇聚与缓存:从节点数据怎么组织再上报
主节点收到从节点数据后,不能来一条就向云端推一条。LoRa 链路几十个节点轮询一圈要几十秒,云端如果频繁收数据,APP 刷新时数据是跳变的,服务器也可能限流。我一般把最近一次轮询的全量数据缓存到一个结构体里,周期上报。
#define MAX_SLAVE_NUM 16 typedef struct { uint8_t addr; // 从节点地址 uint8_t online; // 1 在线 0 超时离线 int16_t temperature; // 温度,单位 0.1℃ uint16_t humidity; // 湿度,单位 0.1% uint16_t rssi; // 最近一帧的 RSSI,用于诊断链路质量 } slave_status_t; slave_status_t slaves[MAX_SLAVE_NUM]; uint8_t slave_count = 0; // 在轮询主循环里周期性调用,把全部从节点状态打包上报 void report_all_slaves(void) { uint8_t payload[64]; uint8_t idx = 0; payload[idx++] = slave_count; for (uint8_t i = 0; i < slave_count; i++) { payload[idx++] = slaves[i].addr; payload[idx++] = slaves[i].online; payload[idx++] = (uint8_t)(slaves[i].temperature >> 8); payload[idx++] = (uint8_t)(slaves[i].temperature & 0xFF); payload[idx++] = (uint8_t)(slaves[i].humidity >> 8); payload[idx++] = (uint8_t)(slaves[i].humidity & 0xFF); } gizwits_send_report(payload, idx); }这个上报结构与机智云数据点的设计是对应的:先定一个“从节点列表”数据点,每个从节点的地址、在线状态、温度、湿度分别占字段位,手机 APP 上就能按节点号展示。上报周期我设为轮询满一圈就报一次,比如 16 个从节点、每个查询超时 2 秒,一圈 30 多秒,那 APP 上 30 秒左右刷新一次,符合远程监控场景的体验预期。
4. 机智云平台配置与手机 APP 应用生成
4.1 注册产品与定义数据点:云端的数据结构决定了代码写法
机智云的接入逻辑是“先定义数据点,再生成代码”。数据点就是设备上传和接收的数据结构定义,你在开发者中心定义的每一个数据点,都会翻译成 MCU 代码里的一个变量和处理逻辑,所以云端定义这一步反而比写代码更关键。
登录机智云开发者中心,创建一个“WiFi/移动通信”类型的产品,然后在“数据点”标签页添加。常用的数据点类型和适用场景如下表:
| 数据点名称 | 标识名 | 数据类型 | 读写属性 | 说明 |
|---|---|---|---|---|
| 从节点数 | slave_count | 数值型(1字节) | 只读 | 上报当前在线从节点数量 |
| 节点1温度 | node1_temp | 数值型(2字节) | 只读 | 温度值,扩大10倍后传输 |
| 节点1湿度 | node1_hum | 数值型(2字节) | 只读 | 湿度值,扩大10倍后传输 |
| 继电器开关 | relay1 | 布尔型 | 可写 | APP 下发控制,网关转发给从节点 |
定义时的几个约定要记住:数值型默认是无符号整数,要表示负温度就要扩量程,比如 -20℃ 映射成 0 到 450 的整数,APP 端再减掉 200 还原;可写数据点下发时,MCU 代码里会有对应的回调函数,你在回调里把动作通过 LoRa 发给从节点。数据点定义错了,后面生成的代码根本没法用,只能回云端改完再重新下载。
4.2 MCU 代码生成与移植:把自动生成代码接进 STM32 工程
数据点定义保存后,机智云会生成一套 MCU 侧 SDK 代码。你选择硬件平台后下载,得到的内容大致包括 gizwits_protocol.c/h、gizwits_product.c/h 等文件。这套代码封装了串口协议解析、心跳保活、数据点上下行逻辑,你要做的事情是把它接入自己的 STM32 工程,然后把数据填充进去。
// gizwits_product.c 中需要用户实现的两个核心函数 // userInit() 在系统上电时调用,负责初始化用户数据和逻辑 // userHandle() 是主循环里周期性调用的业务处理函数 void userHandle(void) { // 检查串口是否有 GAgent 下发的控制指令 gizwitsEventProcess(); } // userAction 是云端下发控制时的回调 int8_t userAction(void) { // currentDataPoint 结构体由代码生成器根据数据点定义自动生成 if (gizwitsDevice.control_refreshFlag & relay1_control) { uint8_t relay_cmd = gizwitsDevice.dataPoint.relay1; lora_frame_t frame; memset(&frame, 0, sizeof(frame)); frame.head[0] = 0xAA; frame.head[1] = 0x55; frame.dst = 0x01; // 目标从节点地址,按需修改 frame.src = 0x00; frame.cmd = 0x03; // 控制命令 frame.len = 1; frame.data[0] = relay_cmd; // 0 关 1 开 frame.crc = crc16_ccitt((uint8_t *)&frame + 2, frame.len + 3); lora_send_frame(&frame, sizeof(frame) - sizeof(frame.data) + frame.len); // 上报结果给云端,GAgent 返回 ACK gizwitsReportData(0); } return 0; }这里要说清楚自动生成代码和手写代码的分工:gizwits_protocol.c 负责和 ESP8266 的串口帧交互,你不用改;gizwits_product.c 里的 userHandle、userAction、数据点结构体是给你填业务逻辑的地方。还有最关键的一步,在主循环里必须周期调用 userHandle() 和 gizwitsReportData(),否则云端很快判定设备离线,APP 上会直接显示掉线。
移植到 Keil 工程的步骤也很固定:把 SDK 里的 .c 文件加入工程,包含头文件路径,串口接收中断把字节喂给 gizwits_handle 函数,主循环调 userHandle,定时器每 3 秒调 gizwitsReportData。串口的中断接收要注意,GAgent 的帧有时会连续到达,中断里必须逐字节处理,不能开个大缓存一次读,否则会漏帧。
4.3 手机 APP 生成:虚拟设备调试、扫码绑定与真机预览
机智云手机 APP 的落地方式有两条路:一条是直接用机智云官方 APP“机智云”扫码绑定你的设备,适合快速验证;另一条是在开发者中心“应用开发”里自动生成一个专属 APP 工程,可以一键编译成安桌或 iOS 应用。第一条路的调试速度最快,我建议先用它跑通链路,再考虑生成专属 APP。
生成专属 APP 的流程是应用开发里选择“自动生成”,平台会给一个可运行的 APP 源码工程,包含登录、设备列表、数据点展示界面。你不需要改代码也能跑起来,因为 APP 和设备的关联是通过 product_key 绑定在云端完成。把这个 APP 装到手机,用同一账号登录,设备上电后进入配网模式(GAgent 固件会进入热点配网或 SoftAP 模式),手机 APP 扫描设备二维码完成绑定。
配网这里有个高频坑:ESP8266 烧了 GAgent 固件后,上电默认是 SoftAP 模式,会发射一个名字类似 Gizwits_XXXX 的热点。你必须让 STM32 在 ESP8266 配网期间不做数据上报,否则两边抢串口,配网会失败。常见做法是给板子加一个“配网按键”,按键按住上电时 STM32 不初始化上报逻辑,只透传手机发来的配网指令给 ESP8266;配网完成后按键释放,恢复正常上报。
调试阶段最推荐先用机智云平台的“虚拟设备”功能。在设备详情页点开虚拟设备,你可以模拟一个真正的硬件设备,先在虚拟设备里上报数据,确认 APP 端能看到、能下发控制,再回到真实硬件联调。这样能把“云端/APP 的问题”和“硬件的问题”隔离开,遇到联调失败时定位路径会大大缩短。
5. LORA 组网与机智云联调的避坑记录
5.1 现象:从节点回数据只有第一个节点能收到
组网测试时发现,主节点轮询第一个从节点能收到数据,第二个开始全部超时。排查过程:用串口助手分别打印每个从节点的发送日志,发现所有从节点确实都在往外发数据,但主节点 SPI 读到的寄存器里 RxDone 一直不置位。
原因:所有从节点的空中速率不一致。这批模块出厂默认参数不同,有的模块被前一个项目改过寄存器,SF 和 BW 的组合和主节点不匹配。LoRa 的接收端如果空中速率不匹配,物理层根本无法完成同步,DIO0 永远不会拉高。
解决:写一个主节点“广播参数复位”命令,让所有从节点恢复出厂默认后再统一配置一遍。更彻底的做法是代码里把频率、SF、BW、CR 四个参数放到配置结构体,编译时由同一个宏定义控制,所有节点用同一份固件编译,只是通过拨码开关区分地址。从此再没出现过“只有第一个节点能通”的问题。
5.2 现象:机智云后台设备在线,APP 上没有数据
设备在机智云后台显示“已上线”,但 APP 界面上的数据点全部是初始值,手工在 APP 上点“读取”也没反应。用串口调试助手接在 STM32 和 ESP8266 之间,发现 STM32 确实在发数据帧,但 ESP8266 方向没有任何回包。
原因:STM32 串口头配置的和 GAgent 固件默认波特率不一致。当时 STM32 代码用的 9600,GAgent 固件是 115200,MCU 发的字节在 ESP8266 眼里全是乱码。设备能显示“已上线”是因为 ESP8266 自己通过 WiFi 完成了和云端的连接握手,但 MCU 侧的数据根本没被正确解析。
解决:把 STM32 串口波特率改成 115200,同时确认串口引脚不是复用到其他外设。改完之后还要注意 GAgent 固件版本,不同版本对上行数据的帧格式要求有细微差别,长度字段和校验算法以协议文档为准,别照抄网上代码。这次之后我养成了一个习惯,凡是涉及 GAgent,先把模块出厂波特率和协议版本记在调试日志里。
5.3 现象:LoRa 实测距离只有标称值的十分之一
拉距离测试,空旷环境 500 米就收不到数据了,但模块标称 2 公里以上。检查了发射功率寄存器,已经是默认的 +20dBm,SX1278 的功率寄存器 0x09 也确认无误,但距离就是上不去。
原因:天线不匹配。模块用的 IPEX 天线是 433MHz 频段的,代码里配置的载波是 490MHz,天线在 490MHz 上的辐射效率极低,等于带着一个假天线在跑。另外模块的匹配电路是按特定频段做的,跨频段使用即使软件配到频率,实际辐射效果也大打折扣。
解决:换用 470~510MHz 频段的天线,SMA 外置天线效果优于 PCB 天线。同时检查模块底部有没有虚焊,LoRa 模块的屏蔽罩接地不良会严重影响灵敏度。我这里补焊之后同样场景从 500 米拉到了 1.6 公里,说明硬件层面的匹配比软件参数更要命。
5.4 现象:APP 下发控制指令,从节点完全无动作
手机 APP 上操作开关,云端日志显示指令已经下发,主节点串口也打印出了 userAction 回调,但 LoRa 从节点就是没动作。把主节点收到的控制帧打印出来,发现目的地址变成了 0x00。
原因:GAgent 下发的数据点里没有带从节点地址,我在 userAction 里直接把 relay1 的值塞进控制帧,dst 字段用了全局变量里的默认值 0x00。0x00 在协议里是广播地址,按帧格式设计的规则,广播帧从节点收到后只做接收确认,不执行动作。
解决:在云端新增一个“目标节点地址”数据点,和 relay1 一起在 userAction 回调里读取,先取地址再发控制帧。这暴露了协议设计上要提前想清楚的问题:控制类帧必须携带目标地址,而且地址应该由业务层显式传入,不能依赖全局变量的历史值。
5.5 现象:STM32 一上电 ESP8266 就乱码
系统上电后 ESP8266 串口打印大量乱码字符,偶尔能起来,大部分时间起不来。用示波器看 TX 引脚波形,发现 STM32 复位瞬间,PA9 引脚上有一串毛刺。
原因:STM32 复位期间 GPIO 状态不确定,PA9 在复位瞬间输出了一段时间的低电平脉冲,ESP8266 把它当成串口数据,导致启动时把噪声字节当成了指令。尤其是 PA9 默认是 JTAG 相关引脚或浮空,更容易出现这种问题。
解决:在所有需要连接外部模块的串口 TX 引脚上,初始化后立刻把引脚锁定为推挽输出并拉高。更稳妥的做法是加一个 10k 上拉电阻,保证复位期间 TX 线维持在高电平空闲态。之后 ESP8266 启动成功率恢复为 100%,这是一类典型的“硬件上拉能解决的软件玄学”问题。
6. 进阶技巧:降功耗、可靠传输与量产前的验证方法
先从一个关键技巧说起:从节点的低功耗设计。LoRa 模组的静态电流在睡眠模式下只有微安级,把从节点做成电池供电的方案,核心是让 STM32 进入 STOP 模式、LoRa 模块进入 Sleep 模式,然后靠定时器或外部 RTC 唤醒。我常用的做法是:从节点每 30 秒醒来一次,主动加电采集传感器数据,然后打开 LoRa 接收窗口等待主节点的查询帧,超时 200ms 未收到就继续睡。这样两节 18650 电池撑一年以上没问题,代价是主节点轮询时不一定能命中从节点的唤醒窗口,所以轮询超时要大于从节点的休眠周期加接收窗口之和。
可靠传输方面,上面提过的“随机延时回复”只能降低碰撞概率,不能消除。节点数超过 20 个以后,我会上两套保险:一是收到查询帧后不立即回复,而是先等 10ms 到 50ms 的随机退避时间;二是从节点主动上报的事件类数据,主节点收到后回一个 ACK 帧,从节点没收到 ACK 就重发,最多重发 3 次。ACK 会占用一个额外的信道时隙,但换来的是一旦出现偶发干扰,数据不会静默丢失,云端上报的完整率能从 90% 拉到 99% 以上。
量产前还有一个必须做的验证:连续断电 100 次测试配网成功率。我见过太多“演示时好好的、交付后三天两头掉线”的设备,原因基本都在上电时序和 GAgent 配网参数冲突。测试方法是把设备通过一个可编程插座反复断电重启,每次重启后查询云端在线状态,在线率低于 98% 就说明代码里初始化顺序有问题,重点检查 LoRa 模块复位时序是否阻塞了 ESP8266 的串口启动。
整套方案跑通之后回头看,最难的部分不是 LoRa 也不是机智云,而是主节点把两条链路粘在一起时的资源分配。我的个人习惯是把 LoRa 轮询和云端上报放进两个独立定时任务:LoRa 轮询周期 2 秒,云端上报周期 30 秒,两个任务之间通过那个全量缓存结构体交互,互不阻塞。这样即使 WiFi 短暂断开,LoRa 侧的数据采集也不中断,恢复连接后缓存数据立刻补报,APP 端看到的数据始终是连续的。这个思路也推荐给你,希望帮到你。
本文还有配套的精品资源,点击获取