12 KiB
架构设计
分层
┌────────────────┐ ┌──────────────────────┐ ┌─────────────────────────┐
│ chassisd │ │ chassis-tune │ │ chassis-bench │
│ 守护进程 │ │ 自动整定(搜索/报告) │ │ 台架(激励/包络/采样) │
│ 1kHz 循环 / 安全│ │ sim ↓ bench ↓ │ │ chassis-spin 等 │
└───┬────────┬───┘ └────┬──────────┬──────┘ └────┬───────────────────┘
│ │ │ │ │
│ │ │ └───────┬───────┘
▼ ▼ ▼ ▼
┌───────────┐ ┌──────────────────┐ ┌───────────────────────┐
│ mecanum │ │ dji-esc-sim │ │ dji-esc-slcan │
│ 麦轮运动学 │ │ 被控对象 / 场景 │ │ dji-esc-socketcan │
└───────────┘ │ 离线 harness │ │ 真实总线 ↔ CanLink │
│ 异步假电调 │ └──────────┬────────────┘
└────────┬─────────┘ │
│ CanLink(tokio mpsc 通道对)
▼ ▼
┌────────────────────┐ ┌──────────────────┐
│ motor-core │◄─│ dji-esc │
│ 能力 trait / Pid │ │ C620/C610 协议 │
│ VelocityGains │ │ EscBus 异步驱动 │
└────────────────────┘ └──────────────────┘
┌────────────────────┐
│ chassis-config │ chassis.toml schema(chassisd + chassis-tune 共用)
└────────────────────┘
核心解耦点:CanLink 是一对 tokio 通道(发送 / 接收 CanFrame)。dji-esc 的协议状态机
只面向通道工作,因此:
- 闭环仿真由
dji-esc-sim提供两条路径:异步假电调(挂在同一对通道上,chassisd --mock与集成测试用)与同步 harness(纯浮点积分、无任务无通道,自动整定用,可并行上千次); dji-esc-socketcan(板载原生 SocketCAN)与dji-esc-slcan(CANable 等 USB 适配器, 串口 Lawicel 协议)只把真实总线桥接到通道,不含任何应用逻辑;应用住在chassis-bench(台架)与chassis-tune(整定);- 未来更换总线介质(串口转 CAN、MCU 转发)只需新增一个桥接 crate。
被控对象模型(dji-esc-sim)
逐拍离散(dt 秒),电调坐标系(正电流 → 正转速):
- 命令延迟:
current_raw进 FIFO,delay_ticks拍后才作用(链路 + 采样延迟); - 摩擦:运动时
net = u − sign(rpm)·coulomb − viscous·rpm;静止且|u| > static时net = u − sign(u)·coulomb;静止且|u| ≤ static时net = 0(粘住); - 一阶转速:
rpm_target = clamp(net × gain, ±max_rpm) − load_rpm, 以时间常数tau一阶趋近; - 量化:机械角按转速积分并对 8192 取模,反馈帧是
EscFeedback, 因此角度被截断为u16、转速为i16——量化由协议类型自带。
PlantParams::gm3508() 是无摩擦无延迟的理想模型(chassisd --mock 与历史闭环测试依赖它,
行为逐位不变);PlantParams::gm3508_bench() 叠加静 / 库仑摩擦(400~600 raw 量级,
取自台架实测)与 2 拍延迟,是整定的默认被控对象。
单点模型整定出来的增益往往对增益 / 时间常数 / 延迟高度敏感,因此 chassis-tune 默认在
PlantEnsemble(域随机化:增益 ±20%、时间常数 ±30%、延迟 0~3 拍、摩擦 ±50%)上做鲁棒优化,
而不是针对一条理想曲线做最优化。
自动整定(chassis-tune)
两阶段搜索:
- 粗扫:Latin Hypercube 分层采样 +
rayon并行评估,用有限次数铺满四维空间; - 精修:以成本最低的 top-k 为起点,在坐标空间(对数刻度,增益恒正)做多起点
argmin局部搜索(Nelder-Mead 默认,可选 PSO)。
成本 = 归一化加权(调节时间 / 超调 / IAE / 电流);硬约束不进权重:过速、饱和占比过高、 不收敛直接给罚值并记录判定——否则搜索会主动挑一个会飞车的解。
可复现性:每个候选的随机性只来自 (种子, 候选下标) 派生的独立 RNG,粗扫结果排序后再取 top-k,
因此同 seed 两次运行逐位一致(CI 的 just tune-smoke 依赖这一点)。
超参数(参数空间 / 场景 / 权重 / 验收阈值 / 搜索预算 / 被控对象 / 真机安全包络)不在命令行
默认值里散着,而是版本化在 config/tune.toml 的具名预设中(见
preset.rs)。优先级为
内置默认 < 预设文件 < 显式 CLI,逐字段覆盖;current_limit / direction / gear_ratio
不是预设字段,写进预设文件会直接报错(安全参数不允许被预设绕过)。
真机后端(--backend bench)与仿真后端实现同一个 Evaluator trait,区别只是"评估一组增益"
从浮点积分换成真机试验:打开链路 → 上电门控(反馈新鲜)→ 逐次热更新增益
(EscMotor::set_velocity_gains,不重启总线)→ 逐拍重发设定值 + 安全包络 → 任何退出路径
先 0 电流 → 试验间静默。搜索强制单线程,因为只有一条总线。
命令租约与链路深度(实车踩过的坑)
- 命令是租约:
EscBus在command_timeout(默认 50ms)内没收到新命令就强制 0 输出 (防上层任务死亡后旧指令持续输出)。所以set_velocity不是"设一次就完事",上层必须 逐拍重发;只在开头下发一次的台架脚本会在 50ms 后自动失电。 - 命令队列必须浅:驱动按 1kHz 生产命令,而 slcan 一次 write 在 CDC ACM 上约 2ms,
下行只有几百帧/秒。若
CanLink命令方向缓冲很深(早期是 512),队列会被过时命令灌满, ESC 执行的是一秒多以前算出的电流——速度环等于开了 1.4s 延迟,轮子必然跑飞。 现在 slcan 桥的CHANNEL_BOUND = 8:send().await立即回压,每一帧都按最新反馈现算。 反馈方向是采样流,来不及就丢(try_send),绝不阻塞泵线程。 - 桥的两个方向不能互相饿死:发送要有上界(
TX_BURST)并在一帧写不动时立刻让位, 接收要批量排空(RX_BURST)。serialcrate 的TTYPort读写共用同一个超时, 写超时属正常现象,不能当致命错误(否则一次写超时就会把接收方向一起带停)。
DJI 电调 CAN 协议要点(C620 手册)
| 项目 | 值 |
|---|---|
| 控制帧 ID | 0x200(电调 1–4)、0x1FF(电调 5–8) |
| 控制帧格式 | 8 字节 = 4 × i16 大端电流命令,按电调 ID 顺序 |
| C620 电流标度 | ±16384 ↔ ±20 A |
| C610 电流标度 | ±10000 ↔ ±10 A(M2006 组合,接硬件前需核对) |
| 反馈帧 ID | 0x200 + 电调ID(0x201–0x208) |
| 反馈帧格式 | u16 角度(0..8191) + i16 转速rpm + i16 转矩电流 + i8 温度℃,均大端 |
| 反馈频率 | 每电调 1kHz |
C620 内部只闭电流环:转速 / 位置环由主控侧 PID 实现(motor-core::Pid,
挂在 EscBus 的 1kHz 发送节拍上)。GM3508 减速比 3591 / 187 ≈ 19.203。
单位与方向约定
- 机器人坐标系:ISO 约定——x 前、y 左、ω 逆时针为正(见
mecanum)。 Motor接口呈现的是物理量(安装方向修正后):EscConfig::direction = -1时,驱动层在写命令与读反馈时统一翻转符号, 上层永远以"轮子正转 = 底盘前进方向"的正向工作。MotorState::rotor_speed_rpm为电机转子转速;轮端转速 = 转子转速 / 减速比。- 电流一律以协议原始值(raw)为主,安培换算通过
EscModel常量提供。
安全设计
- 上电门控:底盘循环在收到任一电机首帧反馈前不下发速度命令。
- 反馈离线:任一电机反馈超时(默认 100ms)→ 该电机命令强制 0 电流并告警。
- 命令超时:上层 twist 命令超时(默认 200ms)→ 目标速度归零。
- 所有超时可经
chassis.toml配置。
测试策略
| 层级 | 内容 | 平台 |
|---|---|---|
| 单元测试 | 帧编解码字节级样例 / 饱和、多圈回绕、PID(阶跃、抗饱和、滤波)、运动学解析解 + 属性测试、slcan 帧形状过滤 | 全平台 |
| 闭环仿真 | 内存通道 + 一阶电机模型:4 电机速度环收敛、群帧组装、离线安全、命令租约到期与续租、增益热更新、守护进程全链路 | 全平台 |
| 模型一致性 | 同步 harness 与 EscBus 异步闭环在同一组增益下的稳态 / effort 对齐(防止"对着错模型调参") |
全平台 |
| 整定 | 指标(合成轨迹解析解)、参数空间(界内 / 确定性 / 对数分布 / 分层覆盖)、成本(硬约束罚值)、搜索(同 seed 逐位复现、优于手调基线)、报告(CSV/TOML/JSON 往返) | 全平台(CI 跑 just tune-smoke) |
| 超参数预设 | schema 往返、逐字段覆盖与优先级、未知键 / 越界 / 缺失报错、config/tune.toml 全部预设可解算、命名与后端一致、安全参数不可注入 |
全平台 |
| vcan 集成 | vcan0 双 socket 收发,真实内核 CAN 栈 | Linux(#[ignore],CI 显式跑) |
| 实车台架 | chassis-spin(chassis-bench)四轮定速旋转:链路/反馈、方向、转速稳态、过速保护、安全停机;chassis-tune --backend bench 逐次试验 + 重复性复验 |
armv7 目标板 + slcan/SocketCAN(人工) |
| 交叉检查 | aarch64-unknown-linux-gnu 全 workspace check |
CI |
部署
chassisd 为单二进制守护进程:--interface can0 --config /etc/chassisd/chassis.toml。
systemd 样例见 deploy/chassisd.service。板端 CAN 两条路径:
- SocketCAN:USB 转 CAN(gs_usb / candleLight)或 SPI CAN HAT(MCP2515)驱动加载后的
can0; - slcan:USB 适配器(CANable2 等)在 CDC ACM 下枚举为
/dev/ttyACM0,由dji-esc-slcan在用户态处理(不需ip link,也绕开了 gs_usb 驱动)。台架工具chassis-spin(chassis-bench)与 自动整定的真机后端走这条路径。整定工具本身是宿主机 / 板端通用的纯 Rust 程序,current_limit等安全参数由 CLI 显式给出,不参与搜索。