这是一个系列文章《如何从零开始实现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贵阳,或远程。如果有工作机会,请联系我要详细的简历。