Skip to content

Real Robot - 真实机器人

在仿真环境中测试完算法后,下一步就是将其部署到真实机器人上。本章将介绍如何在真实机器人上使用 ROS。

从仿真到真实

┌─────────────────────────────────────────────────────────────┐
│                从仿真到真实的迁移过程                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │   仿真      │    │   测试      │    │   部署      │     │
│  │   (Gazebo)  │───▶│   (实验室)  │───▶│   (真实)    │     │
│  └─────────────┘    └─────────────┘    └─────────────┘     │
│                                                             │
│  挑战:                                                     │
│    - 传感器噪声和延迟                                        │
│    - 执行器响应时间                                          │
│    - 环境不确定性                                            │
│    - 安全性考虑                                              │
│                                                             │
│  解决方案:                                                  │
│    - 逐步测试,从小范围开始                                  │
│    - 增加安全机制                                            │
│    - 监控和日志记录                                          │
│    - 远程控制和紧急停止                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

硬件接口

1. 电机控制

#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist

class MotorController:
    def __init__(self):
        # 初始化电机驱动
        self.left_motor = Motor(port='/dev/ttyUSB0')
        self.right_motor = Motor(port='/dev/ttyUSB1')

        # 订阅速度命令
        rospy.Subscriber('/cmd_vel', Twist, self.cmd_vel_callback)

    def cmd_vel_callback(self, msg):
        """处理速度命令"""
        # 差速驱动运动学
        linear_vel = msg.linear.x
        angular_vel = msg.angular.z

        # 计算左右轮速度
        wheel_separation = 0.3  # 轮距(米)
        left_vel = linear_vel - (angular_vel * wheel_separation / 2)
        right_vel = linear_vel + (angular_vel * wheel_separation / 2)

        # 设置电机速度
        self.left_motor.set_velocity(left_vel)
        self.right_motor.set_velocity(right_vel)

2. 传感器驱动

#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import LaserScan

class LidarDriver:
    def __init__(self):
        # 初始化激光雷达
        self.lidar = Lidar(port='/dev/ttyUSB2')

        # 发布激光数据
        self.scan_pub = rospy.Publisher('/scan', LaserScan, queue_size=10)

        # 定时读取数据
        rospy.Timer(rospy.Duration(0.1), self.read_scan)

    def read_scan(self, event):
        """读取激光数据"""
        # 从硬件读取数据
        ranges = self.lidar.get_ranges()

        # 创建 LaserScan 消息
        scan = LaserScan()
        scan.header.stamp = rospy.Time.now()
        scan.header.frame_id = 'laser'
        scan.angle_min = -3.14
        scan.angle_max = 3.14
        scan.angle_increment = 0.01
        scan.range_min = 0.1
        scan.range_max = 10.0
        scan.ranges = ranges

        # 发布数据
        self.scan_pub.publish(scan)

3. 里程计

#!/usr/bin/env python3
import rospy
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Quaternion
import tf

class OdometryPublisher:
    def __init__(self):
        # 初始化编码器
        self.left_encoder = Encoder(port='/dev/ttyUSB3')
        self.right_encoder = Encoder(port='/dev/ttyUSB4')

        # 发布里程计
        self.odom_pub = rospy.Publisher('/odom', Odometry, queue_size=10)

        # 发布 TF
        self.tf_broadcaster = tf.TransformBroadcaster()

        # 机器人参数
        self.wheel_radius = 0.05  # 轮子半径(米)
        self.wheel_separation = 0.3  # 轮距(米)

        # 初始位置
        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0

        # 上一次编码器值
        self.last_left_enc = 0
        self.last_right_enc = 0

        # 定时发布
        rospy.Timer(rospy.Duration(0.05), self.publish_odom)

    def publish_odom(self, event):
        """发布里程计"""
        # 读取编码器
        left_enc = self.left_encoder.get_value()
        right_enc = self.right_encoder.get_value()

        # 计算位移
        delta_left = (left_enc - self.last_left_enc) * self.wheel_radius
        delta_right = (right_enc - self.last_right_enc) * self.wheel_radius

        # 更新上一次编码器值
        self.last_left_enc = left_enc
        self.last_right_enc = right_enc

        # 计算机器人位移
        delta_s = (delta_left + delta_right) / 2
        delta_theta = (delta_right - delta_left) / self.wheel_separation

        # 更新位置
        self.x += delta_s * math.cos(self.theta + delta_theta / 2)
        self.y += delta_s * math.sin(self.theta + delta_theta / 2)
        self.theta += delta_theta

        # 创建里程计消息
        odom = Odometry()
        odom.header.stamp = rospy.Time.now()
        odom.header.frame_id = 'odom'
        odom.child_frame_id = 'base_link'

        # 设置位置
        odom.pose.pose.position.x = self.x
        odom.pose.pose.position.y = self.y
        odom.pose.pose.position.z = 0.0

        # 设置方向
        q = tf.transformations.quaternion_from_euler(0, 0, self.theta)
        odom.pose.pose.orientation = Quaternion(*q)

        # 发布里程计
        self.odom_pub.publish(odom)

        # 发布 TF
        self.tf_broadcaster.sendTransform(
            (self.x, self.y, 0),
            q,
            rospy.Time.now(),
            'base_link',
            'odom'
        )

真实机器人调试

1. 传感器校准

# 检查传感器连接
ls /dev/ttyUSB*

# 测试激光雷达
rostopic echo /scan

# 测试相机
rosrun image_view image_view image:=/camera/image_raw

# 测试编码器
rostopic echo /odom

2. 参数调优

# 查看当前参数
rosparam list

# 修改参数
rosparam set /motor_controller/max_speed 0.5

# 保存参数
rosparam dump params.yaml

3. 可视化

在 RViz 中添加: - 激光扫描数据 - 相机图像 - 里程计轨迹 - TF 坐标系

安全注意事项

1. 紧急停止

#!/usr/bin/env python3
import rospy
from std_msgs.msg import Bool

class EmergencyStop:
    def __init__(self):
        # 订阅紧急停止信号
        rospy.Subscriber('/emergency_stop', Bool, self.emergency_stop_callback)

        # 发布停止命令
        self.stop_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)

        # 紧急停止状态
        self.is_stopped = False

    def emergency_stop_callback(self, msg):
        """处理紧急停止信号"""
        if msg.data:
            # 发送停止命令
            stop_cmd = Twist()
            self.stop_pub.publish(stop_cmd)
            self.is_stopped = True
            rospy.logwarn("Emergency stop activated!")

2. 碰撞检测

#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import LaserScan

class CollisionDetector:
    def __init__(self):
        # 订阅激光数据
        rospy.Subscriber('/scan', LaserScan, self.scan_callback)

        # 发布停止命令
        self.stop_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)

        # 安全距离
        self.safe_distance = 0.3  # 米

    def scan_callback(self, msg):
        """检测碰撞风险"""
        # 检查前方障碍物
        front_ranges = msg.ranges[len(msg.ranges)//4:3*len(msg.ranges)//4]
        min_distance = min(front_ranges)

        if min_distance < self.safe_distance:
            # 发送停止命令
            stop_cmd = Twist()
            self.stop_pub.publish(stop_cmd)
            rospy.logwarn(f"Collision risk detected! Distance: {min_distance:.2f}m")

3. 电池监控

#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import BatteryState

class BatteryMonitor:
    def __init__(self):
        # 订阅电池状态
        rospy.Subscriber('/battery', BatteryState, self.battery_callback)

        # 电池阈值
        self.low_battery_threshold = 0.2  # 20%
        self.critical_battery_threshold = 0.1  # 10%

    def battery_callback(self, msg):
        """监控电池状态"""
        battery_level = msg.percentage

        if battery_level < self.critical_battery_threshold:
            rospy.logerr(f"Critical battery level: {battery_level*100:.1f}%")
            # 执行紧急停止
            self.emergency_stop()
        elif battery_level < self.low_battery_threshold:
            rospy.logwarn(f"Low battery level: {battery_level*100:.1f}%")
            # 返回充电站
            self.return_to_charging_station()

真实机器人测试流程

步骤 1:硬件检查

# 检查所有连接
rostopic list

# 检查传感器数据
rostopic echo /scan
rostopic echo /odom
rostopic echo /camera/image_raw

# 检查电机响应
rostopic pub /cmd_vel geometry_msgs/Twist "linear:
  x: 0.1
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0"

步骤 2:低速测试

# 设置低速限制
rosparam set /motor_controller/max_speed 0.1

# 测试基本移动
rosrun teleop_twist_keyboard teleop_twist_keyboard.py

步骤 3:导航测试

# 启动导航栈
roslaunch my_robot navigation.launch

# 设置初始位置
# 在 RViz 中使用 "2D Pose Estimate"

# 设置目标位置
# 在 RViz 中使用 "2D Nav Goal"

步骤 4:完整测试

# 运行完整任务
roslaunch my_robot full_task.launch

常见问题

1. 传感器数据不准确

原因:传感器噪声或校准错误

解决方案: - 校准传感器 - 使用滤波算法(卡尔曼滤波) - 增加传感器冗余

2. 电机响应慢

原因:通信延迟或驱动程序问题

解决方案: - 优化通信协议 - 使用实时操作系统 - 增加控制频率

3. 导航不准确

原因:里程计漂移或地图不准确

解决方案: - 使用更好的里程计(视觉里程计、IMU) - 重新构建地图 - 使用闭环检测

实验

Lab 4-2: 真实机器人使用

  1. 连接真实机器人
  2. 测试传感器
  3. 测试电机控制
  4. 运行简单导航任务

Lab 5: 真实机器人综合项目

  1. 设计任务
  2. 实现算法
  3. 测试和调试
  4. 部署和演示

下一步

  • 部署 — 部署机器人系统
  • 多机器人 — 多机器人系统
  • SLAM — 在真实机器人上使用 SLAM

参考资源