从零构建ROS2 Humble差分AGV:Gazebo仿真与键盘控制全指南
在工业自动化与机器人研究领域,自主导引车(AGV)的仿真开发是验证算法和系统设计的关键步骤。ROS2 Humble作为机器人操作系统的最新LTS版本,配合Gazebo Classic仿真环境,为开发者提供了强大的工具链。本文将手把手带你完成一个差分驱动AGV的完整开发流程,从URDF建模到Gazebo物理仿真,最终实现键盘控制移动——这不仅是ROS2入门的绝佳实践,更是掌握机器人仿真核心技能的必经之路。
1. 开发环境配置与工作空间搭建
在开始AGV建模前,需要确保基础环境正确配置。推荐使用Ubuntu 22.04 LTS系统,这是ROS2 Humble官方支持的最佳平台。通过以下命令安装核心组件:
sudo apt update sudo apt install ros-humble-desktop ros-humble-gazebo-ros-pkgs创建工作空间是ROS2开发的起点,不同于ROS1的catkin工具,ROS2使用colcon构建系统。执行以下命令初始化工作空间:
mkdir -p ~/agv_ws/src cd ~/agv_ws colcon build每次打开新终端时,需要source安装文件以激活工作空间环境。建议将以下命令添加到~/.bashrc文件中实现自动加载:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc echo "source ~/agv_ws/install/setup.bash" >> ~/.bashrc创建功能包时需注意,ROS2支持多种构建类型。对于AGV描述文件,我们选择ament_cmake构建类型:
cd ~/agv_ws/src ros2 pkg create --build-type ament_cmake agv_description功能包目录结构应包含以下关键文件夹:
urdf/: 存放机器人描述文件meshes/: 存放3D模型文件(如有)launch/: 存放启动文件config/: 存放配置文件
2. AGV机械模型设计与URDF/Xacro编写
差分驱动AGV的机械结构通常包含以下核心组件:
- 底盘(Chassis): 机器人的主体结构
- 驱动轮(Drive Wheels): 左右各一,实现移动和转向
- 万向轮(Caster Wheel): 提供稳定性支撑
使用Xacro(XML宏)编写模型比纯URDF更具优势,它能实现参数化设计和模块化编程。创建一个agv.xacro文件,首先定义机器人命名空间和基础参数:
<?xml version="1.0"?> <robot name="agv" xmlns:xacro="http://www.ros.org/wiki/xacro"> <!-- 基础参数定义 --> <xacro:property name="base_length" value="0.4" /> <xacro:property name="base_width" value="0.3" /> <xacro:property name="base_height" value="0.2" /> <xacro:property name="wheel_radius" value="0.05" /> <xacro:property name="wheel_width" value="0.02" /> <!-- 材料定义 --> <material name="blue"> <color rgba="0 0 0.8 1"/> </material> </robot>底盘(Chassis)的Link定义需要包含视觉(Visual)、碰撞(Collision)和惯性(Inertial)属性:
<link name="base_link"> <visual> <geometry> <box size="${base_length} ${base_width} ${base_height}"/> </geometry> <material name="blue"/> </visual> <collision> <geometry> <box size="${base_length} ${base_width} ${base_height}"/> </geometry> </collision> <inertial> <mass value="5.0"/> <inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/> </inertial> </link>驱动轮需要定义与底盘的连接关节(Joint)和自身的Link。以左轮为例:
<link name="left_wheel"> <visual> <geometry> <cylinder radius="${wheel_radius}" length="${wheel_width}"/> </geometry> <material name="black"/> </visual> <collision> <geometry> <cylinder radius="${wheel_radius}" length="${wheel_width}"/> </geometry> </collision> <inertial> <mass value="0.5"/> <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/> </inertial> </link> <joint name="left_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="left_wheel"/> <origin xyz="0 ${base_width/2} -${(base_height/2-wheel_radius)}" rpy="0 1.5707 0"/> <axis xyz="0 1 0"/> </joint>3. Gazebo仿真集成与物理特性配置
将URDF模型成功导入Gazebo需要添加Gazebo特定标签和插件。首先在Xacro文件中添加Gazebo命名空间:
<gazebo reference="base_link"> <material>Gazebo/Blue</material> </gazebo> <gazebo reference="left_wheel"> <material>Gazebo/Black</material> <mu1 value="1.0"/> <mu2 value="1.0"/> </gazebo>差分驱动控制需要添加gazebo_ros_diff_drive插件,该插件负责将ROS控制命令转换为Gazebo中的车轮运动:
<gazebo> <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so"> <ros> <namespace>/</namespace> </ros> <update_rate>30</update_rate> <left_joint>left_wheel_joint</left_joint> <right_joint>right_wheel_joint</right_joint> <wheel_separation>${base_width}</wheel_separation> <wheel_diameter>${2*wheel_radius}</wheel_diameter> <max_wheel_torque>20</max_wheel_torque> <max_wheel_acceleration>5.0</max_wheel_acceleration> <command_topic>cmd_vel</command_topic> <odometry_topic>odom</odometry_topic> <odometry_frame>odom</odometry_frame> <robot_base_frame>base_link</robot_base_frame> </plugin> </gazebo>创建启动文件agv_gazebo.launch.py,实现一键启动仿真环境:
import os from launch import LaunchDescription from launch.actions import ExecuteProcess, IncludeLaunchDescription, RegisterEventHandler from launch.event_handlers import OnProcessExit from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory def generate_launch_description(): pkg_path = get_package_share_directory('agv_description') urdf_file = os.path.join(pkg_path, 'urdf', 'agv.xacro') # 将Xacro转换为URDF robot_description = Command( ['xacro ', urdf_file]) # 启动Gazebo gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join( get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')]), ) # 生成机器人模型 spawn_entity = Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-topic', 'robot_description', '-entity', 'agv'], output='screen' ) # 发布机器人状态 robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', name='robot_state_publisher', output='both', parameters=[{'robot_description': robot_description}] ) return LaunchDescription([ RegisterEventHandler( event_handler=OnProcessExit( target_action=spawn_entity, on_exit=[robot_state_publisher], ) ), gazebo, spawn_entity, ])4. 运动控制实现与调试技巧
Gazebo中的差分驱动AGV可以通过geometry_msgs/msg/Twist消息控制。ROS2提供了开箱即用的键盘控制节点:
ros2 run teleop_twist_keyboard teleop_twist_keyboard该节点会发布到/cmd_vel话题,控制消息包含两个关键参数:
linear.x: 前进/后退速度(m/s)angular.z: 旋转速度(rad/s)
常见问题排查指南:
模型在Gazebo中下陷或抖动
- 检查所有Link的惯性参数是否正确设置
- 调整万向轮的摩擦系数:
<gazebo reference="caster_wheel"> <mu1 value="0.1"/> <mu2 value="0.1"/> </gazebo>
车轮打滑或控制响应迟缓
- 增加差分驱动插件的
max_wheel_torque值 - 调整车轮的摩擦系数:
<gazebo reference="left_wheel"> <mu1 value="1.5"/> <mu2 value="1.5"/> </gazebo>
- 增加差分驱动插件的
TF坐标变换缺失
- 确保
robot_state_publisher节点正常运行 - 检查URDF中所有关节定义是否正确
- 确保
对于更高级的控制,可以创建自定义控制节点。以下Python示例实现了简单的目标点导航:
import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry import math class AGVController(Node): def __init__(self): super().__init__('agv_controller') self.cmd_vel_pub = self.create_publisher(Twist, '/cmd_vel', 10) self.odom_sub = self.create_subscription( Odometry, '/odom', self.odom_callback, 10) self.target_x = 2.0 self.target_y = 2.0 self.k_linear = 0.5 self.k_angular = 1.0 def odom_callback(self, msg): current_x = msg.pose.pose.position.x current_y = msg.pose.pose.position.y # 计算目标角度 angle_to_target = math.atan2(self.target_y - current_y, self.target_x - current_x) # 计算当前朝向(简化为2D平面) q = msg.pose.pose.orientation current_yaw = math.atan2(2*(q.w*q.z + q.x*q.y), 1-2*(q.y*q.y + q.z*q.z)) # 计算控制命令 cmd = Twist() distance = math.sqrt((self.target_x-current_x)**2 + (self.target_y-current_y)**2) if distance > 0.1: # 到达阈值 # 角度控制 angle_error = angle_to_target - current_yaw if angle_error > math.pi: angle_error -= 2*math.pi elif angle_error < -math.pi: angle_error += 2*math.pi cmd.angular.z = self.k_angular * angle_error # 距离控制 cmd.linear.x = self.k_linear * distance else: cmd.linear.x = 0.0 cmd.angular.z = 0.0 self.get_logger().info("Target reached!") self.cmd_vel_pub.publish(cmd) def main(args=None): rclpy.init(args=args) controller = AGVController() rclpy.spin(controller) controller.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()在实际项目中,我发现差分驱动机器人的转弯半径控制需要特别注意车轮间距参数。当wheel_separation设置不准确时,会导致实际转弯半径与预期不符。建议通过多次实测调整这个参数,使用以下公式作为初始值参考:
转弯半径 = (轮间距/2) / tan(转向角)