CH32 + DW3000 TDOA 工程技术手册
1. 文档说明
本文档用于说明当前工程分支中 CH32V307 / CH32V108 + DW3000 的 TDOA 演示方案,包括系统组成、设备角色、固件启动流程、UWB 空口协议、Anchor 端时间戳汇聚机制、TCP 上传链路、运行时配置方式、AT24C02 持久化能力以及构建部署方法。阅读对象为客户、项目集成方、现场调试人员以及研发支持人员。
当前工程是一个面向 TDOA 上层定位算法的数据采集与上传工程。该工程中:
CH32V307用作ANCHOR_NODECH32V108用作SYNC_NODE和TAG_NODEDW3000 / DWM3000用作 UWB 收发器
软件完成的核心功能如下:
sync周期性广播S类型参考帧tag周期性广播T类型标签帧anchor长期开启接收,记录本地接收时间戳,并解析帧内携带的发送时间戳anchor根据最近两帧sync和收到的tag数据拼接原始 TDOA 数据字符串anchor通过TCP将数据上传到服务器- 所有节点均支持
USART1 @ 115200串口参数查看与修改 CH32V307支持通过AT24C02保存运行时参数
2. 系统组成
2.1 硬件组成
| 设备角色 | 主控平台 | UWB 器件 | 主要职责 |
|---|---|---|---|
| Anchor | CH32V307 |
DW3000 / DWM3000 |
接收 SYNC/TAG 广播帧,采集时间戳,上传原始 TDOA 数据 |
| Sync 节点 | CH32V108 |
DW3000 / DWM3000 |
周期性广播 S 参考帧 |
| Tag 节点 | CH32V108 |
DW3000 / DWM3000 |
周期性广播 T 标签帧 |
| 服务器 / 上位机 | Python上位机 | 无 | 接收 Anchor 上传数据,完成后续定位算法处理 |
2.2 默认部署形态
当前工程及 Makefile 预设的典型部署形态为:
4 x Anchor1 x Sync1..10 x Tag
对应构建产物包括:
ch32v307_anchor_1.hexch32v307_anchor_2.hexch32v307_anchor_3.hexch32v307_anchor_4.hexch32v108_sync.hexch32v108_tag_1.hex至ch32v108_tag_10.hex
3. 方案总体设计
当前分支采用的是“边缘节点采集时间戳 + 中心侧完成 TDOA 计算”的架构,而不是 MCU 本地完成定位解算。其设计要点如下:
sync按固定周期广播参考帧,帧内携带自身发射时间戳。tag按固定周期广播标签帧,帧内携带自身发射时间戳。anchor接收这两类帧,并记录本地接收时间戳。anchor将组合后的原始时间戳字符串通过TCP发送到服务器。- 服务器或上位机在更高算力环境下完成 TDOA 计算、定位和可视化。
这种方案的优点是:
- MCU 侧逻辑简单,重点集中在收发时序与时间戳采集
- 上层算法可以在服务器端独立迭代
- Anchor 节点功能统一,便于多节点部署
- Sync / Tag 节点结构轻量,便于扩展数量
4. 设备角色说明
4.1 Anchor 节点
ANCHOR_NODE 通过 vendor/BP-UWB_LIB/Src/rx_main.c 实现,是当前系统中的接收与上传节点。
主要职责:
- 持续保持 DW3000 接收状态
- 接收 IEEE 802.15.4 数据帧
- 区分
S与T类型消息 - 通过
BPhero_WCHNET_Send_Str()走 TCP 上传 - 在主循环中维持串口配置和 TCP 协议栈运行
4.2 Sync 节点
SYNC_NODE 通过 vendor/BP-UWB_LIB/Src/tx_main.c 实现,是参考时钟广播节点。
主要职责:
- 周期性执行延迟发送
- 广播
S帧 - 将本次发射时间戳写入帧载荷
- 为 Anchor 提供参考时间基准
4.3 Tag 节点
TAG_NODE 同样通过 vendor/BP-UWB_LIB/Src/tx_main.c 实现,是标签广播节点。
主要职责:
- 周期性执行延迟发送
- 广播
T帧 - 将本次发射时间戳写入帧载荷
- 为 Anchor 提供标签事件时间戳
5. 固件启动流程
5.1 启动主链路
固件入口为 src/main.c。系统启动顺序如下:
SystemCoreClockUpdate()Delay_Init()USART_Printf_Init(115200)- 打印欢迎信息、主频和芯片 ID
GPIO_LED_INIT()uwb_led_init()uwb_config_init()tcpclient_init()uwb_init()BPhero_UWB_Init()- 根据角色进入
rx_main()或tx_main()
5.2 启动流程图
flowchart TD
A["系统上电 / 复位"] --> B["main()"]
B --> C["SystemCoreClockUpdate()"]
C --> D["Delay_Init()"]
D --> E["USART_Printf_Init(115200)"]
E --> F["GPIO_LED_INIT() / uwb_led_init()"]
F --> G["uwb_config_init()"]
G --> H["tcpclient_init()"]
H --> I["uwb_init()"]
I --> J["BPhero_UWB_Init()"]
J --> K{"编译角色"}
K -->|ANCHOR_NODE| L["rx_main()"]
K -->|SYNC_NODE| M["tx_main() - Sync"]
K -->|TAG_NODE| N["tx_main() - Tag"]
6. UWB 无线参数配置
无线默认参数定义在 vendor/BP-UWB_LIB/Src/bphero_uwb.c 中。
6.1 当前默认参数
| 参数 | 当前配置 |
|---|---|
| Channel | 5 |
| Preamble Length | 128 |
| PAC | 8 |
| TX Preamble Code | 9 |
| RX Preamble Code | 9 |
| SFD Type | 0 |
| Data Rate | 850K |
| PHR Mode | 标准 |
| STS | 关闭 |
| PDOA Mode | DWT_PDOA_M0 |
| TX PG Delay | 0x34 |
| TX Power | 0xFDFDFDFD |
6.2 运行时无线参数覆盖
bphero_uwb.c 中的 apply_runtime_radio_config() 会在初始化时将 uwb_runtime_config_t 中的参数覆盖到默认无线配置上。当前支持运行时修改的无线项包括:
uwb_channelpreamble_lengthtx_preamble_coderx_preamble_codesfd_typedata_ratetx_pg_delaytx_power
7. DW3000 初始化流程
完整的 DW3000 初始化由 vendor/BP-UWB_LIB/Src/bphero_uwb.c 中的 BPhero_UWB_Init() 完成。
执行步骤如下:
- 加载运行时无线参数
- 设置 SPI 低速
- 释放 DW3000 复位
- 等待上电稳定
- 通过 IO 唤醒器件
dw3000_driver_probe()dwt_softreset(0)- 检查
IDLE_RC dwt_initialise(DWT_DW_INIT)dwt_configure(&config)dwt_configuretxrf(&txconfig_options)- 切换 SPI 到高速
- 设置收发天线延时
- 设置
PAN ID和SHORT_ADDR - 清除状态寄存器
- 打开接收相关中断
- 打开
LNA/PA - 打开 DW3000 活动指示灯
7.1 DW3000 初始化流程图
flowchart TD
A["BPhero_UWB_Init()"] --> B["apply_runtime_radio_config()"]
B --> C["spi_set_rate_low()"]
C --> D["release_DW3000_reset()"]
D --> E["等待 POR 稳定"]
E --> F["wakeup_device_with_io()"]
F --> G["dw3000_driver_probe()"]
G --> H["dwt_softreset(0)"]
H --> I["dwt_checkidlerc()"]
I --> J["dwt_initialise(DWT_DW_INIT)"]
J --> K["dwt_configure(&config)"]
K --> L["dwt_configuretxrf(&txconfig_options)"]
L --> M["spi_set_rate_high()"]
M --> N["设置天线延时 / PANID / 地址"]
N --> O["配置中断 / LNA / LED"]
O --> P["打印 UWB 启动信息"]
8. 空口协议设计
8.1 帧格式基础
当前工程使用 IEEE 802.15.4 短地址数据帧。TDOA 应用载荷固定为 6 字节。
8.2 TDOA 载荷格式
| 字节偏移 | 含义 |
|---|---|
0 |
帧类型 |
1 |
保留标志位 |
2..5 |
发射时间戳低 32 bit |
8.3 帧类型定义
| 帧类型 | 含义 |
|---|---|
S |
Sync 参考帧 |
T |
Tag 标签帧 |
9. Anchor 端接收与上传流程
9.1 Anchor 主循环逻辑
rx_main() 的主循环行为如下:
- 初始化 OLED 文本
- 打印 Anchor 启动日志
- 调用
tdoa_anchor_enter_rx()进入接收态 - 进入死循环:
uwb_config_poll_uart()tcp_runtime_loop()- 如果检测到接收超时或接收错误,则重新进入 RX
- 延时 10ms
9.2 接收回调逻辑
真正的数据接收处理在 Simple_Rx_Callback() 中完成。
处理流程如下:
- 清空本地 RX buffer
- 暂时关闭帧过滤
- 读取
SYS_STATUS - 如果存在
RXFCG: - 清除
RXFCG - 读取帧长
- 读取完整帧数据
- 转换为
srd_msg_dsss - 检查
messageData[TDOA_MSG_TYPE_IDX] - 如果是
S帧: - 更新最近两条
sync记录 - 如果有待处理
tag缓存,则立即 flush - 如果是
T帧: - 将该
tag记录加入队列 - 重新进入 RX
9.3 Anchor 主循环流程图
flowchart TD
A["rx_main()"] --> B["OLED 初始化 / 启动日志"]
B --> C["tdoa_anchor_enter_rx()"]
C --> D["主循环"]
D --> E["uwb_config_poll_uart()"]
E --> F["tcp_runtime_loop()"]
F --> G{"存在 RX 超时或错误?"}
G -->|Yes| C
G -->|No| H["Delay_Ms(10)"]
H --> D
9.4 Anchor 中断回调流程图
flowchart TD
A["Simple_Rx_Callback()"] --> B["读取 SYS_STATUS"]
B --> C{"RXFCG 是否置位?"}
C -->|No| D["清除 RX timeout / RX error"]
C -->|Yes| E["读取帧长和帧数据"]
E --> F{"消息类型"}
F -->|S| G["tdoa_record_sync()"]
G --> H{"是否已有 tag 缓存?"}
H -->|Yes| I["tdoa_flush_tags()"]
H -->|No| J["重新进入 RX"]
F -->|T| K["tdoa_record_tag()"]
K --> J
I --> J
D --> J
10. Sync / Tag 周期发送流程
10.1 共用发送策略
SYNC_NODE 和 TAG_NODE 都通过 vendor/BP-UWB_LIB/Src/tx_main.c 实现,使用同一套延迟发送框架。关键函数包括:
tdoa_period_to_dx_units()tdoa_fill_tx_frame()tdoa_send_periodic()tdoa_wait_tx_done()tdoa_wait_period()
10.2 Sync 节点行为
SYNC_NODE 使用:
- 帧类型:
S - 周期:
sync_period_ms
10.3 Tag 节点行为
TAG_NODE 使用:
- 帧类型:
T - 周期:
tag_period_ms + ((SHORT_ADDR & 0x0F) * 2)
地址低 4 bit 的附加偏移用于在多标签场景下做简单错峰,降低同时发射碰撞概率。
10.4 周期发送流程
每个周期执行步骤如下:
- 计算下一次延迟发送时间
- 将目标发送时间戳写入帧载荷
- 清除 TX 相关状态位
- 写入 delayed TX 时间
- 写 TX buffer 和 frame control
- 必要时强制 TX 时钟打开
- 启动 delayed TX
- 等待
TXFRS - 若超时或启动失败:
- 打印日志
dwt_forcetrxoff()- 清状态
- 恢复时钟
- 若成功:
- 打印发送日志
- 序号递增
10.5 TX 主循环流程图
flowchart TD
A["tx_main()"] --> B["OLED 初始化 / 启动日志"]
B --> C["根据角色计算 period_ms"]
C --> D["主循环"]
D --> E["uwb_config_poll_uart()"]
E --> F["tcp_runtime_loop()"]
F --> G["tdoa_send_periodic()"]
G --> H["tdoa_wait_period(period_ms)"]
H --> D
10.6 延迟发送函数流程图
flowchart TD
A["tdoa_send_periodic()"] --> B["计算 delayed_tx_time"]
B --> C["tdoa_fill_tx_frame()"]
C --> D["清除 TX 状态位"]
D --> E["dwt_setdelayedtrxtime()"]
E --> F["写 TX 数据与控制字段"]
F --> G["tdoa_force_tx_clocks(1)"]
G --> H{"dwt_starttx() 是否成功?"}
H -->|No| I["打印 START_ERR 并恢复"]
H -->|Yes| J["tdoa_wait_tx_done()"]
J --> K{"是否收到 TXFRS?"}
K -->|No| L["打印 TIMEOUT 并恢复"]
K -->|Yes| M["清除 TX 状态"]
M --> N["tdoa_force_tx_clocks(0)"]
N --> O["打印 TX 日志 / seq++"]
11. 时间戳拼接与上传字符串格式
Anchor 通过 tdoa_send_anchor_string() 将时间戳信息拼接成上传字符串。
字符串格式如下:
&&&:<anchor>:<len>$<sync0_rx>:<sync0_tx>$<sync1_rx>:<sync1_tx>$T:<seq>:<tag>$<tag_rx>$<crc>###
上传策略如下:
- 始终保留最近两条
sync - 将收到的
tag临时缓存 - 新的
sync到来时,用这两条sync去 flush 这批tag
这样服务器端收到的每一批数据都带有完整的时间参考信息,便于后续 TDOA 解算。
12. 运行时配置与持久化
12.1 配置结构体
运行时配置结构体为 vendor/User/uwb_param.h 中定义的 uwb_runtime_config_t,字段包括:
anchor_idsync_period_mstag_period_msserver_ipserver_porttcp_cache_linesuwb_channeldata_ratepreamble_lengthtx_preamble_coderx_preamble_codesfd_typetx_pg_delaytx_power
12.2 持久化策略
持久化逻辑在 vendor/User/uwb_param.c 中实现:
CH32V307- 开启持久化
- 使用
AT24C02 - 引脚:
PB8/PB9 CH32V108- 不开启持久化
- 参数只保留在 RAM 中
12.3 配置初始化流程图
flowchart TD
A["uwb_config_init()"] --> B["uwb_config_load_defaults()"]
B --> C["config_try_load_eeprom()"]
C --> D["console_uart_irq_init()"]
D --> E["uwb_print_config()"]
E --> F["打印 UART 命令就绪信息"]
12.4 串口配置命令
当前代码已实现的命令如下:
show
defaults
save
set anchor_id <value>
set sync_ms <value>
set tag_ms <value>
set server_port <value>
set tcp_cache <value>
set uwb_chan <value>
set preamble <128|1024>
set tx_code <value>
set rx_code <value>
set sfd_type <value>
set tx_pg_delay <hex>
set tx_power <hex>
set data_rate 850k|6m8
set server_ip a.b.c.d
命令语义:
show:打印当前配置defaults:加载内置默认值到 RAMsave:保存当前配置set ...:修改当前配置
13. TCP 网络上传路径
13.1 角色限制
只有 ANCHOR_NODE 才会初始化网络。SYNC_NODE 和 TAG_NODE 会打印:
[NET] disabled for non-anchor role
13.2 网络启动流程
网络实现位于 vendor/User/tcpclinet_init.c。启动顺序如下:
- 从运行时配置读取
server_ip和server_port - 打印
WCHNET版本 - 打印本地和远端网络信息
- 初始化
TIM2作为 WCHNET tick - 调用
ETH_LibInit(...) - 如有需要配置 keepalive
- 创建一个 TCP client socket
- 向服务器发起连接
13.3 运行时服务逻辑
tcp_runtime_loop() 负责:
WCHNET_MainTask()- 轮询全局中断
- 处理 socket 中断
- 刷新发送队列
13.4 TCP 流程图
flowchart TD
A["tcpclient_init()"] --> B["加载 runtime server_ip/server_port"]
B --> C["打印网络信息"]
C --> D["TIM2_Init()"]
D --> E["ETH_LibInit()"]
E --> F["create_tcp_socket()"]
F --> G["WCHNET_SocketConnect()"]
G --> H["runtime loop"]
H --> I["WCHNET_MainTask()"]
I --> J["handle_global_interrupt()"]
J --> K["queue_flush()"]
K --> H
14. OLED 与调试输出
如果定义了 LCD_ENABLE:
anchorOLED 显示标题、角色和厂商字符串sync/tagOLED 显示标题、角色和厂商字符串
同时,UART 调试口会输出以下关键信息:
- 启动横幅
- 芯片 ID
- 当前配置
- 网络状态
- UWB 初始化信息
- Anchor 接收与上传状态
- Sync / Tag 周期发送日志
15. 构建与产物输出
15.1 工具链
当前工程使用 RISC-V GCC 工具链。Makefile 会自动尝试查找:
../toolchains/下的本地 xPack 工具链- macOS 全局 xPack 安装路径
15.2 常用构建命令
构建完整 TDOA 镜像集:
make BUILD_DIR=./build/tdoa_release tdoa_hex
产物位于:
build/tdoa_release/tdoa/ch32v307_anchor_1.hexbuild/tdoa_release/tdoa/ch32v307_anchor_2.hexbuild/tdoa_release/tdoa/ch32v307_anchor_3.hexbuild/tdoa_release/tdoa/ch32v307_anchor_4.hexbuild/tdoa_release/tdoa/ch32v108_sync.hexbuild/tdoa_release/tdoa/ch32v108_tag_1.hex至tag_10.hex
16. 当前工程特点总结
当前分支具有以下特点:
- 它是一个
DW3000 TDOA 原始时间戳采集与上传工程 anchor负责接收、采集和上传,不在本地做定位解算sync和tag都是广播节点- 上层定位算法被刻意放在服务器侧
CH32V307提供以太网上传与 EEPROM 持久化能力CH32V108提供轻量级无线广播节点能力
因此,这个工程适合:
- TDOA 方案 bring-up
- 多 Anchor 时间戳采集验证
- 服务器侧定位算法开发
- Anchor / Sync / Tag 多角色部署实验
17. 关键源码文件说明
| 文件 | 主要职责 |
|---|---|
src/main.c |
顶层启动和角色分发 |
vendor/BP-UWB_LIB/Src/bphero_uwb.c |
DW3000 初始化和公共无线配置 |
vendor/BP-UWB_LIB/Src/rx_main.c |
Anchor 主循环和接收回调流程 |
vendor/BP-UWB_LIB/Src/tx_main.c |
Sync / Tag 周期性延迟发送逻辑 |
vendor/User/uwb_param.h |
运行时配置结构定义 |
vendor/User/uwb_param.c |
默认参数、EEPROM 读写、UART 命令 |
vendor/User/tcpclinet_init.c |
WCHNET 和 TCP Client 实现 |
vendor/User/at24c02.c |
AT24C02 EEPROM 驱动 |
Makefile |
多 MCU、多角色构建和导出规则 |