简介:面向ZigBee无线定位入门与进阶开发者的CC2530 RSSI测距工程包,围绕串口读取接收信号强度并换算节点距离这一核心任务,可用于物联网短距无线网络实验、课程设计、毕业项目及定位算法验证,帮助理解信号衰减与距离估算的基本流程。压缩包共637个文件、约12.38MB,包含c/h源码、r51/s51/lst编译中间文件、hex可烧录固件、ewp/ewd工程配置及bat脚本等,目录保留完整IAR工程结构,便于对照编译输出与烧录结果。已有1741人学习下载,说明其在ZigBee RSSI测距与CC2530串口通信实践中有一定参考价值。读者可从中梳理CC2530串口参数配置、RSSI值捕获与滤波处理、自由空间与经验路径损耗模型换算距离的实现脉络,并结合协议栈API、多点定位思路排查通信异常和测距误差,适合作为ZigBee定位实践的基础参考。
1. 从一串 RSSI 原始值说起:ZigBee 测距到底难在哪
串口助手里不断刷出一行行十六进制,末尾那个字节就是 RSSI——不少人到这一步就以为定位做完了。真正把 -62 这种数字直接代进公式,算出来的距离能差出一倍。ZigBee 定位这条链路上,串口读数只是入口,决定精度的在后面两条:RSSI 怎么从芯片寄存器还原成可信的 dBm,以及路径损耗模型的 A、n 怎么用你自己场地的数据标出来。这套流程适合做室内人员与资产标签、实验室课程设计,以及已经有 ZigBee 网络、只想补一层米级粗定位的团队。
2. 串口链路先打通:帧结构、波特率与半包处理
上电接上 USB-TTL,串口助手里能看到数据在刷,很多人就直接按行读取当成解析成功了。单节点、静止、调试阶段这样确实能跑,一旦挂上三个锚点同时上报,或者接收端在读取时恰好碰上模块复位,读到的就是拼接错误的半包:地址串到 RSSI 上,值看着正常,算出来的距离却莫名其妙偏出十几米。这类问题最消耗时间,因为现象不像报错,像“算法不准”。所以串口这一段不能凑合,帧结构、波特率、解析器的重同步策略,得先按能长期跑的标准定下来,再去碰距离公式。
2.1 接线与串口参数:先把物理层确认清楚
ZigBee 模块的 UART 是 3.3V 电平,USB-TTL 的 TX 接模块 RX、RX 接模块 TX,地必须共。用 5V 电平的转接板直连,运气好能用一段时间,运气不好某次热插拔就带走了引脚。流控一定关掉,模块侧基本不支持 RTS/CTS,打开之后会出现“收几十帧就卡住”的假象。
参数上,出厂常见是 115200 8N1,也有固件默认 38400 甚至 9600。别猜,先在模块手册里翻到 UART 配置,或者用串口助手把 9600、19200、38400、115200 各试一遍看哪个不出乱码。接收端一次把三项对齐:波特率、数据位停止位、无流控。
from serial.tools import list_ports # 列出所有串口,hwid 里的 VID/PID 用来区分板载调试口和 USB-TTL for p in list_ports.comports(): print(f"{p.device}\t{p.description}\t{p.hwid}")这段只是枚举,重点是看hwid。同一台机器上经常同时插着板载下载口和外接 USB-TTL,两个都会出现在列表里,靠描述分辨最容易接错。我一般会把 USB-TTL 的 VID/PID 记下来,在代码里按hwid前缀筛选,避免换了 USB 口之后/dev/ttyUSB0变成ttyUSB1导致采错数据。
提示:波特率误差超过 2% 就开始出现偶发乱码,晶振质量差的模块在 115200 上尤其明显。连续丢帧时先把波特率降到 38400 验证一次,能稳定就说明是时钟偏差,不是线的问题。
2.2 自定义帧格式:为什么要放弃 printf 调试
调试阶段用printf("rssi=%d\r\n", rssi)最省事,但文本协议没有边界,接收缓冲区里断在半行就是一条脏数据,而且字符串解析的开销在小 MCU 上也不便宜。多节点场景下,常见做法是改成二进制定长帧,长度、地址、序号、校验都显式带上。
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 2 B | 固定 0xAA 0x55,用于重同步 |
| 长度 | 1 B | 帧体字节数,不含帧头与校验 |
| 源地址 | 2 B | 短地址,小端 |
| 序号 | 1 B | 每帧自增,用于丢帧检测与乱序归并 |
| RSSI | 1 B | 有符号 dBm 按字节传输 |
| 校验 | 1 B | 帧体逐字节累加取反 |
帧头选0xAA 0x55而不是0xFF 0xFF,是因为后者在空闲线上、模块复位瞬间、电平抖动时都会大量出现,容易造成假同步。即便这样,帧体里仍可能出现AA 55,所以重同步不能只靠帧头,必须配合长度字段和校验。
import struct FRAME_HEAD = b'\xAA\x55' def checksum(payload: bytes) -> int: # 和校验:按字节累加后取反,8 位 MCU 上一条循环就够 return (~sum(payload)) & 0xFF def build_frame(src: int, seq: int, rssi_dbm: int) -> bytes: # 帧体:源地址(2B 小端) + 序号(1B) + RSSI(1B,负数按补码) payload = struct.pack('<HBB', src, seq, rssi_dbm & 0xFF) return FRAME_HEAD + bytes([len(payload)]) + payload + bytes([checksum(payload)])struct.pack的<HBB里,<表示小端,H是无符号 16 位,两个B是无符号 8 位。RSSI 是负数,& 0xFF把它转成补码字节;解析端再判断最高位还原符号,两边必须约定一致,否则 -45 会被读成 211。
2.3 Python 解析器:粘包、半包与重同步
串口是按字节流交付的,read(64)拿到的可能是半帧、一帧半、三帧粘一起。接收端不能假设“一次 read 等于一帧”,必须自己做缓冲和状态推进。常见做法是维护一个bytearray,每次追加新数据后循环尝试取出完整帧。
import time class FrameParser: def __init__(self): self.buf = bytearray() def feed(self, data: bytes): self.buf.extend(data) out = [] while True: i = self.buf.find(FRAME_HEAD) if i < 0: # 没有帧头:只保留最后 1 字节,防止帧头被切成两半丢掉 del self.buf[:-1] break if i > 0: del self.buf[:i] # 丢掉帧头之前的垃圾 if len(self.buf) < 3: break plen = self.buf[2] total = 2 + 1 + plen + 1 if len(self.buf) < total: break # 半包,等下一次 feed payload = bytes(self.buf[3:3 + plen]) cs = self.buf[3 + plen] if checksum(payload) != cs: del self.buf[:2] # 只丢帧头,下一轮重新同步 continue src, seq, rssi_raw = struct.unpack('<HBB', payload[:4]) rssi = rssi_raw - 256 if rssi_raw > 127 else rssi_raw out.append({'src': src, 'seq': seq, 'rssi': rssi, 't': time.time()}) del self.buf[:total] return out三个细节决定这个解析器能不能长期跑。一是没有帧头时只保留最后 1 字节,不是清空缓冲,否则恰好断在AA和55之间的那一帧就永远找不回来。二是校验失败时只删 2 字节帧头而不是整帧,因为失败点很可能只是长度字段读错,真正的帧头还在后面。三是每帧带time.time(),后面做多锚点归并和移动目标滤波都要用到它。
2.4 采样节拍:时间戳比采样率更值得关注
三个锚点各自独立上报时,如果各自带自己的本地时间,接收端拿到的三个 RSSI 可能相隔上百毫秒。人走路 1 m/s,100 ms 就是 10 cm,静止场景看不出来,一动就出现坐标来回跳。稳妥的做法是让协调器轮询,一次轮询里连续取完所有锚点,接收端给这一批打同一个时间戳;做不到就在协议里带序号,接收端按序号窗口归并,窗口内没凑齐的一批直接丢弃而不是硬算。
采样率上,10 Hz 对慢速移动够用,5 Hz 适合人员标签,1 Hz 适合资产盘点这类静止或准静止场景。频率拉高并不能提高精度,因为单次 RSSI 本身的方差就有几个 dB,真正起作用的是同一位置上的多次采样再取统计量,这部分留到下一章处理。
3. RSSI 换算成距离:对数距离模型与 A、n 标定
拿到-45这样的数字,直接套一个网上抄来的公式,结果往往和卷尺量出来的对不上,于是开始怀疑模块坏了或者算法错了。绝大多数情况是两件事没做:一是把芯片寄存器原始值当成了 dBm,二是用了别人场地的 A、n。ZigBee 测距的精度天花板基本由这两步决定,后面的滤波和解算只能在这个基础上做修补,补不回来。
3.1 从寄存器原始值到 dBm:别跳过偏移修正
不少模块通过串口吐出的是 MAC 层拿到的 RSSI 寄存器值,不是物理 dBm。以常见的 CC2530 一类 ZigBee SoC 为例,寄存器值以 0.5 dB 为步进,还需要减去一个经验偏移量(数据手册给的典型值在 73 附近,早期 CC2420 是 45)。不做这一步,所有值会整体偏出三四十 dB,算出来的距离完全没有物理意义。
RSSI_OFFSET = 73.0 # 手册典型值,实际批次差异可能有 2~3 dB,需实测复标 def reg_to_dbm(reg: int) -> float: # 寄存器第 7 位为符号位,低 7 位以 0.5 dB 步进 signed = reg - 256 if reg >= 128 else reg return signed / 2.0 - RSSI_OFFSETRSSI_OFFSET一定要复标,不能照抄。标定方法很简单:把发射端固定在 1 m 处,用频谱仪或者一台已经校准过的接收机读实际功率,再看模块输出的 dBm,两者之差就是你这批硬件的真实偏移。换一批模块、换一种天线、甚至换个外壳,这个值都可能变。我见过因为外壳把天线包住,整体偏移了 6 dB 的情况,不改偏移的话后面所有标定都白做。
3.2 对数距离路径损耗模型:A 和 n 的物理含义
工程上最常用的还是对数距离路径损耗模型:
RSSI(d) = A - 10 · n · lg(d)A是参考距离 1 m 处的 RSSI,单位 dBm,它把发射功率、天线增益、线损全部打包进去了。n是路径损耗指数,描述信号随距离衰减的快慢。自由空间理论上 n = 2,实际环境差别很大:
| 场景 | n 典型范围 | 备注 |
|---|---|---|
| 空旷室外 | 2.0 ~ 2.2 | 接近自由空间 |
| 开放办公区 | 2.4 ~ 2.8 | 桌椅、人体散射 |
| 长走廊 | 1.8 ~ 2.1 | 波导效应,衰减反而变慢 |
| 隔墙房间 | 3.0 ~ 4.0 | 墙体附加损耗折算进 n |
| 货架仓库 | 3.5 ~ 5.0 | 金属多径严重,最难的场景 |
把n调大,远距离估出来的距离会变小;调小则相反。所以同一个n不可能同时拟合 1 m 和 15 m,这就是为什么单点标定一定不够,必须用一组不同距离的实测点做拟合。
3.3 最小二乘标定:用一组实测点定 A 和 n
标定流程我一般按这个顺序走:选一条无遮挡的直线路径,从 1 m 开始每隔 1 m 取一个点,最远取到实际部署半径的 1.2 倍;每个点静置采 30 帧以上,取中位数消掉脉冲干扰;把距离和 dBm 配对后做线性最小二乘。模型对lg(d)是线性的,取对数之后就是一条直线拟合,不需要非线性优化。
import numpy as np def fit_path_loss(dist_list, rssi_list): # RSSI = A - 10n·lg(d) => y = a + b·x, x = lg(d), y = RSSI x = np.log10(np.asarray(dist_list, dtype=float)) y = np.asarray(rssi_list, dtype=float) X = np.vstack([np.ones_like(x), x]).T coef, *_ = np.linalg.lstsq(X, y, rcond=None) A, b = coef n = -b / 10.0 pred = X @ coef rmse = float(np.sqrt(np.mean((y - pred) ** 2))) return A, n, rmsercond=None让 NumPy 用默认截断,这个规模的数据不会出问题。返回值里的rmse是判断这次标定靠不靠谱的关键:室内场景 RMSE 在 3~5 dB 属于正常,超过 8 dB 说明采样点里有异常值,通常是某个点被人体遮挡或者贴近金属。我一般的做法是先把残差最大的那个点剔掉再拟一次,如果 RMSE 还是降不下来,就说明这条路径本身不直或者中途有障碍,得换条路径重采。
标定完把A、n写进配置而不是硬编码在函数默认参数里,后面换场地只改配置。同一栋楼的不同楼层、走廊和房间,n差异可以到 0.8,用一个全局值勉强能跑,但误差会明显变大。
3.4 抖动抑制:中值、滑动平均与卡尔曼的取舍
静止站在同一个点,RSSI 摆动 6~10 dB 是常态,换算成距离在 3 m 处可能就是 ±1.5 m。三种滤波各有适用面:中值滤波专门打掉脉冲型异常(有人走过、门开了一下),窗口 5 就够;滑动平均降方差但引入滞后,窗口越大滞后越明显,10 Hz 采样下窗口 10 相当于 1 秒延迟;卡尔曼适合移动目标,需要一个还过得去的运动模型。
from collections import deque class RssiFilter: def __init__(self, win=5): self.win = win self.buf = deque(maxlen=win) def update(self, dbm: float) -> float: self.buf.append(dbm) s = sorted(self.buf) med = s[len(s) // 2] # 中值:先干掉脉冲 # 再对中值附近的数据求均值,进一步压方差 near = [v for v in self.buf if abs(v - med) <= 3.0] return sum(near) / len(near)abs(v - med) <= 3.0这个阈值不是随便定的,它对应“正常抖动范围”。阈值放宽到 6 dB,脉冲就漏进来了;收紧到 1.5 dB,静止时可能只剩一两个样本,均值反而更抖。实际调的时候先录一段静止数据,看方差,再把这个阈值定在 1.5 倍标准差附近。
注意:滤波只能改善显示和坐标平滑,不能提高单帧的物理精度。如果原始数据的 RMSE 就很大,先回头查标定和天线,别指望滤波器救回来。
4. 三边测量落地:从三个距离到一个坐标
有了三个以上锚点到目标的距离,接下来的问题是怎么把它们变成一个 (x, y)。教科书上写的是三个圆求交点,实际写代码时会发现三个圆往往不相交——因为三个距离都带误差。所以工程上不做几何求交,做最小二乘:找一个点,使得到各锚点的距离与测量距离之差的平方和最小。这个转换不难,但几个矩阵细节不注意就会算出明显偏离的结果,或者直接抛奇异矩阵。
4.1 几何直觉与线性化推导
设锚点 i 的坐标为 (xi, yi),测得到目标的距离为 di,目标是 (x, y),则有:
(x - xi)² + (y - yi)² = di²这是圆方程,含 x²、y²,直接解非线性方程组比较麻烦。技巧是拿第一个锚点做参考,把其余方程分别减去参考方程,二次项 x²、y² 正好抵消,得到一组线性方程:
2(xi - x0)·x + 2(yi - y0)·y = xi² - x0² + yi² - y0² + d0² - di²写成矩阵就是A·p = b。三个锚点时 A 是 2×2,可以直接求逆;四个以上就是超定方程,用最小二乘。这一步是整个定位解算的核心,理解了消二次项这个动作,后面加权、残差检查都是在这个框架上加东西。
4.2 最小二乘求解与加权
import numpy as np def trilaterate(anchors, dists): # anchors: [(x, y), ...] 至少 3 个;dists: 与 anchors 一一对应的测量距离 x0, y0 = anchors[0] d0 = dists[0] A, b = [], [] for (xi, yi), di in zip(anchors[1:], dists[1:]): A.append([2 * (xi - x0), 2 * (yi - y0)]) b.append(xi**2 - x0**2 + yi**2 - y0**2 + d0**2 - di**2) A = np.asarray(A, dtype=float) b = np.asarray(b, dtype=float) # 加权:距离越远,测距相对误差越大,权重按 1/d 衰减 w = np.array([1.0 / max(d, 0.5) for d in dists[1:]]) W = np.diag(w) AtW = A.T @ W pos, *_ = np.linalg.lstsq(AtW @ A, AtW @ b, rcond=None) return posw = 1/d的依据是:RSSI 的误差在 dB 域大致恒定,换算到距离域后相对误差随距离增大,所以远距离点的距离值更不可信,权重该低一些。max(d, 0.5)是防止距离接近 0 时权重爆炸。想更精细就按1/σ²加权,σ 用标定阶段的残差拟合出来,一般线性近似就够。
选第一个锚点做参考不是随便挑的。参考点离目标太远时,d0很大,误差也会被放大进 b 向量。常见做法是把测距值最小的锚点(也就是离目标最近的)当参考点,这样 b 向量的数值量级更均衡。我一般会在函数外面先把 anchors 和 dists 按距离排序再传进去,省得每次改。
4.3 锚点布局:为什么共线就会出错
A 矩阵的两列是2(xi - x0)和2(yi - y0)。如果三个锚点在同一条直线上,所有 (xi - x0, yi - y0) 都平行,A 的两行线性相关,矩阵秩亏,lstsq解出来的结果会飘到很远的地方,或者直接给出一个看起来合理但完全错误的点。
实际部署里最容易犯的错是把锚点全贴在一面墙上,或者全放在一条走廊的中轴线上。正确的布局是围成一个不自交的多边形,锚点之间的夹角尽量接近 120 度,高度统一在 2 m 左右(高于人体遮挡,又不太高导致俯仰误差)。三个锚点还有个隐患:解出来的位置可能有两个镜像解,因为圆交点上下对称,只有第四个不共面的锚点才能唯一确定。所以做二维定位,至少四个锚点,别省这一个。
| 锚点数量 | 能否唯一解 | 适用情况 |
|---|---|---|
| 2 | 否 | 只能定一条双曲线,不可用 |
| 3 | 有镜像解 | 需额外先验(比如区域边界)排除 |
| 4 | 是 | 二维定位推荐起点 |
| 6 以上 | 是,且可做残差检验 | 精度与鲁棒性更好 |
4.4 残差自检:把明显错点挡在门外
解出坐标不等于结果可信。每次解算完顺手算一下残差r = A·p - b,残差的范数能反映这组距离自洽程度。正常情况下残差在几十厘米量级,如果突然跳到一个很大的值,说明某个距离测错了(遮挡、多径、丢帧配对错),这时候宁可输出上一帧的位置,也不要把这个点画到屏幕上。
具体阈值没有统一标准,我的经验是按锚点间距的 5% 来定,4 m 间距就取 0.2。超过阈值先看数据:是某个锚点连续几帧 RSSI 异常,还是串口那边帧配错了。真要做鲁棒估计,可以用 RANSAC 的思路——每次随机取三个锚点解一次,重复若干次,取内点最多的一组。计算量不大,但能把突发遮挡带来的跳点压下去。
5. 避坑与排查:五条让实测数据全废的细节
这一节记录的是我在实际项目里踩过、也帮别人排查过的几类问题。它们的共同点是:不报错、不崩溃,就是结果不准,而且看起来像算法问题,实际根因都在数据源头。按“现象 → 原因 → 解决”的顺序写,方便对照。
5.1 现象:整条距离曲线整体平移
同一个位置,白天测和晚上测能差出 20%,但曲线形状是正常的。原因多半在射频前端:天线贴着金属机箱、外壳把天线包住、模块焊接时天线区域铺了地铜,都会让实际辐射效率下降,等效于把所有 RSSI 整体压低几 dB。温度变化也会带来小幅漂移。
解决办法是先把硬件摆正:天线区域净空,离金属面至少 5 mm,外壳用非金属材料。然后重新做一次偏移复标,把RSSI_OFFSET更新掉。如果外壳不能改,就在标定阶段把外壳装上,让 A 和 n 吃掉这部分损耗,别用裸板标定出来的参数去算装壳后的数据。
5.2 现象:串口偶发乱码、丢帧、跑一段时间就卡死
先分三类。乱码集中在开机瞬间,多是电平不匹配或者模块晶振尚未稳定就开始发送,加 200 ms 上电延时能解决。丢帧随机分布且和距离有关,检查杜邦线长度和接地,长线加在 3.3V 系统上很容易受干扰,换屏蔽线或者缩短到 20 cm 以内。跑一段时间卡死,多是因为接收端没有做重同步,缓冲里堆了一堆半包之后find一直命中在错误位置,整个解析器就停在那儿了。
对应的修法就是 2.3 节那三条:无帧头只留 1 字节、校验失败只删帧头、长度字段做上限保护(比如超过 64 直接判断为非法,避免一个错字节导致等待一个巨大的长度)。
5.3 现象:同一位置 RSSI 摆动超过 10 dB
静止点位摆动这么大,通常不是模块问题。人体是 2.4 GHz 的良好吸收体,站在发射端和接收端之间,衰减能到 10 dB 以上。多径也是主因,室内金属柜、白板、玻璃幕墙都会造成干涉,移动几厘米信号强度就大幅变化。
处理上分两步:采样阶段每点采 30 帧以上取中值,把人走动带来的脉冲打掉;部署阶段把锚点抬高到 2 m 以上,避开人体高度,同时让天线极化方向一致(全都竖直)。如果场地里金属结构特别多,n会明显偏大,标定时把测量范围分段拟合,比强行用一个全局n效果好得多。
5.4 现象:解算时抛出奇异矩阵,或者坐标飞到几百米外
前面提过共线问题,但还有一种更隐蔽的情况:锚点坐标本身写错了。比如地图上量的是米,配置里写的是厘米;或者三个锚点里有一个的 y 坐标符号写反了。矩阵看起来不奇异,解出来的点却完全不对。
排查顺序是先打印 A 矩阵和它的秩,秩小于 2 一定是布局问题;秩正常就逐个核对锚点坐标,用卷尺量一遍锚点之间的实际距离,和配置里的坐标反算距离对比,差值超过 10 cm 就是配置错了。这一步很土,但比盯着公式看快得多。
5.5 现象:距离单位与坐标单位混用
RSSI 换算出来的距离是米,锚点坐标用的是厘米,两边一混,解出来的位置会整体缩放到 1/100 或者放大 100 倍。这种错误在拼接别人代码的时候特别常见,尤其是从不同来源抄了两段代码,一段用 m 一段用 mm。
统一约定:内部计算全程用米,只在最终显示时按需换算。所有配置项命名带上单位后缀,anchor_x_m、anchor_y_m这种写法看着啰嗦,但能省掉后面无数次排查。
注意:以上五条里有四条的根因都在数据源头,不在解算算法。出现精度问题时,先按这个顺序把数据链路捋一遍,再回头看公式。
6. 验证与提精度:一张固定距离对照表能省很多事
标定完参数、跑通解算之后,最容易犯的错是直接拿去现场用,看到坐标大致落在正确区域就认为完成了。真正要交付的时候,得有一张能拿得出手的验证表:同一批点位,卷尺量出的真实距离、静态滤波后的距离、解算坐标的误差,全列在一张表里。这张表既能证明系统能跑,也能在出问题时快速定位是哪一环变差了。
验证点位不用多,1、2、3、5、8、12 m 各一组,每组静置 30 秒,记录中值和标准差,再和真实距离对照。我一般会把结果整理成这样:
| 真实距离 | 标定前估算 | 标定后估算 | 绝对误差 | 标准差 |
|---|---|---|---|---|
| 1 m | 1.8 m | 1.1 m | 0.1 m | 0.09 |
| 3 m | 5.6 m | 3.2 m | 0.2 m | 0.31 |
| 5 m | 10.4 m | 5.4 m | 0.4 m | 0.58 |
| 8 m | 18.2 m | 8.9 m | 0.9 m | 1.02 |
| 12 m | 30.5 m | 13.6 m | 1.6 m | 1.85 |
从表里能看出一件事:误差随距离非线性增长,越远越不准,这是 RSSI 测距的固有限制,不是参数没调好。所以实际部署时要根据精度要求反推锚点间距,需要 2 m 以内精度,锚点间距就别超过 8 m。
提升精度有两个低成本的抓手。一是分区间标定:把 0~5 m 和 5 m 以上分开拟合,各用一组 A、n,远端的系统性偏差能压掉一半以上。二是把标定之后的残差方差带进加权最小二乘,用1/σ²替代1/d,实测下来坐标的均方根误差能再降 15% 左右。这两个改动都不动硬件,只在参数层调,性价比最高。
# 分区间标定:两个区间各自一组参数,按测量距离选择用哪组 PARAMS = { 'near': {'max_d': 5.0, 'A': -43.2, 'n': 2.35}, 'far': {'max_d': 99.0, 'A': -47.6, 'n': 2.95}, } def rssi_to_distance(rssi, last_d=None): # last_d 用上一帧距离选区间,避免在阈值附近来回抖 key = 'near' if (last_d or 0) <= 5.0 else 'far' p = PARAMS[key] return 10 ** ((p['A'] - rssi) / (10.0 * p['n']))last_d这个参数看着不起眼,作用是不让距离在 5 m 阈值附近来回跳变——RSSI 抖一下,区间切换,参数换了一套,距离就会突跳半米。用上一帧的结果做滞回,成本几乎为零。
最后说一条我的习惯:每换一次场地、每换一批模块、甚至每改一次外壳结构,标定那一步都强制重跑一遍,不管上次的参数看起来多合适。有一次赶进度,直接拿前一天走廊里标好的参数去算房间里的数据,误差从 1 m 涨到 4 m,查了两个小时才发现是n不匹配。从那以后我每次上电第一件事就是拿 1 m 和 5 m 两个点位各采一组,跟标定表对不上就直接重标,不抱侥幸。希望帮到你。
本文还有配套的精品资源,点击获取