ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

lora-mesh:用LoRa模块搭建多跳网状网络的源码方案解析

lora-mesh:用LoRa模块搭建多跳网状网络的源码方案解析

简介:针对LoRa模块网状网络探索的Arduino源码包,适合物联网开发者、嵌入式爱好者及希望基于TTGO T-Beams硬件实验路由算法的技术人员。项目以8个LILYGO TTGO T-Beams v0.7为节点,演示LoRa节点(LN)将随机数据载荷经LoRa发送至网关(GW),网关再通过WiFi连接Socket服务器,并最终以POST请求写入数据库,载荷含节点ID、跳数、GPS经纬度与时间,可用于集装箱等资产跟踪场景。压缩包共34个文件,以21个ino Arduino程序为主,另有7个md说明文档、2个socket_server服务端脚本及少量c/h源码,便于对照理解硬件端与服务器端交互逻辑,整体体积仅43KB。已有1389人学习。内容涵盖多个收发试验示例、GPS数据接收、网关双工通信等,可直接烧录至T-Beam进行测试,并在此基础上调整路由算法,减少从零搭建的复杂度。

1. lora-mesh 是什么:用 LoRa 模块搭多跳网状网络的源码方案

lora-mesh 这个标题直白地讲,就是用 LoRa 射频模块搭建多跳网状网络的源码工程。野外传感器布点、农场环境监测、仓库设备巡检这类场景里,节点之间往往隔着几公里又没有基站,LoRaWAN 的星型拓扑撑不起来,网状网络让每个节点既能发数据又能帮邻居转发,形成一张自组织的接力网络。做这套东西的开发者主要是嵌入式工程师和物联网方案商,拿到的是一份能编译、能烧录、能自己改路由策略的完整工程,不是黑匣子固件。这篇文章按硬件选型、编译烧录、路由机制、部署踩坑这条线,把 lora-mesh 从能跑到跑稳的关键参数和抗坑经验讲透。

2. 为什么 LoRa 网状网络是必要补位:点对点与 LoRaWAN 的边界

2.1 点对点与 LoRaWAN 的瓶颈:网关依赖和覆盖盲区

LoRa 本质上是 Chirp 扩频调制,工作在 sub-GHz 频段,穿透性和绕射能力比 2.4GHz 强不少。点对点模式最简单,两个模块配置相同的频点、扩频因子、带宽就能通信,但它只能解决固定两点之间的链路。节点分散在几平方公里的山坡、农田、厂区时,点对点要求每个节点都在对方射频覆盖半径内,遮挡一多就断链,而且中低频段在城市环境里受建筑遮挡影响非常明显,实际覆盖半径远低于理想值。

LoRaWAN 改用星型拓扑,所有节点只和网关通信,覆盖半径能到数公里,但代价是你必须部署网关并且给它供网供电。网关是单点,挂了整片网络就瘫了;节点之间的通信也要绕经网关再走服务器回来,两跳的空中时间加服务器处理时间,实时性大打折扣。最难受的是,很多临时布点场景根本没有条件放网关——野外考察、灾害应急、工地巡检,连电源都没有,更别提网关回传链路。

网状网络正好补这个位。节点既是终端又是中继,一个包可以一跳一跳地往前递,覆盖随节点密度自然扩展,单点故障只影响局部。把 LoRa 的低功耗长距离和中继机制结合,就是 lora-mesh 这类方案的核心价值。它特别适合三类人:第一类做农业和环境监测,节点撒在野外用电池供电;第二类做工业巡检,设备分布在厂房和仓库里不方便拉线;第三类做应急通信,需要快速组一张没有基础设施的临时网络。

2.2 路由策略选型:洪泛、AODV 与受限洪泛的取舍

网状网络的核心是路由。LoRa 的有效数据速率很低——以 SF12、125kHz 带宽为例,实际吞吐只有几百 bps——所以路由策略必须把信道占用压到最低。常见做法有三类。

洪泛最原始:每个节点收到包就广播出去,直到目标节点确认收到。实现代码只有十几行,但代价是网络里同时存在大量重复包,节点一多信道立刻拥塞,延迟和丢包同时恶化。我见过有人拿洪泛版本做 Demo,三个节点跑得挺欢,加到十几个节点以后每秒有一半时间信道是忙的,基本不可用。

AODV(按需距离矢量)是更工程化的选择。节点只在需要发包时才发路由请求(RREQ),收到路由回复(RREP)后建立路由表条目,后续数据按表转发。它比洪泛省信道,但路由发现过程有延迟,而且 LoRa 信道上 RREQ 本身也是广播,节点密集时同样会撞。折中方案是受限洪泛:洪泛时带上跳数上限(TTL)和包序号,节点只转发序号未见过且 TTL 未耗尽的数据包。实现比 AODV 简单,又比裸洪泛可控得多。

我在评估 lora-mesh 这类源码时,第一件事就是去源码里搜转发函数,看它有没有 TTL 判断和去重表。没有这两个机制的版本,基本只能当学习 Demo,不能直接拿去布点。有 TTL 和去重的受限洪泛,在几十个节点的规模下能稳定跑,是性价比最高的起点。真正要上生产,我会在受限洪泛之上再加一层按目的地址的路由表缓存,把高频通信的两节点路径固定下来,进一步省信道。

2.3 物理层参数与网状网络的匹配:扩频因子、带宽与占空比

同一个频段里,LoRa 的物理层参数决定了链路余量和信道容量,这是部署前必须定死的参数组。扩频因子(SF)从 7 到 12,SF 越高灵敏度越好、通信距离越远,但数据速率越低、空中时间越长;带宽(BW)选 125/250/500kHz,带宽越大速率越高但灵敏度越低。网状网络的节点距离不一,链路预算差异大,我一般建议 SF 用 9 或 10 做折中——SF9 在 125kHz 下的速率大约 1.76kbps,够跑短报文,灵敏度也够三四公里的视距链路。

占空比是另一个容易被忽略的约束。很多地区的 sub-GHz 频段有占空比法规限制(例如 1% 或 10%),这意味着节点每小时实际发射时间不能超过限制。网状网络里中继节点既要发自己的包又要转发别人的包,空中时间很容易超标。设计时要把单包长度压到最短——LoRa 调制下相同的载荷字节数,SF 越高空中时间越长,所以转发包尽量只带最小路由头加负载,不要带冗余字段。

还有一个和物理层直接相关的坑:整个网络所有节点必须用同一组频点、SF、带宽、编码率参数,否则物理层根本解不出对方的包。lora-mesh 源码里通常把这些参数收敛在头文件里的一个配置宏,部署前逐台核对。我在实际布点时会把这一组参数写在节点的标签纸上,防止现场刷错固件后玄学式排查。

3. 用 lora-mesh 源码搭最小网状网络:硬件与编译烧录

3.1 硬件选型:主控、LoRa 射频模块与天线

lora-mesh 这类方案的硬件组合,常见的是某款低功耗 MCU(比如 ESP32 或 STM32L 系列)外挂一颗 LoRa 射频芯片(常见型号如 SX1276/SX1278 模组),通过 SPI 接口通信。选择主控时主要看三点:是否支持低功耗休眠、SPI 速率是否够快、调试接口是否方便。ESP32 生态成熟、开发方便,但休眠电流偏大;STM32L 系列休眠电流低,适合电池供电但开发门槛略高。选型没有标准答案,关键是把 LoRa 芯片放在 SPI 外设资源充足、中断引脚分配合理的主控上。

射频部分,模组本身自带 PA 和匹配网络,重点在天线和供电。天线用 1/4 波长单极子或者弹簧天线都行,中频段 1/4 波长大约 17cm,长度不能随意截短。供电上,LoRa 发射瞬间电流能到 120mA 以上,如果用电池供电,必须并联大容量电容(我一般并 100µF 电解加 100nF 陶瓷)缓冲瞬时压降,否则发射瞬间电压跌落会导致射频输出功率不稳甚至主控复位。

我建议第一步用三块同样的板子搭最小验证环境:两块当普通节点,一块当中继,节点间距按实际覆盖半径的一半部署。硬件接好以后,先用最朴素的点对点收发测试确认射频链路本身没有问题,再上网状网络固件——这样出了问题才能把变量隔离开。

3.2 编译工程与烧录:最小三节点拓扑跑通

lora-mesh 源码工程一般按平台组织目录,MCU 相关的初始化、LoRa 驱动的 SPI 读写、网状网络协议栈和示例应用是分离的。编译前要做三件事:改频点参数、分配节点地址、确认串口调试输出引脚。以下是一个典型的配置修改流程,以常见构建方式为例:

# 1. 进入工程根目录,检查平台配置 make platform=esp32 clean # 2. 修改 LoRa 物理层参数(在 lora_config.h 中) # 频点默认 470.0MHz,SF10,带宽 125kHz,编码率 4/5 # 三个节点必须完全一致,否则物理层解调失败 make platform=esp32 menuconfig
/* lora_config.h 中需要核对的关键参数 */ #define LORA_FREQ_HZ 470000000UL /* 载波频率,单位 Hz */ #define LORA_SF 10 /* 扩频因子,7~12 */ #define LORA_BW 125000 /* 带宽,125/250/500 kHz */ #define LORA_CR 5 /* 编码率分母 4/5 */ #define NODE_ADDR 0x01 /* 节点自身地址,全网唯一 */ #define NETWORK_ID 0x5A /* 网络标识,防止异网串扰 */
# 3. 编译并烧录到串口设备 make platform=esp32 flash SERIAL=/dev/ttyUSB0 # 4. 在另外一个终端打开日志,观察节点启动和入网过程 python3 -m serial.tools.miniterm /dev/ttyUSB1 115200

这段配置里最关键的是三个节点的物理层参数必须一致,而NETWORK_ID用于软件层过滤——同一个信道上可能有别的 LoRa 设备在发数据,网络标识不匹配的帧会在协议栈里直接丢弃。NODE_ADDR是节点在网状网络里的逻辑地址,用来替代 MAC 层寻址,源码里根据它决定转发还是吸收。

烧录完三个节点,分别打开串口日志,让 A 节点周期性地向 C 节点发包,B 节点在中间做中继。日志里能看到 A 发送、B 收到并转发、C 收到并回复确认,这个三节点链路通了,说明编译参数和协议栈转发链路都正常。最小拓扑跑通后,再逐步增加节点数,观察路由表和信道占用率的变化。

4. 读懂 lora-mesh 源码里的路由与转发:帧格式与维护逻辑

4.1 数据帧格式:地址、跳数与包序号如何编码

网状网络协议栈的核心是数据帧设计。lora-mesh 这类方案里帧结构一般分成物理层头部和网络层头部两部分,LoRa 芯片的显式头部模式会自动处理 CRC 和长度字段,网络层需要自己定义的是源地址、目的地址、跳数上限(TTL)和包序号。一个典型的最小帧结构如下:

字段长度说明
DST_ADDR1 字节目的节点地址,0x00 表示广播
SRC_ADDR1 字节源节点地址
TTL1 字节剩余跳数,每转发一次减 1
SEQ2 字节包序号,用于去重和确认
PAYLOADN 字节应用数据,长度按实际需求裁剪
| DST(1B) | SRC(1B) | TTL(1B) | SEQ(2B) | PAYLOAD(NB) |

节点收到一个包后,处理顺序是:先查目的地址是不是自己,是则交给应用层并回 ACK;不是则查包序号是否在去重表里,在则丢弃,不在则把 TTL 减 1 后继续广播。这个「先查目的、再去重、再转发」的顺序是源码里最值得读的一段逻辑,很多改坏的路由都是把去重放在了目的地址判断之后,导致目标节点重复收到同一个包。

帧格式设计里最容易被忽略的是 ACK 机制。网状网络里 ACK 可以是逐跳的,也可以是端到端的。逐跳 ACK 在中继节点确认转发成功,速度快但每跳都要等;端到端 ACK 只在目的节点回复,省信道但丢包时无法定位是哪一跳丢的。lora-mesh 这类源码里通常默认逐跳 ACK,做低功耗场景时要谨慎——逐跳 ACK 意味着每个中继节点都要保持接收窗口,无法深度休眠。

4.2 路由表与转发条件:去重、TTL 和邻居发现

路由表不是必须的——纯洪泛方案没有路由表,但要上规模、要低功耗,路由表就不可或缺。lora-mesh 源码里常见的路由表结构是「目的地址 + 下一跳地址 + 跳数 + 过期时间」四元组,通过定期交换邻居信息来维护。邻居发现一般有两种做法:显式广播 Hello 包,或者被动嗅探数据包的源地址。被动方式不额外占信道,但节点静默时无法互相发现;显式 Hello 简单直接,适合网络拓扑变化不频繁的静态布点场景。

转发条件判断是协议栈性能的分水岭。我在源码里会重点看三个条件:TTL 是否大于 1、包序号是否在最近收到列表里、以及信号强度是否超过设定阈值。第三个条件在 LoRa 网状网络里特别重要——LoRa 的接收灵敏度极高,能解调很远的弱信号,但弱信号包往往误码率高,转发这样的包只会浪费信道。给转发加 RSSI 阈值(我常用 -110dBm 以下不转发),能明显降低端到端丢包率。

路由维护的另一个细节是过期时间。LoRa 链路质量随天气和遮挡变化明显,固定路由表如果不定期刷新,路径断了以后要等超时才重新发现,这段时间内所有包都会丢。我习惯把路由过期时间设在 30 秒左右,配合被动嗅探更新,既不会让路由表频繁抖动,又能在链路中断后较快恢复。这个参数在源码里通常是一个宏,按场景调即可,没有万能值。

源路由(Source Routing)是另一个值得了解的进阶思路:源节点在包头里写完整路径,中继节点只按路径转发。它的好处是中继节点无需维护路由表、可以更深度休眠,坏处是包头开销大、路径失效时源节点要重新计算。lora-mesh 这类源码如果同时实现了路由表和源路由两种模式,我会在静态拓扑下用源路由,在节点移动或拓扑频繁变化的场景下切回动态路由表。

5. 部署避坑:LoRa 网状网络常见问题与排查

5.1 现象:节点之间互收不到包

这是最多人踩的第一个坑。现象很直接:两个节点距离只有几百米,串口日志干干净净,一条接收记录都没有。原因按概率从高到低排列:频点、扩频因子、带宽、编码率四件套不一致;天线没有接好或 SMA 头松动;发射功率配置过低。解决方法是先在点对点模式下用最保守的参数(SF12、125kHz、最大功率)验证链路,确认物理层通以后再逐步收紧参数。我遇到过一次特别隐蔽的情况:两个板子的晶振频率标称一致但实际偏差超过了 LoRa 芯片的容忍范围,表现为近距离能通、远距离必丢,最后换了同一批次的板子才解决。

5.2 现象:加入中继节点后丢包率反而上升

三节点链路刚跑通时一切正常,增加到五个、八个节点后,端到端确认率直线下降。原因几乎总是广播风暴:没有做去重或 TTL 限制的洪泛协议,每个包在网络里被指数级复制,信道被无效转发占满,真正有用的数据反而发不出去。解决方法是先确认源码里有没有包序号去重表和 TTL 递减逻辑,没有就先加上;然后给转发加 RSSI 阈值,滤掉弱信号包;最后把转发优先级做成「只转发比自己序号新且 TTL 未耗尽」的包。这三个条件全部满足后,五个节点的网络丢包率通常能回到 5% 以下。

5.3 现象:低功耗模式下节点收不到中继包

为了省电把节点改成休眠-周期性唤醒后,自己发的包能出去,但别人经它转发的包大量丢失。原因是 LoRa 模块在休眠时接收窗口关闭,中继包到达时节点还在睡,没有机会接收。解决思路有两个方向:一是协调全网休眠调度,所有节点在相同的时间片醒来接收;二是采用「先听后说」的占空比策略,节点每次醒来先扫信道,发现载波就保持接收。低速率的 LoRa 信道上,第二种方案更实用,因为 LoRa 的空中时间长,一个 SF10 的包在空中要几百毫秒,节点醒来扫描到的概率并不低。我在一个低功耗项目里用这个方法,把中继成功率从 20% 拉到了 85% 以上。

5.4 现象:RSSI 和误码率读数异常但射频参数全部正确

节点之间能通信,但 RSSI 读数比预期低 20dB 以上,加长天线也没改善。这个坑我排查过一整天才定位:LoRa 模块的射频输出匹配网络对地平面非常敏感,模组底下的 PCB 如果铺铜不完整,或者天线接地焊盘虚焊,输出功率会被严重拉低。另一个常见原因是天线类型不匹配——低频频段的弹簧天线用在高频频段上,驻波比高得离谱,功率全反射回功放。解决方法是检查模组数据手册里的参考地平面设计,用网络分析仪或者至少用驻波比表测一下天线,别凭肉眼判断「天线接上了就行」。

6. 进阶:给 lora-mesh 加休眠调度与端到端压测

6.1 全网时间同步与休眠窗口调度

低功耗场景里,网状网络最大的敌人是「异步唤醒」。如果每个节点各自按随机周期醒来,中继包到达的时候中继节点大概率在睡觉。常见做法是让根节点周期性地广播信标帧,包含当前网络时间和下一个休眠窗口的起始时刻;普通节点收到信标后校准本地时钟,在约定的窗口统一醒来处理转发和接收。LoRa 模块的时钟漂移一般在几十 ppm 量级,一分钟误差几十微秒,信标周期设 30 秒就足够把全网时间误差控制在接收窗口宽度内。

我在实际项目里把休眠窗口设为 200ms、周期设为 2 秒,这样节点 90% 时间在睡,电池可以用几个月;代价是端到端延迟平均增加 1 秒左右。如果你的业务对延迟敏感,就缩短周期或者采用事件驱动唤醒——节点只在检测到信道载波时醒来。这个取舍没有标准答案,建议先用模拟流量跑一次,记录延迟分布再决定。

6.2 端到端压测:报文成功率与延迟分布的验证方法

上生产前,我用一个简单的脚本做端到端压测。方法是让源节点每 10 秒发一个带序号和本地时间戳的测试包,目的节点收到后回传确认,脚本统计确认率、平均延迟和 95 分位延迟。跑 24 小时,重点观察夜间和雨后的指标变化——LoRa 链路受天气和多径影响明显,白天的指标不能代表全天。以下是一个典型的结果记录表:

时段发送数确认数成功率平均延迟
白天8640812494.0%320ms
夜间8640790191.5%410ms
雨后8640709382.1%560ms

像「雨后成功率掉到 82%」这类结果,往往不是单纯的信号衰减,而是雨水改变了天线附近的介电常数,驻波比漂移导致实际辐射功率下降。遇到这种情况,我会优先检查天线馈点是否做了防水密封处理,而不是盲目加大发射功率——加大功率只会让电池更快耗尽,治标不治本。

最后说一个我自己的习惯:每次部署 lora-mesh 这类网状网络,我会在节点外壳上贴一张纸条,写清楚频点、SF、带宽、节点地址和固件版本。这个习惯救过我很多次——半年后回现场维护,设备换了人或换了电池,重新上电发现不通,看一下纸条就能排除参数问题,不用重新抓日志分析。做无线组网,最贵的永远是现场定位问题的时间,把参数文档化、把验证流程脚本化,比任何花哨的优化都值。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进