refactor: 重构架构并整理仓库

This commit is contained in:
Horldsence
2026-09-23 10:56:57 +08:00
parent 680adae339
commit f8aeafdfc2
101 changed files with 14417 additions and 11399 deletions
+49 -94
View File
@@ -32,12 +32,11 @@
| 文档 | 回答的问题 | 读者 |
| --- | --- | --- |
| [`ARCHITECTURE.md`](../ARCHITECTURE.md) | 整套 RoboMaster CAN 控制系统的**全系统分层规划**(含云台、发射机构、delta10 IMU、T153 实时性、9 电机) | 架构决策者 |
| [`docs/architecture.md`](architecture.md) | 本仓库**已实现**部分的解耦点:`CanLink` seam、被控对象模型、自动整定两阶段搜索、测试策略 | 改代码的人 |
| **本文** | **底盘这一块**怎么开发、怎么标定、怎么整定、怎么上车、出事怎么办 | 底盘开发 / 调试 / 上车的人 |
| [`ARCHITECTURE.md`](../ARCHITECTURE.md) | 职责型 workspace、允许依赖、std/unsafe 策略、实现边界 | 架构决策者 |
| [`docs/architecture.md`](architecture.md) | 架构文档入口,不复制规范 | 改代码的人 |
| **本文** | **底盘这一块**怎么开发、标定、整定、上车与排障 | 底盘开发 / 调试 / 上车的人 |
`ARCHITECTURE.md` 里的 `rm-*` crate 划分是**规划**;本仓库实际落地为下面这套 crate 命名
(功能对应关系见第 5 节)。本文只覆盖底盘切片,云台 / 发射 / IMU 不在范围内。
本文只覆盖四轮底盘切片;云台、发射机构与 DETA10 真实载荷布局不在当前范围内。
---
@@ -46,7 +45,7 @@
### 2.1 无硬件(macOS / Linux / CI 均可)
```sh
cargo test --workspace # 单测 + 闭环仿真 + 整定,全部无需硬件
cargo test --workspace --all-targets # 单测 + 闭环仿真 + 整定,全部无需硬件
cargo run -p chassisd -- --mock # 进程内电机模型的全链路演示
cargo run -p chassis-tune -- presets # 看有哪些整定超参数预设
just tune-smoke # 确定性整定冒烟(CI 同款)
@@ -57,10 +56,10 @@ just lint # fmt + clippy(与 CI 同标准)
```sh
# 1) 只验链路与反馈,绝不发非零电流(接线排查第一步)
cargo run --release -p chassis-bench --bin chassis-spin -- --device /dev/ttyACM0 --dry-run
cargo run --release -p spin -- chassis --device /dev/ttyACM0 --dry-run
# 2) 四轮定性旋转(轮子必须架空或整车固定,急停可及)
cargo run --release -p chassis-bench --bin chassis-spin -- --device /dev/ttyACM0 --rpm 500
cargo run --release -p spin -- chassis --device /dev/ttyACM0 --rpm 500
# 3) 先离线整定,再上台架复验(用版本化的超参数预设)
cargo run --release -p chassis-tune -- sweep --preset config/tune.toml --preset-name sim_bench_model
@@ -89,8 +88,8 @@ T153 (Linux) Gm3508 × 4 + C620
| 路径 | 打开方式 | 适用 |
| --- | --- | --- |
| **slcan**(`dji-esc-slcan`) | 用户态直接读写 `/dev/ttyACM0`(Lawicel ASCII 协议) | 免 `ip link`、绕开 `gs_usb` 驱动;台架与初始联调首选 |
| **SocketCAN**(`dji-esc-socketcan`) | `ip link set can0 up type can bitrate 1000000 restart-ms 100` | 板载原生 CAN;生产部署 |
| **slcan**(`slcan`) | 用户态直接读写 `/dev/ttyACM0`(Lawicel ASCII 协议) | 免 `ip link`、绕开 `gs_usb` 驱动;台架与初始联调首选 |
| **SocketCAN**(`socketcan`) | `ip link set can0 up type can bitrate 1000000 restart-ms 100` | 板载原生 CAN;生产部署 |
### 3.2 设备与协议常量(C620 手册)
@@ -105,11 +104,11 @@ T153 (Linux) Gm3508 × 4 + C620
| 反馈频率 | 每电调 1 kHz |
| 减速比 | GM3508 = 3591 / 187 ≈ **19.203**(转子 : 输出轴) |
C620 **内部只闭电流环**:转速环 / 位置环由主控侧实现(`motor-core::Pid`,挂在 `EscBus` 的 1 kHz 节拍上)。
C620 **内部只闭电流环**:转速环 / 位置环由主控侧实现(`motor-control::Pid`,挂在 `EscBus` 的 1 kHz 节拍上)。
### 3.3 电调 ID 与安装方向
默认约定(`config/chassis.toml.example`、`chassis-spin` 默认值一致):
默认约定(`config/chassis.toml.example`、`spin chassis` 默认值一致):
| 轮位 | 轮序下标 | 电调 ID | 安装方向 |
| --- | --- | --- | --- |
@@ -125,7 +124,7 @@ C620 **内部只闭电流环**:转速环 / 位置环由主控侧实现(`moto
| 场景 | 建议限幅(raw) | 说明 |
| --- | --- | --- |
| 空载台架(`chassis-spin` 默认) | **1500**(≈1.8 A) | 需越过静摩擦(实测 400~600 raw)并留整定余量 |
| 空载台架(`spin chassis` 默认) | **1500**(≈1.8 A) | 需越过静摩擦(实测 400~600 raw)并留整定余量 |
| 整车实车 | 6000 ~ 12000 | 视供电与机械而定;同时是 PID 抗饱和的钳位点 |
> **限流不是"越大越安全"**:它同时是 PID 输出的钳位点。设得远大于实际所需,
@@ -135,7 +134,7 @@ C620 **内部只闭电流环**:转速环 / 位置环由主控侧实现(`moto
- 电调供电与主控**共地**;CAN_H / CAN_L 不接反,两端 120Ω 终端电阻齐全。
- 轮子**架空**或整车固定;急停开关在手可及处。
- 首次上电**先 `--dry-run`**,确认四轮反馈都在刷(`chassis-spin` 会打印每个电调的
- 首次上电**先 `--dry-run`**,确认四轮反馈都在刷(`spin chassis` 会打印每个电调的
转速 / 温度 / 反馈年龄)。
---
@@ -161,7 +160,7 @@ v_RR = vx - vy + a·ω
```
正解是逆解的左伪逆(JᵀJ 对角,可解析求平均)。实现见
[`crates/mecanum`](../crates/mecanum):`Mecanum::wheel_speeds` / `twist` /
[`mecanum`](../crates/mecanum):`Mecanum::wheel_speeds` / `twist` /
`wheel_rpms` / `twist_from_wheel_rpms`。
### 4.3 单位与层级(最容易出错的地方)
@@ -175,7 +174,7 @@ v_RR = vx - vy + a·ω
| 控制量 effort | 驱动 / PID | 协议 **raw**(ESC = 电流,C620 ±16384) |
| `kp/ki/kd/feedforward` | 速度环 | raw/rpm、raw/(rpm·s)、raw/(rpm/s)、raw/rpm |
- **减速比换算只发生在一个地方**:`motor-core::OutputShaft` 包装(`转子 = 输出轴 × gear_ratio`)。
- **减速比换算只发生在一个地方**:`motor / esc-bus::OutputShaft` 包装(`转子 = 输出轴 × gear_ratio`)。
`EscBus` / `EscMotor` 只认转子,绝不碰减速比;`EscConfig::gear_ratio` 仅作配置元数据。
- PID 的接线顺序:`ChassisController` 算出**轮端** rpm → `OutputShaft` 换算成转子 rpm →
`EscMotor` 的速度环以转子 rpm 闭环。
@@ -184,77 +183,34 @@ v_RR = vx - vy + a·ω
## 5. 软件架构
### 5.1 Crate 分层与职责
### 5.1 职责边界
**依赖只能向下**(上层可依赖下层,反之不行):
完整 crate 清单和允许依赖见 [`ARCHITECTURE.md`](../ARCHITECTURE.md)。底盘路径上的关键职责是:
```
第 4 层 应用 / 工具 chassisd chassis-tune chassis-bench
(含两个 bin)
第 3 层 领域 chassis-config mecanum dji-esc-sim
(schema / 运动学 / 被控对象 + 离线 harness)
第 2 层 协议 / 设备 dji-esc(C620 编解码 + EscBus 驱动 + 命令租约)
第 1 层 抽象 can-link(CanFrame + 通道对) motor-core(能力 trait + PID)
第 0 层 传输 dji-esc-slcan(串口) / dji-esc-socketcan(内核 CAN)
```
| 职责 | crate |
| --- | --- |
| CAN 帧、总线接口、链路与 Mock | `can` |
| 电调协议 | `motor-protocol` |
| 设备契约、命令/状态/减速比/注册表 | `motor` |
| 帧泵、租约、反馈新鲜度、零电流 | `esc-bus` |
| PID、速度/位置环、组合策略 | `pid`、`motor-control` |
| 麦轮运动学 | `mecanum` |
| 配置 schema | `chassis-config` |
| 台架安全与采样 | `bench` |
| 应用入口 | `chassisd`、`spin`、`chassis-tune` |
`dji-esc-slcan` / `dji-esc-socketcan` 只实现 `can-link` 的通道语义,**不含任何应用逻辑**;
它们被第 4 层的工具与守护进程选用,而不是被协议层依赖。
crate 内源码保持扁平,不建立 `src/<module>/` 子目录。
| Crate | 职责 | 平台 |
| --- | --- | --- |
| [`motor-core`](../crates/motor-core) | 能力 trait(`VelocityDrive` 等)、`MotorState`、`Pid`、`VelocityController`、`OutputShaft` | 纯逻辑,全平台 |
| [`can-link`](../crates/can-link) | `CanFrame` + `CanLink` 通道对(**无协议知识**) | 纯逻辑,全平台 |
| [`dji-esc`](../crates/dji-esc) | C620/C610 编解码、多圈累计、`EscBus` 异步驱动、命令租约 | 纯逻辑,全平台 |
| [`dji-esc-sim`](../crates/dji-esc-sim) | 被控对象模型、场景、**离线 harness**、异步假电调 | 纯逻辑,全平台 |
| [`dji-esc-slcan`](../crates/dji-esc-slcan) | slcan(Lawicel)串口 ↔ `CanLink` 桥接 | 全平台 |
| [`dji-esc-socketcan`](../crates/dji-esc-socketcan) | SocketCAN ↔ `CanLink` 桥接 | Linux |
| [`mecanum`](../crates/mecanum) | 麦轮正 / 逆运动学(纯数学) | 全平台 |
| [`chassis-config`](../crates/chassis-config) | `chassis.toml` schema:解析 / 校验 / 写回 | 全平台 |
| [`chassis-bench`](../crates/chassis-bench) | 台架:安全包络、采样、`chassis-spin` / `socketcan-spin` | 全平台(SocketCAN 工具需 Linux) |
| [`chassis-tune`](../crates/chassis-tune) | 速度环 PID 自动整定 + **超参数预设** | 全平台 |
| [`chassisd`](../crates/chassisd) | 底盘守护进程:1 kHz 循环、安全逻辑、指令源 seam | 全平台(实车需 Linux) |
### 5.2 依赖与组合
### 5.2 依赖方向(硬约束)
`esc-bus` 不依赖 PID 或 `motor-control`。`motor::EffortPolicy` 是两者的组合点:
应用构造 `motor-control::MotorPolicy`,并把它传给 `EscBus::spawn`。
```
chassisd ─┬─► chassis-config ─► motor-core, mecanum
├─► dji-esc ──► can-link, motor-core
├─► dji-esc-sim ─► dji-esc(协议类型), can-link
└─► dji-esc-socketcan ─► can-link
总线仍然在 1 kHz 节拍上运行策略,因此控制时序不变;变化的是控制律不再被封在总线驱动里。
反馈离线、命令租约到期和模式切换会把对应策略状态复位并强制零输出。
chassis-tune ─┬─► dji-esc-sim(离线 harness)
├─► chassis-bench(安全包络 + 采样)
├─► chassis-config(共用 schema)
└─► dji-esc ─► can-link, motor-core
chassis-bench ─┬─► dji-esc ─► can-link, motor-core
├─► dji-esc-sim(场景 / 轨迹类型)
├─► dji-esc-slcan / dji-esc-socketcan
└─► chassis-config
```
**传输后端 crate 只做桥接,不含应用逻辑**:`chassis-spin` 曾住在 `dji-esc-slcan` 的
example 里,现在统一收归 `chassis-bench`;整定逻辑住在 `chassis-tune`。要换总线介质
(串口转 CAN、MCU 转发),只需**新增一个桥接 crate**,协议 / 控制 / 整定全部不动。
### 5.3 一个 1 ms 拍里发生了什么
```
tick (1ms)
├─ 读取 watch<Twist>(有新值则更新,记录时间戳)
├─ 指令超时判定(默认 200ms)→ 超时则 Twist::ZERO
├─ 幅值限幅 Twist::clamped(max_vx, max_vy, max_omega)(按比例收缩,保方向)
├─ 上电门控:四轮是否都收到过反馈?否则全部 disable 并告警
├─ 麦轮逆解 → 四轮轮端 rpm
├─ OutputShaft 换算 → 转子 rpm
└─ set_velocity(续租命令)→ EscBus 在自身 1 kHz 节拍上组帧、发送
```
同时 `EscBus` 侧持续:接收反馈帧 → 分发到各 `EscMotor` → 更新 `MotorState`
→ 检查反馈离线(默认 100 ms)→ 检查命令租约(默认 50 ms)。
---
`bench` 只产生试验轨迹和安全中止原因;指标、成本、搜索和报告适配由
`tuning` 与 `chassis-tune` 完成。
## 6. 关键机制(实车踩过的坑)
@@ -292,7 +248,7 @@ tick (1ms)
## 7. 控制设计
### 7.1 PID(`motor-core::Pid`)
### 7.1 PID(`motor-control::Pid`)
并联式 PID,显式时间步 `dt`(秒),特性:
@@ -302,7 +258,7 @@ tick (1ms)
4. 可选**微分一阶低通**(`with_derivative_tau`),抑制编码器量化噪声;
5. `dt <= 0` 或非有限输入 → 返回上次输出且不更新内部状态(防御跳帧 / 首帧)。
### 7.2 速度环(`motor-core::VelocityController`)
### 7.2 速度环(`motor-control::VelocityController`)
```
effort_raw = clamp( PID(target_rpm, measured_rpm, dt) + feedforward × target_rpm, ±limit )
@@ -324,7 +280,7 @@ effort_raw = clamp( PID(target_rpm, measured_rpm, dt) + feedforward × target_rp
| 一起动就飞 | 前馈过大 or 限流远大于实际所需 | ff → 0、限流贴近实际 |
| 稳态有静差 | 摩擦未被积分吃掉 | 检查 `ki` 是否被抗饱和长期冻结 |
### 7.4 被控对象模型(`dji-esc-sim`)
### 7.4 被控对象模型(`sim-harness::motor_model`)
逐拍离散(`dt` 秒,电调坐标系:正电流 → 正转速):
@@ -394,9 +350,9 @@ schema 由 [`chassis-config`](../crates/chassis-config) 定义,**守护进程
| # | 步骤 | 命令 | 通过判据 |
| --- | --- | --- | --- |
| 1 | **链路与反馈** | `chassis-spin --dry-run` | 四个电调都在刷反馈(转速 ≈ 0、温度正常、反馈年龄 < 100 ms)。缺一个就查线 / 电调 ID |
| 1 | **链路与反馈** | `spin chassis --dry-run` | 四个电调都在刷反馈(转速 ≈ 0、温度正常、反馈年龄 < 100 ms)。缺一个就查线 / 电调 ID |
| 2 | **电调 ID 对应** | 同 1,逐个拔插 | 拔掉哪个电调,哪个 ID 的反馈消失 → ID 映射正确 |
| 3 | **方向** | `chassis-spin --rpm 200`(低速) | 四个轮子都朝"底盘前进"方向转;反了就改 `direction` |
| 3 | **方向** | `spin chassis --rpm 200`(低速) | 四个轮子都朝"底盘前进"方向转;反了就改 `direction` |
| 4 | **减速比 / 轮径** | 手转一圈 + 读反馈角度 | 反馈角度 × 减速比 ≈ 轮子转过角度;不符则改 `gear_ratio` |
| 5 | **几何参数** | 卷尺量 `half_length` / `half_width` / `wheel_radius` | 填入 `chassis.toml`(影响运动学精度,不影响稳定性) |
| 6 | **限流** | 从 1500 raw 起,逐步加到"能可靠起动且不冲" | 越小越好;实车再按需上调 |
@@ -485,7 +441,7 @@ chassis-tune sweep --preset config/tune.toml --samples 128 # CLI 覆盖预
| `field_updown` | bench | **up-down** 1200 rpm | 实战验收:升 / 降 / 回零双向,阈值收紧到 12% / 3% |
**预设 schema**(`version = 1`,未知键报错,见
[`crates/chassis-tune/src/preset.rs`](../crates/chassis-tune/src/preset.rs)):
[`crates/tuning/src/lib.rs`](../crates/tuning/src/lib.rs)):
```toml
version = 1
@@ -556,7 +512,7 @@ feedforward = { min = 0.0, max = 0.0, scale = "linear" } # 单点区间 = 搜
## 11. 台架工具
### 11.1 `chassis-spin`(`chassis-bench`,slcan,全平台)
### 11.1 `spin chassis`(`spin`,slcan,全平台)
四轮同时以固定**转子**转速旋转。关键参数:
@@ -586,13 +542,13 @@ feedforward = { min = 0.0, max = 0.0, scale = "linear" } # 单点区间 = 搜
- 上电门控:四个电调都上报新鲜反馈前绝不下发速度;
- 空载的合适整定与带载**完全不同**,空载**不要**加前馈。
### 11.2 `socketcan-spin`(`chassis-bench`,仅 Linux)
### 11.2 `spin motor`(`spin`,仅 Linux)
单电机链路验证,走 SocketCAN。用于板载原生 CAN 的接线排查:
```sh
ip link set can0 up type can bitrate 1000000 restart-ms 100
cargo run --release -p chassis-bench --bin socketcan-spin -- --interface can0 --dry-run
cargo run --release -p spin -- motor -- --interface can0 --dry-run
```
---
@@ -632,7 +588,7 @@ cargo run --release -p chassis-bench --bin socketcan-spin -- --interface can0 --
| 整定 | 指标(合成轨迹解析解)、参数空间(界内 / 确定性 / 对数分布 / 分层覆盖)、成本(硬约束罚值)、搜索(同 seed 逐位复现、优于手调基线)、报告(CSV/TOML/JSON 往返) | 全平台(CI 跑 `just tune-smoke`) |
| **预设** | schema 往返、逐字段覆盖、优先级(CLI > 预设 > 内置)、未知键 / 越界 / 缺失报错、**`config/tune.toml` 全部预设可解算**、命名与后端一致、安全参数不可注入 | 全平台 |
| vcan 集成 | vcan0 双 socket 收发,真实内核 CAN 栈 | Linux(`#[ignore]`,CI 显式跑) |
| 实车台架 | `chassis-spin` 四轮定速:链路/反馈、方向、转速稳态、过速保护、安全停机;`chassis-tune --backend bench` 逐次试验 + 重复性复验 | armv7 + slcan/SocketCAN(人工) |
| 实车台架 | `spin chassis` 四轮定速:链路/反馈、方向、转速稳态、过速保护、安全停机;`chassis-tune --backend bench` 逐次试验 + 重复性复验 | armv7 + slcan/SocketCAN(人工) |
| 交叉检查 | `aarch64-unknown-linux-gnu` 全 workspace check;armv7 静态构建 `chassisd` | CI |
### 13.2 每个阶段完成后的门禁(`just lint` + 以下)
@@ -660,7 +616,7 @@ just build-armv7 # cargo build --release -p chassisd --target armv7-unknown-
just deploy # 编译 + 推送 + mock 试运行(默认 root@192.168.2.2)
```
目标板实测:创龙 T153 / Ubuntu 22.04(armv7)。二进制静态链接,不依赖板端 libc。
目标板实测:创龙 T153 / Ubuntu 22.04(armv7)。发布二进制使用 musl 静态链接。
### 14.2 板端拉起 CAN
@@ -692,9 +648,8 @@ RestartSec=1
> (`ip -d link show can0` 可见),`chassisd` 侧表现为四电机反馈离线、强制零电流——
> **这正是安全设计的预期行为**,不是故障。
>
> 调度策略:`ARCHITECTURE.md` 规划了 `SCHED_FIFO` + `mlockall`(对应 `rm-app::realtime`),
> 本仓库目前**未实现**实时优先级提升,当前只依赖 1 kHz tokio 定时器 +
> `MissedTickBehavior::Skip`。板端抖动实测与是否需要加固见第 15 节的抖动条目。
> 调度策略:应用不调用 libc FFI。
> 应用不调用 libc FFI。`SCHED_FIFO` 与资源限制由 systemd 服务单元设置;std 程序不调用 `mlockall`。
---