☰
基于LoRa的多节点追踪系统:RSSI测距与航向解算实战
2026/10/2 20:37:06 网站建设 项目流程

如果你最近搜“LoRa”,大概率会看到一长串“LoRa微调”“LoRa训练”的教程——那些说的是大模型领域里的 Low-Rank Adaptation,跟咱们这篇要聊的通信 LoRa 完全是两个世界。我这篇要说的 LoRa,全称 Long Range,是做远距离低功耗无线通信的老技术。这个项目叫 TrackPulse,是一套基于 LoRa 的多节点追踪系统,核心功能就两件事:在多节点组网下把目标的坐标实时解算出来,同时上报目标的航向(Heading)。

它解决的核心痛点是:在没有卫星信号覆盖、或者 GPS 精度严重下降的场合,你依然能知道目标在哪、朝着哪个方向动。项目整体成本不高,一套下来用不了几百块钱,适合做户外设备追踪、人员区域定位、车辆进出管理,甚至给小型无人车做航向辅助。我会把从硬件选型到定位算法、再到实测中踩过的坑,全部拆开讲清楚,代码也是可以直接拿走去用的程度。

1. 项目拆解:先搞懂“多节点 LoRa 雷达追踪”到底在做什么

1.1 这个项目解决什么问题

GPS 是大家最熟悉的定位方案,但它在室内、地下停车场、密林、厂区高架下方、山区峡谷这些场景里基本等于报废。即便是半开放环境,GPS 的漂移也能让你怀疑人生。TrackPulse 想做的,就是一套不依赖卫星、可以自己组网覆盖一片区域的追踪系统,标签(Tag)挂在人或设备上,基站(Anchor)布置在区域周围,网关(Gateway)负责汇总数据并解算位置。

我最初做这个项目,是因为要给一堆野外作业的设备做“电子围栏”和轨迹回放。那地方没有网络信号,布设 WiFi 或蓝牙信标也不现实——覆盖范围太小,要布几十个点。后来发现 LoRa 的覆盖能力非常夸张,几百米的距离用几 dBm 的发射功率就能打通,而且节点本身极其省电,一节 18650 电池撑几个月没问题。

1.2 为什么选 LoRa 而不是 GPS、蓝牙、WiFi 或 UWB

这是我在项目设计阶段反复对比才定的方向。先给一张我当时的选型对比表:

方案覆盖范围定位精度功耗成本主要短板
GPS全球3~10m中低室内/遮挡失效
蓝牙 BLE10~50m2~5m(指纹)极低低覆盖太小,需密集部署
WiFi50~100m3~10m高中依赖已有基础设施
UWB10~100m0.1~0.5m中高很高成本高、覆盖有限
LoRa300m~5km2~10m(RSSI)极低低带宽窄、测距精度有限

从表格能看出来,LoRa 并不是精度最高的,但它在“广覆盖 + 低功耗 + 低成本 + 中等精度”这个象限里是综合最优解。对于追踪场景,2~10 米的误差可以接受,毕竟你不需要知道设备在哪个厘米级坐标,只需要知道它大致在哪个区域、朝哪个方向移动。

1.3 “多节点”和“Heading”分别解决什么

单基站是定不了位的。一个基站只能告诉你“目标在我附近”,最多通过 RSSI 估出一个粗糙距离,方向角基本无从谈起。所以这里必须引入多节点:至少三个基站同时对同一个标签的信号做测量,才能用几何关系解出目标坐标。我在实际项目里用了四个基站,四个点的冗余度更好,某一个基站被遮挡时系统还能继续工作,这就是 Radar Tracker 里“Radar”的含义——多个节点像雷达阵列一样周期性扫描区域里的所有标签,形成一个动态态势图。

Heading (航向)是个容易被忽略但很关键的信息。纯坐标轨迹是离散的点,两个点之间目标到底怎么走、面朝哪个方向,坐标是看不出来的。加上航向后,你可以做三件事:一是判断静止目标的面朝方向(比如设备检修时朝向哪边);二是在低速移动时补足轨迹方向(低速时坐标点几乎重叠,只有航向能提供运动趋势);三是做态势预判,知道目标接下来大概率往哪个方向去。这些信息对车辆管理、人员巡检、机器人回航都很有价值。

2. 核心硬件与测距原理:LoRa 定位的命根子

2.1 LoRa 模块怎么选:芯片、频段、参数

LoRa 模块的核心是 Semtech 家的芯片,现在市面上主流的有三个方向。SX1276/SX1278 是老将,成熟便宜稳定,国内 433MHz 模块基本都基于它;SX1262 是新一代,支持频段更多、灵敏度略好、功耗也低一点,价格稍高。我自己用的是基于 SX1278 的现成模块,十几块钱一片,项目原型阶段完全不心疼。

选频段有讲究。国内民用的微功率设备常用 470~510MHz 这个范围,而 433MHz 在很多国家属于 ISM 免许可频段,开发调试非常方便。但要注意,不同频段要配不同长度的天线,433MHz 的 1/4 波长单极天线大概是 16.4cm,选模块的时候一定把天线方案一起定了。

LoRa 的调制参数是决定“通信距离”和“数据率”并直接影响定位效果的关键,给一张我常用的参数表:

参数可选值对定位的影响
扩频因子 SFSF7~SF12SF 越大灵敏度越高、airtime 越长,RSSI 值更稳
带宽 BW125/250/500 kHzBW 越大速率越高,但灵敏度下降
编码率 CR4/5~4/8影响抗干扰,对 RSSI 影响较小
发射功率2~22 dBm功率越高信噪比越好,但耗电大

我在定位场景里固定用 SF9 + BW125kHz,这个组合 airtime 不算太长,RSSI 读数也稳定。SF7 虽然速率快,但灵敏度低,远距离时 RSSI 波动会明显变大,对测距不利。

2.2 RSSI 测距的数学模型与标定方法

LoRa 定位最常见也最容易上手的测距手段是 RSSI(接收信号强度指示)。基本原理是无线信号在空间传播时会衰减,距离越远、衰减越大。用对数距离路径损耗模型来描述:

RSSI(dBm) = RSSI_0 - 10 * n * log10(d / d_0)

其中RSSI_0是在参考距离d_0(通常取 1 米)处的接收强度,n是路径损耗指数,环境不同 n 差异很大:开阔场地 n 约等于 2;有障碍物和地面反射的环境 n 可能到 3~4。

这个模型告诉我们一个残酷的事实:RSSI 测距的精度上限取决于你能不能把n和RSSI_0标定准确。我项目的做法是:先在测试场地拉一根卷尺,从基站 1 米开始,每隔 10 米记录一次 RSSI,每个点采 50 个样本取均值,然后用最小二乘拟合出公式里的两个参数。拟合完之后,还要在另一个方向重新验证一遍,因为天线方向图会让不同角度的 RSSI 不一致。

需要特别小心的是:RSSI 在短距离(10 米以内)衰减非常快,在远距离(超过 500 米)衰减又变得非常平缓。这意味着短距离时 RSSI 差几个 dB 就会导致距离估计偏差巨大,远距离时 RSSI 稍微抖动一下又是十几米的误差。所以 RSSI 定位适合的场景是“区域级追踪”,别指望它做厘米级测量。

2.3 航向检测的三条路线对比

项目名称里带 Heading,这个部分我花了不少功夫调研。航向检测有三条路线:

路线 A:目标节点自带磁力计 + 加速度计。用电子罗盘直接测地磁方向,配合加速度计做倾角补偿,实时解算出节点的朝向。这是最直观的方案,成本低、实现简单,尤其适合标签集成在小型 PCB 上的场景。缺点是磁力计受周围铁磁物质干扰大,电机、铁皮、钢筋旁边必须现场重新校准。

路线 B:用连续两个定位点反推航向。这个方法不需要额外传感器,直接算atan2(ΔE, ΔN)得到运动方向角。但问题也很明显:目标一动不动或慢速移动时,坐标本身的误差会让航向角像抽风一样乱跳,必须配合卡尔曼滤波才有可用性。我把它作为辅助交叉验证,不单独依赖。

路线 C:基站端做双天线载波相位测向。这个方案理论上精度最高,但需要相干接收两路射频信号来提取相位差,对前端电路和同步要求极其苛刻,个人项目基本不现实,我调研完就放弃了。

TrackPulse 最终用的是路线 A 为主、路线 B 为辅:标签上的 MPU6050 做姿态参考,QMC5883L 磁力计输出航向角,经过 Madgwick 滤波融合后通过 LoRa 随 beacon 一起上报。这样即使标签静止,系统也知道它面朝哪个方向。

3. 整体架构与固件实现:从零跑通一版可用的系统

3.1 系统组成与通信协议设计

整套系统分为三类节点。Target Tag 是目标节点,挂在被追踪的人或设备上;Anchor 是基站节点,固定在区域边界,负责监听标签的信号;Gateway 是网关节点,汇总所有基站的数据做定位解算,同时提供串口或 WiFi 给上位机显示。

通信协议我设计得很简单,避免掉进复杂协议的坑。标签周期性广播一个 Beacon 帧,帧里面只放有效信息:标签 ID、帧序号、电量、磁力计航向、IMU 姿态四元数。基站收到后不回复,而是把 RSSI、SNR、接收时间一并打包上报给网关。网关拿到同一标签在多个基站上的测量结果后,统一进入定位算法。

Beacon 帧我用了固定格式:

字段长度说明
Preamble8 byte物理层自动添加
Tag ID2 byte标签唯一编号
Frame ID1 byte序列号,防重放/丢包统计
Battery1 byte电量百分比
Heading2 byte0~3599,精度 0.1 度
CRC2 byte帧校验

广播周期我设成 500ms 一次。这个频率能让轨迹看起来比较连贯,airtime 也完全扛得住。一个 LoRa 信道里如果标签数量多,就让每个标签在随机时隙内广播,碰撞的概率通过载波监听进一步降低。

3.2 目标节点固件:低频 beacon 与航向采集

目标节点我用的是 ESP32 + SX1278,虽然 ESP32 比 STM32 耗电大一点,但它开发效率高,原型阶段非常合适。为了省电,标签平时进 Deep Sleep,只靠定时器每 500ms 唤醒一次,做三件事:读 IMU 和磁力计、组装 Beacon 帧、发送。

#include <SPI.h> #include <RH_RF95.h> RH_RF95 rf95(SS_PIN, RESET_PIN); MPU6050 mpu; QMC5883L mag; float headingDeg = 0.0f; uint8_t tagId = 0x01; uint16_t frameId = 0; void setup() { Wire.begin(); mpu.initialize(); mpu.dmpInitialize(); mag.init(); SPI.begin(); rf95.init(); rf95.setFrequency(433.0); rf95.setTxPower(20); rf95.setModemConfig(RH_RF95::Bw125Cr45Sf128); // SF9: 使用 Bw125Cr45Sf128 或对应配置 } void loop() { // 读取 DMP 解算后的姿态四元数 mpu.dmpGetQuaternion(&q, mpu.getFIFOBytes()); // 读取磁力计并做倾角补偿 mag.readRaw(&mx, &my, &mz); headingDeg = computeTiltCompensatedHeading(mx, my, mz, q); // 组装 Beacon 帧 uint8_t buf[8]; buf[0] = tagId; buf[1] = frameId++; buf[2] = batteryPercent(); buf[3] = (uint8_t)(headingDeg / 0.1f); buf[4] = (uint8_t)((int)headingDeg >> 8); buf[5] = buf[4] ^ buf[3] ^ buf[2] ^ buf[1] ^ buf[0]; // 简单校验 rf95.send(buf, sizeof(buf)); rf95.waitPacketSent(); // 进入深睡眠,定时唤醒 esp_sleep_enable_timer_wakeup(500 * 1000); // 500ms esp_deep_sleep_start(); }

这段代码里最容易出问题的是磁力计的倾角补偿。磁力计在水平放置时可以直接输出航向,但标签戴在人或车上时通常是有倾斜角度的,必须把加速度计/陀螺仪解算出的姿态用于把磁场分量投影回水平面,否则航向角会随着标签倾斜产生严重偏差。这是我第一版固件最大的坑,后面在实测部分细说。

3.3 基站与网关固件:RSSI 采集与聚合

基站节点不主动发数据,一直在监听模式。LoRa 芯片有 CAD(Channel Activity Detection)机制,可以先以极低功耗检测空中有没有前导码,有信号才切到全速接收模式。这能省不少电,基站如果用电池供电的话这一步必须做。

#include <SPI.h> #include <RH_RF95.h> RH_RF95 rf95(SS_PIN, RESET_PIN); uint8_t anchorId = 0x03; void setup() { SPI.begin(); rf95.init(); rf95.setFrequency(433.0); rf95.setModemConfig(RH_RF95::Bw125Cr45Sf128); rf95.setModeRx(); } void loop() { if (rf95.available()) { uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; uint8_t len = sizeof(buf); if (rf95.recv(buf, &len)) { // 提取关键信息 int rssi = rf95.lastRssi(); // dBm,带符号 int snr = rf95.lastSNR(); // 上报给网关:anchorId + 标签ID + RSSI + SNR buildAndSendReport(anchorId, buf[0], rssi, snr); } } // 如果短时间内无信号,切回 CAD 低功耗模式 }

网关把所有基站的 10 次最新 RSSI 数据缓存在环形缓冲区里,解算时取滑动平均值,避免单次抖动把坐标带飞。上报数据的时间戳用网关本地时间,所有基站都要定期和网关做一次时间校正——RSSI 方案虽然不依赖高精度时间同步,但数据对齐还是需要的。

3.4 定位解算代码:从最小二乘到加权质心

收到 3 个以上基站的数据后,定位解算可以走两步。第一步用加权质心法快速出一个初始坐标,第二步用最小二乘法迭代优化。加权质心的思路是:RSSI 越强的基站说明目标离它越近,权重应该越大。把权重设成1/d^2,把所有基站坐标加权平均即得初始位置。

最小二乘更严谨。已知第 i 个基站坐标(xi, yi),估计目标(x, y),对应的距离观测值是di。把第一个基站的方程减去其余基站,得到线性方程组A·p = b,其中 p 是目标坐标向量:

(x - x1)^2 + (y - y1)^2 = d1^2 (x - xi)^2 + (y - yi)^2 = di^2 (i = 2..n) 相减得到: 2*(xi - x1)*x + 2*(yi - y1)*y = d1^2 - di^2 + xi^2 + yi^2 - x1^2 - y1^2

最小二乘解就是p = (A^T·A)^(-1)·A^T·b。实际项目中我习惯做加权最小二乘,用信号质量(SNR)作为权重,质量差的基站少信它一点。代码并不复杂:

struct Anchor { double x, y; double d; double w; }; bool solveLeastSquares(std::vector<Anchor>& anchors, double& outX, double& outY) { size_t n = anchors.size(); if (n < 3) return false; // 用第一个基站作为参考,构造线性方程组 double a00 = 0, a01 = 0, a10 = 0, a11 = 0; double b0 = 0, b1 = 0; for (size_t i = 1; i < n; i++) { double dx = anchors[i].x - anchors[0].x; double dy = anchors[i].y - anchors[0].y; double w = anchors[i].w; double right = squares(anchors[0].d) - squares(anchors[i].d) + squares(anchors[i].x) - squares(anchors[0].x) + squares(anchors[i].y) - squares(anchors[0].y); // 方程左右两端都除以 2 right *= 2.0; a00 += w * dx * dx; a01 += w * dx * dy; a10 += w * dx * dy; a11 += w * dy * dy; b0 += w * dx * right; b1 += w * dy * right; } double det = a00 * a11 - a01 * a10; if (fabs(det) < 1e-9) return false; // 基站共线或几乎共线 outX = (a11 * b0 - a01 * b1) / det; outY = (a00 * b1 - a10 * b0) / det; return true; }

注意最小二乘容易受异常值影响,如果某个基站的 RSSI 被干扰导致距离观测值严重偏大或偏小,会把整个解拉偏。我的做法是:算完第一次定位后,计算每个基站的距离残差,把残差大于 3 倍中位数的基站剔除,然后重新解算一次。

4. 实测数据与性能调优:数字不会骗人

4.1 实测场景与误差分布

我在一片开阔场地做了为期两天的实测,场地大概 300m × 200m,四个基站放在四角,标签沿预设直线以 1m/s 的速度行走,用 RTK GPS 打出的坐标当真值对比。

选几个典型位置的对比数据:

真实坐标 (m)系统解算坐标 (m)误差 (m)
(40, 30)(42.3, 31.8)2.9
(120, 90)(118.5, 92.2)2.8
(200, 130)(204.1, 127.6)4.7
(260, 60)(263.9, 64.2)5.6
(150, 160)(146.7, 158.9)3.8

整体看,空旷场地 RSSI 定位的误差均值在 3~5 米,边缘区域的误差明显大于中心区域。这个精度对“知道设备在哪个区域”“判断是否越界”完全够用,但要画精细轨迹就不太行。航向角在静态测试时误差能控制在 3~5 度,动态跟随时因为有姿态融合的滞后,误差会到 8 度左右。

4.2 布站策略与几何精度因子

实测中我深刻体会到:基站摆放位置比算法本身影响更大。定位误差会被基站与目标的几何关系放大,这个放大倍数叫 GDOP(Geometric Dilution of Precision)。简单的理解是:如果三个基站和目标基本在一条直线上,三圆交会区域会被拉成一个很长的椭圆,纵向误差极大;如果基站把目标围在中间,交会区域是圆形的,定位精度就高。

我的布站经验是三条:基站离目标区域不要太远,目标尽量待在各基站围成的多边形内部;四个基站不要放在同一边,最好东南西北各一个;天线全部垂直极化,离地高度 1.5~2 米,避免草坪和地面反射的干扰。

4.3 RSSI 波动与滤波优化

RSSI 的波动比我想象中大。同一位置、同一姿态,连续收到的 RSSI 最大能差到 6~8 dB,换算成距离误差相当可观。人体遮挡是最大的变量:手挡住天线正面,RSSI 能掉 5 dB 以上;人站在标签和基站之间,掉得更多。

后来我把滤波策略改成:网关侧对每个基站的 RSSI 维护一个长度为 10 的滑动窗口,先去掉最高和最低两个值,再取平均。这个“去极值平均”能很好地把突发干扰压掉。如果还想更平滑,可以加一阶低通滤波:

rssi_filtered = alpha * rssi_raw + (1 - alpha) * rssi_filtered;

alpha 取 0.3~0.4 比较合适,太小响应慢,太大又滤不干净。注意滤波会让位置更新变“钝”,标签快速跑动时轨迹会有点拖影,但追踪类的应用里这点影响可以接受。

5. 常见问题与避坑清单:这些坑我替你踩过了

5.1 十个典型问题速查表

问题现象可能原因排查与解决
某个基站一直收不到 Beacon频点/扩频因子不匹配核对频率、SF、BW、CR 设置,检查天线接头
RSSI 值反复横跳人体遮挡、天线方向变化、多径固定测试姿态,用去极值平均,提高天线高度
定位结果跑到基站外面某基站 RSSI 被干扰导致距离偏大按 SNR 加权,设置 SNR 阈值剔除弱信号
航向角乱跳周围铁磁物质干扰磁力计现场重新做椭圆校准,远离电机和铁皮
航向角佩戴倾斜时误差大缺少倾角补偿确认已把磁力计投影到水平面再算航向
网关回传延迟高SF 过大、airtime 过长降低 SF 或增大 BW,缩短 Beacon 负载
标签电池撑不住唤醒太频繁或未用 CAD 省电广播周期拉长到 1s,用 CAD 低功耗监听
基站天线贴着金属支架天线被金属物体拉偏方向图天线远离金属至少 30cm,用塑料或木质支架
两个标签同时广播会碰撞随机时隙重叠给每个标签分配固定偏移时隙,或启用载波监听
网关解算偶尔卡死缓存溢出或浮点运算野值加异常值剔除,环形缓冲越界保护

5.2 我最想强调的三个坑

第一个是磁力计校准。QMC5883L 这类芯片出厂有一定精度的硬铁校准,但焊在 PCB 上之后,板内的走线、周边的电阻电容都会引入偏差。上手第一件事就是做“8 字校准”:拿着标签在空中画椭圆,采集所有方向的磁场强度,然后做椭圆拟合补偿。不校准的话,航向角能偏 30 度以上,根本不是滤波能救回来的。

第二个是 LoRa 芯片的电源纹波。这是我调了很久才发现的:标签上如果同时运行 OLED 屏幕和 LoRa 发送,屏幕刷新瞬间会把电源电压拉低,LoRa 芯片的接收灵敏度随之劣化,RSSI 读数直接失真。解决方法是给 LoRa 模块单独加一个 LC 滤波,或者用独立 LDO 供电,数字部分和射频部分分开。

第三个是 RSSI 测距的短距离失效问题。10 米以内 RSSI 变化太快,距离估算的方差极大,基站离标签太近时反而会把定位结果严重带偏。我的处理方法是:给距离观测值设下限,小于 5 米一律按 5 米算,同时降低近距基站的权重。这是很多人忽略的细节,但实测效果非常明显。

最后再说两句个人体会。这套系统真正有价值的地方,不在于它能达到雷达那样厘米级的探测精度,而在于用极低的硬件成本、极低的功耗,撑起一个区域内的持续性态势感知。我后续还想做两个扩展方向:把 RSSI 指纹库和地图信息结合起来做约束定位,以及在标签端跑一个轻量级卡尔曼滤波,把航向、速度、位置做闭环融合。如果你想搭类似的系统,建议先按这篇的方案跑通一版,再逐步往里面加自己的创新点。还有一个小提醒:搜 LoRa 资料时,如果看到的是“LoRa 微调”“LoRa 训练”,那多半是大模型领域的 Low-Rank Adaptation,跟射频 LoRa 是两条完全不同的路,千万别买错模块。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询