首页
从零实现 UWB 下行 TDOA 定位系统
- 菜单项设置
- 作者: zhang
- 分类:技术
1. 前言
此前,我写了几篇介绍从零开始实现一个使用 TDOA 技术的 UWB 精确定位系统的文章,其中介绍的 TDOA 技术是上行 TDOA。最近,我完整实现了基于下行 TDOA 技术的 UWB 精确定位系统。在研究下行 TDOA 技术期间,我写过几篇相关的文章。现在,我把全部信息汇总,并结合最近的一些心得体会,写成此文。
1.1 UWB 简介
在进入 TDOA 原理之前,先简单介绍一下 UWB(Ultra-Wide Band,超宽带)的基本概念,帮助没有 UWB 背景的工程师快速建立认知。
UWB 是一种短距离无线通讯技术,它与传统的 WiFi、蓝牙有本质的不同:
- WiFi/蓝牙使用持续的正弦载波,接收方通过解调载波来恢复数据。这类信号的带宽通常只有几十 MHz。
- UWB 使用极短的脉冲信号(通常在亚纳秒到几纳秒之间),信号带宽达到 500MHz 以上。正是因为带宽极大,所以被称为"超宽带"。
为什么"带宽大"就能"定位准"?
简单来说,脉冲越窄,在时间轴上的分辨率就越高。UWB 脉冲信号的分辨能力可以精确到亚纳秒级别。考虑到电磁波以光速传播——1 纳秒在空气中飞行大约 30 厘米——这意味着如果我们能精确测量信号到达的时间,理论上就能得到厘米级的距离测量精度。这是 WiFi 信号(带宽只有几十 MHz,对应数米的分辨率)根本无法企及的。
如果你对"基于时间的定位"还不太理解,可以想象一下打雷: 闪电瞬间发生(光速传播几乎没有延迟),之后我们才听到雷声(声速约 340 m/s)。通过闪电和雷声之间的时间差,我们可以估算出雷电发生地离我们的距离。UWB 定位的原理非常类似,只不过我们捕捉的不是声波而是电磁脉冲——因为光速极快,所以 UWB 芯片里的计时器需要拥有极高的分辨率(DW3000 芯片的内部计时器分辨率约 15.65 皮秒,相当于约 4.7 毫米的空间分辨率),才能精准抓取这短暂的飞行时间差异。
在本文中,我们选用的 UWB 芯片是 Qorvo DW3000,其内部计时器为 40 位宽,分辨率约 15.65ps/tick。40 位计时器的满量程约为 17.2 秒——也就是说,计时器每隔约 17.2 秒就会溢出归零(overflow)。在软件设计中需要注意处理这个溢出,后续章节我们会讲到。
1.2 TDOA 定位的原理
TDOA 是什么?TDOA 的英文全称是 Time Difference of Arrival(到达时间差)。其实我们日常使用的 GPS/北斗导航定位技术就是 TDOA——手机端的 GPS 芯片根据接收到的不同卫星信号的时间差,计算手机所在位置。GPS/北斗使用的是下行 TDOA 定位,就是由被定位的终端自己计算自己的坐标。
如何从零开始实现TDOA技术的 UWB 精确定位系统(4)
- 菜单项设置
- 作者: zhang
- 分类:技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第4部分。
标签的固件设计
之前的文章,我介绍了定位基站和标签的硬件设计、基站的固件设计(包括时钟同步的算法原理)。按正常的 roadmap,接下来应该是标签的固件设计。但是标签的固件实在是简单,没多少内容可写啊。想了想,还是为它写一篇吧。
标签最重要的功能是定期发送TDOA定位数据包,其他的功能都属于附加的。这个功能实现起来太简单了,decawave提供的样例代码中,很多都可以发送uwb数据包,只是数据包的格式需要符合我们的定义。
我们定义的 UWB TDOA定位数据包的格式是这样:
// Tag测距帧
// 广播帧, Tag 发出的测距消息, 各个 Anchor 根据收到这个帧的时间差来应用 TDOA 测距
typedef struct ieee154_broadcast_tag_range_frame {
uint8_t frameCtrl[2]; // 0, 2: frame control bytes 00-01: 0x01 (Frame Type 0x01=date), 0xC8 (0xC0=src extended address 64 bits, 0x08=dest address 16 bits)
uint8_t seq8; // 2, 1: sequence_number 02
uint8_t panID[2]; // 3, 2: PAN ID 03-04
uint8_t destAddr[2]; // 5, 2: 0xFFFF
uint8_t sourceAddr[8]; // 7, 8: 64Bits EUI地址
uint8_t messageType; // 15, 1: Message Type = RTLS_MSG_TYPE_TAG_RANGE
uint8_t seq64[8]; // 16, 8: Tag 发出的测距消息序号, 比 seq8 有更在的最大值
uint8_t powerVoltage[2]; // 24, 2: 电源电压*1000
uint8_t batteryVoltage[2]; // 26, 2: 电池电压*1000
uint8_t lighteness[2]; // 28, 2: 亮度, 直接 ADC 转换过来的值
uint8_t switchStatus; // 30, 1: 开关状态
int16_t imu_aacx; // 31, 2:
int16_t imu_aacy; // 33, 2:
int16_t imu_aacz; // 35, 2:
int32_t imu_roll; // 37, 4:
int32_t imu_pitch; // 41, 4:
int32_t imu_yaw; // 45, 4:
int16_t imu_temp; // 49, 2:
uint8_t fcs[2]; // 51, 2
} BROADCAST_TAG_RANGE_MESSAGE;
如何从零开始实现TDOA技术的 UWB 精确定位系统(3)
- 菜单项设置
- 作者: zhang
- 分类:技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第3部分。
前一篇文章介绍了时钟同步的方法,是为了尽快拿点干货出来,免得读者觉得我只写一些泛泛而谈的大路货,没技术含量。下一步慢慢写吧。
基站固件设计-Flash布局
给MCU写固件的时候,如果是初学者,可能一上来直接就开始写应用程序了。老手都会先考虑布局,都要做些什么事,它们之间的关系是什么等等,要考虑周全。
基站的Flash分为4块:Bootloader、Application1、Application2、Data。
基站固件设计-在线升级
产品的固件可以现场升级很重要。现场升级是指不用把已经安装好的产品拆下,直接通过局域网之类的,在客户现场就可以升级固件。要知道,当产品已经安装在几千公里外的客户现场,突然发现固件中有个bug,你怎么办?如果能现场升级,在办公室把代码修正后,生成新固件,发给现场的客户,在现场升级固件。在这个过程中,你根本不需要离开你的办公室。
如果你的产品不能现场升级固件,对不起,发现bug后,只能请客户把产品拆下,寄回公司,你刷入新固件,再寄给客户......想起来都头大。
基站固件设计-Bootloader
Bootloader是启动代码,Application1/Application2是应用程序,Data放配置数据。
上电的时候,Bootloader中的程序先被执行,它判断Application1/Application2哪个有效,就跳转到有效的那个Application去执行。在Data区有两个变量: Application1Valid和Application2Valid,如果为True表示对应的Application有效。
在生产阶段,负责生产的工人只负责刷入Bootloader,这时,Application1Valid和Application2Valid都是False。
上电后,如果找不到有效的Application,Bootloader会启动网络部分的代码: DHCP Client/TCP Client/DNS Client/UDP Server/UDP Client等。如果它连接在网络,会一直在网络中发出一个UDP包,请求初始化。
出厂初始化程序收到请求初始化的UDP包后,会登记新基站的基本信息,如MCU ID等,然后给它分配一个 EUI64 的地址,这个地址以后就是这个基站的ID了。还会分配一个系列号。然后还要写入缺省的配置数据。出厂初始化程序接下来开始发送固件给新基站。Bootloader收到固件后,写入到一个无效的Application区(刚开始,Application1区肯定是无效的)。固件会被分很多个包发送。固件接收完成后,Bootloader会置Applicaiton1Valid为True,然后重启。
再次上电后,Bootloader发现Application1有效,就跳转到Application1去执行。
这就完成了新固件的启动。
其实,Bootloader中的网络相关代码和固件接收代码,只在出厂初始化的时候执行一次,以后就不会再被执行到。因为在出厂之后,Applicaiton1和Application2总有一个是有效的。
如果Bootloader发现两个区都有效,会把第二个区置为无效。只允许一个区有效,无效的那个区用保留给新的升级固件使用。
如何从零开始实现TDOA技术的 UWB 精确定位系统(2)
- 菜单项设置
- 作者: zhang
- 分类:技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第2部分。
基站固件设计-时钟同步
时钟同步原理
我们在前面已经说过,对于TDOA技术来说,各个基站需要有统一的时间,这是一个难点。时钟怎么同步呢?
假设一个定位区域有A/B/C/D四个基站,我们另外再把拿一个基站CS作为时钟源,定期发送时钟同步包。这个时钟同步包的结构如下:
typedef struct ieee154_broadcast_clock_sync_frame {
uint8_t frameCtrl[2]; // 0, 2, frame control bytes 00-01: 0x01 (Frame Type 0x01=date), 0xC8 (0xC0=src extended address 64 bits, 0x08=dest address 16 bits)
uint8_t seq8; // 2, 1, sequence_number 02
uint8_t panID[2]; // 3, 2, PAN ID 03-04
uint8_t destAddr[2]; // 5, 2, 0xFFFF
uint8_t sourceAddr[8]; // 7, 8, 64Bits EUI地址
uint8_t messageType; //15, 1, Message Type = RTLS_MSG_TYPE_CLOCK_SYNC
uint8_t seq64[8]; //16, 8, 发出的测距消息序号, 比 seq8 有更大的最大值
uint8_t timeStamp[8]; //24, 8, 时间戳
uint8_t fcs[2]; //32, 2
} BROADCAST_CLOCK_SYNC_MESSAGE;
这是一个DW1000的典型的UWB数据包。这是一个广播类型的包,其中有几个重要字段,timeStamp这是时间戳,也就是A基站发送这个包的时刻。
当,A/B/C/D四个基站收到这个同步包后,记下这个时间戳和收到这个包的时刻(本地时间戳),这样,我们就有两个时间戳了。
过一会,CS再发一个同步包。某个基站例如B基站收到后,再记录下CS基站的远程时间戳和本地收到的时候戳,又得到两个时间戳。
假设CS发出这两个包之间间隔100ms,也就是两个同步发送时间间隔100ms。那么在正常情况下,B基站收到这两个包时,本地的时间间隔应该也是100ms。实际上并不是!
因为两个基站的DWM1000使用的晶振会因为各种原因,频率并不会完全相同,总会有一些差异。
第 2 页 共 5 页