快捷键

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UWB 定位实现

本文以一个标签、四个固定基站为例,串起测距、数据上报和二维坐标解算。代码使用模拟距离验证计算流程,接入设备后用同一轮实测距离替换输入。

1. 硬件与坐标规划

需要一个可移动标签、四个支持 TWR 的 UWB 基站,以及一台接收测距结果的上位机。板卡之间使用匹配的测距固件、信道与帧参数,标签和基站分别分配唯一 ID。

以下坐标单位均为米,原点放在场地一角,X、Y 方向按现场固定:

基站XY
A000
A160
A264
A304

本例让标签和基站处于同一高度,因此实测距离可直接用于二维平面解算。二维定位至少需要三个不共线的基站,第四个基站增加一条观测约束。

现场应尽量让定位区域落在基站围成的范围内,测量并记录每个基站的实际坐标。需要解算高度时,应采用三维模型与不共面的基站布置。

2. 从双向测距得到距离

TWR 通过消息往返和芯片时间戳估计无线信号的飞行时间,再按 距离 = 传播速度 × 飞行时间 换算。

采用 Poll、Response、Final 的双边测距时,定义:

Ra:标签发送 Poll 到收到 Response 的时间间隔
Da:标签收到 Response 到发送 Final 的时间间隔
Rb:基站发送 Response 到收到 Final 的时间间隔
Db:基站收到 Poll 到发送 Response 的时间间隔

ToF = (Ra × Rb - Da × Db) / (Ra + Rb + Da + Db)

间隔取自各设备本地的硬件时间戳,并按芯片的时间单位换算,不能用上位机接收串口数据的时间代替。双边测距的原理和时间戳关系可参考 Qorvo / Decawave APS013。

先跑通一个标签与一个基站之间的测距,再扩展为标签按时隙依次与各基站交换数据。每一轮只汇总同一个标签、同一个轮次的距离。

3. 约定上报格式

上位机示例使用一行一个 JSON 对象。下面是本教程约定的输入协议;设备或串口适配程序负责把厂商原始报文转换成这个格式:

{"tag_id":"T1","round_id":1,"ranges":{"A0":2.5,"A1":4.2720018727,"A2":4.7169905660,"A3":3.2015621187}}
  • tag_id:标签标识。
  • round_id:测距轮次,用于区分不同时刻的观测。
  • ranges:基站 ID 到距离的映射,单位统一为米。

这组数据对应模拟位置 (2.0, 1.5)。某轮只有两个有效基站时应跳过定位,不要用上一轮的数据补齐。

4. 二维位置解算

标签位置为 (x, y),基站位置为 (xi, yi),测得距离为 ri,则每个基站提供一个距离约束:

(x - xi)² + (y - yi)² = ri²

选第一个基站为参考,把其他方程减去参考方程,二次项会消掉。以参考基站为局部原点,得到:

dx = xi - x0
dy = yi - y0
u = x - x0
v = y - y0

2 × dx × u + 2 × dy × v = r0² - ri² + dx² + dy²

用多条线性方程求最小二乘解,再把局部坐标加回参考基站坐标。核心实现如下,只依赖 Python 标准库:

import math


def locate_2d(anchors, ranges):
    """Return (x, y, range_rmse) in metres; anchors and tag share one height."""
    if not isinstance(ranges, dict) or len(ranges) < 3:
        raise ValueError("至少需要三个基站的距离")
    points = []
    for anchor_id, raw_range in ranges.items():
        if anchor_id not in anchors:
            raise ValueError(f"未知基站:{anchor_id}")
        if isinstance(raw_range, bool):
            raise ValueError("距离必须为正数")
        distance = float(raw_range)
        coordinate = tuple(float(v) for v in anchors[anchor_id])
        if len(coordinate) != 2 or not all(math.isfinite(v) for v in coordinate):
            raise ValueError("基站坐标必须是两个有限数值")
        if not math.isfinite(distance) or distance <= 0:
            raise ValueError("距离必须是有限正数")
        points.append((*coordinate, distance))

    x0, y0, r0 = points[0]
    rows = []
    for xi, yi, ri in points[1:]:
        dx, dy = xi - x0, yi - y0
        rows.append((2 * dx, 2 * dy, r0 * r0 - ri * ri + dx * dx + dy * dy))
    aa = sum(a * a for a, b, c in rows)
    ab = sum(a * b for a, b, c in rows)
    bb = sum(b * b for a, b, c in rows)
    ac = sum(a * c for a, b, c in rows)
    bc = sum(b * c for a, b, c in rows)
    determinant = aa * bb - ab * ab
    if determinant <= 1e-12 * max(aa * bb, 1.0):
        raise ValueError("基站共线或布置退化,无法进行二维定位")
    x = x0 + (ac * bb - bc * ab) / determinant
    y = y0 + (bc * aa - ac * ab) / determinant
    rmse = math.sqrt(sum((math.hypot(x - xi, y - yi) - ri) ** 2
                         for xi, yi, ri in points) / len(points))
    return x, y, rmse

该算法适合验证数据链路和生成位置初值。存在噪声时,可以继续对原始距离残差做非线性优化。输出的 range_rmse_m 表示测距约束的拟合残差,不能直接当成真实位置误差。

5. 运行示例

下载 完整 Python 示例,保存为 uwb_position.py。Python 3.8 及以上可运行:

python uwb_position.py --demo

模拟数据的预期输出:

{"tag_id":"T1","round_id":1,"x_m":2.0,"y_m":1.5,"range_rmse_m":0.0}

把第 3 节的 JSON 保存到 ranges.jsonl,也可以从文件输入:

python uwb_position.py < ranges.jsonl

在现场使用时,先按实际测量值修改脚本中的 ANCHORS,再让串口、网络接收程序按上述格式输出完整测距轮次,交给解算程序读取。

6. 接入实测数据

推荐按以下顺序完成联调:

  1. 单链路测距:在多个已知距离上记录连续测距结果,检查单位、稳定性和系统偏差。
  2. 基站坐标测量:统一原点、坐标方向和高度,填写真实坐标。
  3. 轮次汇总:检查标签 ID、基站 ID、轮次和有效状态,拒绝过期数据与无效距离。
  4. 静态定位:把标签放到几个已知点位,对照解算坐标。
  5. 移动定位:记录完整轨迹,观察丢包、跳点和延迟,再决定滤波与更新频率。

标签与基站高度不同时,若高度差 dz 已知,可先把空间距离 r 转为水平距离 sqrt(r² - dz²),并确认 r >= |dz|;高度未知时需要调整定位模型。

脚本会拒绝距离不足、未知基站、非有限数值、非正距离,以及共线或严重退化的基站布局。

7. 校准与误差排查

  • 固定偏移:核对坐标原点、长度单位、天线参考位置与天线延迟校准。
  • 部分区域偏差大:检查遮挡、反射和基站布局,先观察原始测距再判断是否需要滤波。
  • 位置突然跳变:检查是否混入不同轮次或不同标签的数据,记录参与解算的原始距离。
  • 残差较小但位置不准:重新核对基站坐标与高度模型,拟合良好不代表坐标基准正确。

天线延迟与板卡设计、安装方式有关,应按所用模块的校准流程处理,可参考 Qorvo 关于天线延迟的说明。

滤波应放在数据校验与基础测距确认之后。评估时同时记录位置误差、有效定位比例和端到端延迟,用现场数据决定参数。