跳转至

调试与测试

概述

调试是机器人开发中最耗时的环节——编码占 20%,调试占 80%。好的调试方法和工具能极大提升效率。本节覆盖硬件调试、软件调试和系统级测试。


硬件调试工具

万用表(Multimeter)

最基本也最重要的调试工具。

必查项目

测量项 方法 预期值
电源电压 DC 电压档 与设计值一致 ±5%
接地连续性 通断档(蜂鸣) 所有 GND 连通
电流消耗 串联电流档 不超过额定值
短路检查 电阻档 VCC-GND 不应短路
信号电平 DC 电压档 高电平/低电平正确

调试步骤(上电前必做):

  1. 检查 VCC-GND 无短路
  2. 确认所有连接器插接正确
  3. 上电后立即测量各路电压
  4. 测量电流消耗是否在预期范围

示波器(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 时:

  1. 记录现象:截图/录屏/rosbag
  2. 简化环境:去掉不相关的模块
  3. 最小复现:用最少的步骤触发问题
  4. 修复验证:修复后用相同步骤验证

日志最佳实践

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

参考资源


评论 #