CH32 + DW3000 DS-TWR 测距工程技术手册
1. 文档说明
本文档面向当前的 CH32V307 / CH32V108 + DW3000 双角色测距工程,说明系统组成、软件分层、启动流程、DS-TWR 空口交互、RX/TX 代码流程、持久化配置机制、网络上报路径以及构建产物。本文档的目标读者包括研发工程师、集成工程师、现场调试工程师以及交付工程师。
当前工程实现的是标准 DS-TWR 测距链路。工程通过编译宏区分 RX_NODE 与 TX_NODE,其中:
RX_NODE作为应答端,负责接收POLL、发送ACK、接收FINAL并计算距离TX_NODE作为发起端,负责发送POLL、等待ACK、发送延迟FINALCH32V307在具备以太网能力时,可将测距结果通过TCP以JSON形式上报CH32V307 / CH32V108均支持AT24C02持久化参数存储OLED与UART在当前工程中均已接入运行时显示与调试输出
2. 工程介绍
当前工程采用 DW3000 / DWM3000 作为 UWB 收发芯片,采用 CH32V307 与 CH32V108 作为主控平台,构成一套双角色 DS-TWR 测距系统。工程支持在两个 MCU 平台上分别编译为 RX 或 TX 节点,支持 OLED 显示、串口调试、AT24C02 持久化配置以及 CH32V307 上的 TCP 上报能力。测距流程完整覆盖了 POLL -> ACK -> FINAL 三帧交互、时间戳采集、距离计算、结果缓存、前后台分工处理和对外输出。在当前代码状态下,该工程已经不是简单 demo,而是一个具备真实部署接口的嵌入式测距工程基础版本,可用于同类模块互测、异构模块兼容性验证、协议联调以及上层系统集成。
3. 系统组成
3.1 硬件角色
| 角色 | 主控 | UWB器件 | 主要职责 |
|---|---|---|---|
RX_NODE |
CH32V307 或 CH32V108 |
DW3000 / DWM3000 |
接收 POLL、发送 ACK、接收 FINAL、计算距离 |
TX_NODE |
CH32V307 或 CH32V108 |
DW3000 / DWM3000 |
发送 POLL、等待 ACK、发送延迟 FINAL、接收距离结果 |
3.2 软件模块
| 模块 | 文件 | 主要职责 |
|---|---|---|
| 系统入口 | src/main.c |
启动顺序控制与角色分流 |
| 通用UWB初始化 | vendor/BP-UWB_LIB/Src/bphero_uwb.c |
DW3000 探测、配置、地址应用、发射功率配置 |
| RX测距主流程 | vendor/BP-UWB_LIB/Src/rx_main.c |
应答端协议处理、距离计算、OLED/UART/TCP 输出 |
| TX测距主流程 | vendor/BP-UWB_LIB/Src/tx_main.c |
发起端协议处理、周期测距、延迟 FINAL 发送、结果输出 |
| 持久化配置核心 | vendor/User/persist_core.c |
EEPROM 读写、命令解析、运行时配置应用 |
| EEPROM驱动 | vendor/User/at24c02.c |
AT24C02 初始化、读写、自检 |
| 网络客户端 | vendor/User/tcpclinet_init.c |
WCHNET 初始化与 TCP 上报 |
| 板级UWB初始化 | vendor/User/uwb_init.c |
SPI、GPIO、中断与平台初始化 |
4. 总体设计方案
当前工程采用典型的“公共底层 + 角色协议逻辑 + 配置层 + 上报层”设计。
主要设计要点如下:
- 通过编译宏决定固件角色,不在运行时动态切换角色。
- 通过
persist_core在上电时加载 EEPROM 配置,并在运行时同步到 UWB 地址和网络参数。 - 通过
bphero_uwb.c统一管理DW3000初始化、无线参数和发射 RF 配置。 - 通过
tx_main.c与rx_main.c分别实现DS-TWR发起端与应答端逻辑。 - 通过
tcpclinet_init.c在CH32V307平台上提供 TCP 遥测能力。
这一架构的好处是:
- 无线初始化与协议状态机分离
- 配置存储与业务逻辑分离
- 网络层与测距主链路解耦
- 便于在多个 MCU 平台和多个工程分支中复用
5. 启动流程
5.1 主启动链路
系统入口位于 src/main.c,启动顺序如下:
SystemCoreClockUpdate()Delay_Init()USART_Printf_Init(115200)- 打印启动日志、主频、芯片 ID
GPIO_LED_INIT()uwb_led_init()persist_core_init()tcpclient_init()uwb_init()- 根据编译角色进入:
RX_NODE -> rx_main()TX_NODE -> 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["persist_core_init()"]
G --> H["tcpclient_init()"]
H --> I["uwb_init()"]
I --> J{"编译角色"}
J -->|RX_NODE| K["rx_main()"]
J -->|TX_NODE| L["tx_main()"]
6. 持久化配置系统
6.1 存储模型
当前分支使用统一的 persist_core 配置块,并从 EEPROM 地址 0x00 开始读写。配置结构包括:
magicversionmcu_typenode_roleshort_addrpan_idpoll_msdest_addrlocal_ipgateway_ipsubnet_maskserver_ipserver_portchecksum
配置合法性校验条件:
magic匹配version匹配mcu_type与当前主控一致node_role与当前角色一致checksum正确
任一条件不满足时,系统回退到默认配置。
6.2 串口命令集合
当前串口命令解析位于 vendor/User/persist_core.c,统一支持:
showset <key> <value>savedefaultseeprom_test
不同角色支持的键值不同。
无以太网 RX
short_addrpan_id
有以太网 RX
short_addrpan_idlocal_ipgateway_ipsubnet_maskserver_ipserver_port
无以太网 TX
short_addrpan_idpoll_msdest_addr
有以太网 TX
short_addrpan_idpoll_msdest_addrlocal_ipgateway_ipsubnet_maskserver_ipserver_port
6.3 配置初始化流程图
flowchart TD
A["persist_core_init()"] --> B["加载默认值"]
B --> C["初始化 AT24C02"]
C --> D["从 EEPROM 0x00 读取配置"]
D --> E{"配置有效?"}
E -->|否| F["保留默认值"]
E -->|是| G["加载存储配置"]
F --> H["finalize 配置"]
G --> H
H --> I["开启 UART RX IRQ"]
I --> J["应用 UWB 地址配置"]
J --> K["应用网络配置"]
K --> L["打印 show 配置与 CLI ready"]
7. DW3000 初始化
7.1 默认无线参数
公共无线参数在 vendor/BP-UWB_LIB/Src/bphero_uwb.c 中定义。
| 参数 | 当前值 |
|---|---|
| Channel | 5 |
| Preamble Length | 128 |
| PAC | 8 |
| TX Preamble Code | 9 |
| RX Preamble Code | 9 |
| SFD Type | 0 |
| Data Rate | 850K |
| STS | OFF |
| PDOA Mode | DWT_PDOA_M0 |
| TX PG Delay | 0x34 |
| TX Power | 0xFEFEFEFE |
7.2 初始化步骤
BPhero_UWB_Init() 中的主要执行步骤如下:
- 应用运行时无线参数覆盖
- 切换 SPI 低速
- 释放
DW3000复位 - 等待器件上电稳定
- 通过 IO 唤醒器件
dw3000_driver_probe()dwt_softreset(0)dwt_checkidlerc()dwt_initialise(DWT_DW_INIT)dwt_configure(&config)dwt_configuretxrf(&txconfig_options)- 切换 SPI 高速
- 设置天线延时、PAN ID、短地址
- 清状态位
- 开中断
- 使能
LNA/PA与器件 LED
7.3 初始化流程图
flowchart TD
A["BPhero_UWB_Init()"] --> B["apply_runtime_radio_config()"]
B --> C["spi_set_rate_low()"]
C --> D["release reset / wait settle"]
D --> E["wakeup_device_with_io()"]
E --> F["dw3000_driver_probe()"]
F --> G["dwt_softreset(0)"]
G --> H["dwt_checkidlerc()"]
H --> I["dwt_initialise(DWT_DW_INIT)"]
I --> J["dwt_configure(&config)"]
J --> K["dwt_configuretxrf(&txconfig_options)"]
K --> L["spi_set_rate_high()"]
L --> M["Apply PAN ID / short address"]
M --> N["Enable IRQ / LNA / LEDs"]
8. DS-TWR 空口协议
8.1 帧类型
当前测距协议为标准 DS-TWR,使用三类消息:
P:POLLA:ACKF:FINAL
8.2 各帧职责
| 帧类型 | 发送方 | 作用 |
|---|---|---|
P |
TX |
发起一轮测距 |
A |
RX |
对 POLL 立即响应,同时携带缓存的距离/功率信息 |
F |
TX |
携带三组时间戳,用于 RX 端完成 DS-TWR 计算 |
8.3 时间戳集合
FINAL 中携带:
poll_tx_tsresp_rx_tsfinal_tx_ts
RX 端本地采集:
poll_rx_tsresp_tx_tsfinal_rx_ts
随后 RX 端使用标准漂移补偿 DS-TWR 公式计算飞行时间。
8.4 空口时序图
sequenceDiagram
participant TX as "TX Node"
participant RX as "RX Node"
TX->>RX: "POLL (P)"
Note right of RX: "记录 poll_rx_ts"
RX->>TX: "ACK (A)"
Note right of RX: "记录 resp_tx_ts"
Note left of TX: "记录 poll_tx_ts / resp_rx_ts"
TX->>RX: "FINAL (F)"
Note left of TX: "写入 poll_tx_ts / resp_rx_ts / final_tx_ts"
Note right of RX: "记录 final_rx_ts"
RX->>RX: "执行 DS-TWR 距离计算"
9. RX 节点代码流程
9.1 RX 角色说明
RX_NODE 是当前系统中的应答端。它的职责包括:
- 进入立即接收状态并开启帧过滤
- 接收
POLL - 发送
ACK - 接收
FINAL - 计算距离
- 更新 OLED
- 输出 UART 日志
- 在具备网络能力时发送 TCP JSON
9.2 中断处理路径
中断核心逻辑在 Simple_Rx_Callback() 中。
收到 POLL 时
- 读取状态并校验帧
- 校验 STS(若启用)
- 解析源地址
- 构造
ACK - 把最近缓存距离和功率写入
ACKpayload - 配置
RX timeout与RX-after-TX - 立即发送
ACK - 记录
poll_rx_ts - 保持硬件 RX 持续等待
FINAL
收到 FINAL 时
- 获取
resp_tx_ts - 获取
final_rx_ts - 从
FINAL中提取poll_tx_ts / resp_rx_ts / final_tx_ts - 计算
Ra / Rb / Da / Db - 计算
tof_dtu - 转换为米
- 加入
RX_DISTANCE_BIAS_MM - 按
tag地址缓存距离与接收功率 - 设置前台打印标志
9.3 前台主循环
rx_main() 前台主循环负责:
- 调用
persist_core_poll() - 调用
tcp_runtime_loop() - 在非中断上下文中消费距离结果
- 打印
[UWB][RX][DIST] ... - 构造并发送 TCP JSON
- 在超时或接收错误时重新进入
rx_enter_mode()
9.4 RX 前台流程图
flowchart TD
A["rx_main() start"] --> B["rx_enter_mode()"]
B --> C["前台主循环"]
C --> D["persist_core_poll()"]
D --> E["tcp_runtime_loop()"]
E --> F{"是否有待输出距离?"}
F -->|是| G["打印 UART 距离日志"]
G --> H["发送 TCP JSON"]
F -->|否| I{"是否 RX timeout / error?"}
H --> I
I -->|是| J["rx_enter_mode()"]
I -->|否| C
J --> C
9.5 RX 中断流程图
flowchart TD
A["Simple_Rx_Callback()"] --> B{"SYS_STATUS_RXFCG?"}
B -->|否| Z["清错误并重入 RX"]
B -->|是| C["读取帧数据"]
C --> D{"消息类型"}
D -->|P| E["构造 ACK"]
E --> F["发送 ACK + RESPONSE_EXPECTED"]
F --> G["记录 poll_rx_ts"]
G --> Y["保持 RX 持续等待"]
D -->|F| H["读取时间戳"]
H --> I["执行 DS-TWR 计算"]
I --> J["加入距离偏置"]
J --> K["缓存距离与功率"]
K --> L["置位前台日志标志"]
L --> Z
D -->|M| M["中继文本到 UART/TCP"]
M --> Z
Y --> N{"keep_rx_armed?"}
N -->|是| O["退出中断"]
Z --> P["rx_enter_mode()"]
P --> O
10. TX 节点代码流程
10.1 TX 角色说明
TX_NODE 是当前系统中的发起端。它的职责包括:
- 周期性启动一轮测距
- 发送
POLL - 等待
ACK - 发送延迟
FINAL - 接收基站返回的缓存距离
- 更新 OLED
- 输出 UART 日志
- 在
CH32V307上可通过 TCP 输出结果
10.2 状态机
当前状态机只有两个状态:
TAG_INITTAG_POLL_SENT
TAG_INIT
- 空闲状态
- 前台主循环会立即发起下一轮测距
TAG_POLL_SENT
- 表示
POLL已经发送,正在等待ACK - 前台主循环会累计等待时间
- 若超过阈值则打印超时日志并强制结束本轮测距
10.3 中断处理路径
核心中断逻辑位于 Tx_Simple_Rx_Callback()。
收到期望 ACK 时
- 读取帧和源地址
- 获取
poll_tx_ts - 获取
resp_rx_ts - 计算
final_tx_time - 配置延迟发送时间
- 填充
FINAL时间戳 - 发出延迟
FINAL - 等待
TXFRS - 从
ACK中提取锚点返回的距离和功率 - 缓存结果并置位前台输出标志
- 结束本轮状态机
收到异常帧或超时/错误时
- 清状态
forcetrxoff- 恢复到
TAG_INIT
10.4 前台主循环
tx_main() 前台主循环负责:
- 调用
persist_core_poll() - 调用
tcp_runtime_loop() - 消费待输出距离结果
- 若当前空闲,则调用
BPhero_Distance_Measure_Specail_TAG() - 若处于等待
ACK,则累加等待时间 - 若超时,则打印
[UWB][TX][TIMEOUT] ...并重置状态 - 按
poll_ms延时
10.5 TX 前台流程图
flowchart TD
A["tx_main() start"] --> B["渲染 OLED 初始布局"]
B --> C["前台主循环"]
C --> D["persist_core_poll()"]
D --> E["tcp_runtime_loop()"]
E --> F{"是否有待输出距离?"}
F -->|是| G["打印 UART 日志 / 发送 TCP JSON"]
F -->|否| H{"Tag_State == TAG_INIT?"}
G --> H
H -->|是| I["发送 POLL"]
I --> J["进入等待 ACK 状态"]
H -->|否| K["累计等待时间"]
K --> L{"是否超时?"}
L -->|是| M["打印 TIMEOUT 并重置状态"]
L -->|否| N["Delay poll_ms"]
J --> N
M --> N
N --> C
10.6 TX 中断流程图
flowchart TD
A["Tx_Simple_Rx_Callback()"] --> B{"RXFCG?"}
B -->|否| Z["清状态并结束本轮"]
B -->|是| C["读取 ACK 帧"]
C --> D{"ACK 且 TAG_POLL_SENT?"}
D -->|否| Z
D -->|是| E["获取 poll_tx_ts / resp_rx_ts"]
E --> F["计算 delayed final_tx_time"]
F --> G["填充 FINAL 时间戳"]
G --> H["dwt_starttx(DWT_START_TX_DELAYED)"]
H --> I{"TXFRS 是否完成?"}
I -->|否| Z
I -->|是| J["解析锚点返回距离/功率"]
J --> K["缓存结果"]
K --> L["置位前台日志标志"]
L --> Z
11. 网络上报链路
11.1 以太网能力模型
CH32V307 支持以太网和 WCHNET TCP 客户端;CH32V108 保留同一组接口,但网络路径为空实现,仅打印:
[NET] disabled for current MCU build
11.2 默认网络参数
vendor/User/tcpclinet_init.c 中当前默认值为:
- Local IP:
192.168.2.12 - Gateway IP:
192.168.2.1 - Subnet Mask:
255.255.255.0 - Remote IP:
192.168.2.249 - Remote Port:
8888
这些参数在启动时会被持久化配置覆盖。
11.3 网络运行时流程
tcpclient_init() 执行流程:
- 应用持久化网络参数
- 初始化
TIM2作为WCHNET时基 - 初始化 Ethernet 库
- 创建 TCP client socket
- 发起连接
前台必须周期调用 tcp_runtime_loop(),以便:
- 运行
WCHNET_MainTask() - 分发全局中断
- 处理 socket connect / recv / disconnect / timeout
- 保持遥测链路存活
11.4 JSON 上报格式
RX 侧示例:
{"type":"uwb_range","seq":12,"role":"rx","anchor":"0x0001","tag":"0x0002","dist_cm":153,"rx_dbm":"-78.25"}
TX 侧示例:
{"type":"uwb_range","seq":12,"role":"tx","tag":"0x0002","anchor":"0x0001","dist_cm":153,"rx_dbm":"-78.25"}
12. OLED 与 UART 输出
12.1 OLED
在 LCD_ENABLE 打开时,RX 和 TX 都会启用 OLED 显示。
RX OLED 显示内容:
- 角色名称
- 基站地址
- 标签地址
- 距离与接收功率
TX OLED 显示内容:
- 角色名称
- 标签地址
- 当前目标基站地址
- 距离与接收功率
12.2 UART
UART 默认 115200。当前典型输出内容包括:
- Boot 启动日志
- Config 配置日志
- Net 网络日志
- UWB 启动日志
- 距离成功日志
- 超时诊断日志
13. 编译系统
13.1 默认入口
顶层编译入口为:
make
默认展开为:
make four_hex
13.2 产物
four_hex 会生成:
ch32v307_demo_rx.hexch32v307_demo_tx.hexch32v108_demo_rx.hexch32v108_demo_tx.hex
14. 调试说明
14.1 TX TIMEOUT 的含义
[UWB][TX][TIMEOUT] 表示 TX 节点在 TAG_POLL_SENT 状态停留过久,本质含义是:
- 没有按预期收到
ACK - 或者收发状态机没有正常完成
它不是距离公式错误,而是链路交互状态异常。
14.2 RX 恢复逻辑
当 RX 检测到 RX timeout 或 RX error 标志时,会调用 rx_enter_mode() 强制重新进入清洁接收态。这是当前代码里的标准恢复路径。
14.3 持久化配置失效
当 EEPROM 中配置不满足以下条件时:
magicversion- MCU 类型
- 角色
checksum
系统会回退到默认参数。
15. 文件职责总表
| 文件 | 职责 |
|---|---|
src/main.c |
启动顺序与角色分流 |
vendor/BP-UWB_LIB/Src/bphero_uwb.c |
DW3000 公共初始化、地址应用、RF 配置 |
vendor/BP-UWB_LIB/Src/rx_main.c |
应答端测距与 RX 侧结果输出 |
vendor/BP-UWB_LIB/Src/tx_main.c |
发起端测距与 TX 侧结果输出 |
vendor/User/persist_core.c |
持久化配置、UART CLI、运行时应用 |
vendor/User/at24c02.c |
EEPROM 驱动与自检 |
vendor/User/tcpclinet_init.c |
以太网、TCP 初始化与上报 |
Makefile |
双 MCU、双角色构建与导出 |
16. 结论
当前 main 分支已经具备一套完整的 DW3000 DS-TWR 测距固件框架,支持:
CH32V307 / CH32V108双平台RX / TX双角色OLED与UART本地输出AT24C02持久化配置CH32V307侧TCP遥测上报
从软件结构上看,该分支已经清晰地分为:
- 启动与平台层
- 通用 UWB 初始化层
- 协议状态机层
- 持久化配置层
- 网络上报层
这使得当前工程既适合直接做测距联调,也适合作为后续扩展与交付的基础版本。