这是一个系列文章《如何从零开始实现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。较短的时钟同步周期,可以保持当前的时钟同步数据较新,与最新的各种影响因素匹配。
卡尔曼滤波或者其他方法滤波。总体上,频率的变化是类似正态分布的一些随机点:频率有一个总体的变化趋势,如果我们时钟的差异率画出来,它是总体上是一条逐步变化的曲线,但是每次计算出来的点,并不在正好落在曲线上,而是在曲线附近。我们可以使用滤器得到曲线上的值。
使用滤波的效果很在限,最重要的还是缩短时钟同步周期。因为通过滤波器得到的值,并不是真的实际情况,而是一个理想值。比如,因为空气温度变化导致电波从时钟到基站的传输速度变化了,我们得出一个排除这个影响的值,但实际上这个值意义不大。因为标签发出的定位包的速度也一样会受到影响,应该一道排除空气变化影响才行,如果只对时钟同步排除,而标签发出的定位包不排除,那么计算结果肯定会有较大的偏差。
原来打算基站固件设计写一篇就搞定,结果发现仅是时钟同步就写了这么多内容。看来要写的东西还是有点多。慢慢写吧。