简介:基于LoRa模块探索网状网络方法的实验性源码包,面向物联网/嵌入式开发者及LoRa组网学习者。项目以8块LILYGO TTGO T-Beams v0.7为硬件平台,由网关节点(GN)通过LoRa接收多个LoRa节点(LN)的随机数据有效载荷,再经WiFi连接套接字服务器,最终以POST请求存入数据库;载荷含节点ID、跳数、经纬度及时间信息,可用于集装箱等资产跟踪场景。包内共34个文件,核心为21个Arduino例程(.ino)及2个socket_server、2个.c、1个.h,另有7个Markdown文档辅助说明,压缩包体积仅43KB,目录按不同收发模式、网关双工等场景划分。已有1389人学习下载;通过例程可逐一验证LoRa点对点收发、网关与数据库交互等关键环节,适合想从物理层出发模拟网状路由、了解LoRa节点数据上云全链路的人参考。
1. 为什么说 LoRa mesh 是低速网络里最难做的一种自组网
如果把无线组网比作盖楼,WiFi 是框架结构、5G 是精装高层,那 LoRa mesh 更像是在一片空旷厂区里用砖头垒平房——看起来工程量不大,可一旦你想把几栋平房连成一片社区,麻烦就来了。LoRa 模块的空中速率通常只有几百 bps 到几十 kbps,而 mesh 网络偏偏要在这条窄得可怜的信道上完成邻居发现、路由维护、多跳转发和可靠性确认。每一个在高速网络里微不足道的开销,在这里都会被放大成数秒的延迟和成串的丢包。这个方向的价值恰恰就在这种「夹缝」里:它解决的是 WiFi 覆盖不到、蜂窝信号进不去的那些区域里,少量、低频、低功耗的数据回传问题——牧场环境监测、地下管廊巡检、应急通信演练,都是典型的适用场景。这篇笔记面向的是想拿 LoRa 模块真正组一张多跳网络的嵌入式开发者,以及准备选型评估「用 LoRa 做 mesh 到底靠不靠谱」的人。下文的内容全部围绕实际可复现的源码方案展开,我会把物理层参数、协议栈模块、最小验证链路和常见坑一次讲透。
2. 先摸清 LoRa 的物理边界:速率、距离和半双工怎么决定 mesh 协议的设计
2.1 SF、BW、CR 三个互锁参数,搭配错了直接翻车
LoRa 调制有三个核心参数,但它们不是独立变量,而是三把锁在一起的螺丝刀:扩频因子 SF、带宽 BW、编码率 CR。SF 决定每个符号承载的比特数,SF 越大,接收灵敏度越高、抗干扰越强,但空中时间越长;BW 决定信道占用的宽度,BW 越大速率越快但灵敏度越差;CR 是前向纠错的冗余度,从 4/5 到 4/8,冗余越多越抗噪声,但有效载荷占比越低。
实践中,三者组合的差异很大。同样是 64 字节的有效载荷,SF7/BW125/CR4/5 的单包空中时间约 46ms,而 SF12/BW125/CR4/8 可以膨胀到 1200ms 以上——差了 26 倍。这意味着同样的信道里,一组参数每秒能过的包数量,另一组可能连一个完整往返都支撑不起。下表给出几组常用的参数组合及其对 mesh 组网的实际影响。
| 参数组合 | 有效速率(理论) | 单包空中时间(64B) | 接收灵敏度 | mesh 场景评价 |
|---|---|---|---|---|
| SF7 / BW125 / CR4/5 | 约 5.5 kbps | 约 46 ms | 约 -123 dBm | 数据量大、节点密集,适合短距测试 |
| SF8 / BW125 / CR4/5 | 约 3.1 kbps | 约 82 ms | 约 -126 dBm | 折中;中继转发时延可接受 |
| SF10 / BW125 / CR4/5 | 约 1.0 kbps | 约 247 ms | 约 -132 dBm | 距离优先,单跳覆盖好,但多跳时延显著 |
| SF12 / BW125 / CR4/8 | 约 0.27 kbps | 约 1260 ms | 约 -137 dBm | 极限距离;mesh 基本只适合两跳以内 |
mesh 场景里我最常用 SF8 加 BW125,因为它的吞吐和接收灵敏度相对均衡。需要注意的是,所有 LoRa 芯片的空中速率都比以太网低好几个数量级,这决定了 mesh 协议里不能出现频繁的握手确认、不能周期性广播大路由表,更不可能做大数据分片重组。
2.2 半双工机制:同一时刻只能收或发,这让「谁转发、转几跳、何时停」成为协议设计的生死线
LoRa 模块是半双工设备,同一时刻只能要么收、要么发,并且从发射切换到接收的 turnaround 时间通常需要数毫秒。这带来一个直接影响:mesh 节点不可能「边收边转」,转发必须先把一帧完整收进来,处理完,再占用信道发出去。每多一跳,一帧的端到端时延就至少要增加「一收一发的时间加处理时间」。一个 SF8/BW125 的 64 字节帧,单跳转发时延就已经接近 200ms,三跳接近 600ms。如果应用要求秒级响应,三跳基本是上限。
所以,LoRa mesh 的协议设计必须明确回答三个问题。第一,谁转发?常见做法是只由目标地址的邻居节点转发,或者由收到广播包且距离目标更近的节点转发。第二,转几跳?必须有一个 TTL 或者最大跳数限制,否则一个广播包会在全网里无限扩散。第三,何时停?每个节点必须维护最近收到的消息 ID 缓存,重复帧直接丢弃,否则同一条消息会被多个邻居重复转发,形成广播风暴。这三个问题不是「优化项」,而是能不能组网的生死线。
2.3 和 BLE mesh、WiFi mesh 相比,LoRa mesh 的位置在哪里
很多人会拿 LoRa mesh 跟蓝牙 mesh、WiFi mesh 做对比,但它们的定位几乎不重叠。蓝牙 mesh 走 2.4GHz,节点密集、速率中等,适合室内灯控这类几十米范围内的场景,但单个 Mesh 网络对消息洪泛的承载能力有限;WiFi mesh 速率高、生态成熟,但功耗和成本摆在那里,一个网关动辄数瓦功耗,不可能靠电池撑几个月。ZigBee mesh 倒是低功耗、多跳的经典方案,但它用的是 2.4GHz,穿墙能力和绕射能力远不如 LoRa 的 Sub-GHz 频段。
LoRa mesh 真正的位置是「低速率、低功耗、远覆盖」三元交集。它不跟蓝牙抢室内,不跟 WiFi 抢带宽,也不跟蜂窝抢移动性。它抢的是那些「一年只需要传几千条数据、但每条数据都不能丢」的场景——比如分布式土壤墒情监测,节点相隔一两公里,地形起伏,没有供电。在这种场景里,LoRa mesh 允许你中间加几个中继节点,让末端的传感器不必直接打到一个十几公里外的网关,而是通过邻居节点接力传回数据。代价就是速率低、实时性差,但这恰好不是这类场景的刚需。
3. 读 lora-mesh 源码:射频中断到应用回调之间的数据流设计
3.1 射频驱动抽象层:为什么收发逻辑必须封装成一个状态机
拿到任何一个 LoRa mesh 源码工程,第一件事不是去看 main 函数,而是去翻射频驱动层。LoRa 芯片(常见的是 SX126x 或 SX127x 系列)虽然寄存器多、模式复杂,但软件驱动层的核心其实就是一个状态机:IDLE(空闲)→ TX(发射)→ RX(接收)→ 回调唤醒,然后回到 IDLE。为什么必须这样封装?因为 LoRa 芯片的中断机制是按事件触发的:一个包发送完成、一个包接收完成、或者接收超时,都会拉高 DIO 引脚触发外部中断。如果驱动层不做状态机,直接用「发完就切接收」这种线性流程,一旦发送失败或接收超时,整个射频链路就卡死了。
正确的抽象方式是把射频芯片的读写操作封装为一个独立模块,向上提供四个基础接口:初始化、发送单帧、启动接收、注册回调。收发完成的信号通过中断回调通知协议栈,而不是让协议栈轮询等待。这样协议栈的代码只需要关心「发完会回调我」「收到会回调我」,不用管芯片内部的时序细节。
typedef struct { uint8_t *buf; uint16_t len; int16_t rssi; /* 接收信号强度,单位 dBm */ int8_t snr; /* 信噪比,单位 dB */ uint32_t timestamp_ms; } radio_rx_packet_t; /* 驱动层向上提供的回调,协议栈注册进来 */ typedef struct { void (*on_tx_done)(uint8_t seq); void (*on_rx_done)(radio_rx_packet_t *pkt); void (*on_tx_timeout)(void); } radio_callbacks_t; void radio_init(radio_callbacks_t *cb); int radio_send(uint8_t *buf, uint16_t len); void radio_start_rx(void);上面这段代码是这类源码里驱动层的典型骨架。注意on_rx_done回调里拿到的不只是数据本身,还把 RSSI、SNR、时间戳一并带上来。这三个附加字段在 mesh 协议里用途很大:RSSI 用于邻居发现时的链路质量排序,SNR 用于判断链路是否需要换参数,时间戳用于后续的丢包统计和时延分析。radio_start_rx()必须每次收到包之后重新调用一次,因为很多 LoRa 芯片接收完成后会自动回到 standby 模式,不重新启动接收,下一包就丢了。
3.2 邻居发现与路由维护:不做洪泛、不做大规模路由表,靠什么转发
LoRa mesh 源码里最值得读的就是网络层的邻居管理模块。由于带宽限制,这类 mesh 网络几乎不会采用类似 OLSR 那种周期交换完整路由表的方案——一张几十个节点的路由表全量广播一次,就能把信道占满好几分钟。常见做法是「受限洪泛加轻量邻居表」:每个节点周期性地发送一个很短的 beacon 帧,里面只包含自己的节点 ID 和一个递增序号,收到 beacon 的邻居记录发送者的 ID、RSSI 和最近活跃时间,存入一张上限 20 到 50 条的邻居表。
路由决策时,节点并不需要知道全网拓扑,只需要知道「我该把包转发给哪个邻居」。最简单的可靠做法是:如果目标节点是我的邻居,直接单播给它;如果不认识目标,就把包广播出去,靠 TTL 限制洪泛范围。这个做法实现简单,而且天然适应节点增减。代价是同一包可能被多个节点重复转发,所以必须配合消息缓存去重。
typedef struct { uint16_t node_id; int16_t rssi; uint32_t last_seen; /* 最后一次收到该节点 beacon 的时间戳 */ uint8_t hop_count; /* beacon 经过的跳数 */ } neighbor_entry_t; typedef struct { neighbor_entry_t table[MAX_NEIGHBORS]; uint8_t count; uint16_t self_id; } neighbor_table_t; void neighbor_table_update(neighbor_table_t *tbl, uint16_t id, int16_t rssi); bool neighbor_table_exists(neighbor_table_t *tbl, uint16_t id);邻居表更新的逻辑里有一个关键细节:RSSI 不能直接用单次测量值,要做指数滑动平均。因为 LoRa 信号在远距离传输时存在慢衰落和快衰落叠加,单次测得的 RSSI 波动可能达到 10dB 以上。如果直接拿瞬时值去更新路由,会出现「上一秒觉得 A 邻居信号好,下一秒又觉得 B 邻居更好」,路由表反复横跳,转发路径抖动。常见做法是维护每个邻居的平滑后 RSSI,当差值超过 5dB 才更新路由方向。
3.3 消息的一次完整旅程:从应用层 send 到目标节点 recv 的帧格式与转发逻辑
把整个数据流串起来看,一次消息从发送端到目标端会经过四个层次:应用层协议、网络层封装、射频驱动、物理天线。应用层把一个业务数据包交给网络层,网络层在它前面加一个 mesh 头,里面放源节点 ID、目标节点 ID、消息序列号、TTL 跳数限制,再整体交给射频驱动发送。接收端收到后先剥离 mesh 头,检查消息缓存里有没有处理过这个消息——有就丢弃,没有就判断目标是不是自己,不是自己且 TTL 大于 1,就把 TTL 减一后重新广播转发。这个「先查重、再转发、最后上抛应用」的顺序不能颠倒。
typedef struct { uint8_t version; /* 协议版本号 */ uint8_t ttl; /* 剩余跳数,每转发一次减 1 */ uint16_t src_id; /* 源节点 ID */ uint16_t dst_id; /* 目标节点 ID:0xFFFF 表示广播 */ uint16_t seq; /* 源节点消息序号,用于去重 */ uint8_t payload_len; uint8_t payload[]; /* 应用数据,建议不超过 180 字节 */ } mesh_frame_t;这个帧格式是 LoRa mesh 源码里最常见的一种设计。seq字段配合src_id一起作为消息的唯一标识,进入去重缓存。关键在于payload_len的设计约束:LoRa 单包最大载荷通常不超过 251 字节,但强烈不建议塞满。因为载荷越大,空中时间越长,转发时延越大,被干扰的概率也越高。我一般会把 mesh 层最大载荷限制在 180 字节以内,给射频驱动留出协议头的空间。如果应用层一次要发几 KB 数据,正确做法是拆成多个 mesh 帧分开传,不要试图用一帧硬塞。
4. 把 lora-mesh 跑起来:从最小双节点验证到三节点中继
4.1 硬件接线与编译配置:一组 SPI 引脚、一根天线、三块模块就能搭中继链路
动手的第一件事是确认硬件接法。绝大多数 LoRa 模块都是 SPI 接口加几个 GPIO 控制引脚,接线方式高度相似。下面这张表按最常见的模块排线给出参考,如果你手上的模块定义不同,以模块厂商的数据手册为准。
| 模块引脚 | 单片机引脚 | 说明 |
|---|---|---|
| SPI_SCK | SPI 时钟 | 频率建议 1MHz 以下,LoRa 芯片不支持高速 SPI 写寄存器 |
| SPI_MOSI | 主出从入 | — |
| SPI_MISO | 主入从出 | — |
| SPI_CS | 任意 GPIO | 片选,必须由软件控制 |
| RST | 任意 GPIO | 复位,上电后建议拉低再拉高做一次硬复位 |
| DIO0 | 任意 GPIO(外部中断) | 发送完成/接收完成中断脚 |
| DIO1 | 任意 GPIO | 超时/接收错误中断脚,可选 |
接线完成后,编译配置里最需要关注的是频段、速率参数和串口波特率。这个方向常见的源代码工程会把配置集中放在一个头文件里,方便按场景切换。
/* lora_mesh_config.h */ #define LORA_FREQ_MHZ 470.0f /* 中国 Sub-GHz 免授权频段 */ #define LORA_SF 8 /* 扩频因子,7-12 可调 */ #define LORA_BW_KHZ 125 /* 带宽 */ #define LORA_CR 5 /* 编码率分母,4/5 */ #define LORA_POWER_DBM 20 /* 发射功率,长距场景可调到 22 */ #define LORA_SYNC_WORD 0x12 /* 同步字,全网必须一致 */ #define RADIO_TX_TIMEOUT_MS 3000 /* 单包发送超时 */ #define NET_TTL_DEFAULT 5 /* 最大跳数 */ #define NET_DEDUP_CACHE 64 /* 去重缓存容量 */ #define NET_NEIGHBOR_TIMEOUT 90000 /* 邻居超过 90s 未活跃则剔除 */配置项里有几个值得单独说明的参数。LORA_CR填 5 表示编码率 4/5,这个值适合信道质量尚可的场景;如果现场干扰明显,可以改成 8,即 4/8 编码率,抗干扰能力显著增强,但速率会掉近一半。RADIO_TX_TIMEOUT_MS的作用是防止信道被占或硬件异常时发送永远不返回,超时后驱动层要强制复位射频状态机。NET_NEIGHBOR_TIMEOUT决定一个邻居从「活跃」变成「失联」需要的静默时间——设太短,节点稍微休眠就被误判离线;设太长,坏邻居会一直占着路由表。
4.2 启动顺序和最小验证:先点对点,再加中继,顺序错了浪费时间
很多第一次做 LoRa mesh 的开发者上来就把三个模块全编好程序、同时上电,然后发现链路时通时断,根本分不清是射频参数问题还是路由逻辑问题。我一般会强制自己按三步走,每一步的验证目标都很明确。
第一步是双节点点对点验证。两块模块一个作发送端、一个作接收端,用最简单的单播模式测试。这一步只验证三件事:SPI 通信正常、频率和同步字一致、接收灵敏度在合理范围。如果这一步都不稳,不要碰 mesh。
# verify_point_to_point.py # 运行在接收端上位机,统计串口收到的消息序号情况 import serial ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1) expected = 1 lost = 0 out_of_order = 0 total = 0 while total < 1000: line = ser.readline().decode().strip() if not line.startswith('SEQ='): continue seq = int(line.split('=')[1]) total += 1 if seq == expected: expected += 1 elif seq > expected: lost += seq - expected expected = seq + 1 else: out_of_order += 1 print(f"total={total}, lost={lost}, out_of_order={out_of_order}")这段统计脚本的逻辑很直接:发送端每 2 秒发一条带递增序号的消息,接收端转交串口,脚本负责比对序号。SEQ=5到达后发现上一次期望是SEQ=3,说明 4 丢了;如果收到的序号小于期望值,说明发生了乱序——这在单跳链路里几乎不应该出现,一旦出现就要去查射频参数和天线。点对点测试丢包率控制在 1% 以内,才允许进入下一步。
第二步是加中继。三个节点排成一条线:A 距离 B 约 50 米,B 距离 C 约 50 米,A 发、C 收、B 转发。用一个简单的中继固件,B 不做任何路由决策,只做「收到就转发」。这一步验证的是转发时延和 TTL 机制是否正常工作。如果 C 收不到,优先检查 B 的中断处理:B 在收完一包到重新启动接收之间,是否有过长的阻塞逻辑。
第三步才是把完整的 mesh 路由固件烧进去,重复第二步的测试。三步都通过,这张 mesh 才算是「基本能跑」而不是「偶尔能通」。
4.3 直接影响组网成败的射频参数:按场景对照调整
不同的环境,LoRa mesh 需要不同的参数组合。室内短距多节点,优先考虑信道容量,用 SF7 加 BW250,让单位时间内能处理的帧数尽量多;野外远距离少节点,用 SF10 加 BW125,牺牲速率换覆盖;复杂地形如山谷、建筑密集区,把 CR 调到 4/8,抗干扰优先。还有两个容易被忽略的参数:一个是发射功率,20dBm 在开阔地够用,但遇到密集树林,提高 2dBm 可能比换更高增益天线更有效;另一个是同步字,它相当于 LoRa 世界里的「网络 ID」,同一区域有多组 mesh 系统在跑时,把两组系统的同步字设成不同值,可以避免它们互相串扰。
5. LoRa mesh 避坑记录:从「点对点通了」到「mesh 老是断」的排查清单
5.1 ACK 超时设太短:重传风暴把信道彻底打死
现象:多跳网络里加入第三个节点后,整体吞吐量急剧下降,甚至出现所有节点都发不出去数据的假死状态。排查方向从天线、供电到最后定位到协议层。
原因:每次转发都有处理时延和空中时延,开发者最初做点对点测试时 ACK 超时设为 200ms,用了三跳以后,A→B→C 的往返可能已经接近 600ms。发送端 200ms 没收到 ACK 就重传,重传又叠加在正常转发流量上,信道负载翻了好几倍,最终信道被重传包完全占满——典型的雪崩式崩溃。这是 LoRa mesh 里最常见、最隐蔽的坑之一,因为点对点测不出来,它只会在多跳拓扑下爆发。
解决:ACK 超时不能按一跳往返来设,要按「全网最大跳数往返时间加余量」来设。如果最大五跳,单跳 200ms,那么 ACK 超时至少 1200ms 起步。更稳妥的做法是尽量少用逐跳 ACK,只在应用层做端到端确认,中间转发不确认,因为 LoRa 信道承载不起每一跳都确认的开销。改成这个策略后,同样的三节点网络吞吐恢复正常,重传次数大幅下降。
5.2 同一消息被多个邻居重复转发,形成回路风暴
现象:节点 A 广播一条消息,节点 B 和 C 都收到并转发,同一路由区域内的无线信号出现密集碰撞,RSSI 读数高但丢包率反而上升。
原因:这是 mesh 泛洪最常见的病——缺失去重机制。B 和 C 各自转发,D 节点可能同时从 B、C 各收到一份副本,如果 D 没有「最近处理过这个消息」的缓存,它会把两条副本分别再转发出去,形成一个指数膨胀的转发树。在低速信道上这几乎是毁灭性的:每一份副本都要占用数百毫秒的空中时间。
解决:实现一个消息去重缓存,缓存条目用(src_id, seq)二元组作为键。收到一帧后先查缓存,命中直接丢弃,未命中则记录并继续处理。缓存的容量不用很大,64 到 128 条足够,但要注意按时间覆盖淘汰旧条目,否则缓存满了以后新消息反而全被丢。这个去重步骤必须放在网络层入口,任何业务逻辑之前,顺序不能变。
5.3 串口日志抢占射频中断:RSSI 出现「幽灵跳变」
现象:节点工作一段时间后,上报的 RSSI 值偶尔出现异常跳变,比如上一帧还是 -85dBm,下一帧变成 -110dBm,再下一帧又恢复正常。排查天线、更换模块后依旧如此。
原因:打开定位后发现,问题出在调试代码上。串口打印日志的阻塞时间在低主频单片机上可能长达几十毫秒,如果这期间 LoRa 芯片的中断触发,中断服务程序不能及时执行读取接收 FIFO,芯片内部的 FIFO 溢出,驱动层读到的 RSSI 值取自损坏的帧尾部,自然就出现了「幽灵跳变」。本质上不是射频问题,而是软件中断响应不及时。这是我被耗得最久的一个坑,因为现象完全指向射频硬件。
解决:中断服务函数里只做两件事——记录事件标志位、清中断,然后立刻返回。数据处理、打印、转发决策全部放到主循环或者任务调度里做。另外,串口打印字符串之前,可以暂时关闭射频接收中断,打印完再打开,用时间换健壮性。这样处理后 RSSI 曲线变得平稳,丢包率也显著下降。
5.4 同批次模块频偏不一致:近距离通信反而「听不见」
现象:两套 LoRa 模块摆放距离只有一米,天线都接好了,一个发一个收,却迟迟收不到数据。拿频谱仪看,发射端有信号,接收端灵敏度也在标称范围内,就是不同步——典型的频偏问题。
原因:LoRa 模块的射频中心频率由晶体振荡器决定,不同批次模块的晶体精度有差异。标称 30MHz 晶体的实际频率可能偏差上千 Hz,在窄带宽模式下这个偏移可能超出接收端的解调范围。SF 越大、BW 越窄,频偏影响越严重。如果模块没有做出厂校准,这个问题就是玄学级别——两台设备单独测都正常,放在一起反而对不上。
解决:先确认模块是否使用 TCXO 温补晶振,如果是普通 XO 晶振,优先在射频初始化里打开芯片自带的频偏补偿功能。其次在驱动层加入接收频偏估计和校正逻辑,常见做法是接收完一帧后读取芯片的频偏寄存器,将其反馈到发送频率上进行修正。如果固件里没有这个功能,就改成更宽容的参数组合:把 BW 从 125kHz 提到 250kHz,能容忍的频率偏差范围更大。
6. 验证 mesh 不是「看着通」:用 RSSI 曲线和端到端时延给网络下结论
跑通三节点中继只是完成了第一步。要让这套 mesh 真正可以交付,你需要三组可量化的数据来证明它稳定:端到端时延的分布、RSSI 的波动范围、以及路由切换后的收敛时间。
端到端时延建议用「请求-响应往返法」测量:A 节点发出带时间戳的探测包,C 节点收到后立刻回一个应答包,A 节点收到应答时记录总耗时,除以 2 得到单程端到端时延的近似值。注意这个方法假设往返路径对称,实际网络中两条方向的路由可能不同,所以还要在报告中同时记录最大偏差。连续测 50 次,如果时延标准差小于平均值的 30%,可以认为网络时延是稳定的,否则就说明路由在频繁切换。
RSSI 稳定性验证同样重要。配置若干个节点在固定位置,每秒上报一次邻居节点的平滑 RSSI,持续 24 小时,画出时间曲线。如果波动范围在 8dB 以内,说明链路质量稳定;超过 15dB 就要检查天线方向、周围是否有大型金属物体移动,或者该路径上是否存在同频干扰源。RSSI 数据不只是看一眼平均值,它的波动形态能反映很多无线环境问题——白天波动大、夜里平稳,多半是人体走动或者车辆移动造成的多径衰落。
路由收敛时间的测量更适合在一次真实测试中模拟节点断电:A→B→C 的链路里,拔掉 B 的电源,记录从拔电到 A 开始把数据转向通过 D 节点传送的耗时。这个指标直接决定了网络对故障的响应速度,如果超过 30 秒才能切换,很多实时性要求不高的场景还能接受,但任何超过 3 分钟的收敛都说明邻居发现周期太长,需要调短 beacon 发送间隔。
这套 mesh 方案做到最后,我最大的体会是:LoRa mesh 的难点从来不在射频硬件,而在「接受它的慢」。很多开发者习惯了 WiFi 和串口的思维定式,下意识地往协议里塞各种确认机制、握手流程、状态同步,结果就是把有限的信道塞爆。换个思路,把每一帧都当作珍贵资源来对待,去思考哪些控制消息真的必须发、哪些冗余真的有必要,网络自然就稳定了。希望这些踩过的坑和验证方法能帮你少走几段弯路,把更多时间花在真正有价值的数据传输上。
本文还有配套的精品资源,点击获取