CH32 + DW1000 一维 UWB 系统

1. 文档范围

它整合了以下多个模块内容,包括:

  • 项目概览和 README 内容
  • 一维系统设计说明
  • 固件与上位机流程分析
  • 动态基站扫描设计
  • 编译、烧录和联调说明
  • 配置命令参考
  • AT24C02 持久化说明
  • 调试指导
  • DW1000 适配总结

当前项目分支聚焦于:

  • 基站:CH32V307 + DW1000 + Ethernet
  • 标签:CH32V108 + DW1000
  • 测距模式:DS-TWR
  • 定位模式:上位机侧一维定位

2. 最终系统方案

部署后的系统由以下部分组成:

  • 多个基站沿一条轴线安装
  • 一个或多个移动标签沿该轴线运动
  • 基站通过 TCP 向上位机发送 range / heartbeat / status / config JSON 行
  • 上位机根据相邻基站解算标签 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 在板级引脚复用上需要同时兼顾 DW1000RMIIOLED
  • 最终硬件走线必须以实际板卡原理图为准,不能只依据软件默认配置。
  • PE0 恢复按键按 上拉 + 低电平有效 处理。

5. 地址规划

5.1 保留范围

  • 基站短地址范围:0x0001 ~ 0x00FF
  • 标签短地址范围:0x0101 ~ 0x01FF

5.2 默认输出镜像

执行 makemake 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 新设计

标签现在支持自动发现与跟踪:

  1. 全范围发现
    如果 anchor_count == 0,标签会扫描整个基站短地址范围 0x0001 ~ 0x00FF

  2. 命中并锁定基站
    当某个基站回复后,标签会记录该短地址,并将其作为当前活跃邻域中心。

  3. 邻域跟踪
    一次成功回复后,标签会优先访问:

  4. 当前基站
  5. 下一个基站
  6. 上一个基站

这与标签沿直线移动的实际模型一致。

  1. 自动解锁
    如果被跟踪的邻域连续多轮无响应,标签会退出锁定状态并恢复全范围扫描。

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_addr
  • g_dynamic_lock_anchor_addr
  • g_dynamic_lock_active
  • g_dynamic_track_index
  • g_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 交互:

  1. 标签发送 POLL
  2. 基站回复 ACK
  3. 标签延迟发送 FINAL
  4. 基站计算 ToF
  5. 基站将 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

默认串口:

  • UART1
  • 115200 8N1

命令:

命令 含义
show 打印当前配置
set <key> <value> 修改运行时配置
save 将配置持久化到 AT24C02
defaults 恢复内存中的默认配置
eeprom_test 运行全空间 EEPROM 自检

支持的 set 键:

  • short_addr
  • pan_id
  • axis_x_mm
  • heartbeat_ms
  • poll_ms
  • server_ip
  • server_port
  • local_ip
  • gateway_ip
  • subnet_mask
  • anchor_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_config
  • set_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 映射:

  • CH32V307PB8 = SCLPB9 = SDA
  • CH32V108PB6 = SCLPB7 = 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 保存路径

  1. 完成运行时配置归一化
  2. 重新计算 checksum
  3. 从地址 0x00 开始将结构体写入 EEPROM

14.3 加载路径

  1. 准备默认配置
  2. 从 EEPROM 读回配置
  3. 校验 magic / version / checksum
  4. 使用有效数据覆盖默认配置
  5. 将最终配置应用到网络和 UWB 运行时

14.4 AT24C02 自检

串口命令:

eeprom_test

测试顺序:

  1. 备份全部 256 bytes
  2. 向全空间写入测试 pattern
  3. 读回并比较
  4. 恢复原始内容
  5. 再次读回并验证恢复结果

15. 编译与导出

15.1 默认编译

make

或:

make one_dim_hex

15.2 输出文件

  • CH32V307_ANCHOR_1.hex
  • CH32V307_ANCHOR_2.hex
  • CH32V307_ANCHOR_3.hex
  • CH32V108_TAG_1.hex
  • CH32V108_TAG_2.hex

15.3 默认导出目录

  • /Volumes/df_瀛樺偍绌洪棿1/CH32_Test/build/

当前 Makefile 已经配置为默认导出到该远程目录。

16. 联调与调试指导

16.1 基本现场检查清单

  1. 只给一个基站上电
  2. 确认串口启动日志正常
  3. 确认上位机显示该基站在线
  4. 给一个标签上电
  5. 确认基站打印 range
  6. 确认上位机显示标签位置
  7. 再逐步扩展到更多基站和标签

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 基站扫描模式下运行
  • 支持运行时地址修改