保姆级教程:用ROS2 Humble和Gazebo Classic从零搭建一个能跑能停的差分AGV模型
从零构建ROS2 Humble差分AGVGazebo仿真与键盘控制全指南在工业自动化与机器人研究领域自主导引车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 version1.0? robot nameagv xmlns:xacrohttp://www.ros.org/wiki/xacro !-- 基础参数定义 -- xacro:property namebase_length value0.4 / xacro:property namebase_width value0.3 / xacro:property namebase_height value0.2 / xacro:property namewheel_radius value0.05 / xacro:property namewheel_width value0.02 / !-- 材料定义 -- material nameblue color rgba0 0 0.8 1/ /material /robot底盘(Chassis)的Link定义需要包含视觉(Visual)、碰撞(Collision)和惯性(Inertial)属性link namebase_link visual geometry box size${base_length} ${base_width} ${base_height}/ /geometry material nameblue/ /visual collision geometry box size${base_length} ${base_width} ${base_height}/ /geometry /collision inertial mass value5.0/ inertia ixx0.1 ixy0 ixz0 iyy0.1 iyz0 izz0.1/ /inertial /link驱动轮需要定义与底盘的连接关节(Joint)和自身的Link。以左轮为例link nameleft_wheel visual geometry cylinder radius${wheel_radius} length${wheel_width}/ /geometry material nameblack/ /visual collision geometry cylinder radius${wheel_radius} length${wheel_width}/ /geometry /collision inertial mass value0.5/ inertia ixx0.001 ixy0 ixz0 iyy0.001 iyz0 izz0.001/ /inertial /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 ${base_width/2} -${(base_height/2-wheel_radius)} rpy0 1.5707 0/ axis xyz0 1 0/ /joint3. Gazebo仿真集成与物理特性配置将URDF模型成功导入Gazebo需要添加Gazebo特定标签和插件。首先在Xacro文件中添加Gazebo命名空间gazebo referencebase_link materialGazebo/Blue/material /gazebo gazebo referenceleft_wheel materialGazebo/Black/material mu1 value1.0/ mu2 value1.0/ /gazebo差分驱动控制需要添加gazebo_ros_diff_drive插件该插件负责将ROS控制命令转换为Gazebo中的车轮运动gazebo plugin namediff_drive filenamelibgazebo_ros_diff_drive.so ros namespace//namespace /ros update_rate30/update_rate left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation${base_width}/wheel_separation wheel_diameter${2*wheel_radius}/wheel_diameter max_wheel_torque20/max_wheel_torque max_wheel_acceleration5.0/max_wheel_acceleration command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic odometry_frameodom/odometry_frame robot_base_framebase_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( packagegazebo_ros, executablespawn_entity.py, arguments[-topic, robot_description, -entity, agv], outputscreen ) # 发布机器人状态 robot_state_publisher Node( packagerobot_state_publisher, executablerobot_state_publisher, namerobot_state_publisher, outputboth, parameters[{robot_description: robot_description}] ) return LaunchDescription([ RegisterEventHandler( event_handlerOnProcessExit( target_actionspawn_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 referencecaster_wheel mu1 value0.1/ mu2 value0.1/ /gazebo车轮打滑或控制响应迟缓增加差分驱动插件的max_wheel_torque值调整车轮的摩擦系数gazebo referenceleft_wheel mu1 value1.5/ mu2 value1.5/ /gazeboTF坐标变换缺失确保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(argsNone): rclpy.init(argsargs) controller AGVController() rclpy.spin(controller) controller.destroy_node() rclpy.shutdown() if __name__ __main__: main()在实际项目中我发现差分驱动机器人的转弯半径控制需要特别注意车轮间距参数。当wheel_separation设置不准确时会导致实际转弯半径与预期不符。建议通过多次实测调整这个参数使用以下公式作为初始值参考转弯半径 (轮间距/2) / tan(转向角)