CH32 + DW3000 TDOA 工程技术手册

1. 文档说明

本文档用于说明当前工程分支中 CH32V307 / CH32V108 + DW3000TDOA 演示方案,包括系统组成、设备角色、固件启动流程、UWB 空口协议、Anchor 端时间戳汇聚机制、TCP 上传链路、运行时配置方式、AT24C02 持久化能力以及构建部署方法。阅读对象为客户、项目集成方、现场调试人员以及研发支持人员。

当前工程是一个面向 TDOA 上层定位算法的数据采集与上传工程。该工程中:

  • CH32V307 用作 ANCHOR_NODE
  • CH32V108 用作 SYNC_NODETAG_NODE
  • DW3000 / 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 Anchor
  • 1 x Sync
  • 1..10 x Tag

对应构建产物包括:

  • ch32v307_anchor_1.hex
  • ch32v307_anchor_2.hex
  • ch32v307_anchor_3.hex
  • ch32v307_anchor_4.hex
  • ch32v108_sync.hex
  • ch32v108_tag_1.hexch32v108_tag_10.hex

3. 方案总体设计

当前分支采用的是“边缘节点采集时间戳 + 中心侧完成 TDOA 计算”的架构,而不是 MCU 本地完成定位解算。其设计要点如下:

  1. sync 按固定周期广播参考帧,帧内携带自身发射时间戳。
  2. tag 按固定周期广播标签帧,帧内携带自身发射时间戳。
  3. anchor 接收这两类帧,并记录本地接收时间戳。
  4. anchor 将组合后的原始时间戳字符串通过 TCP 发送到服务器。
  5. 服务器或上位机在更高算力环境下完成 TDOA 计算、定位和可视化。

这种方案的优点是:

  • MCU 侧逻辑简单,重点集中在收发时序与时间戳采集
  • 上层算法可以在服务器端独立迭代
  • Anchor 节点功能统一,便于多节点部署
  • Sync / Tag 节点结构轻量,便于扩展数量

4. 设备角色说明

4.1 Anchor 节点

ANCHOR_NODE 通过 vendor/BP-UWB_LIB/Src/rx_main.c 实现,是当前系统中的接收与上传节点。

主要职责:

  • 持续保持 DW3000 接收状态
  • 接收 IEEE 802.15.4 数据帧
  • 区分 ST 类型消息
  • 通过 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。系统启动顺序如下:

  1. SystemCoreClockUpdate()
  2. Delay_Init()
  3. USART_Printf_Init(115200)
  4. 打印欢迎信息、主频和芯片 ID
  5. GPIO_LED_INIT()
  6. uwb_led_init()
  7. uwb_config_init()
  8. tcpclient_init()
  9. uwb_init()
  10. BPhero_UWB_Init()
  11. 根据角色进入 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_channel
  • preamble_length
  • tx_preamble_code
  • rx_preamble_code
  • sfd_type
  • data_rate
  • tx_pg_delay
  • tx_power

7. DW3000 初始化流程

完整的 DW3000 初始化由 vendor/BP-UWB_LIB/Src/bphero_uwb.c 中的 BPhero_UWB_Init() 完成。

执行步骤如下:

  1. 加载运行时无线参数
  2. 设置 SPI 低速
  3. 释放 DW3000 复位
  4. 等待上电稳定
  5. 通过 IO 唤醒器件
  6. dw3000_driver_probe()
  7. dwt_softreset(0)
  8. 检查 IDLE_RC
  9. dwt_initialise(DWT_DW_INIT)
  10. dwt_configure(&config)
  11. dwt_configuretxrf(&txconfig_options)
  12. 切换 SPI 到高速
  13. 设置收发天线延时
  14. 设置 PAN IDSHORT_ADDR
  15. 清除状态寄存器
  16. 打开接收相关中断
  17. 打开 LNA/PA
  18. 打开 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() 的主循环行为如下:

  1. 初始化 OLED 文本
  2. 打印 Anchor 启动日志
  3. 调用 tdoa_anchor_enter_rx() 进入接收态
  4. 进入死循环:
  5. uwb_config_poll_uart()
  6. tcp_runtime_loop()
  7. 如果检测到接收超时或接收错误,则重新进入 RX
  8. 延时 10ms

9.2 接收回调逻辑

真正的数据接收处理在 Simple_Rx_Callback() 中完成。

处理流程如下:

  1. 清空本地 RX buffer
  2. 暂时关闭帧过滤
  3. 读取 SYS_STATUS
  4. 如果存在 RXFCG
  5. 清除 RXFCG
  6. 读取帧长
  7. 读取完整帧数据
  8. 转换为 srd_msg_dsss
  9. 检查 messageData[TDOA_MSG_TYPE_IDX]
  10. 如果是 S 帧:
  11. 更新最近两条 sync 记录
  12. 如果有待处理 tag 缓存,则立即 flush
  13. 如果是 T 帧:
  14. 将该 tag 记录加入队列
  15. 重新进入 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_NODETAG_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 周期发送流程

每个周期执行步骤如下:

  1. 计算下一次延迟发送时间
  2. 将目标发送时间戳写入帧载荷
  3. 清除 TX 相关状态位
  4. 写入 delayed TX 时间
  5. 写 TX buffer 和 frame control
  6. 必要时强制 TX 时钟打开
  7. 启动 delayed TX
  8. 等待 TXFRS
  9. 若超时或启动失败:
  10. 打印日志
  11. dwt_forcetrxoff()
  12. 清状态
  13. 恢复时钟
  14. 若成功:
  15. 打印发送日志
  16. 序号递增

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_id
  • sync_period_ms
  • tag_period_ms
  • server_ip
  • server_port
  • tcp_cache_lines
  • uwb_channel
  • data_rate
  • preamble_length
  • tx_preamble_code
  • rx_preamble_code
  • sfd_type
  • tx_pg_delay
  • tx_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:加载内置默认值到 RAM
  • save:保存当前配置
  • set ...:修改当前配置

13. TCP 网络上传路径

13.1 角色限制

只有 ANCHOR_NODE 才会初始化网络。SYNC_NODETAG_NODE 会打印:

[NET] disabled for non-anchor role

13.2 网络启动流程

网络实现位于 vendor/User/tcpclinet_init.c。启动顺序如下:

  1. 从运行时配置读取 server_ipserver_port
  2. 打印 WCHNET 版本
  3. 打印本地和远端网络信息
  4. 初始化 TIM2 作为 WCHNET tick
  5. 调用 ETH_LibInit(...)
  6. 如有需要配置 keepalive
  7. 创建一个 TCP client socket
  8. 向服务器发起连接

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

  • anchor OLED 显示标题、角色和厂商字符串
  • sync/tag OLED 显示标题、角色和厂商字符串

同时,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.hex
  • build/tdoa_release/tdoa/ch32v307_anchor_2.hex
  • build/tdoa_release/tdoa/ch32v307_anchor_3.hex
  • build/tdoa_release/tdoa/ch32v307_anchor_4.hex
  • build/tdoa_release/tdoa/ch32v108_sync.hex
  • build/tdoa_release/tdoa/ch32v108_tag_1.hextag_10.hex

16. 当前工程特点总结

当前分支具有以下特点:

  • 它是一个 DW3000 TDOA 原始时间戳采集与上传工程
  • anchor 负责接收、采集和上传,不在本地做定位解算
  • synctag 都是广播节点
  • 上层定位算法被刻意放在服务器侧
  • 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、多角色构建和导出规则