CH32 + DW1000 一维 UWB 系统
1. 文档范围
它整合了以下多个模块内容,包括:
- 项目概览和 README 内容
- 一维系统设计说明
- 固件与上位机流程分析
- 动态基站扫描设计
- 编译、烧录和联调说明
- 配置命令参考
- AT24C02 持久化说明
- 调试指导
- DW1000 适配总结
当前项目分支聚焦于:
- 基站:
CH32V307 + DW1000 + Ethernet - 标签:
CH32V108 + DW1000 - 测距模式:
DS-TWR - 定位模式:上位机侧一维定位
2. 最终系统方案
部署后的系统由以下部分组成:
- 多个基站沿一条轴线安装
- 一个或多个移动标签沿该轴线运动
- 基站通过 TCP 向上位机发送
range / heartbeat / status / configJSON 行 - 上位机根据相邻基站解算标签 X 坐标
系统目标不仅是输出原始距离,还要提供:
- 可部署的基站固件
- 可部署的标签固件
- 基站以太网上行链路
- 持久化与现场配置能力
- 用于运行、可视化和维护的上位机 GUI
3. 系统架构
3.1 标签
标签固件运行在 CH32V108 上。
它负责:
- 初始化 DW1000
- 执行 DS-TWR 发起端角色
- 在短地址范围
0x0001 ~ 0x00FF内动态发现基站 - 锁定当前活跃的基站邻域
- 在移动过程中持续测距
- 提供本地串口配置与持久化能力
3.2 基站
基站固件运行在 CH32V307 上。
它负责:
- 初始化以太网和 DW1000
- 保持 DW1000 处于接收模式
- 响应标签
POLL - 接收
FINAL - 计算一次距离采样
- 向上位机发布距离与健康状态数据
- 将运行时配置存储到
AT24C02 - 支持通过长按
PE0恢复默认配置
3.3 上位机
上位机应用基于 Python + PySide6。
它负责:
- 接收基站 TCP 连接
- 跟踪基站在线/离线状态
- 维护基站别名和 X 坐标
- 缓存每个标签到各基站的距离
- 选择最优相邻基站对
- 解算一维位置
- 显示实时日志和管理界面
4. 硬件映射
4.1 CH32V307 基站
DW1000 接口
| MCU IO | 外设 | 说明 |
|---|---|---|
PA4 |
DW1000 CSn |
SPI1 片选 |
PA5 |
DW1000 SCK |
SPI1 时钟 |
PA6 |
DW1000 MISO |
SPI1 MISO |
PA7 |
DW1000 MOSI |
SPI1 MOSI |
PB4 |
DW1000 RSTn |
硬件复位 |
PB0 |
DW1000 IRQ |
UWB 中断,EXTI0 |
调试串口
| MCU IO | 外设 | 说明 |
|---|---|---|
PA9 |
UART1_TX |
默认日志输出 |
PA10 |
UART1_RX |
默认 CLI 输入 |
AT24C02
| MCU IO | 外设 | 说明 |
|---|---|---|
PB8 |
AT24C02 SCL |
软件 I2C 时钟 |
PB9 |
AT24C02 SDA |
软件 I2C 数据 |
恢复按键
| MCU IO | 外设 | 说明 |
|---|---|---|
PE0 |
按键 | 低电平有效,长按恢复 |
Ethernet RMII
| MCU IO | 外设 |
|---|---|
PA1 |
RMII_REF_CLK |
PA2 |
RMII_MDIO |
PA7 |
RMII_CRS_DV |
PC1 |
RMII_MDC |
PC4 |
RMII_RXD0 |
PC5 |
RMII_RXD1 |
PB11 |
RMII_TX_EN |
PB12 |
RMII_TXD0 |
PB13 |
RMII_TXD1 |
OLED
| MCU IO | 外设 |
|---|---|
PB10 |
OLED DC |
PB12 |
OLED CS |
PB13 |
OLED SCLK |
PB14 |
OLED RST |
PB15 |
OLED DIN |
4.2 CH32V108 标签
| MCU IO | 外设 | 说明 |
|---|---|---|
PA4 |
DW1000 CSn |
SPI1 片选 |
PA5 |
DW1000 SCK |
SPI1 时钟 |
PA6 |
DW1000 MISO |
SPI1 MISO |
PA7 |
DW1000 MOSI |
SPI1 MOSI |
PB4 |
DW1000 RSTn |
硬件复位 |
PB0 |
DW1000 IRQ |
UWB 中断 |
PA9 |
UART1_TX |
默认日志输出 |
PA10 |
UART1_RX |
默认 CLI 输入 |
PB6 |
AT24C02 SCL |
软件 I2C 时钟 |
PB7 |
AT24C02 SDA |
软件 I2C 数据 |
4.3 硬件注意事项
CH32V307在板级引脚复用上需要同时兼顾DW1000、RMII和OLED。- 最终硬件走线必须以实际板卡原理图为准,不能只依据软件默认配置。
PE0恢复按键按上拉 + 低电平有效处理。
5. 地址规划
5.1 保留范围
- 基站短地址范围:
0x0001 ~ 0x00FF - 标签短地址范围:
0x0101 ~ 0x01FF
5.2 默认输出镜像
执行 make 或 make one_dim_hex 会生成五个镜像:
| 文件 | 角色 | 默认短地址 | 默认 X / 模式 |
|---|---|---|---|
CH32V307_ANCHOR_1.hex |
基站 1 | 0x0001 |
0 mm |
CH32V307_ANCHOR_2.hex |
基站 2 | 0x0002 |
5000 mm |
CH32V307_ANCHOR_3.hex |
基站 3 | 0x0003 |
10000 mm |
CH32V108_TAG_1.hex |
标签 1 | 0x0101 |
自动扫描 |
CH32V108_TAG_2.hex |
标签 2 | 0x0102 |
自动扫描 |
5.3 部署策略
- 基站应按物理位置从左到右编号
- 基站 X 坐标应与编号顺序保持一致
- 标签不再需要静态完整基站列表
- 标签默认会在
0x0001 ~ 0x00FF范围内自动扫描基站
6. 动态基站扫描原理
6.1 为什么之前的静态列表不够用
旧版标签设计依赖 anchor_addrs[]。
在真实现场部署 100-200 个基站时,这种设计会失效,原因包括:
- 标签每次移动时不能依赖人工重新配置
- 标签沿线移动时,活跃基站会不断变化
- 如果运行时修改了基站短地址,而标签列表没有同步更新,测距会立即中断
6.2 新设计
标签现在支持自动发现与跟踪:
-
全范围发现
如果anchor_count == 0,标签会扫描整个基站短地址范围0x0001 ~ 0x00FF。 -
命中并锁定基站
当某个基站回复后,标签会记录该短地址,并将其作为当前活跃邻域中心。 -
邻域跟踪
一次成功回复后,标签会优先访问: - 当前基站
- 下一个基站
- 上一个基站
这与标签沿直线移动的实际模型一致。
- 自动解锁
如果被跟踪的邻域连续多轮无响应,标签会退出锁定状态并恢复全范围扫描。
6.3 运行时行为
你会看到类似日志:
[UWB][TAG] anchors=auto scan=0x0001-0x00FF
[UWB][TAG][LOCK] anchor=0x0011
[UWB][TAG][SCAN] resume=0x0012 after_miss=8
这表示:
- 标签没有固定在静态列表上
- 基站地址改变后,标签可以重新发现新地址
- 标签可以在大规模基站部署中持续移动测距
6.4 代码实现位置
动态扫描逻辑主要位于:
关键状态变量:
g_dynamic_scan_next_addrg_dynamic_lock_anchor_addrg_dynamic_lock_activeg_dynamic_track_indexg_dynamic_track_miss_count
关键辅助函数:
tx_dynamic_scan_enabled()tx_dynamic_next_addr()tx_dynamic_reset_scan()tx_dynamic_pick_target()tx_dynamic_on_poll_sent()tx_dynamic_on_anchor_hit()tx_dynamic_on_anchor_miss()
7. DS-TWR 测距原理
测距链路采用标准三帧 DS-TWR 交互:
- 标签发送
POLL - 基站回复
ACK - 标签延迟发送
FINAL - 基站计算
ToF - 基站将
ToF转换为距离
基站侧计算路径:
- 接收
POLL - 记录时间戳
poll_rx_ts - 发送
ACK - 接收
FINAL - 解析标签嵌入的时间戳
- 计算
tof_dtu - 转换为米
- 补偿距离偏差
- 通过卡尔曼滤波平滑
- 发布
range
8. 一维定位原理
最终 X 坐标由上位机计算。
已知:
- 基站 A 坐标
xA - 基站 B 坐标
xB - 到 A 的距离
dA - 到 B 的距离
dB - 区段长度
L = xB - xA
标签在该区段上的局部坐标为:
x_local = (L + dA - dB) / 2
全局坐标为:
x = xA + clamp(x_local, 0, L)
只有相邻基站才会被视为有效基站对。
9. 固件流程
9.1 顶层启动流程
flowchart TD
A["上电 / 复位"] --> B["main()"]
B --> C["时钟 / 延时 / UART 初始化"]
C --> D["OLED / LED 初始化"]
D --> E["app_config_init()"]
E --> F["加载默认配置或 AT24C02 配置"]
F --> G["tcpclient_init()"]
G --> H["BPhero_UWB_Init()"]
H --> I{"角色"}
I -->|基站| J["rx_main()"]
I -->|标签| K["tx_main()"]
9.2 基站流程
flowchart TD
A["rx_main()"] --> B["rx_enter_mode()"]
B --> C["主循环"]
C --> D["tcp_runtime_loop()"]
D --> E["app_config_poll()"]
E --> F["app_config_heartbeat_task()"]
F --> G["app_config_button_task()"]
G --> H{"IRQ / 帧"}
H -->|POLL| I["立即发送 ACK"]
H -->|FINAL| J["计算距离"]
J --> K["卡尔曼滤波"]
K --> L["app_config_publish_range()"]
L --> C
H -->|RX error| M["rx_enter_mode()"]
M --> C
9.3 带动态扫描的标签流程
flowchart TD
A["tx_main()"] --> B["初始化动态扫描状态"]
B --> C["主循环"]
C --> D["tcp_runtime_loop()"]
D --> E["app_config_poll()"]
E --> F{"tag_state == INIT?"}
F -->|否| G["等待 ACK 或检测 RX 错误"]
G --> H{"收到 ACK?"}
H -->|是| I["锁定当前基站"]
I --> J["发送延迟 FINAL"]
J --> C
H -->|否| K["累计 miss"]
K --> L["达到 miss 阈值后解锁"]
L --> C
F -->|是| M{"启用 auto scan?"}
M -->|是| N["选择动态目标"]
M -->|否| O["使用已配置基站列表"]
N --> P["发送 POLL"]
O --> P
P --> Q["记录目标地址"]
Q --> R["开启 RX 并等待 ACK"]
R --> C
9.4 运行时地址更新流程
这条路径对现场运行非常关键。
flowchart TD
A["CLI set short_addr / TCP set_config short_addr"] --> B["更新 app_config.short_addr"]
B --> C["app_config_finalize()"]
C --> D["BPhero_UWB_ApplyRuntimeAddress()"]
D --> E["dwt_setpanid()"]
E --> F["dwt_setaddress16()"]
F --> G{"基站固件?"}
G -->|是| H["带帧过滤重新进入 RX"]
G -->|否| I["标签下一次 TX 使用新的源地址"]
9.5 AT24C02 + 恢复按键流程
flowchart TD
A["上电"] --> B["app_config_init()"]
B --> C["读取 AT24C02 配置"]
C --> D{"magic/version/checksum 有效?"}
D -->|是| E["应用持久化配置"]
D -->|否| F["使用默认配置"]
E --> G["运行系统"]
F --> G
G --> H["app_config_button_task()"]
H --> I{"PE0 稳定低电平 5s?"}
I -->|否| G
I -->|是| J["恢复默认配置"]
J --> K["保存到 AT24C02"]
K --> G
10. 上位机流程
flowchart TD
A["启动上位机 TCP server"] --> B["基站连接"]
B --> C["读取一行 JSON"]
C --> D{"消息类型"}
D -->|status| E["更新基站状态"]
D -->|heartbeat| F["刷新在线状态"]
D -->|range| G["更新标签-基站距离缓存"]
G --> H["查找相邻基站对"]
H --> I["解算一维 X"]
I --> J["刷新 GUI"]
D -->|config| K["更新配置界面"]
11. 主要源码文件职责
| 文件 | 职责 |
|---|---|
| src/main.c | 项目入口 |
| vendor/User/app_config.c | 运行时配置、CLI、持久化、按键逻辑 |
| vendor/User/at24c02.c | EEPROM 驱动与自检 |
| vendor/User/tcpclinet_init.c | 以太网/TCP 客户端 |
| vendor/User/uwb_init.c | 平台 UWB 初始化桥接 |
| vendor/BP-UWB_LIB/Src/bphero_uwb.c | DW1000 设置与运行时地址应用 |
| vendor/BP-UWB_LIB/Src/rx_main.c | 基站测距逻辑 |
| vendor/BP-UWB_LIB/Src/tx_main.c | 标签测距逻辑与动态扫描 |
| one_dim_location_host/main.py | 上位机入口 |
| one_dim_location_host/app/main_window.py | 上位机 GUI |
| one_dim_location_host/app/solver.py | 一维解算器 |
| one_dim_location_host/app/serial_config_window.py | 串口配置工具 |
12. 通信协议
12.1 UWB 帧
| 类型 | messageData[0] |
方向 | 含义 |
|---|---|---|---|
POLL |
'P' |
标签 -> 基站 | 测距请求 |
ACK |
'A' |
基站 -> 标签 | 测距响应 |
FINAL |
'F' |
标签 -> 基站 | 携带时间戳的最终帧 |
12.2 TCP JSON 行
每条消息都是:
- UTF-8
- 每帧一行
- 以
\n结尾
range
{"type":"range","anchor_id":1,"tag_id":257,"distance_mm":350,"quality":53,"seq":75,"rssi_dbm_x10":-843,"axis_x_mm":0}
heartbeat
{"type":"heartbeat","anchor_id":1,"axis_x_mm":0,"link":1}
status
{"type":"status","role":"anchor","anchor_id":1,"axis_x_mm":0,"fw":"one-d-dw1000"}
get_config
{"type":"get_config"}
set_config
{"type":"set_config","short_addr":17,"axis_x_mm":5000,"heartbeat_ms":1000,"save":1}
config
{"type":"config","result":"ok","role":"anchor","short_addr":17,"pan_id":57034,"axis_x_mm":5000,"heartbeat_ms":1000,"poll_ms":0,"server_ip":"192.168.2.122","server_port":9000}
13. 配置命令
13.1 串口 CLI
默认串口:
UART1115200 8N1
命令:
| 命令 | 含义 |
|---|---|
show |
打印当前配置 |
set <key> <value> |
修改运行时配置 |
save |
将配置持久化到 AT24C02 |
defaults |
恢复内存中的默认配置 |
eeprom_test |
运行全空间 EEPROM 自检 |
支持的 set 键:
short_addrpan_idaxis_x_mmheartbeat_mspoll_msserver_ipserver_portlocal_ipgateway_ipsubnet_maskanchor_addrs
示例:
基站示例
show
set short_addr 0x0011
set axis_x_mm 5000
set local_ip 192.168.2.202
set server_ip 192.168.2.122
set server_port 9000
save
show
标签自动扫描示例
show
set short_addr 0x0101
set poll_ms 120
set anchor_addrs auto
save
show
标签固定列表示例
set anchor_addrs 0x0011,0x0012,0x0013
save
13.2 TCP 远程配置
支持:
get_configset_config
示例:
{"type":"get_config"}
{"type":"set_config","short_addr":17,"axis_x_mm":5000,"heartbeat_ms":1000,"save":1}
14. 持久化设计
14.1 存储对象
项目会将 app_config_t 持久化到 AT24C02。
GPIO 映射:
CH32V307:PB8=SCL,PB9=SDACH32V108:PB6=SCL,PB7=SDA
存储字段包括:
- magic
- version
- pan id
- short address
- heartbeat 或 poll interval
- axis X
- anchor count
- anchor address list
- local/gateway/mask/server IP
- server port
- checksum
14.2 保存路径
- 完成运行时配置归一化
- 重新计算 checksum
- 从地址
0x00开始将结构体写入 EEPROM
14.3 加载路径
- 准备默认配置
- 从 EEPROM 读回配置
- 校验
magic / version / checksum - 使用有效数据覆盖默认配置
- 将最终配置应用到网络和 UWB 运行时
14.4 AT24C02 自检
串口命令:
eeprom_test
测试顺序:
- 备份全部
256 bytes - 向全空间写入测试 pattern
- 读回并比较
- 恢复原始内容
- 再次读回并验证恢复结果
15. 编译与导出
15.1 默认编译
make
或:
make one_dim_hex
15.2 输出文件
CH32V307_ANCHOR_1.hexCH32V307_ANCHOR_2.hexCH32V307_ANCHOR_3.hexCH32V108_TAG_1.hexCH32V108_TAG_2.hex
15.3 默认导出目录
/Volumes/df_瀛樺偍绌洪棿1/CH32_Test/build/
当前 Makefile 已经配置为默认导出到该远程目录。
16. 联调与调试指导
16.1 基本现场检查清单
- 只给一个基站上电
- 确认串口启动日志正常
- 确认上位机显示该基站在线
- 给一个标签上电
- 确认基站打印
range - 确认上位机显示标签位置
- 再逐步扩展到更多基站和标签
16.2 修改基站地址后测距消失
当前设计预期:
- 标签自动扫描模式应能在无需人工重新配置的情况下发现新的基站地址
- 运行时短地址修改应立即更新 DW1000 硬件过滤器
相关修复已经存在于:
tx_main.c中的动态扫描支持bphero_uwb.c中的运行时 UWB 地址应用
16.3 有用日志
标签侧:
[UWB][TAG] anchors=auto scan=0x0001-0x00FF
[UWB][TAG][LOCK] anchor=0x0011
[UWB][TX][NO_ACK] tag=0x0101 target=0x0011 status=...
基站侧:
[UWB][ANCHOR] start short=0x0011 axis_x_mm=0
[UWB][ANCHOR][DIST] ...
16.4 按键误触发保护
当前 PE0 恢复按键逻辑包括:
- 低电平有效假设
50 ms消抖1 s启动忽略窗口
其目的是防止启动阶段和现场干扰导致误判长按。
17. 上位机 UI 与串口工具
17.1 上位机 GUI
主要能力:
- 基站表格
- 标签表格
- 支持缩放和平移的轴线视图
- 实时日志
- 日志文件持久化
- 基站别名管理
- 基站行拖拽重排
- 彩色标签可视化
17.2 串口配置工具
主要能力:
- 接收串口输出
- 发送快捷命令
- 通过表单输入应用配置
- 将
show响应解析回表单 - 日志区域自动滚动
- 保存本地日志文件
串口工具现在支持 anchor_addrs=auto。
18. DW1000 适配总结
本项目已经完成从旧版面向 DW3000 的代码路径到 DW1000 的迁移。
适配工作包括:
- 将底层驱动调用替换为 DW1000 驱动调用
- 保留 RX/TX 角色结构
- 保留 CH32V307 和 CH32V108 双 MCU 支持
- 围绕基于 DW1000 的测距流程重建项目输出结构
20. 最终结论
该项目现在已经形成完整的一维 UWB 系统,具备:
- CH32V307 基站固件
- CH32V108 标签固件
- DW1000 DS-TWR 测距
- 自动基站发现与跟踪
- TCP 上位机集成
- 持久化与现场配置
- 图形化上位机工具
- 面向生产的调试与维护功能
对于当前现场部署,最重要的实践要点是:
- 基站地址保持在
0x0001 ~ 0x00FF - 标签地址保持在
0x0101 ~ 0x01FF - 标签可以在
auto基站扫描模式下运行 - 支持运行时地址修改