首页
工牌标签 T1201C
- Written by: zhang
- Category: 产品
工牌标签 T1201C
我们量产的定位标签是工牌款式,使用DW1000作为UWB收发器。在前期使用DWM1000模块,后期使用自行设计的UWB模块,部分型号还增加了LNA/PA,以增加定位范围。
使用STM32F103RBT6作为主控MCU。这个芯片我们开始的采购价大约12元~14元左右,后来曾经涨到180元1片,真是太疯狂了,搞得我们很被动。不过,后来又跌回去了。
供电使用一片800mah的锂电作为电源,使用了一个USB接口充电,顺便作为标签配置接口。
标签上设计了一个MPU6050三维加速度计芯片,量产的时候,发现作用不是很大,后期在生产中就没贴这个芯片。
标签上设计了2个LED,一个红色一个绿色。
标签上设计了1个按钮,作为报警之类的用途。具体用途需要服务器端的设计。固件设计可以支持多个按钮,后来有OEM客户需要增加别的功能(如防拆等)。
如何从零开始实现TDOA技术的 UWB 精确定位系统(4)
- Written by: zhang
- Category: 技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第4部分。
重要提示(劝退说明):
Q:做这个定位系统需要基础么?
A:文章不是写给小白看的,需要有电子技术和软件编程的基础
Q:你的这些硬件/软件是开源的吗?
A:不是开源的。这一系列文章是授人以“渔”,而不是授人以“鱼”。文章中我会介绍怎么实现UWB定位系统,告诉你如何克服难点,但不会直接把PCB的Gerber文件给你去做板子,不会把软件的源代码给你,不会把编译好的固件给你。我不会给你任何直接的结果,我只是告诉你方法。
Q:我个人对UWB定位很兴趣,可不可以做出一个定位系统?
A:如果是有很强的硬件/软件背景,并且有大量的时间,当然可以做得出来。文章就是写给你看的!
Q:我是商业公司,我想把UWB定位系统搞成一个商业产品。
A:当然可以。这文章也是写给你看的。如果你想自己从头构建整个系统,看了我的文章后,只需要画电路打板;构思软件结构再编码。就这样,所有的难点我都会在文中提到,并介绍了解决方法。你不需要招人来做算法研究。如果你想省事省时间,可以直接购买我们的电路图(AD工程文件),购买我们的软件源代码,然后快速进入生产环节。(网站: https://uwbhome.top)
标签的固件设计
之前的文章,我介绍了定位基站和标签的硬件设计、基站的固件设计(包括时钟同步的算法原理)。按正常的 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;
后来我们发现这个数据包太大了,有很多字段是没有必要的,所以换了一种小的格式:
// Tag测距帧, 最小测距帧
// 广播帧, Tag 发出的测距消息, 各个 Anchor 根据收到这个帧的时间差来应用 TDOA 测距
typedef struct ieee154_broadcast_tag_range_min_frame_v2 {
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_MIN_RANGE_V2
uint8_t seq32_3[3]; // 16, 3: Tag 发出的测距消息序号的高3字节, 与 seq8 组合为 seq32, 比 seq8 有更在的最大值
uint8_t switchStatus; // 19, 1: 开关状态
uint8_t fcs[2]; // 20, we allow space for the CRC as it is logically part of the message. However ScenSor TX calculates and adds these bytes.
} BROADCAST_TAG_RANGE_MIN_MESSAGE_V2; // 以上合计 22 字节
从53字节减小到22字节。这样,一个定位数据包在发送期间占用频率的时间减小了42%。这意味着可以容纳更多的标签。
你应该注意到,最小化的这个数据包中甚至没有包含电压数据,我们另外定义了一个包含电压数据的定位数据包,隔一段时间定期发送,因为电池电压不会快速变化,几分钟发一个带电压信息的包就可以了。
有客户定制了带心率传感器的标签,我们还增加了一个包含心率、血氧数据的定位数据包。
标签不像基站可以通过网络通讯,标签只能使用USB通讯。USB接口使用HID方式,可以免驱动。不要使用模拟COM口,因为如果使用COM通讯,一方面是需要安装驱动,另一方面是有可能会出现COM冲突,总之多出一些麻烦事。而使用HID方式则简单得多。
USB通讯需要使用外部晶振,不能使用STM32的内置RC振荡器。因为RC振荡器的频率不是很准确,如果使用RC振荡器,可能会导致兼容性问题,在一些电脑上因为频率不准而导致通讯失败。
STM32的省电模式使用 STOP 模式,这个模式比较适中。
要注意STM32的时钟频率。正常情况下,STM32的时钟是72MHz,因为我们不需要那么高的运算能力,所以使用48MHz就可以了,频率低一些可以省电。其他的更低频率不行,因为我们还需要给USB部分提供12MHz的频率,更低的频率组合不出12MHz。
还有就是,这么简单的程序不需要跑OS,直接裸奔就可以了。不跑OS还有两个原因:
- RTOS的结构复杂,我们还需要进行入STOP状态,这导致程序逻辑会复杂得多。
- STM32F103CBT6这个芯片的RAM/Flash都比较小,不适合太大的程序。
有什么想法,欢迎给我留言、评论。
这篇就这样吧,只差直接给出源代码了。下一篇开始介绍TDOA算法和定位引擎。
顺便做个找工作的广告。因为经营上的原因,公司散伙了。本人30+年工作经验,C/C++/Java/Delphi有20+年经验,Javascript/Python/Lua会一点。199x年x86汇编写过汉字系统,徒手Delphi写过邮件服务器,写过的应用系统无数,写过的代码应该超过200万行。10+年硬件设计经验,设计过多款嵌入式产品。这个UWB定位系统在初期,硬件软件都是我一个人弄出来的,产品成型之后才增加人手组团队。Base贵阳,或远程。如果有工作机会,请联系我要详细的简历。
如何从零开始实现TDOA技术的 UWB 精确定位系统(3)
- Written by: zhang
- Category: 技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第3部分。
重要提示(劝退说明):
Q:做这个定位系统需要基础么?
A:文章不是写给小白看的,需要有电子技术和软件编程的基础
Q:你的这些硬件/软件是开源的吗?
A:不是开源的。这一系列文章是授人以“渔”,而不是授人以“鱼”。文章中我会介绍怎么实现UWB定位系统,告诉你如何克服难点,但不会直接把PCB的Gerber文件给你去做板子,不会把软件的源代码给你,不会把编译好的固件给你。我不会给你任何直接的结果,我只是告诉你方法。
Q:我个人对UWB定位很兴趣,可不可以做出一个定位系统?
A:如果是有很强的硬件/软件背景,并且有大量的时间,当然可以做得出来。文章就是写给你看的!
Q:我是商业公司,我想把UWB定位系统搞成一个商业产品。
A:当然可以。这文章也是写给你看的。如果你想自己从头构建整个系统,看了我的文章后,只需要画电路打板;构思软件结构再编码。就这样,所有的难点我都会在文中提到,并介绍了解决方法。你不需要招人来做算法研究。如果你想省事省时间,可以直接购买我们的电路图(AD工程文件),购买我们的软件源代码,然后快速进入生产环节。(网站: )
前一篇文章介绍了时钟同步的方法,是为了尽快拿点干货出来,免得读者觉得我只写一些泛泛而谈的大路货,没技术含量。下一步慢慢写吧。
基站固件设计-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发现两个区都有效,会把第二个区置为无效。只允许一个区有效,无效的那个区用保留给新的升级固件使用。
基站固件设计-固件升级
在固件的Application中,也有类似前述Bootloader的固件升级代码。只是它要判断哪个区无效,把收到的新固件保存到无效的那个区,并置对应的变量为有效,把正在运行的这个区对应的变量置为无效。下次上电时,Bootloader就会跳转到新固件去执行。
基站固件设计-程序结构
现在介绍Application怎么写。
因为基站的功能还是有点多,好几个功能需要“并发”执行。这里的“并发”是指逻辑上的,因为STM32不支持抢先式执行/不支持多线程/只有一个核,所有代码都只能顺序执行,并不会有真正的并发。
所以,MCU不能裸奔,得使用RTOS。当初,因为比较熟悉uCOS,所以就用它了。如果是现在选择,大概率会用FreeRTOS。
- 时钟同步部分,如果作为时钟源,需要定期发送UWB时钟同步包;
- UWB芯片处理,负责DW1000的状态管理,以及UWB数据包的收发。
- 网络处理,包括DHCP Client/DNS Client/UDP Server/UDP Client等。
- 看门狗任务
先从简单的部分开始介绍。
看门狗任务
STM32有看门狗,如果不及时调用喂狗函数,MCU就会认为程序挂了,它会自动复位。我们的程序分为很多个任务,每一个任务都有挂掉的可能,所以,我们搞一个看门狗任务。每个任务有一个看门狗变量,由各个任务及时把它置0,看门狗任务循环给这些变量加1,并判断这些变量的值,如果超过某个界限,就判断对应的任务挂了,就复位MCU。看门狗任务还负责喂狗。
总之,我们要保证各个任务都能正常工作,当任务挂掉后,立即复位。
DW1000处理任务
考虑到将来可能会在一个基站内置多个DW1000,所以我们把DW1000相关数据定义为一个结构:
这样的话,有些代码就可以复用了,程序处理起来会灵活一些。
在这里要吐槽一下DecaWave,DW1000的工作情况并不很稳定。有时会出现一些奇怪的现象。所以,我们还要经常判断DW1000状态,如果不在期望的状态,判定死了,我们要及时对DW1000进行复位。当然,这种情况并不太多,但是只要有,我们就得处理。否则在客户现场,基站活着、固件活着,但是DW1000死了,无法收发数据,就很尴尬了。
基站的主要工作就是接收UWB数据包,为了保证收到的数据包能及时处理,不影响后续的UWB包的接收,我们搞了一个队列来做缓存。DW1000任务只管收UWB包,收到后立即放入队列,然后又处理后续的UWB包。队列中的包由其他任务来处理。
网络任务
网络任务的工作比较杂乱。
它最重要的一个工作就是把收到的UWB重新组装成一个新的数据包,发给定位引擎。
其它的工作就是辅助性的了。
DHCP客户端。很多时候,IP地址的管理很麻烦,如果每个基站都一一配置IP地址,是一件比较辛苦的事。一般来说,除非客户要求每台设置都要配置指定的IP地址,否则使用DHCP来分配IP地址是比较方便的。W5500芯片有DHCP客户的代码例子,直接复制过来改改就可以了。它的代码在处理上不太完整,DHCP的IP地址合约到期后的续签,还有如果有IP地址冲突怎么处理等等,我们对代码做了一些完善工作。
DNS客户端。其实可以不用的。如果定位引擎不在局域网内,而是在很远的地方,例如Internet上的云主机。使用DNS指定定位引擎的地址会好一些,万一定位引擎的IP地址变了,但是域名可以不变。我们没有使用DNS,之前的客户基本都在局域网内,把定位引擎放在Internet上的只有为数不多的几个。
UDP。我们使用UDP来实现定位引擎自动发现。如果配置为定位引擎自动发现,网络任务会检查是否与定位引擎建立起TCP连接,如果没有,它会广播一个UDP包(定位引擎发现包),要求定位引擎给回复。定位引擎收到UDP包后,会回复自己的IP地址之类的信息。基站就会与定位引擎建立起TCP连接。
基站配置。在早期的版本中,我们使用UDP来传送基站配置信息。使用UDP的好处是可以广播,缺点是UDP是无连接的,有丢失的可能。后来我们统一改成使用TCP来传送基站配置信息。基站配置程序先通过UDP广播,查找局域网内都有哪些基站,然后与它们建立TCP连接,双向传送配置信息。
基站固件设计-杂项功能
恢复出厂设置。为了保证数据安全,基站设置有密码,基站配置程序必须使用正确的用户名和密码才能对基站进行配置。既然是密码,就有忘记的可能。另外也有可能用户配置基站,把数据弄乱了,自己懒得一个一个的改,所以我们设计了一个恢复出厂设置功能。
USART1_TX和USART1_RX在出厂的产品上没用,对应的是PA9和PA10被挪用于检测是否恢复出厂设置。需要恢复出厂设置时,把这两个引脚之一与GND短接,重新上电就可以了。
报警功能。有个客户打算把我们的系统用在煤矿(重要提醒,产品需要本安认证才用在煤矿),需要增加一个报警功能。当发生险情时,系统可以发报警信息给标签,让携带标签的工人可以得时得到撤离信号。流程大致是这样,应用程序调用定位引擎的接口,定位引擎通过网络把数据包发给时钟源(或扮演时钟名的基站),时钟源在发送时钟同步数据包的任务中发出UWB报警数据包,标签收到UWB报警数据包后,立即报警(红灯,或震动)。报警消息有两种:广播给所有人,指定ID的标签。
基站固件设计-可能会遇到的坑
第一个坑是DW1000的状态。前面已经说过了。我们在系统中要定期检查DW1000的工作状态,如果不正常,要及时对DW1000进行复位。
第二坑是W5500的晶振。我们第一次量产基站时,W5500使用的是无源晶振,这是很正常的电路设计,我们之前的测试板都是这样设计的。我们在之前的一些产品中用到W5500也是使用无源晶振。但是基站生产完成,收到货后测试发现有很多基站的W5500的晶振不起振。当时想了各种办法来找原因,换pad电容,重新焊接等等。这个起振问题,似乎处于一个不确定状态,有些上电一段时间会自己起来,有些就总是不起振,有些甚至开始起振了后面会自己停。以至于我们只好在固件初始化的时候判断W5500的状态,看它状态正常不,还在网络任务中经常性的判断W5500的状态。后来我们的硬件设计改用有源晶振,就一切OK了。再之后,我们其他用到W5500的产品中,都使用有源晶振。
这是基站固件设计的第二篇文章,以后再回头修改修改充实一些细节吧。
顺便做个找工作的广告。因为经营上的原因,公司散伙了。本人30+年工作经验,顺手的语言C/C++/Java/Delphi,这个UWB定位系统在初期硬件软件都是我一个人弄出来的,产品成型之后才增加人手组团队。Base贵阳,或远程。如果有工作机会,请联系我要详细的简历。
如何从零开始实现TDOA技术的 UWB 精确定位系统(2)
- Written by: zhang
- Category: 技术
这是一个系列文章《如何从零开始实现TDOA技术的 UWB 精确定位系统》第2部分。
重要提示(劝退说明):
Q:做这个定位系统需要基础么?
A:文章不是写给小白看的,需要有电子技术和软件编程的基础
Q:你的这些硬件/软件是开源的吗?
A:不是开源的。这一系列文章是授人以“渔”,而不是授人以“鱼”。文章中我会介绍怎么实现UWB定位系统,告诉你如何克服难点,但不会直接把PCB的Gerber文件给你去做板子,不会把软件的源代码给你,不会把编译好的固件给你。我不会给你任何直接的结果,我只是告诉你方法。
Q:我个人对UWB定位很兴趣,可不可以做出一个定位系统?
A:如果是有很强的硬件/软件背景,并且有大量的时间,当然可以做得出来。文章就是写给你看的!
Q:我是商业公司,我想把UWB定位系统搞成一个商业产品。
A:当然可以。这文章也是写给你看的。如果你想自己从头构建整个系统,看了我的文章后,只需要画电路打板;构思软件结构再编码。就这样,所有的难点我都会在文中提到,并介绍了解决方法。你不需要招人来做算法研究。如果你想省事省时间,可以直接购买我们的电路图(AD工程文件),购买我们的软件源代码,然后快速进入生产环节。(网站: https://uwbhome.top)
基站固件设计-时钟同步
时钟同步原理
我们在前面已经说过,对于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使用的晶振会因为各种原因,频率并不会完全相同,总会有一些差异。
两个基站之间的时钟差异是多少呢?
deta = (ST2 - ST1) - (RT2 - RT1)。其中 deta 是差异,ST2是第二个包的发送时间,ST1是第一个包的发送时间,RT2是第二个包的接收时间,RT是第一个包的接收时间。
差异率是 Ratio = deta / (ST2-ST1)
这样,开始时间知道了,时钟的差异率也知道了。我们就可以把任意一个本地时间戳转换为以CS为准的时间戳。
当某一个标签发出UWB定位数据包时,A/B/C/D四个基站都收到这个包,A/B/C/D把收到时的本地时间戳转换为以CS的时间为基准的标准时间戳,大家都把这个时间戳送到定位引擎,定位引擎得到4个时间,根据它们的差,就可以计算出标签的坐标了。
看到这里,你心应该会有一个疑问。CS发出的同步数据包的发送时刻,是UWB数据包离开CS内的DW1000芯片的一个闸门那一刻。UWB信号然后经过了芯片内部线路,再经过发射天线发射到空中,到达B基站的天线,在B基站的天线内部传输,经过一些芯片内部线路,到达B基站中的DW1000的那个接收闸门,那个时刻是接收到的时刻。这个过程需要不少时间,这个时间怎么得到?在DW1000芯片的手册中有介绍,我们要考虑发射天线延迟/接收天线延迟。
回答是,不用管它。
我们假设,B基站的DW1000与天线之间通过一条长长的射频电缆连接,时钟同步包到达B基站的天线后,再经过长长的电缆才到DW1000芯片。当然,标签发出的定位数据包也一样,到达B基站的天线后,再经过长长的电缆才到DW1000芯片。假设电缆增加一段,导致无线电波要在电缆中多跑1ms,当然,时钟同步包和定位包都要多花1ms在路上。如果我们在计算B基站接收时钟同步包时的时间戳时把这1ms考虑进去,那么在定位引擎计算时间差的时候,要把标签的定位包多花的这1ms扣出。所以这两个包在电缆中所花的时间,在计算公式中会被抵消。
在安装基站时,我们关心“基站安装在哪个位置”,其实并不真的关心基站的位置,而是关心基站天线的位置。天线的位置才是最重要的。时钟同步包到达天线,定位数据包到达天线,基站本身在哪里并不重要。
这也是TDOA的优势之一,不用关心天线延迟,不需像TOF基站和TOF标签那样,在出厂前要做天线延迟标定。
上述的分析,是在使用单独的基站作为时钟源的场景下。我们最初的系统就是使用单独的时钟源。时钟源的硬件和固件与正常的基站完全相同,根据配置作为时钟源还是作为普通基站。作为时钟源的时候,功能只有一个,就是定期发送时钟同步包,时钟源的DW1000只发射不接收;作为普通基站的时候,接收时钟源发出的时钟同步包和标签发出的定位包,普通基站的DW1000只接收不发射。后来,为了降低成本,我们把两个功能合并,普通基站既可以作为基站又可以作为时钟源,平时处于接收状态,定期切换到发射状态发送一个时钟同步包后立即又切换回接收状态。这样,可以节省一个单独的时钟源。
如果基站同时作为时钟源,那么作为时钟源的这个基站,它的天线延迟在计算时就会有影响。对于时钟同步包来说,它是发射延迟;对于定位数据包来说,它是接收延迟。DW1000内部发射延迟和接收延迟差异不大,计算时可以忽略不计。但是,如果有外接PA/LNA,发射和接收经过的路线差异比较大,就需要考虑到这个异常了。
我们最初设计整个系统时,曾经考虑整个系统使用统一的时间。大的定位区域,可以划分为多个小的定位区域。我们可以部署一个根时钟,各个区域有一个分时钟,从根时钟同步时间,逐级同步时钟就OK。
然而事情不是这么简单,最主要的问题是误差。时钟之间的误差我们是无法排除的。
例如,根时钟R的时间同步到A1/B1,然后A1同步到A2,B1同步到B2。然后我们会发现A2和B2之间的误差会比较大。因为时钟同步本身就有误差,多级同步会导致误差积累,最后的误差越来越大。
最后,我们发现整个系统统一时间意义不大,只需要某个小的定位区域内的几个基站之间统一时间就可以了。
细心的同学肯定注意到,前面定义的时钟同步包结构中,一个字段seq64。这是一个64位整数。DW1000芯片内部定义有一个8位的seq,放在帧控制字段后。我们发现,1个字节表达数据包序列号,最多只能256个,很快就会回卷。有时,我们关心更大时间尺度内的数据包,就无法分辨了。所以,我们单独设置了一个64位的数据包序列号。
另外,DW1000的时间戳的有效数据是40位,其单位约为15.6ps。
后期,精益求精的考虑,我们新定义了一个精练一些的时钟同步包。
typedef struct ieee154_broadcast_mini_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: 64 Bits EUI地址
uint8_t messageType; // 15,1: Message Type = RTLS_MSG_TYPE_MINI_CLOCK_SYNC
uint8_t seq32_3[3]; // 16,3: 发出的测距消息序号的高 24 位,即高 3 字节
uint8_t timeStamp[5]; // 19,5: 时间戳, 40 Bits
uint8_t fcs[2]; // 24,2:
} BROADCAST_MINI_CLOCK_SYNC_MESSAGE; // 以上合计 26 字节
seq没有必要搞成64位,32位足够了,前面有一个seq8,那么后面只需要3个字节就可以了,所以把seq64改为seq32_3;时间戳的有效位只有40位,所以不需要使用64位。
新的时钟同步包大小由34个字节减小到26个字节。
因为无线信号本质上是广播,同一时刻空中只能有一个声音,如果出现其它的声音,就是干扰。所以,我们要尽可能的减小信道的占用,传送的数据包越小,占用的时间越短。可以腾出时间,以容纳更多的标签;并且,数据包占用信道的时间越短,被干扰的几率也就越小。
基站共用-与多个时钟源同步时钟
因为UWB业务的定义就是近场无线通信,几乎所有国家的无线电管理部门对UWB的信号功率都有严格限制,不允许大功率发射。因为UWB占用的带宽太宽了,如果功率大了,它就变成干扰源了。
原厂DW1000最大功率发射时,以850K的速率通讯时,覆盖范围大约200米~300米范围。实际上,这个最功率是超标的。如果按照无线电管理局要求的信号强度,通讯距离大概会在20米以内。
无论如何,当我们要对一个很大的区域进行定位,经常需要把它划分为多个小的定位区域。我们假设有一个100米x100米的区域,划分为4块25米x25米的4个区域。假设每个区域使用4个基站,那么4个区域就需要4x4=16个基站。
如果基站可以共用,定位区域边缘的基站可以供相邻的区域共用,那么我们只需要有“田”字的每个顶点安装一个基站,只需要3*3=9个基站就可以了。
使用尽量少的基站,可以减少用户的投资,也减小项目施工难度。
前面我们所述的时钟同步,基站可以多记录几个时钟源的时间戳,就可以达到与多个时钟源同步时间的效果。
typedef struct tag_ClockSource_Sync_Info {
uint8_t clockSourceId[8];
double factor;
double lastFactors[CLOCKSOURCE_SYNC_INFO_LAST_FACTOR_NUM];
int64_t offset;
int64_t localStartTime;
int64_t lastSyncTime;
SAMPLE_KALMAN_FILTER_PARAMETER kfp;
SAMPLE_RC_FILTER_PARAMETER rfp;
double ppm;
double lastFactorAcc[CLOCKSOURCE_SYNC_INFO_LAST_FACTOR_NUM];
double factorAcc;
} CLOCKSOURCE_SYNC_INFO;
上面这个结构是记录了某个时钟源的时钟同步数据。
#define CLOCKSOURCE_SYNC_INFO_NUM 15 // 记录 15 个时钟源的同步信息
CLOCKSOURCE_SYNC_INFO clockSourceSyncInfo[CLOCKSOURCE_SYNC_INFO_NUM];
上面这个数组记录多个时钟源的数据。
具体允许与多少个时钟源同步,这与MCU的RAM有关。在现实中,其实也不会有太多的区域共用同一个基站,15个基本上不同能了。
时钟同步的干扰因素
如果每次收到时钟同步包时,都计算时钟的差异率,那么会发现它一直在变化。我们期望得到一个稳定的,至少一段时间内变化不大的差异率。但是,这不可能!有太多“干扰”因素。
晶振的频率变化
DW1000的晶振是时钟的源头。对晶振的频率生产影响的因素很多,温度湿度变化/电压变化都会影响晶振的频率。晶振两端的pad电容的容量变化,也会对频率产生影响(有些电路就是通过调整这两个pad电容的容量来调整电路的频率),温度湿度电压的变化对这两个电容也会产生影响。
DW1000的Datasheet中建议“Crystal (38.4 MHz +/-10ppm)”,“TCXO (optional use in Anchor nodes. 38.4 MHz)”。DWM1000模组把DW1000芯片封装在一个铁盒子中,对晶振频率的稳定是有一定好处的,因为在铁盒子中有一定的保温效果,可以让晶振的温度不会快速受到空气流动的影响。
其实,频率不准并不可怕。频率偏差再大,我们也可以通过计算校准。我们怕的是无规律的快速变化。
无线电波传输速度的变化
无线电波在空气中传输,空气作为介质,是不稳定的。空气的温度/湿度/气压等都会影响无线电波在空气中的传输速度。
曾经有一次,我把开发板固定在三角架上,放在我办公桌旁边,我在观察时钟同步差异率的变化。刚好有一位同事从旁边路过,我马上看到曲线发生剧烈变化。这也是我在前面基站电路设计部分强调外壳很重要的原因。
保持时钟同步稳定性
因为影响时钟同步差异的因素很多,所以我们要想办法保持稳定。有几个办法:
缩短时钟同步周期。我们把时钟同步周期设定为250ms。较短的时钟同步周期,可以保持当前的时钟同步数据较新,与最新的各种影响因素匹配。
卡尔曼滤波或者其他方法滤波。总体上,频率的变化是类似正态分布的一些随机点:频率有一个总体的变化趋势,如果我们时钟的差异率画出来,它是总体上是一条逐步变化的曲线,但是每次计算出来的点,并不在正好落在曲线上,而是在曲线附近。我们可以使用滤器得到曲线上的值。
使用滤波的效果很在限,最重要的还是缩短时钟同步周期。因为通过滤波器得到的值,并不是真的实际情况,而是一个理想值。比如,因为空气温度变化导致电波从时钟到基站的传输速度变化了,我们得出一个排除这个影响的值,但实际上这个值意义不大。因为标签发出的定位包的速度也一样会受到影响,应该一道排除空气变化影响才行,如果只对时钟同步排除,而标签发出的定位包不排除,那么计算结果肯定会有较大的偏差。
原来打算基站固件设计写一篇就搞定,结果发现仅是时钟同步就写了这么多内容。看来要写的东西还是有点多。慢慢写吧。
Page 5 of 10