机器人实战:调车与开发经验

5.8k 词

调车不是玄学,是数据驱动的闭环:先记录 → 再分析 → 只改一个变量 → 复测。本文把 RoboMaster 步兵/哨兵从新车部署到赛前调参的完整流程讲透,结合 rm_pid_tuner(ROS 自动调参系统)的实际架构:部署检查清单每条为什么 → 电机/云台调试方法论(阶跃响应、扫频)→ 自动调参状态机 → rosbag 回放驱动的安全调车 → 故障排查路径 → 工程化保障(看门狗、日志、参数管理)。

调试方法论:三条铁律

铁律 1:数据先行,先记录再分析

调车前先问自己:这次改动要观察哪个指标? 没有记录就调参 = 凭感觉。正确姿势:

1
2
3
4
# 调车前录一段基线,调车后再录一段,对比才有意义
rosbag record /chassis/cmd /chassis/state /gimbal/state -O before.bag --duration 15
# 改参数、跑动作
rosbag record /chassis/cmd /chassis/state /gimbal/state -O after.bag --duration 15

对比指标要量化,不是”看起来稳了”:阶跃响应的超调量(%)、调节时间(s)、稳态误差(rad 或 m)、振荡频率(Hz)。rm_pid_tuner 的 pid_analyzer 就是干这个的——从 bag 里切出阶跃段,自动算超调/调节时间/稳态误差,再给出参数建议。

铁律 2:一次只改一个变量

同时改 Kp 和 Ki,出问题了你不知道是哪个引起的。调参记录表:

时间 改动 现象(量化) 结论
10:02 Kp 3.0→5.0 超调 8%→22%,调节时间 120ms→90ms Kp 到头,改 Kd
10:15 Kd 0→0.05 超调 22%→11% 有效,继续加

铁律 3:最小复现

问题不稳定复现时,先构造最小系统:只保留出问题的那条链路(比如云台 yaw 单独一个节点 + 一个正弦激励话题),关掉裁判系统、关掉自瞄、关掉底盘。最小系统能复现 → 问题在这条链路内;不能 → 是耦合问题(多半是底盘振动/供电/通信干扰)。

新车部署:从零到能跑

上电检查清单(每条都标了”为什么”)

# 检查项 命令 为什么
1 工控机 IP 同网段 ip addr + ping 路由器 ROS 多机发现依赖广播,跨网段直接失联
2 CAN 接口起来没有 ip link set can0 up type can bitrate 1000000 + candump can0 接口没 up,任何程序都收不到数据
3 电调有没有在发反馈 candump can0 -T 1000 看 0x201+ID 帧 C620 上电即 1kHz 发反馈,看不到 = 供电/线序/波特率问题
4 IMU 数据合理性 rostopic echo /imu/data 静置看 accel 是否 ≈ 9.8 且稳定 零偏过大说明需要标定或硬件有问题
5 遥控器通道映射 拨杆看对应话题值变化 映射错 = 急停拨杆变成开火
6 电机 ID 与代码一致 逐个 cansend 控制帧看哪个电机转 ID 配错 = 指令发给别的电机,经典”云台自己动”事故

CAN 上电时序:电调的失联保护

C620 电调有个要命的保护:持续约 500ms 收不到控制帧就自动关断输出。所以上电顺序必须是:先启动控制程序(开始发帧)→ 再给电机主电源,或者程序里保证上电后立刻以 1kHz 发控制帧。反过来(先上电后启程序)的表现是:电机”咔”一声后怎么发指令都不转——不是坏了,是电调进了保护,重新上电或连发几帧唤醒即可。

电机与云台调试

PID 调参方法论:从”手感”到”数据”

旧式调参(先 P 后 I 再 D,凭耳朵听振荡)只能到”能用”,到不了”最优”。工程做法:

  1. 给阶跃,看响应:位置环给 30° 阶跃,速度环给 100rpm 阶跃,记录响应曲线
  2. 读三个数:超调量 $\sigma%$、调节时间 $t_s$(进入 ±5% 带)、稳态误差 $e_{ss}$
  3. 按规律调:
    • 超调大 + 振荡 → 降 Kp 或加 Kd(Kd 抑制超调,但过大会放大噪声,典型上限:Kd 引起的控制量噪声 < 电流环满量程的 5%)
    • 稳态误差不为零(位置环)→ 加 Ki,Ki 每翻倍看 $e_{ss}$ 是否减半——不减半说明是死区/摩擦非线性,加 Ki 没用,要加前馈
    • 响应慢 + 无超调 → 加 Kp
  4. 扫频找极限:给正弦激励(频率从 1Hz 扫到 30Hz),记录幅值/相位。谐振峰出现且增益 > 0dB 的频率就是稳定裕度的极限——云台 15Hz 有谐振峰,你的控制带宽就别想超过 5Hz,硬调参数只会抖。

rm_pid_tuner 的自动调参状态机

手动调参慢且主观,rm_pid_tuner 把它自动化,状态机如下(这是 ROS 里调参节点的标准骨架):

1
2
3
4
5
6
7
8
9
10
11
12
13
IDLE ──start──> COLLECTING(录阶跃/扫频数据)
│ 数据量够
▼
ANALYZING(算超调/调节时间/稳态误差 + 频域指标)
│
▼
SUGGESTING(规则/LLM 生成新参数,带安全边界)
│ 人工确认或自动下发
▼
APPLYING(限幅后下发 → 回到 COLLECTING 复测)
│ 连续 N 轮无改善
▼
DONE

安全设计是灵魂(调参本质是”让系统变不稳定”的探索):

1
2
3
4
5
6
7
8
9
10
// 下发前必做三件事:限幅、校验、备份
double clamp(double v, double lo, double hi) { return std::min(hi, std::max(lo, v)); }

// 1. 参数限幅:Kp 不允许超过上界(防"建议值"直接炸车)
double kp_new = clamp(suggested.kp, 0.0, kp_max_);
// 2. 合理性校验:建议值与当前值跳变超过 5 倍 → 拒绝
if (std::abs(kp_new - kp_cur) > 5.0 * std::abs(kp_cur))
{ ROS_WARN("Reject kp jump %f->%f", kp_cur, kp_new); return; }
// 3. 下发前保存当前参数,出问题一键回滚
tuning_history_.push_back({kp_cur, ki_cur, kd_cur});

加上急停链路:任何环节检测到(a)反馈丢包超 100ms(b)电流超限(c)遥控器急停信号,状态机立刻回 IDLE 并下发安全参数。自动调参 ≠ 无人值守,只是把”手拧参数”变成”审核建议”。

云台抖动排查:机械谐振 vs 参数过大

云台抖,先别调参。排查顺序:

  1. 手摸云台:电机发热但云台不抖 → 电流环问题;云台肉眼可见高频微颤 → 先查机械
  2. 看频谱:把角度误差做 FFT,峰值频率:
    • 与电机转速成比例 → 转子动平衡/齿轮啮合
    • 固定频率(如 12~18Hz)→ 机械谐振,调参治标不治本,先加配重/换刚度/加阻尼
    • 低频 (<2Hz) 摆动 → 重心偏了或减震垫太软,云台质心必须落在 yaw 轴线上
  3. 只有确认机械没问题,才允许动 PID——这是调车现场 80% 时间浪费在”调参”上的根源

调车工具链:rosbag 回放驱动的调试闭环

回放是安全的”时光机”

实车反复试错又危险又慢。正确闭环:实车录 30s → 回放调参 → 仿真/离线验证 → 实车复测。回放关键:

1
2
3
4
5
6
# 回放时用 bag 的时间(配合 use_sim_time: true),保证 TF/时间戳一致
rosbag play --clock before.bag
# 只回放需要的部分(--start/--duration 裁剪)
rosbag play --start 5 --duration 10 before.bag
# 先看 bag 里有什么
rosbag info before.bag

坑:回放 + use_sim_time 后,所有用 ros::Time::now() 的节点都走仿真时间;如果有节点没设 use_sim_time,时间戳错位,TF 直接报 “Lookup would require extrapolation”。回放前统一所有节点的时间源。

调参分析脚本:别只会 rqt_plot 肉眼看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
"""从 bag 里提取阶跃段,算超调/调节时间——调参判断的客观依据"""
import rosbag, numpy as np

bag = rosbag.Bag('before.bag')
t, y = [], []
for topic, msg, ts in bag.read_messages(topics=['/gimbal/state']):
t.append(ts.to_sec()); y.append(msg.yaw_angle)
t, y = np.array(t), np.array(y)

# 找阶跃起点(指令突变处),切出响应段
step_idx = np.argmax(np.abs(np.diff(y)) > 0.1)
seg = y[step_idx:step_idx + 500]
ref = seg[-1] # 稳态值
overshoot = (seg.max() - ref) / abs(ref) * 100 # 超调 %
settle_idx = np.where(np.abs(seg - ref) < 0.05 * abs(ref))[0]
settle_time = t[settle_idx[0]] - t[step_idx] if len(settle_idx) else float('inf')
print(f"overshoot={overshoot:.1f}% settle_time={settle_time*1000:.0f}ms")

这套脚本就是 rm_pid_tuner pid_analyzer 的雏形——先有客观指标,才有”调好了”的判据。

现场工具速查

1
2
3
4
5
rqt_graph          # 节点/话题拓扑,排查"消息根本没发出来"
rqt_plot # 多曲线对比:指令 vs 反馈 vs 误差
rqt_reconfigure # 动态改参(不用重启节点),改完立刻在 rqt_plot 看效果
rqt_console # 日志过滤(只看 WARN/ERROR,按节点过滤)
rosrun tf view_frames # TF 树,坐标变换链一眼看清

常见故障排查:症状 → 路径

症状 优先排查路径(按概率排序)
电机不转 ① candump can0 看有没有反馈帧(供电/线序)② 电调是否进失联保护(连发帧唤醒)③ 控制帧 ID 与电机 ID 是否匹配 ④ 电流指令是不是 0
云台抖动 ① FFT 查谐振(固定频率→机械)② 质心是否偏 ③ 参数过大(超调>15% 就降 Kp)
跑着跑着电机全停 ① 主电源掉压(电池过放保护,看电压曲线)② CAN 总线 bus-off(错误帧风暴,ip -s link show can0 看错误计数)③ 程序崩溃(看 systemd 日志)
偶尔丢帧/数据跳变 ① 终端电阻(120Ω×2)② CAN_H/L 接反 ③ 线缆过长/质量差 ④ 与功率线并行干扰
TF 报 extrapolation ① 时间源不统一(use_sim_time 混用)② 某节点没发布 TF ③ 回放时钟没开 –clock
一开自瞄就卡 ① CPU 占用(htop,视觉节点是不是把核吃满)② 话题 QoS 队列积压(depth 太大)③ 控制节点与视觉节点抢锁

工程化保障:让车”过夜不炸”

看门狗与失联保护(必须写进每个控制节点)

1
2
3
4
5
6
7
8
9
// 控制节点:反馈新鲜度看门狗——比任何 PID 都重要
void watchdog_check(const ros::TimerEvent&) {
double age = (ros::Time::now() - last_feedback_time_).toSec();
if (age > 0.1) { // 100ms 没收到反馈
ROS_FATAL("Feedback lost %.2fs, entering safe mode!", age);
publish_zero_command(); // 输出清零(电机刹停)
state_ = State::SAFE; // 状态机切安全态,禁止任何自动模式
}
}

注意:失联后输出 0 不是万能——云台在重力方向失联会”砸头”,底盘失联会急刹翻车。工程上按关节特性分级:云台 yaw 失联保持最后指令(位置环保持)、pitch 失联回中、底盘失联刹停。安全策略要按机构写,不能一刀切。

systemd 自启 + 崩溃自愈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Robot Core
After=network.target can.service

[Service]
Type=simple
User=robot
ExecStart=/home/robot/start.sh
Restart=on-failure # 崩溃自动拉起
RestartSec=3
# 看门狗:主循环必须周期喂狗,卡死 30s 强制重启
WatchdogSec=30

[Install]
WantedBy=multi-user.target

参数与代码一起进 git

调参记录、标定值、配置文件全部版本化。config/ 目录按”日期-改动点”命名分支,参数改动必须带 commit message(”gimbal_kp 5.0→8.0 超调22%→11%”)。没有版本管理的调参 = 回滚全靠记忆,赛前一夜回到解放前的痛经历一次就懂。

踩坑清单(实战汇总)

  1. 先录数据再调参,没有基线就没有对比,调完都不知道是好是坏
  2. 一次只改一个变量,同时改两个参数出问题必抓瞎
  3. 云台抖先查机械:FFT 看是不是固定频率谐振,别上来就动 PID
  4. C620 失联保护:先启程序后上电,或保证上电后立刻 1kHz 发帧
  5. 回放要统一时间源:use_sim_time 不一致 → TF extrapolation
  6. 自动调参必须限幅 + 校验 + 备份:建议值直接下发 = 炸车脚本
  7. 失联输出 0 不是万能:按机构特性分级安全策略
  8. 看门狗是底线:反馈新鲜度检查优先于一切控制逻辑
  9. 参数进 git:没有版本化的调参记录,回滚全靠记忆

一句话方法论:调车 = 可观测(数据全录)+ 可量化(指标明确)+ 可回滚(参数版本化)+ 可兜底(看门狗/急停)。四样齐了,剩下的就是按数据迭代。