Files

35 KiB
Raw Permalink Blame History

底盘开发文档

适用对象:哨兵机器人麦轮底盘(4 × GM3508 + C620 电调) 目标平台:Allwinner T153(Cortex-A7,Linux,armv7)+ CANable2(slcan)或板载 SocketCAN 控制频率:1 kHz(2ms 是旧方案;本项目实际为 1ms 节拍) 文档目的:从零到上车的完整开发路径——接口约定、标定步骤、自动整定与超参数、安全与故障处理


目录

  1. 范围与文档分工
  2. 快速上手
  3. 硬件与接线
  4. 坐标、单位与符号约定
  5. 软件架构
  6. 关键机制(实车踩过的坑)
  7. 控制设计
  8. 配置参考(chassis.toml)
  9. 标定与上车流程
  10. 自动整定与超参数预设
  11. 台架工具
  12. 安全设计
  13. 测试与质量门禁
  14. 部署
  15. 故障排查
  16. 术语表

1. 范围与文档分工

文档 回答的问题 读者
ARCHITECTURE.md 职责型 workspace、允许依赖、std/unsafe 策略、实现边界 架构决策者
docs/architecture.md 架构文档入口,不复制规范 改代码的人
本文 底盘这一块怎么开发、标定、整定、上车与排障 底盘开发 / 调试 / 上车的人

本文只覆盖四轮底盘切片;云台、发射机构与 DETA10 真实载荷布局不在当前范围内。


2. 快速上手

2.1 无硬件(macOS / Linux / CI 均可)

cargo test --workspace --all-targets # 单测 + 闭环仿真 + 整定,全部无需硬件
cargo run -p chassisd -- --mock   # 进程内电机模型的全链路演示
cargo run -p chassis-tune -- presets   # 看有哪些整定超参数预设
just tune-smoke                   # 确定性整定冒烟(CI 同款)
just lint                         # fmt + clippy(与 CI 同标准)

2.2 上车(详细步骤见第 9 节)

# 1) 只验链路与反馈,绝不发非零电流(接线排查第一步)
cargo run --release -p spin -- chassis --device /dev/ttyACM0 --dry-run

# 2) 四轮定性旋转(轮子必须架空或整车固定,急停可及)
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
cargo run --release -p chassis-tune -- sweep --preset config/tune.toml --preset-name bench_noload \
  --backend bench --device /dev/ttyACM0 --i-know-the-wheel-spins

# 4) 把结果写回配置并跑守护进程
cargo run --release -p chassis-tune -- sweep --config config/chassis.toml.example --motor front_left \
  --preset config/tune.toml --preset-name sim_bench_model --write-config /tmp/chassis.tuned.toml
chassisd --config /tmp/chassis.tuned.toml --interface can0

3. 硬件与接线

3.1 拓扑

T153 (Linux)                                    Gm3508 × 4 + C620
├── USB CDC /dev/ttyACM0 ──► CANable2 (slcan 固件) ──► CAN 总线 ──► 4 个电调
└── 板载 CAN 控制器 can0   ──────────────────────────► (二选一)

两条路径互斥地二选一,应用层无感知(都由 CanLink 抽象屏蔽):

路径 打开方式 适用
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 手册)

项目 值
控制帧 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 温度℃,均大端
反馈频率 每电调 1 kHz
减速比 GM3508 = 3591 / 187 ≈ 19.203(转子 : 输出轴)

C620 内部只闭电流环:转速环 / 位置环由主控侧实现(motor-control::Pid,挂在 EscBus 的 1 kHz 节拍上)。

3.3 电调 ID 与安装方向

默认约定(config/chassis.toml.example、spin chassis 默认值一致):

轮位 轮序下标 电调 ID 安装方向
前左 FL 0 1 +1
前右 FR 1 2 -1
后左 RL 2 3 +1
后右 RR 3 4 -1

左右镜像安装(direction 相反)使四轮在"底盘前进"时物理同向。direction = -1 的电机 由驱动层在写命令与读反馈两个方向上统一翻转符号,上层永远看到"正转 = 前进方向"。

3.4 电流限幅

场景 建议限幅(raw) 说明
空载台架(spin chassis 默认) 1500(≈1.8 A) 需越过静摩擦(实测 400~600 raw)并留整定余量
整车实车 6000 ~ 12000 视供电与机械而定;同时是 PID 抗饱和的钳位点

限流不是"越大越安全":它同时是 PID 输出的钳位点。设得远大于实际所需, 积分会在堵转期间持续积累,一旦起动就冲过头。必须贴近实际所需电流。

3.5 接线前置条件(缺一不可)

  • 电调供电与主控共地;CAN_H / CAN_L 不接反,两端 120Ω 终端电阻齐全。
  • 轮子架空或整车固定;急停开关在手可及处。
  • 首次上电先 --dry-run,确认四轮反馈都在刷(spin chassis 会打印每个电调的 转速 / 温度 / 反馈年龄)。

4. 坐标、单位与符号约定

4.1 机器人坐标系(ISO)

  • x 前、y 左、ω 绕 z 逆时针为正(mecanum::Twist);
  • 轮序固定为 前左 FL、前右 FR、后左 RL、后右 RR(常量 mecanum::FL/FR/RL/RR, chassis.toml 的 [[motor]] 顺序、--esc-ids / --directions 的顺序都遵循它);
  • 辊子 45°、俯视 X 型布置(RoboMaster 标准布局)。

4.2 运动学(mecanum)

设 a = half_length + half_width,四轮线速度:

v_FL = vx - vy - a·ω
v_FR = vx + vy + a·ω
v_RL = vx + vy - a·ω
v_RR = vx - vy + a·ω

正解是逆解的左伪逆(JᵀJ 对角,可解析求平均)。实现见 mecanum:Mecanum::wheel_speeds / twist / wheel_rpms / twist_from_wheel_rpms。

4.3 单位与层级(最容易出错的地方)

量 所在层 单位
Twist.vx/vy 应用 / 控制 m/s
Twist.omega 应用 / 控制 rad/s
轮端转速 OutputShaft 呈现 rpm(输出轴)
转子转速 EscBus / EscMotor / MotorState::rotor_speed_rpm rpm(转子)
控制量 effort 驱动 / PID 协议 raw(ESC = 电流,C620 ±16384)
kp/ki/kd/feedforward 速度环 raw/rpm、raw/(rpm·s)、raw/(rpm/s)、raw/rpm
  • 减速比换算只发生在一个地方:motor / esc-bus::OutputShaft 包装(转子 = 输出轴 × gear_ratio)。 EscBus / EscMotor 只认转子,绝不碰减速比;EscConfig::gear_ratio 仅作配置元数据。
  • PID 的接线顺序:ChassisController 算出轮端 rpm → OutputShaft 换算成转子 rpm → EscMotor 的速度环以转子 rpm 闭环。

5. 软件架构

5.1 职责边界

完整 crate 清单和允许依赖见 ARCHITECTURE.md。底盘路径上的关键职责是:

职责 crate
CAN 帧、总线接口、链路与 Mock can
电调协议 motor-protocol
设备契约、命令/状态/减速比/注册表 motor
帧泵、租约、反馈新鲜度、零电流 esc-bus
PID、速度/位置环、组合策略 pid、motor-control
麦轮运动学 mecanum
配置 schema chassis-config
台架安全与采样 bench
应用入口 chassisd、spin、chassis-tune

crate 内源码保持扁平,不建立 src/<module>/ 子目录。

5.2 依赖与组合

esc-bus 不依赖 PID 或 motor-control。motor::EffortPolicy 是两者的组合点: 应用构造 motor-control::MotorPolicy,并把它传给 EscBus::spawn。

总线仍然在 1 kHz 节拍上运行策略,因此控制时序不变;变化的是控制律不再被封在总线驱动里。 反馈离线、命令租约到期和模式切换会把对应策略状态复位并强制零输出。

bench 只产生试验轨迹和安全中止原因;指标、成本、搜索和报告适配由 tuning 与 chassis-tune 完成。

6. 关键机制(实车踩过的坑)

6.1 命令是租约,不是设定值

EscMotor::set_velocity / set_effort_raw / set_position 每次调用即续租。超过 BusOptions::command_timeout(默认 50 ms)没有新命令,驱动强制 0 电流。

  • 这是"上层任务死亡后旧指令继续输出"的防线;
  • 因此上层必须逐拍重发同一个设定值(chassisd 的 1 kHz 循环天然满足);
  • 只在开头下发一次的台架脚本会在 50 ms 后自动失电——这是预期行为,不是 bug。

6.2 命令队列必须浅

驱动按 1 kHz 生产命令,而 slcan 一次 write 在 CDC ACM 上约 2 ms(下行只有几百帧/秒)。 若命令方向缓冲很深,队列会被过时命令灌满,ESC 执行的是一秒多以前算出的电流—— 速度环等于开了 1.4 s 延迟,轮子必然跑飞。

因此 slcan 桥的 CHANNEL_BOUND = 8:send().await 立即回压,每一帧都按最新反馈现算; 反馈方向是采样流,来不及就丢(try_send),绝不阻塞泵线程。

6.3 桥的两个方向不能互相饿死

  • 发送有上界(TX_BURST),一帧写不动时立刻让位给接收;
  • 接收批量排空(RX_BURST);
  • serial crate 的 TTYPort 读写共用同一个超时,写超时属正常现象,不能当致命错误 (否则一次写超时就会把接收方向一起带停)。

6.4 反馈离线

任一电机反馈超时(feedback_timeout,默认 100 ms)→ 该电机命令强制 0 电流并告警。 顶层另有上电门控:任一电机从未收到过反馈前,整组不下发速度命令。


7. 控制设计

7.1 PID(motor-control::Pid)

并联式 PID,显式时间步 dt(秒),特性:

  1. 输出限幅 [out_min, out_max](速度环里取 ±current_limit);
  2. 积分抗饱和(clamping):输出已饱和且误差继续同向时暂停积分;
  3. 微分作用于测量值(不是误差),避免设定值阶跃的微分冲击;
  4. 可选微分一阶低通(with_derivative_tau),抑制编码器量化噪声;
  5. dt <= 0 或非有限输入 → 返回上次输出且不更新内部状态(防御跳帧 / 首帧)。

7.2 速度环(motor-control::VelocityController)

effort_raw = clamp( PID(target_rpm, measured_rpm, dt) + feedforward × target_rpm, ±limit )

feedforward 单位 raw / rpm:稳态下所需控制量与转速近似成正比。

  • 空载台架必须 ff = 0:轮子架空时维持转速只需克服摩擦的极小电流,任何接近 1/K(GM3508 空载 K ≈ 0.57 rpm/raw → 1/K ≈ 1.77)的前馈都会把空载轮子直接推到最高转速。
  • 带载才需要正前馈(抵消地面摩擦),典型 ≈ 1.77 raw/rpm,必须按实车整定。
  • 前馈可能把输出推出限幅,因此 update 在加完前馈后再夹一次。

7.3 增益的物理直觉(空载台架)

现象 原因 方向
高 kp(8~18)震荡 被控对象近似积分器(≈12 rpm/s per raw)+ 反馈延迟 降 kp、提 ki
ki 太小长期停在 300~400 rpm 静摩擦需要积分缓慢"顶"到起动电流(stick-slip) 提 ki(有 current_limit 钳位兜底)
一起动就飞 前馈过大 or 限流远大于实际所需 ff → 0、限流贴近实际
稳态有静差 摩擦未被积分吃掉 检查 ki 是否被抗饱和长期冻结

7.4 被控对象模型(motor-model::MotorPlant)

逐拍离散(dt 秒,电调坐标系:正电流 → 正转速):

  1. 命令延迟:current_raw 进 FIFO,delay_ticks 拍后才作用;
  2. 摩擦:运动时 net = u − sign(rpm)·coulomb − viscous·rpm;静止且 |u| > static 时 net = u − sign(u)·coulomb;静止且 |u| ≤ static 时 net = 0(粘住);
  3. 一阶转速:rpm_target = clamp(net × gain, ±max_rpm) − load_rpm,以时间常数 tau 一阶趋近;
  4. 量化:机械角按转速积分并对 8192 取模,角度截断为 u16、转速为 i16 (量化由协议类型自带)。

PlantParams::gm3508() 是无摩擦无延迟的理想模型;gm3508_bench() 叠加实测量级的 静 / 库仑摩擦(400~600 raw)与 2 拍延迟,是整定的默认被控对象。


8. 配置参考(chassis.toml)

样例:config/chassis.toml.example。 schema 由 chassis-config 定义,守护进程与整定工具共用同一份—— 这样"整定工具用的限流"和"守护进程用的限流"不可能悄悄不一致。

8.1 顶层

键 含义
[can] interface 底盘总线 SocketCAN 接口名(如 can0)
[can.function] interface 预留的功能总线(云台 / 拨弹),必须与底盘总线不同名
[remote] 可选 DR16/D-BUS 遥控输入;缺省时保持静止
[chassis] 控制循环与安全超时、速度上限
[mecanum] half_length / half_width / wheel_radius(米)
[[motor]] × 4 顺序固定 FL / FR / RL / RR

8.2 [remote](可选)

键 默认 含义
kind 必填 当前支持 dr16
device /dev/ttyS2 D-BUS UART 设备
timeout_ms 100 合法帧断流后立即失能
deadband 0.05 摇杆满量程死区,之后使用平方曲线
ramp_ms 500 零到满量程逐轴斜坡;失能/断流立即归零
invert_vx/vy/omega false 实车方向校正

8.3 [chassis](缺省值)

键 默认 含义
control_period_ms 1 控制节拍(1 kHz)
command_timeout_ms 200 上层 twist 超时 → 目标速度归零
feedback_timeout_ms 100 反馈离线 → 强制 0 电流
motor_command_timeout_ms 50 驱动层命令租约(必须 ≥ control_period_ms)
max_vx / max_vy / max_omega 3.0 / 3.0 / 6.0 速度限幅(m/s、m/s、rad/s)

8.4 [[motor]]

键 含义
name 轮名(front_left …),整定的 --motor 用它选目标
esc_id 电调 ID(1–8,即 CAN 拨码)
model C620 / C610
direction 安装方向 +1 / -1
gear_ratio 减速比(转子 : 输出轴),GM3508 ≈ 19.203
current_limit effort 限幅(raw),同时是 PID 钳位点
kp / ki / kd / feedforward 速度环增益(整定的产物写回这里)

8.5 校验规则(Config::validate)

  • 电机数必须恰好 4;麦轮几何必须为正;四个超时必须 > 0; motor_command_timeout_ms ≥ control_period_ms;速度上限 > 0;gear_ratio > 0;
  • 底盘总线只允许一种协议族(四轮不能混用 C620 / C610);
  • 功能总线接口名不得与底盘总线相同(双 CAN 物理隔离);
  • [remote] 若启用:设备名非空,timeout_ms / ramp_ms > 0,deadband ∈ [0, 0.5), 且遥控断流超时不得晚于底盘指令超时;
  • deny_unknown_fields:拼错的键直接报错,不会被静默忽略。

9. 标定与上车流程

每一步都有明确的通过判据。不要跳步——跳过的步骤会在后面以"轮子跑飞"的形式还回来。

# 步骤 命令 通过判据
1 链路与反馈 spin chassis --dry-run 四个电调都在刷反馈(转速 ≈ 0、温度正常、反馈年龄 < 100 ms)。缺一个就查线 / 电调 ID
2 电调 ID 对应 同 1,逐个拔插 拔掉哪个电调,哪个 ID 的反馈消失 → ID 映射正确
3 方向 spin chassis --rpm 200(低速) 四个轮子都朝"底盘前进"方向转;反了就改 direction
4 减速比 / 轮径 手转一圈 + 读反馈角度 反馈角度 × 减速比 ≈ 轮子转过角度;不符则改 gear_ratio
5 几何参数 卷尺量 half_length / half_width / wheel_radius 填入 chassis.toml(影响运动学精度,不影响稳定性)
6 限流 从 1500 raw 起,逐步加到"能可靠起动且不冲" 越小越好;实车再按需上调
7 空载整定 chassis-tune sweep --preset-name bench_noload 出一组增益:超调 ≤ 25%、稳态误差 ≤ 8%、三次复验一致
8 带载复验 整车落地,--preset-name bench_loaded 超调 ≤ 15%、稳态误差 ≤ 5%;空载参数直接上车通常不行
9 双向验收 --preset-name field_updown 升速 / 降速 / 回零三个方向都达标,重复性 ≥ 4/5
10 固化 --write-config + 人工评审 增益写入 chassis.toml,限流 / 方向 / 减速比未被改动

关于"仿真整定能不能直接上车":不能。仿真最优解只在被控对象模型内最优,模型里的 摩擦 / 延迟是量级估计且不含测量噪声,因此可能偏激进。必须用同一场景在台架上复验 (第 10.6 节的迁移证据)。


10. 自动整定与超参数预设

手动试凑 kp/ki/kd/ff 既慢又难复现。chassis-tune 把它变成参数空间搜索 + 版本化超参数。

10.1 搜索算法

两阶段:

  1. 粗扫:Latin Hypercube 分层采样 + rayon 并行评估,用有限次数铺满四维空间;
  2. 精修:以成本最低的 top-k 为起点,在坐标空间做多起点 argmin 局部搜索 (Nelder-Mead 默认,可选 PSO)。

坐标空间是关键的数值技巧:kp/ki/kd 合法取值跨数量级且必须为正,每个轴映射到 (对数轴取 ln,线性轴恒等)之后——

  • 无约束优化器不会走出正增益区域;
  • 步长在不同数量级上等价(±0.1 坐标步长 = ×1.1 比例步长);
  • LogOrZero 轴在粗扫阶段可精确取 0(ff = 0 是台架常态),在精修阶段退化为对数轴 (零不可导,本地搜索无收益)。

10.2 成本函数与硬约束

成本 = 归一化加权(调节时间 / 超调 / IAE / 电流),各项已归一化到 O(1),因此权重在不同 场景 / 电机之间可解释。

硬约束不进权重:过速、饱和占比过高、不收敛 → 直接给罚值 1e9 并记录判定。 否则搜索会主动挑一个"飞车但其它项很好看"的解。

判定 触发条件
rejected 增益含 NaN/inf,或场景退化
diverged 数值发散
overspeed 峰值转速 > 目标 × 3 + 500
saturated 饱和占比 > 40%
no-settle 场景内未收敛

10.3 鲁棒优化(默认)

单点模型整定出的增益对增益 / 时间常数 / 延迟高度敏感,因此默认在 PlantEnsemble (域随机化:增益 ±20%、时间常数 ±30%、延迟 0~3 拍、摩擦 ±50%,8 个成员)上评估, 成本按 robust_weight 在"均值"与"最坏成员"之间折衷(默认 0.5)。

10.4 超参数预设(config/tune.toml)

这是"实验条件"的版本管理:搜什么区间、用哪个场景、什么算好、什么算达标、搜多细, 全部写在 config/tune.toml 里,评审一次、长期复用、任何机器上可复现。

优先级(逐字段覆盖):

内置默认  <  预设文件里的预设  <  显式命令行参数

因此"用预设跑一次 + 临时改一个权重"是安全的,不会把其它超参数打回硬编码值。

chassis-tune presets                                             # 列出全部预设(只读)
chassis-tune sweep --preset config/tune.toml                     # 用 default_preset
chassis-tune sweep --preset config/tune.toml --preset-name bench_loaded
chassis-tune sweep --preset-name bench_loaded                    # 缺省路径即 config/tune.toml
chassis-tune sweep --preset config/tune.toml --samples 128       # CLI 覆盖预设的单项

随仓库提供的预设:

预设名 后端 场景 用途
sim_bench_model(默认) sim ramp-and-hold 1500 rpm 台架量级模型全量搜索:评审、CI、代码回归的基准
sim_ideal sim ramp-and-hold 1500 rpm 理想模型秒级回归;结果不可直接上车
bench_noload bench ramp-and-hold 800 rpm 空载台架:小样本局部搜索,前馈固定为 0
bench_loaded bench ramp-and-hold 1200 rpm 带载(整车落地):前馈区间上调至 0.5~2.5
field_updown bench up-down 1200 rpm 实战验收:升 / 降 / 回零双向,阈值收紧到 12% / 3%

预设 schema(version = 1,未知键报错,见 crates/tooling/tuning/src/lib.rs):

version = 1
default_preset = "sim_bench_model"

[preset.bench_noload]
description = "空载台架:摩擦小、静差靠积分压制"
backend = "bench"

[preset.bench_noload.space]
kp = { min = 0.2, max = 12.0, scale = "log" }     # scale: linear | log | log0
feedforward = { min = 0.0, max = 0.0, scale = "linear" }  # 单点区间 = 搜索时恒定

[preset.bench_noload.cost]        # settle / overshoot / iae / effort
[preset.bench_noload.acceptance]  # max_overshoot_pct / max_ss_error_pct
[preset.bench_noload.search]      # seed / samples / top_k / solver / max_iters / particles / max_evals / threads
[preset.bench_noload.sim]         # plant / ensemble / robust_weight / gain_pct / tau_pct / delay_ticks_max / friction_pct
[preset.bench_noload.bench]       # max_rpm / max_temp_c / trial_timeout_ms / between_trials_ms /
                                  # startup_timeout_ms / continue_on_abort / validate_runs

每个小节都可缺省,只写"与默认不同"的项;刻度(scale)与区间可以分别覆盖 (--kp-scale linear 不改区间)。

安全边界:current_limit、direction、gear_ratio、电调 ID、串口设备名 不是预设字段——写进预设文件会直接报错。它们只来自 chassis.toml 或显式 CLI, 否则预设文件就成了绕过限流的口子。

新增一个预设:在 config/tune.toml 里加一节 → 跑 chassis-tune presets 确认能解算 → 跑一次小样本 chassis-tune sweep --preset config/tune.toml --preset-name <新名> --samples 16 确认可用。 测试会强制:每个预设都有 description;bench_* / field_* 必须 backend = "bench" (防止"以为在台架验过、其实是离线跑的")。

10.5 产物与判读(--out-dir)

文件 内容
trials.csv 全部候选 + 判定(可复现性 diff 的基准)
best.toml 可直接粘贴的增益片段
best-trace.csv 最优解的轨迹
report.md 人类可读报告:参数空间、权重、验收结论、边界提示
summary.json 机器可读摘要
hil-validation.csv 仅真机后端:同一组增益的重复性复验

判读要点:

  1. 最优解贴到参数空间边界时报告会明确提示——那是"约束在决定结果",应放宽 --kp / --ki 再搜,而不是把边界值当最优解交付;
  2. 达标数 / 评估数太低(如 9/198)说明大部分候选被硬约束拦下,通常意味着限流太小 或参数空间太宽;
  3. 真机 hil-validation.csv 的离散度比均值更重要:三次结果差很多 = 参数贴着噪声,不该上车。

10.6 真机后端与迁移证据

--backend bench 与仿真实现同一个 Evaluator trait,区别只是"评估一组增益"从浮点积分 换成真机试验:打开链路 → 上电门控 → 逐次热更新增益(不重启总线)→ 逐拍重发设定值 + 安全包络 → 任何退出路径先 0 电流 → 试验间静默。搜索强制单线程(只有一条总线)。

仿真整定 → 台架复验(同场景、同阈值)→ 重复性达标 → 写回 chassis.toml → 整车验收

可复现性:同一 seed + 同一参数空间 + 同一后端 → 逐位一致。CI 的 just tune-smoke 依赖这一点(同一命令跑两次,diff trials.csv 必须为空)。


11. 台架工具

11.1 spin chassis(spin,slcan,全平台)

四轮同时以固定转子转速旋转。关键参数:

参数 默认 说明
--device /dev/ttyACM0 slcan 串口设备
--serial-baud 2000000 CDC ACM 一般忽略;CH340 类需与固件一致
--serial-timeout-ms 50 读写共用
--can-speed 1m 1m / 500k
--esc-ids 1,2,3,4 顺序 = FL、FR、RL、RR
--directions 1,-1,1,-1 各轮安装方向
--rpm 500 目标转子转速(轮端 = 转子 ÷ 减速比)
--gear-ratio 19.203 减速比
--current-limit 1500 raw;同时是 PID 钳位点
--ramp-ms 3000 缓升时间(让积分在起动前平滑建立,避免"先憋住、一动就飞")
--hold-secs 10 目标转速保持时长
--startup-timeout-ms 3000 等待全部电调反馈的超时
--dry-run — 只验证链路与反馈,不发任何非零电流
--allow-partial — 允许缺失部分电调(默认要求四个全在线)
--kp/--ki/--kd/--feedforward 2 / 10 / 0 / 0 空载台架默认值(见 7.3)
--max-rpm 目标 × 2 + 200 过速保护上限

安全与使用要点:

  • 轮子必须架空或整车固定;ctrl-c 随时安全停机(先 0 电流再关总线);
  • 过速保护:任一电调超过 --max-rpm 持续 60 ms 即立即停机;
  • 上电门控:四个电调都上报新鲜反馈前绝不下发速度;
  • 空载的合适整定与带载完全不同,空载不要加前馈。

11.2 spin motor(spin,仅 Linux)

单电机链路验证,走 SocketCAN。用于板载原生 CAN 的接线排查:

ip link set can0 up type can bitrate 1000000 restart-ms 100
cargo run --release -p spin -- motor -- --interface can0 --dry-run

12. 安全设计

按"故障点 → 防线"排列,每一层都独立生效:

# 故障 防线 默认阈值
1 上层任务死亡,旧指令继续输出 命令租约:超时未刷新 → 0 输出 50 ms
2 电机 / CAN 掉线 反馈离线:超时 → 该电机 0 电流 + 告警 100 ms
3 上电瞬间反馈未到就发速度 上电门控:全部见到反馈前全体 disable —
4 上层不再发指令 指令超时:twist 归零 200 ms
5 轮子飞车 台架 过速保护(超过上限持续 60 ms 停机) 目标 × 2 + 200 rpm
6 堵转烧电机 限流(同时是 PID 钳位点,抗饱和) 台架 1500 / 实车 6000~12000 raw
7 电调过热 台架温度保护 70 ℃(空载预设 60 ℃)
8 整定 / 台架脚本失控 试验超时 + 立即 0 电流 + 试验间静默 6 s / 500 ms
9 误触真机模式 --i-know-the-wheel-spins 强制显式确认(预设也无法绕过) —
10 命令队列积压导致延迟 slcan 桥 CHANNEL_BOUND = 8 + send().await 回压 —
11 进程被杀 systemd Restart=always(板端) 1 s

出事时的正确操作顺序:① 急停 / 断电 → ② ctrl-c 让进程走优雅停机(先 0 电流)→ ③ 记下 --dry-run 与 hil-validation.csv 的现象 → ④ 再分析。


13. 测试与质量门禁

13.1 分层测试

层级 内容 平台
单元 帧编解码字节级样例 / 饱和、多圈回绕、PID(阶跃、抗饱和、滤波)、运动学解析解 + 属性测试、slcan 帧形状过滤 全平台
闭环仿真 内存通道 + 一阶电机模型:4 电机速度环收敛、群帧组装、离线安全、命令租约到期与续租、增益热更新、守护进程全链路 全平台
模型一致性 同步 harness 与 EscBus 异步闭环在同一组增益下的稳态 / effort 对齐(防止"对着错模型调参") 全平台
整定 指标(合成轨迹解析解)、参数空间(界内 / 确定性 / 对数分布 / 分层覆盖)、成本(硬约束罚值)、搜索(同 seed 逐位复现、优于手调基线)、报告(CSV/TOML/JSON 往返) 全平台(CI 跑 just tune-smoke)
预设 schema 往返、逐字段覆盖、优先级(CLI > 预设 > 内置)、未知键 / 越界 / 缺失报错、config/tune.toml 全部预设可解算、命名与后端一致、安全参数不可注入 全平台
vcan 集成 vcan0 双 socket 收发,真实内核 CAN 栈 Linux(#[ignore],CI 显式跑)
实车台架 spin chassis 四轮定速:链路/反馈、方向、转速稳态、过速保护、安全停机;spin rc 只读监视 DR16;chassis-tune --backend bench 逐次试验 + 重复性复验 armv7 + slcan/SocketCAN(人工)
交叉检查 aarch64-unknown-linux-gnu 全 workspace check;armv7 静态构建 chassisd CI

13.2 每个阶段完成后的门禁(just lint + 以下)

cargo build --workspace                       # 无警告
cargo test --workspace                        # 全绿
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
cargo check -p chassis-tune --features plot --all-targets      # 可选 feature 防腐烂
just tune-smoke                               # 同 seed 逐位一致 + 达标

新增代码要求:有文档注释;关键路径有测试覆盖;跨 crate 的常量(如限流、减速比)只有一个来源。


14. 部署

14.1 交叉编译(musl 静态,无需交叉工具链)

just build-armv7     # cargo build --release -p chassisd --target armv7-unknown-linux-musleabihf
just deploy          # 编译 + 推送 + mock 试运行(默认 root@192.168.2.2)

目标板实测:创龙 T153 / Ubuntu 22.04(armv7)。发布二进制使用 musl 静态链接。

14.2 板端拉起 CAN

# SocketCAN(板载原生控制器)
ip link set can0 type can bitrate 1000000 restart-ms 100
ip link set can0 up
ip -d link show can0

# slcan(USB 适配器):不需要 ip link,应用层直接打开串口
chmod 666 /dev/ttyACM0

14.3 systemd

deploy/chassisd.service:

ExecStart=/usr/local/bin/chassisd --interface can0 --config /etc/chassisd/chassis.toml
Restart=always
RestartSec=1
# 实时性(按需打开):
# CPUSchedulingPolicy=fifo
# CPUSchedulingPriority=20
# LimitRTPRIO=95

电调未上电 / CAN 线未接时,SocketCAN 侧会因无 ACK 进入 bus-off (ip -d link show can0 可见),chassisd 侧表现为四电机反馈离线、强制零电流—— 这正是安全设计的预期行为,不是故障。

调度策略:应用不调用 libc FFI。 应用不调用 libc FFI。SCHED_FIFO 与资源限制由 systemd 服务单元设置;std 程序不调用 mlockall。


15. 故障排查

症状 可能原因 处理
/dev/ttyACM0 打不开 权限 / 设备名不符 / 固件非 slcan ls -l /dev/ttyACM*;chmod 666;确认适配器是 Lawicel 固件(不是 gs_usb)
四个电调都无反馈 CAN_H/L 接反、无终端电阻、电调未上电、波特率不符 万用表量差分电阻(约 60Ω);--dry-run 复查;确认 --can-speed 1m
只有部分电调有反馈 电调 ID 冲突 / 拨码错 / 该段线断 逐个拔插定位(流程第 2 步)
上电后轮子纹丝不动 正常:上电门控未满足 等反馈同时打印"waiting for first feedback";查反馈丢失原因
台架脚本 50 ms 后失电 命令租约到期(只下发了一次) 每拍重发命令;这不是 bug
轮子"先憋住、一动就飞" 前馈过大 / 限流远大于实际所需 / 缓升太短 ff → 0;限流贴近实际;--ramp-ms 加大
速度环高频震荡 kp 太大(近似积分器 + 延迟) 降 kp、提 ki;参考第 7.3 节
稳态长期差 100+ rpm 静摩擦未被积分吃掉 提 ki;确认 current_limit 大于静摩擦(400~600 raw)
整定报告说"贴边界" 约束在决定结果 放宽对应轴区间再搜,别交付边界值
仿真整定的参数上车就飞 模型不含摩擦 / 噪声,仿真解偏激进 用 bench_loaded / field_updown 复验;以台架结果为准
trials.csv 两次不一致 改了参数空间 / 预设 / 后端 检查 超参数来源 打印行;同 seed 必须同空间同后端
守护进程起来就退出 配置校验失败 / --config 缺失 看 stderr;Config::validate 的报错会指出具体键
抖动大(>500 µs) 标准 Linux 非硬实时 先降 chassis 控制频率或合并任务;再考虑调度优先级 / 异构核(ARCHITECTURE.md 决策点 D4)

16. 术语表

术语 含义
SLCAN Serial CAN,通过串口传输 CAN 帧的 ASCII(Lawicel)协议
SocketCAN Linux 内核 CAN 子系统
raw 协议原始控制量单位(C620 电流 ±16384 ↔ ±20 A)
命令租约 set_* 每次调用续租,超时未刷新即强制 0 输出
上电门控 未见全部电机反馈前不下发速度命令
转子 / 轮端 电机轴 / 减速器输出轴;换算只在 OutputShaft 发生
域随机化 在一组扰动参数(增益 / 时间常数 / 延迟 / 摩擦)上做鲁棒优化
代价空间 对数(或恒等)映射后的参数坐标,使正增益约束天然满足
超参数预设 版本化的搜索条件:空间 / 场景 / 权重 / 阈值 / 预算 / 被控对象 / 安全包络
HIL Hardware-in-the-loop,此处指 --backend bench 的真机逐次试验

相关文档

版本 日期 变更
1.0 2026-09-23 首版:底盘接口约定、标定流程、自动整定与超参数预设、安全与故障处理