1. TrackPulse到底是个什么东西
1.1 这个项目是被一次翻船逼出来的
TrackPulse这名字起得有点膨胀,但它做的事情确实和我之前做过的那套GPS追踪器完全不一样:一套Multi-Node的LoRa网络,一个中心站像雷达一样周期性扫描所有节点,每个目标除了位置,还带一个可信的Heading航向。
起因是帮朋友的帆船俱乐部做训练轨迹回放,当时市面上的方案试了一圈。GPS模块加4G模块的方案最成熟,但船的器材舱里多塞一个蜂窝模块,维护起来很烦,而且海上很多区域根本没信号,船队一出港就变成一串离线点。蓝牙手环和手机方案更不用提,蓝牙距离限制摆在那,手机在海风里裸奔两天基本就报废了。最要命的是功耗——我要的是能连续跑一周、不需要频繁拆装充电的跟踪器,现有消费级产品几乎没有这个选项。
所以干脆自己动手,目标是做出一套不依赖基站、不需要手机、节点电池能撑七天以上的多节点跟踪系统。核心链路选LoRa而不是WiFi或者蓝牙,原因后面细说。当时定的性能指标是:单个节点和中心站的直线通信距离至少一公里,二十个节点同时在线,中心站屏幕上能看到每个点的运动方向箭头,刷新周期不超过五秒。
项目做到第三版才真正跑顺。中间踩了无数坑,从天线极化到SF参数不一致,从节点永久离线到RSSI雨天漂移,每一步都有实际数据支撑。这篇文章把整个系统从选型、组网、航向解算到功耗优化完整拆一遍,给想做类似多节点定位跟踪的朋友一个直接能抄的底稿。
1.2 为什么不直接用现成的GPS加蜂窝方案
有人会问,现在追踪器方案这么多,为什么非要自己造轮子。我把当时对比的几个方案列一下,每个都有明确短板:
| 方案 | 覆盖范围 | 功耗水平 | 痛点 |
|---|---|---|---|
| GPS + 4G | 依赖公网 | 峰值极高 | 蜂窝模块发射功耗高,电池撑不住长期部署;野外区域无信号 |
| WiFi 定位 | 百米级 | 中等 | 覆盖面太窄,适合室内不适合野外运动跟踪 |
| BLE 信标 | 几十米 | 低 | 距离太短,不做网关中继的话没法用 |
| UWB | 百米级 | 中等 | 测距精度高但容量有限,价格也高,做广域粗跟踪是浪费 |
| LoRa | 公里级 | 极低 | 数据速率低,但定位跟踪场景根本不需要高带宽 |
从表格里能看出来,LoRa是唯一在"远距离"和"低功耗"这两个硬指标上同时满足的方案。4G方案看起来省事,实际部署起来要办卡、要担心流量消耗、要处理信号盲区,运维成本远超硬件成本本身。
1.3 系统组成拆解
TrackPulse分成两类设备:
中心站(Hub)负责轮询所有节点、解算位置和航向、对外提供可视化数据。我用的主控是ESP32,搭配两个SX1262 LoRa模块做双通道接收,一个通道负责正常的轮询和数据接收,另一个通道专门用来做相位差测向。为什么需要双通道,第四章会详细讲。
Tag节点是每个被跟踪目标佩戴的端机。主控选了STM32L4系列,优势是睡眠电流低,外设丰富。LoRa模块用一个SX1262,板载一颗九轴IMU,提供磁力计航向和角速度。电池用单节18650,看安装位置灵活调整容量,标称3400mAh。
通信拓扑是星型结构:中心站发轮询帧,Tag收到后按指定顺序回传数据包。这套结构和传统雷达的工作方式确实很像——中心站对外扫一圈,各个目标对扫描做出回应,中心站根据回波的信号特征反算方位和距离。LoRa本身没有测距能力,但RSSI和相位差给了我们足够的信息来做这件事。
2. LoRa链路:为什么是SF9而不是SF12
2.1 LoRa关键参数和选型逻辑
LoRa有四个核心参数,扩频因子(SF)、信号带宽(BW)、编码率(CR)和发射功率。它们互相制约,决定了通信距离、抗干扰能力和网络容量。
SF越高,接收灵敏度越好,通信距离越远,但一个数据包在空中的时间也越长。SF12的灵敏度比SF7大约高8个dB,代价是同样的数据量要多占用十几倍的时间。BW正好相反,带宽越窄灵敏度越好,但数据率也越低。CR是前向纠错的冗余度,CR4/8比CR4/5抗干扰强,但也更占时间。
我当时拿SX1262的手册做了一张表,列出不同SF在125kHz带宽下的典型灵敏度和单包时间(按20字节payload估算):
| 扩频因子 | 典型灵敏度 (dBm) | 20字节包空中时间 | 相对容量 |
|---|---|---|---|
| SF7 | -123 | 约46ms | 高 |
| SF8 | -126 | 约82ms | 较高 |
| SF9 | -129 | 约147ms | 中 |
| SF10 | -132 | 约253ms | 较低 |
| SF11 | -134 | 约443ms | 低 |
| SF12 | -137 | 约790ms | 最低 |
从表里能直观看到,SF12虽然灵敏度最高,但一个包就要将近800ms。二十个节点轮流上报,光通信时间就占了16秒,这还没算间隔和重传,刷新率完全没法看。所以我最后选了SF9作为系统默认参数,接收灵敏度-129dBm,单包时间大约150ms,二十个节点轮询一圈三个多秒就能搞定。只有在低功耗远距离模式或者极端天气下,才动态切换到SF11/SF12。
这个选择本质上是在距离、容量和实时性之间找平衡。这项目实测下来,SF9加125kHz带宽在城市环境下能跑600米左右,开阔地能到1.5公里以上,配合适当的轮询策略完全够用。
2.2 链路预算:我算了三遍才敢装机
决定用SF9之后,我还是不太放心,老老实实把链路预算推了一遍。这个过程对任何基于LoRa的项目都有参考价值。
链路预算的计算非常简单:
链路预算 = 发射功率 + 发射天线增益 + 接收天线增益 - 接收灵敏度
发射功率按国内微功率设备的常用限值,我压在了+17dBm附近,也就是约50mW。天线增益方面,Tag端是小型弹簧天线,按0dBi算;中心站用了一根吸盘式玻璃钢天线,增益约2dBi。接收灵敏度取SF9/BW125下的-129dBm。
链路预算 = 17 + 0 + 2 - (-129) = 148dB
这意味着从发射端到接收端,总信号衰减只要不超过148dB,通信就能成立。
再算自由空间路径损耗。频率取490MHz,距离d的单位是公里,公式是:
L = 20 × log10(d) + 20 × log10(f) + 32.44
把距离1公里代进去,L = 0 + 20 × log10(0.49) + 32.44?不对,我刚才写错了,重新来。正确计算如下:
L = 20 × log10(1) + 20 × log10(490) + 32.44 = 0 + 53.8 + 32.44 = 86.24dB
1公里自由空间损耗只有86dB,看起来离148dB的预算还很远。但自由空间是理想条件,实际情况要考虑人体遮挡(通常有10到20dB的穿透损耗)、地面反射、树叶吸收、多径衰落,这些加起来额外吃掉30到40dB。实测下来,SF9在城市环境的丢包率拐点出现在600到800米左右,和链路预算的推论基本吻合。
所以装机的时候我把设计指标定在一公里,留了足够裕量应对雨天和金属遮挡。节点离中心站超过一公里后,中心站会主动给这个节点发指令,让它降速到SF11来换距离。这套机制后面再细说。
2.3 为什么不直接用LoRaWAN
既然用了LoRa,很多人第一反应是直接用LoRaWAN协议栈,省得自己写协议。我也试过,但很快就放弃了。
LoRaWAN的设计目标是接入公网服务器,节点通过OTAA或者ABP流程入网,数据上报到网络服务器,再由应用服务器处理。这套架构适合"节点向云端上报数据"的场景,比如水表、电表这类传感器。但TrackPulse需要的是中心站主动发起轮询,要求中心站能精确控制每个节点的收发时序,而且要在亚秒级内完成调度。LoRaWAN的Class A模式只能在节点上报之后打开接收窗口,中心站没法随时抓到节点;Class C模式倒是能随时下行,但接收功耗太高,Tag端电池根本扛不住。
另外LoRaWAN的入网流程对场景也很不合适。每个节点要烧录DevEUI、AppEUI、AppKey,要处理JoinRequest和JoinAccept的往返流程。在野外临时加一个节点,还要先跑到中心站旁边完成入网,这体验太灾难了。私有协议里我直接在节点初始化时预置网络ID和密钥,上电就是在线状态,省掉一切摩擦。
还有一个更实际的原因:LoRaWAN的网络服务器通常部署在云上,我要是依赖它,离线部署能力就没了。帆船船队在海上的时候根本没有互联网,中心站必须本地独立完成全部解算和展示。走LoRaWAN的话,哪怕自建服务器,也要在船上的小主机跑一堆容器,复杂度完全不必要。直接点对点LoRa Radio加私有调度协议,我能在单片机上把整个系统跑完,这才是最可靠的形态。
3. 多节点组网:从轮询到heartbeat
3.1 星型拓扑与时隙划分
TrackPulse的通信拓扑是典型的星型,中心站是所有通信的中心,Tag节点只和中心站对话,节点之间不直接通信。选择星型而不是Mesh,原因很朴素:Mesh需要的转发逻辑会显著增加节点固件复杂度和功耗,而LoRa本身覆盖能力强,中心站架在高处,一公里内所有节点直接可达,根本不需要多跳。
多节点接入的调度方式,我用的是中心站主动轮询,比CSMA类协议更适合这个场景。中心站广播一个轮询帧,带上目标节点地址,所有Tag都收得到,但只有地址匹配的那个节点才会在当前时隙内回复。这样时间片在轮询帧广播的时候就已经隐含划分好了,天然规避碰撞问题。
轮询周期需要仔细设计。每个节点回包后中心站要留一小段保护时间防止时钟漂移,我设了20ms的结束时隙。20个节点、每个节点占用大约170ms,一轮完整轮询时间接近4秒,这是默认档位。如果节点数增加到40个,轮询周期会超过7秒,航向更新的实时性就要打折。这种架构最适合中等规模、数量在几十个以内的跟踪场景,再往上就得做频率分区或者分簇处理了。
3.2 CAD与重传:LoRa信道也不是完全不打架
LoRa虽然有很强的抗干扰能力,但两个Tag如果同时回包,中心站不见得能解出任何一个。所以在轮询帧之后,正常回包之前,节点还会先做一次信道活性检测(CAD),确认信道里没有其他信号才能发包。
CAD的原理是SX1262在极短时间内扫描当前频段,判断是否检测到LoRa前导码。如果检测到,说明信道正忙,节点就退避一个随机时间再试。实测下来CAD能让碰撞概率明显下降,但代价是每个节点回包前多花大约几十毫秒的检测时间。轮询场景本身已经不怎么会碰撞了,CAD更多是防止外部设备踏频,所以这里的时间开销可以接受。
重传策略我也做了简化,每个节点最多重传两次,重传间隔随机取400到700ms。这个随机范围不是拍脑袋定的,太短的话重传还是会撞在一起,太长则航向刷新被拖慢。实测下来,这两个时间值在二十个节点的网络里碰撞概率已经很低了。
3.3 帧结构和加密设计
LoRa是广播介质,任何在覆盖范围内的LoRa接收机都能收到你的报文,不加密等于把所有人的位置暴露在公共频段上。所以TrackPulse在数据链路层直接加了AES-128加密,密钥在出厂时预置在节点和中心站里。加密的对象是payload部分,LoRa的物理层前导码和头部仍保持明文,这样中心站能正常识别和过滤。
我设计的Tag上行payload固定16字节,结构如下:
| 字段 | 长度 (字节) | 说明 |
|---|---|---|
| 帧头 | 1 | 0xAA固定值,用于快速校验 |
| 节点ID | 1 | 节点地址,支持0-255 |
| 序列号 | 1 | 每包递增,用于防重放和丢包统计 |
| 电压 | 2 | 电池电压,单位mV |
| IMU航向 | 2 | 磁力计解算的yaw角,单位0.1° |
| 角速度 | 2 | 陀螺仪z轴角速度,单位0.1°/s |
| 温度 | 2 | 环境温度,单位0.1℃ |
| 状态位 | 1 | 低电量、GPS锁定、天线段错误等标志 |
| 保留 | 2 | 预留字段,未来扩展 |
这16字节正好能让SF9在125kHz带宽下一次发完。中心站下行轮询帧更短,只有6字节左右,包含帧头、节点地址、指令和CRC。
序列号这个字段很多人会偷懒省略,但实际排查网络问题的时候非常关键。接收端通过检查序列号能立刻算出丢包率,判断链路质量,我后面调降速参数全靠这个数据。
3.4 处理离线节点:heartbeat机制
最初版本有一个很蠢的问题:中心站按静态列表轮询节点,如果某次轮询某个节点没回包,它会在下一轮继续尝试这个地址,但尝试两次之后就把它从轮询列表里移除。问题是节点完全不知道自己已经被移除了,它还在傻等轮询,结果就是永远掉线,只能重新上电才能恢复。这个问题在帆船训练这种节点经常跑出覆盖范围再跑回来的场景里会反复出现。
修复方案是引入heartbeat机制。Tag节点不再完全依赖轮询帧来上报,而是在每次醒来时自动在公共信道发一包状态帧,包含ID、序列号和基本状态。中心站收到heartbeat后,才把该节点加入活跃列表并开始正常轮询。如果超过30秒没有收到某个节点的任何消息,中心站才把它标记为离线,保留记录但不继续浪费时间轮询。节点一旦重新进入覆盖范围,一个heartbeat就能恢复入网。
这个设计把网络维护逻辑从"中心站单方面决定"改成了"节点主动宣告存在",鲁棒性好了很多。现在哪怕节点半夜被挪到了另一艘船上,第二天重新上电也能在几秒内被中心站重新发现。
4. Heading是怎么从一堆信号里抠出来的
4.1 三种获取航向的方式对比
TrackPulse的核心卖点之一是"with Heading",也就是每个节点的运动方向。获取航向有三个途径,各有优劣:
| 方式 | 精度 | 更新频率 | 依赖条件 | 限制 |
|---|---|---|---|---|
| 位置差分航向 | 中 | 取决于位置更新率 | 节点在运动中 | 静止时方向噪声爆炸 |
| 双天线相位差测向 | 较高 | 高 | 中心站双通道硬件 | 多径环境下恶化明显 |
| IMU磁力计航向 | 中 | 最高 | 板载九轴IMU | 存在磁干扰和长期漂移 |
最终系统把三种方式做了融合,没有一个方案单独扛大梁。这个设计的考虑是:位置差分航向在节点静止时完全失效,相位差在室内和树木密集区域容易出偏差,磁力计在船体这类有金属结构的环境里需要频繁校准。单独拿出任何一种来用,航向值都不够稳定,但三种信号源加权融合之后,输出就相当可用了。
4.2 多点RSSI定位加位置差分
LoRa节点本身没有测距能力,但接收信号强度指示(RSSI)可以换算成距离,这是最基础的定位手段。
RSSI测距的原理是路径损耗模型:信号强度随距离呈对数衰减。拿到RSSI之后,用公式反推距离。实际使用中这个模型很不稳定,同一点上RSSI波动3到5dB是常态,对应距离误差可以达到几十米。所以我的做法是做多帧滑动平均,取最近十次测量的中位数而不是瞬时值,把波动压下去。
距离出来之后,三边定位需要至少三个参考点。在开阔场景里这很好办:在场地周边放三个位置已知的中心站,每个中心站都测同一批Tag的RSSI,把数据汇总到主中心站解算位置。更常见的场景是只有一台中心站,那位置解算就退化成"测距加方向"的方式——中心站通过相位差得到方位角,通过RSSI得到距离,合成极坐标位置。
节点运动时,连续两次位置差分就得到速度矢量,速度方向就是航向。这个方法在节点移动速度超过0.5m/s时表现不错,速度越低,位置噪声对方向角的影响越大。静止时位置差分产生的航向完全是噪声,必须切换到其他信号源。
4.3 双天线相位差测向
给中心站加第二根天线,利用到达相位差来做方向估计,这是我做Heading信息最重要的升级。
原理不复杂。两个天线间距d,来波方向与天线连线夹角为θ,那么两路信号的相位差φ满足:
φ = 2π × d × cos(θ) / λ
其中λ是波长,490MHz对应的波长大约是0.61米。天线间距取d = λ/2,也就是大约0.3米,这样相位差和角度有一一对应的关系,不会产生多解。
实际硬件实现用两路SX1262同时接收同一个Tag的包,分别采集I/Q数据,处理出各自的相位,再做差。SX1262本身能输出I/Q数据,所以固件层面不需要额外加硬件。
相位差测向的精度受多径影响最大。开阔地实测误差能控制在10到15度以内,但只要有墙面或树林导致反射,误差会迅速恶化到30度以上。所以我只在中心站的视距范围比较干净时启用相位差作为主要航向信号源,周围反射面多的时候自动降低它的融合权重。
4.4 航向融合:卡尔曼和互补滤波的实用组合
三种测向源都有短板,所以我用了一个非常务实的状态估计算法:卡耳曼滤波做数据融合。不展开推导,只讲实际用的状态量和观测量。
状态量取终端的横纵坐标和航向角:x, y, yaw。观测量有三个来源:RSSI换算出的距离、相位差得到的方位角、IMU解算的磁航向。预测部分用IMU的角速度积分来更新航向,用上一次的速度估计来预测位置的移动。
一维的方法能让你理解整个逻辑,以航向为例:
yaw_pred = yaw_prev + gyro_z * dt yaw_filt = yaw_pred + K * (mag_yaw - yaw_pred)K是卡尔曼增益,它的取值取决于两个东西:磁力计观测噪声方差R和陀螺仪预测噪声方差Q。R大说明磁力计当时不可信,K变小,滤波结果更依赖陀螺积分;R小则K变大,滤波结果快速跟随时磁力计方向。
实测中我把磁力计在开阔场校准好之后方差设得比较小,这样磁场干净时航向精确;一旦进入金属结构附近,磁力计读数异常跳动,我让卡尔曼检测到新息偏差过大后自动增大R,让系统切到陀螺积分和位置差分信号。切换逻辑和手动增益基本一致,把调参经验写成了规则。
航向输出的最终口径是0到359度的绝对角度,附加一个置信度字段。置信度根据融合时的残差判断,残差大说明各信号源互相矛盾,置信度自动降低,前端可视化会把这条航向箭头的透明度调低。这个设计在观察帆船转向时特别有用,能明显看出来哪些航向数据是可信的。
4.5 航向接口与数据流
中心站在每个更新周期结束时会生成一条完整的事件记录,示例:
{ "id": 12, "x": 123.4, "y": 456.7, "heading": 247, "confidence": 0.83, "speed": 2.4 }x和y是相对于中心站的本地坐标,heading是0到359度的航向角,confidence是0到1的置信度。前端展示就走WebSocket推给浏览器,画成雷达扫描线的样式,每个节点一个圆点加一条航向箭头。二十个节点同时运动时,屏幕上看起来就像一个有方向信息的雷达图,和项目名"Radar Tracker"呼应上了。
5. 硬件的几个关键决定
5.1 功耗预算:把每一毫安都算清楚
LoRa的低功耗不是天生就有,需要认真设计唤醒策略和占空比。我以Tag节点为例,把功耗账算一遍。
Tag节点主要器件:STM32L4、SX1262、九轴IMU。各个状态的电流大致如下:
| 状态 | 电流 |
|---|---|
| 深度睡眠 (MCU + radio + IMU) | 约35μA |
| MCU唤醒运行 | 约5mA |
| SX1262 接收 | 约6mA |
| SX1262 发射 (+17dBm) | 约95mA |
| IMU 采样 | 约1mA |
我的唤醒周期设计是每5秒醒来一次,一次完整的收发过程包括:MCU启动和IMU采样加无线收发加休眠,大约400ms。做个平均值计算:
平均电流 = (95mA × 0.15s + 6mA × 0.15s + 5mA × 0.1s) / 5s ≈ 3.28mA
再加上睡眠电流和偶尔的重传,实际总均流大约在3.6mA左右。用一节18650电池3400mAh容量来算:
续航 = 3400mAh / 3.6mA ≈ 944小时,大约39天。
这个续航水平对绝大多数户外使用场景都足够了。如果想跑更久,把上报周期从5秒拉长到30秒,平均电流能再降一个数量级,续航直接奔着半年去。实际部署时我在节点上留了太阳能充电口,晴天状态下能实现长期不间断运行。
5.2 天线选型和极化
天线是LoRa项目里最容易翻车的地方。同样的板子,天线姿态不对,通信距离能缩水到十分之一。
LoRa一般用垂直极化天线,Tag端我用的是1/4波长单极子弹簧天线,中心站用玻璃钢垂直偶极子天线。关键是所有天线必须保持垂直极化一致。测试时发现,把Tag横着放在口袋里,和竖着挂在胸前背包带上,中心站接收到的RSSI能差20dB以上。这不是巧合,而是极化失配带来的物理损耗。
所以我在节点外壳上专门设计了一个天线固定卡槽,强制用户把天线朝上插入,同时在固件里通过状态位记录天线状态,提醒佩戴者调整放置位置。
中心站的架设同样影响明显。三米高和五米高的位置,覆盖半径差异很大。架高能减少地面反射的影响,原理上更好理解,第一菲涅尔区被地面切割的比例越小,多径衰落越弱。有条件的情况下,中心站尽量架到六米以上的杆子。
5.3 电池电压采样和自耗电
电池电压采样看起来是个小事,实际上是个容易掉坑的细节。我第一次直接用两个电阻分压接ADC,结果发现电池放一天就掉了好几个百分点,测量电路本身在自耗电。
问题出在分压电阻一直在消耗电流。虽然两个几百kΩ的电阻并在一起只有几微安的电流,但对一个目标平均电流3.6mA的系统来说,几微安就是近1%的续航损失。如果放在深度睡眠电路里,那比例就不可接受了。
修复方案是用一个MOS管开关把分压电路隔开,只在采样时打开电源。MCU引脚拉高一下,MOS管导通,ADC读取完毕之后拉低,回路彻底断开。这个改动让睡眠电流从60多微安降到了35微安,效果立竿见影。
电压校准也值得提一句。STM32的ADC参考电压有误差,我用了芯片内部1.2V参考电压做比例校准,每块板子在出厂前都要通过串口写入一组校准系数,这样电量显示才准。实测下来电量误差控制在正负2%以内,对电池寿命预测足够了。
6. 实测数据与踩坑复盘
6.1 实测场景和结果
系统稳定之后,我在三类典型场景做了测试:城市公园、湿地草地、以及帆船码头周边。每个场景放一台中心站,带十五个节点,连续跑48小时,统计丢包率和航向误差。
| 场景 | 有效覆盖半径 | 平均丢包率 | 位置误差 (中位数) | 航向误差 (运动中) |
|---|---|---|---|---|
| 城市公园 | 约700m | 4.2% | 约35m | 约18° |
| 湿地草地 | 约1.4km | 1.8% | 约40m | 约15° |
| 帆船码头 | 约1.1km | 6.7% | 约55m | 约22° |
城市公园丢包率偏高是因为树木密集吸收信号,码头场景则是金属桅杆和船体反射干扰了相位差测向。整体来说,位置精度虽然比不上GPS和UWB,但对"知道每个节点在哪个区域、朝哪个方向移动"这种粗粒度跟踪需求,已经非常够用。航向误差在运动状态下能压到20度以内,静止时的航向完全依赖磁力计,精度大约在5到8度。
6.2 三个印象最深的坑
第一个坑是节点SF参数不一致。有一批节点在出厂时烧录了SF9配置,另一批节点在测试中手滑改成了SF11。结果就是中心站时不时丢失某些节点的回包,而且毫无规律。最开始的排查方向完全跑偏,我以为是天线问题,换了三根天线没有任何改善。后来逐个把节点拿回实验室读配置,才发现SF不一致。SX1262接收的时候虽然能解调多种SF,但同一时刻只能配置一组参数,轮流切SF会浪费大量时间。解决办法是在Bootloader阶段强制所有节点从参数区读取无线配置,统一为SF9,只有中心站显式下发降速指令时才允许临时切换。
第二个坑是天线极化失配导致的信号骤降。某天测试中一直正常的几个节点突然全部丢包率暴涨,中心站收到的RSSI平均低了15到18dB,看起来像是中心站模块坏了。我带着频谱仪和备用中心站去现场替换,结果问题依旧。最后仔细观察发现,这几个节点装在了测试人员的外套口袋里,天线被身体压成了近似水平。换一个背包外挂位置之后,RSSI立刻恢复。后来在所有节点固件里加了天线状态自检,通过IMU判断天线轴向是否偏离垂直方向超过45度,一旦偏离就在状态位里上报提醒。
第三个坑是漏轮询导致节点永久离线。这个在3.4节里已经详细说过,是最隐蔽的逻辑坑。它的排查过程值得提一笔:现象是某个节点在跑出覆盖范围再回来之后,中心站就再也搜不到它。我一开始怀疑接收机出问题,重新上电节点又好了,但过一会儿又失联。后来通过抓中心站串口日志才发现,中心站在轮询完一轮之后直接把该节点从列表里删了,而节点还在傻等。这个坑暴露了"中心站单方面决定节点状态"的架构缺陷,heartbeat机制就是从这个教训里长出来的。
6.3 动态降速和调参经验
我最后把默认参数固定在SF9、125kHz带宽、CR4/5,这套组合在容量和距离之间最平衡。但固定参数解决不了所有问题,雨天或者节点逼近覆盖边缘时,丢包率会明显上升。
所以中心站在每一轮轮询结束后,根据每个节点的序列号连续性计算丢包率。如果某个节点的丢包率连续三轮超过15%,中心站会给它单独下发降速指令,让它切换到SF11。SF11的灵敏度比SF9高约5dB,理论上能多覆盖四成左右的距离,代价是这个节点每包的空中时间涨了将近三倍。航向刷新率会掉一些,但保通信比保实时性重要。
恢复机制是双向的。节点在SF11模式下会附带上报当前的丢包统计,中心站观察到该节点丢包率连续十轮低于5%,就下发指令切回SF9。这个切换要做到无感,节点收到指令后下一包就换参数,中心站同步轮询配置,不能出现切换窗口期相互失联的情况。
还有一个调参的心得:轮询帧本身也可以调整。中心站把轮询帧放在最短的SF7档位发送,Tag的回包放在SF9档位。调度帧短意味着单轮轮询的控制开销很低,二十个节点的调度广播只占几百毫秒,数据上报的时间留给实际数据包。这套"控制信道短帧、数据信道长帧"的分时设计非常实用,控制面的可靠性和数据面的容量都照顾到了。
6.4 后续可以怎么扩展
TrackPulse做到现在这个状态,对我来说已经是一个足够稳定的工具了。如果要继续做精度增强,我会在Tag节点里加UWB模块,LoRa负责广域粗跟踪,UWB负责进入近距离范围后的高精度定位。两者之间通过状态位切换,远距离时LoRa主导,近距离时UWB主导,航向融合逻辑可以沿用现在这套框架,只是把UWB的测距信息当成一个新的高精度观测源塞进卡尔曼滤波里。
另外,如果要做更大规模的节点接入,可以考虑把频段切成两到三个子信道,节点按ID哈希分布到不同频率上,中心站用多路SX1262并行接收,容量能翻三倍。这套架构的代价是硬件成本上升,但对几十上百个节点的训练场地来说,是比换协议更平滑的扩容路径。
T最后一个经验是文档的重要性。这项目前前后后改了三个版本,每一版我都坚持更新一份参数记录表和一份协议变更日志。好几次深夜排查问题,都是靠翻旧版本的记录定位到哪一次改动引入了回归。做硬件和底层协议的项目,版本管理和代码管理同样重要,千万别觉得只有软件工程需要这个。