调试与测试
概述
调试是机器人开发中最耗时的环节——编码占 20%,调试占 80%。好的调试方法和工具能极大提升效率。本节覆盖硬件调试、软件调试和系统级测试。
硬件调试工具
万用表(Multimeter)
最基本也最重要的调试工具。
必查项目:
| 测量项 | 方法 | 预期值 |
|---|---|---|
| 电源电压 | DC 电压档 | 与设计值一致 ±5% |
| 接地连续性 | 通断档(蜂鸣) | 所有 GND 连通 |
| 电流消耗 | 串联电流档 | 不超过额定值 |
| 短路检查 | 电阻档 | VCC-GND 不应短路 |
| 信号电平 | DC 电压档 | 高电平/低电平正确 |
调试步骤(上电前必做):
- 检查 VCC-GND 无短路
- 确认所有连接器插接正确
- 上电后立即测量各路电压
- 测量电流消耗是否在预期范围
示波器(Oscilloscope)
观察时变信号的必备工具。
适用场景:
| 信号 | 要观察的 | 常见问题 |
|---|---|---|
| PWM | 频率、占空比、上升沿 | 频率不对、抖动大 |
| SPI | 时钟、数据、片选时序 | CPOL/CPHA 配置错误 |
| I2C | SCL/SDA 波形 | 上拉电阻不合适 |
| UART | 波特率、数据帧 | 波特率不匹配 |
| CAN | 差分信号、位时序 | 终端电阻缺失 |
| 电源 | 纹波、瞬态响应 | 纹波过大、压降 |
推荐设备:
| 设备 | 带宽 | 价格 | 适合 |
|---|---|---|---|
| Rigol DS1054Z | 50 MHz | ~$400 | 入门推荐(可破解到 100MHz) |
| Siglent SDS1104 | 100 MHz | ~$400 | 性价比高 |
| Keysight DSOX1204G | 70 MHz | ~$500 | 品牌保障 |
| Analog Discovery 2 | 30 MHz | ~$300 | 便携+逻辑分析仪 |
逻辑分析仪(Logic Analyzer)
观察数字通信协议的利器。
Saleae Logic 系列(推荐):
- Logic 8:8 通道,25 MS/s,~$400
- Logic Pro 16:16 通道,500 MS/s,~$1000
- 软件自动解码:SPI, I2C, UART, CAN, 1-Wire 等
使用场景:
问题:STM32 与 IMU 的 SPI 通信失败
排查:
1. 逻辑分析仪接入 SCK, MOSI, MISO, CS 四根线
2. 软件自动解码 SPI 帧
3. 发现:CS 信号反相了(Active High 而非 Active Low)
4. 修复:配置 SPI CS 极性
廉价替代:
- Sigrok + fx2lafw 兼容分析仪(~$10)
- Raspberry Pi Pico 改装逻辑分析仪(~$5)
串口终端
与 MCU/传感器的基本通信调试:
| 工具 | 平台 | 特点 |
|---|---|---|
| minicom | Linux | 轻量命令行 |
| PuTTY | Windows | 经典图形界面 |
| screen | Linux/Mac | 最简单 |
| CoolTerm | 跨平台 | 功能丰富 |
| Arduino Serial Monitor | 跨平台 | 入门友好 |
# Linux 串口连接
minicom -D /dev/ttyUSB0 -b 115200
# 快速查看串口数据
screen /dev/ttyUSB0 115200
ROS2 调试工具
命令行工具
# 查看所有活跃话题
ros2 topic list
# 查看话题数据(实时)
ros2 topic echo /imu/data
# 查看话题频率
ros2 topic hz /camera/image_raw
# 查看话题带宽
ros2 topic bw /camera/image_raw
# 查看话题消息类型
ros2 topic info /cmd_vel
# 查看所有节点
ros2 node list
# 查看节点信息(发布/订阅/服务)
ros2 node info /controller_node
# 发布测试消息
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.5}, angular: {z: 0.3}}"
# 调用服务
ros2 service call /set_mode std_srvs/srv/SetBool "{data: true}"
# 查看 tf 树
ros2 run tf2_tools view_frames
rqt 工具集
| 工具 | 命令 | 功能 |
|---|---|---|
| rqt_graph | rqt_graph |
节点-话题连接图 |
| rqt_plot | rqt_plot |
实时数据绘图 |
| rqt_image_view | rqt_image_view |
图像话题查看 |
| rqt_console | rqt_console |
日志查看/过滤 |
| rqt_reconfigure | rqt_reconfigure |
动态参数调节 |
rviz2
3D 可视化是调试机器人感知和运动的核心工具:
常用显示项:
| 显示类型 | 话题 | 用途 |
|---|---|---|
| RobotModel | /robot_description |
机器人模型 |
| TF | /tf |
坐标系可视化 |
| PointCloud2 | /lidar/points |
点云 |
| Image | /camera/image_raw |
图像 |
| LaserScan | /scan |
2D 激光扫描 |
| Marker | /visualization_marker |
自定义标记 |
| Path | /planned_path |
规划路径 |
| Wrench | /ft_sensor/wrench |
力/力矩向量 |
rosbag2 数据录制
# 录制所有话题
ros2 bag record -a
# 录制指定话题
ros2 bag record /imu/data /camera/image_raw /joint_states
# 回放
ros2 bag play my_recording/
# 查看录制信息
ros2 bag info my_recording/
调试流程:在出问题时录制 → 事后回放分析 → 不需要重复触发问题。
Foxglove Studio
基于 Web 的现代可视化工具,替代 rviz2 + rqt:
- 支持 ROS2 实时连接和 rosbag 回放
- 丰富的面板:3D、图表、图像、日志
- 远程访问(Web 浏览器)
- 可分享可视化配置
系统级测试
测试金字塔
╱╲
╱ ╲
╱ 现场 ╲ ← 少量,高成本
╱ 测试 ╲
╱──────────╲
╱ 集成测试 ╲ ← 中量,中成本
╱──────────────╲
╱ 单元测试 ╲ ← 大量,低成本
╱──────────────────╲
单元测试
每个节点/模块独立测试:
# test_controller.py
import pytest
from my_robot.controller import PIDController
def test_pid_output():
pid = PIDController(kp=1.0, ki=0.1, kd=0.01)
# 设定目标
pid.set_target(10.0)
# 当前值低于目标,输出应为正
output = pid.compute(current=5.0, dt=0.01)
assert output > 0
# 到达目标,输出应接近零
for _ in range(1000):
output = pid.compute(current=10.0, dt=0.01)
assert abs(output) < 0.1
def test_pid_limits():
pid = PIDController(kp=100.0, ki=0, kd=0)
pid.set_target(100.0)
pid.set_output_limits(-50, 50)
output = pid.compute(current=0.0, dt=0.01)
assert output <= 50 # 应被限幅
ROS2 集成测试框架:
# test_camera_node.py (launch_testing)
import launch_testing
import rclpy
from sensor_msgs.msg import Image
def test_camera_publishes(self):
"""验证相机节点在 5 秒内发布图像"""
received = []
def callback(msg):
received.append(msg)
node.create_subscription(Image, '/camera/image_raw', callback, 10)
# 等待最多 5 秒
end_time = time.time() + 5.0
while time.time() < end_time and len(received) == 0:
rclpy.spin_once(node, timeout_sec=0.1)
assert len(received) > 0, "未在 5 秒内收到图像"
assert received[0].width == 640
assert received[0].height == 480
集成测试
验证子系统之间的交互:
| 测试项 | 验证内容 |
|---|---|
| 感知→控制 | 传感器数据正确传递到控制器 |
| 控制→执行 | 控制命令正确驱动电机 |
| 全链路延迟 | 从感知到执行的端到端延迟 |
| 故障传播 | 一个节点崩溃不影响其他节点 |
| 资源消耗 | CPU/内存/带宽在可接受范围 |
现场测试
实际环境中的测试:
=== 现场测试检查清单 ===
启动前:
[ ] 电池充满
[ ] 所有连接器牢固
[ ] 安全停止按钮功能正常
[ ] 遥控器连接正常
[ ] 测试场地清理安全
基本功能:
[ ] 原地启动,无异常振动
[ ] 低速前进/后退/转弯
[ ] 传感器数据流正常
[ ] 急停功能正常
性能测试:
[ ] 最大速度测试
[ ] 坡度/障碍物测试
[ ] 连续运行 30 分钟
[ ] 电池消耗记录
故障测试:
[ ] 遥控器断开 → 安全停止
[ ] 碰撞 → 检测并停止
[ ] 低电量 → 警告并停机
常见故障模式与诊断
硬件故障
| 症状 | 可能原因 | 诊断方法 |
|---|---|---|
| 设备不供电 | 保险丝熔断/接触不良 | 万用表测电压 |
| 间歇性断连 | 连接器松动/虚焊 | 摇晃线缆观察 |
| 电机抖动 | 编码器噪声/接地问题 | 示波器看编码器信号 |
| 传感器读数异常 | EMI 干扰 | 关闭电机后对比 |
| 过热关机 | 散热不足 | 热成像/温度传感器 |
| 通信错误 | 波特率/终端电阻 | 逻辑分析仪抓包 |
软件故障
| 症状 | 可能原因 | 诊断方法 |
|---|---|---|
| 节点崩溃 | 段错误/异常未捕获 | GDB/core dump |
| 数据延迟增大 | 处理瓶颈/队列溢出 | top/htop 看 CPU |
| 话题不通 | QoS 不匹配/DDS 配置 | ros2 doctor |
| tf 错误 | 变换缺失/时间戳问题 | ros2 run tf2_tools view_frames |
| 内存泄漏 | 未释放的缓冲区 | Valgrind / AddressSanitizer |
| 数据不同步 | 时间戳不一致 | 检查 header.stamp |
系统级故障
| 症状 | 可能原因 | 诊断方法 |
|---|---|---|
| 性能周期性下降 | 散热节流(throttling) | sensors / 温度日志 |
| 行为不确定 | 竞态条件/时序问题 | 加日志 + 时间戳分析 |
| 偶发崩溃 | 电源瞬态/EMI | 电源纹波测量 |
| 定位漂移 | IMU 积分误差/标定问题 | 对比真值系统 |
调试方法论
二分法排错
系统不工作时,不要盲目尝试——使用二分法缩小问题范围:
全系统不工作
├── 硬件还是软件? → 用最简单的测试程序验证硬件
│ ├── 硬件问题
│ │ ├── 电源还是信号? → 万用表测电压
│ │ └── 哪个模块? → 逐个断开排查
│ └── 软件问题
│ ├── 哪个节点? → 逐个启动
│ └── 哪个功能? → 注释代码二分
最小复现
当发现 bug 时:
- 记录现象:截图/录屏/rosbag
- 简化环境:去掉不相关的模块
- 最小复现:用最少的步骤触发问题
- 修复验证:修复后用相同步骤验证
日志最佳实践
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_node')
def process(self):
self.get_logger().debug('详细内部状态: ...') # 开发时
self.get_logger().info('正常操作信息') # 日常
self.get_logger().warn('接近极限: temp=85°C') # 需关注
self.get_logger().error('传感器读取失败') # 出错
self.get_logger().fatal('不可恢复的错误') # 严重
# 运行时设置日志级别
ros2 run my_pkg my_node --ros-args --log-level debug
性能分析
CPU 性能
# 实时监控
htop
# ROS2 节点 CPU 使用
ros2 run rqt_top rqt_top
# 代码级性能分析(C++)
perf record ros2 run my_pkg my_node
perf report
# Python 性能分析
python3 -m cProfile -o profile.out my_script.py
通信延迟
# 测量话题端到端延迟
def callback(msg):
now = self.get_clock().now()
msg_time = rclpy.time.Time.from_msg(msg.header.stamp)
latency = (now - msg_time).nanoseconds / 1e6 # ms
self.get_logger().info(f'延迟: {latency:.1f} ms')
带宽监控
# 网络带宽
iftop
# ROS2 话题带宽
ros2 topic bw /camera/image_raw
# 输出: average: 18.5 MB/s
参考资源
- ROS2 调试指南: docs.ros.org/en/humble/Tutorials/
- Saleae Logic Analyzer: saleae.com
- Foxglove Studio: foxglove.dev
- Rigol DS1054Z: EEVblog 评测