【机器人开发四】从零开始创建一个ROS2机器人工程(1):创建一个ROS2小车,掌握ROS2工程以及消息订阅机制

【机器人开发四】从零开始创建一个ROS2机器人工程(1):创建一个ROS2小车,掌握ROS2工程以及消息订阅机制

【机器人开发四】从零开始创建一个ROS2机器人工程(1):熟悉ROS2工程目录,做一个turtle3小车

本文将手把手教你从零开始搭建一个能用键盘控制移动的两轮机器人。基于 ROS 2 Jazzy + Gazebo Harmonic 环境。

本文的目的

  1. 深入理解一个标准的ROS2工程的目录结构
  2. 理解ROS2工程中最核心的文件
  3. 掌握ROS2项目的启动方法
  4. 创建一个turtle3机器人,熟悉话题的发布订阅机制

文章目录

1. 创建工作空间和功能包

一个标准的 ROS 工程源码目录应有如下结构。ros2_ws 是项目目录,src 子目录是所有源码的目录。

# 创建工作空间mkdir-p ~/ros2_ws/src cd ~/ros2_ws/src # 创建 ROS 2 功能包(使用 ament_cmake 类型,支持 Gazebo 插件) ros2 pkg create --build-type ament_cmake my_robot_description # 进入包目录并创建标准子目录cd my_robot_description mkdir urdf launch worlds config meshes models 

执行完后,目录结构如下:

ros2_ws/ # 工作空间根目录 └── src/ # 源代码目录(所有功能包放这里) └── my_robot_description/ # 机器人描述功能包目录 ├── CMakeLists.txt # 编译脚本(自动生成) ├── package.xml # 包描述文件(自动生成) ├── src/ # C++源码目录,用于机器人控制编程(自动生成) ├── include/ # C++头文件目录(自动生成) ├── urdf/ # 机器人 URDF/Xacro 文件(核心) ├── launch/ # Launch 启动文件(核心) ├── worlds/ # Gazebo 世界场景文件(可选) ├── config/ # RViz/Gazebo 配置文件(可选) ├── meshes/ # 3D 网格模型文件(可选) └── models/ # Gazebo 模型定义(可选) 
提示:标注为"核心"的文件和目录是必须创建的,机器人缺少它们无法工作标注为"自动生成"的文件由 ros2 pkg create 命令自动生成,一般不需要手动修改标注为"可选"的目录可以根据需要创建,不是完成本教程的必要条件
提示:标准 ROS 2 工程的目录结构让代码易于维护,colcon build 能正确识别所有资源。

注意:功能包必须放在 src/ 目录下。如果放错位置(如直接在 ros2_ws/ 下创建),colcon build 将无法识别。


2. 编写 Launch 文件

~/ros2_ws/src/my_robot_description/launch/ 目录下创建 rsp.launch.py

import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node import xacro defgenerate_launch_description(): pkg_name ='my_robot_description'# 声明 use_sim_time 参数(仿真工程的标配) declare_use_sim_time = DeclareLaunchArgument('use_sim_time', default_value='false', description='Use simulation (Gazebo) clock if true')# 获取参数值 use_sim_time = LaunchConfiguration('use_sim_time')# 解析 Xacro 文件 xacro_file = os.path.join(get_package_share_directory(pkg_name),'urdf','robot.urdf.xacro') robot_description_config = xacro.process_file(xacro_file).toxml()# 配置 robot_state_publisher 节点 node_robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', output='screen', parameters=[{'robot_description': robot_description_config,'use_sim_time': use_sim_time }])return LaunchDescription([ declare_use_sim_time, node_robot_state_publisher ])
提示get_package_share_directory() 会自动找到包的安装路径,避免硬编码路径。

注意:路径不要写死(如 /home/kason/...),否则换一台电脑或换一个用户名就会失效。


3. 配置 CMakeLists.txt

编辑 ~/ros2_ws/src/my_robot_description/CMakeLists.txt,在末尾添加:

# 安装 launch, urdf, worlds 等目录到 share 路径下 install(DIRECTORY launch urdf worlds config DESTINATION share/${PROJECT_NAME} ) 

然后编译:

cd ~/ros2_ws colcon build --symlink-install 

看到以下输出说明编译成功:

Starting >>> my_robot_description Finished <<< my_robot_description 

如果使用 --symlink-install,以后修改 launch 文件或 urdf 文件后,直接运行即可生效,不需要重新编译。

刷新环境:

source install/setup.bash 

验证 launch 文件是否被正确安装:

ros2 launch my_robot_description rsp.launch.py --show-args 

成功时会显示:

Arguments: 'use_sim_time': Use simulation (Gazebo) clock if true (default: 'false') 

注意:如果忘记修改 CMakeLists.txt,直接编译后运行会报错 file 'rsp.launch.py' was not found in the share directory。这是因为 ros2 launch 只会去 install/ 目录查找文件,不会去 src/。


4. 编写机器人 URDF 模型

~/ros2_ws/src/my_robot_description/urdf/ 目录下创建 robot.urdf.xacro

<?xml version="1.0"?><robotxmlns:xacro="http://www.ros.org/wiki/xacro"name="my_robot"><materialname="white"><colorrgba="1 1 1 1"/></material><!-- base_link 是机器人投影到地面的中心点,所有传感器和轮子都相对于它偏移 --><linkname="base_link"></link><jointname="chassis_joint"type="fixed"><parentlink="base_link"/><childlink="chassis"/><originxyz="-0.1 0 0"/></joint><linkname="chassis"><visual><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry><materialname="white"/></visual></link></robot>

注意:机器人模型必须以 base_link 作为根节点。Nav2 导航算法需要以它为旋转中心,直接以 chassis 开头会导致后续导航功能无法使用。


5. 在 RViz2 中可视化机器人

运行 Launch 文件:

ros2 launch my_robot_description rsp.launch.py 

成功启动后你会看到类似输出:

[INFO] [launch]: All log files can be found below /home/user/.ros/log/... [INFO] [launch]: Default logging verbosity is set to INFO [INFO] [robot_state_publisher-1]: process started with pid [1234] [robot_state_publisher-1] [INFO] [1774688498.607462389] [robot_state_publisher]: Robot initialized 

关键信号是看到 Robot initialized 这行字,说明机器人模型已被成功发布。

新开一个终端,打开 RViz2:

rviz2 

配置步骤:

  1. Fixed Frame:将 map 改为 base_link
  2. Add → 选择 RobotModel
  3. Add → 选择 TF

配置正确后,你将在 RViz 中央看到一个白色方块(chassis)。

图1 Fixed Frame,Add,RobotModel的位置:

请添加图片描述

图2 TF的位置:

请添加图片描述


图3 Description的位置,显示的白色方块

请添加图片描述
提示:如果看不到机器人,检查 RobotModel 下的 Description Topic 是否为 /robot_description

如果看到 TF 坐标轴但看不到白色方块,说明 RViz2 默认不知道去哪个话题找机器人描述,需要在 RobotModel 配置中手动选择 /robot_description


6. 添加轮子

修改 robot.urdf.xacro,在 </robot> 前添加轮子定义:

<materialname="blue"><colorrgba="0.2 0.2 1 1"/></material><!-- 左轮 --><jointname="left_wheel_joint"type="continuous"><parentlink="base_link"/><childlink="left_wheel"/><originxyz="0 0.175 0"rpy="-${pi/2} 0 0"/><axisxyz="0 0 1"/></joint><linkname="left_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual></link><!-- 右轮 --><jointname="right_wheel_joint"type="continuous"><parentlink="base_link"/><childlink="right_wheel"/><originxyz="0 -0.175 0"rpy="-${pi/2} 0 0"/><axisxyz="0 0 1"/></joint><linkname="right_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual></link>
提示continuous 类型的关节可以无限旋转,适合用于动力轮。

重新运行 ros2 launch my_robot_description rsp.launch.py,RViz 中将显示白色底盘和两个蓝色轮子。

如果 RViz2 显示如下错误:

Status: Error - right_wheel: No transform from [right_wheel] to [base_link] - left_wheel: No transform from [left_wheel] to [base_link] 

这是因为 robot_state_publisher 知道轮子存在,但不知道轮子的实时角度。需要在 rsp.launch.py 的 generate_launch_description() 函数中,在 return LaunchDescription([ 之前添加 joint_state_publisher_gui 节点:

defgenerate_launch_description():# ... 前面的代码保持不变 ... node_joint_state_publisher_gui = Node( package='joint_state_publisher_gui', executable='joint_state_publisher_gui', output='screen')return LaunchDescription([ declare_use_sim_time, node_robot_state_publisher, node_joint_state_publisher_gui # <-- 新增])

如果报错找不到包,运行:

sudoaptinstall ros-jazzy-joint-state-publisher-gui 

添加后重新运行,将弹出一个带滑块的小窗口,拖动滑块可以看到轮子随之转动,RViz 中的错误也会消失。

请添加图片描述

7. 添加物理属性

为了让机器人在 Gazebo 中不"穿墙"或"飞天",需要添加 <collision><inertial> 标签。

定义惯性计算宏

修改 robot.urdf.xacro,将下列代码添加到文件的开头,注意要在 <robot> 标签内、第一个 <link> 定义之前添加:

<!-- 长方体惯性宏 --><xacro:macroname="inertial_box"params="mass x y z *origin"><inertial><xacro:insert_blockname="origin"/><massvalue="${mass}"/><inertiaixx="${(1/12) * mass * (y*y+z*z)}"ixy="0.0"ixz="0.0"iyy="${(1/12) * mass * (x*x+z*z)}"iyz="0.0"izz="${(1/12) * mass * (x*x+y*y)}"/></inertial></xacro:macro><!-- 圆柱体惯性宏(轮子用) --><xacro:macroname="inertial_cylinder"params="mass radius length *origin"><inertial><xacro:insert_blockname="origin"/><massvalue="${mass}"/><inertiaixx="${(1/12) * mass * (3*radius*radius + length*length)}"ixy="0.0"ixz="0.0"iyy="${(1/12) * mass * (3*radius*radius + length*length)}"iyz="0.0"izz="${(1/2) * mass * (radius*radius)}"/></inertial></xacro:macro>

为底盘添加物理属性

<linkname="chassis"><visual><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry><materialname="white"/></visual><collision><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry></collision><xacro:inertial_boxmass="0.5"x="0.3"y="0.3"z="0.15"><originxyz="0.15 0 0.075"rpy="0 0 0"/></xacro:inertial_box></link>

为轮子添加物理属性

<linkname="left_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual><collision><geometry><cylinderradius="0.05"length="0.04"/></geometry></collision><xacro:inertial_cylindermass="0.1"radius="0.05"length="0.04"><originxyz="0 0 0"rpy="0 0 0"/></xacro:inertial_cylinder></link>

同样为 right_wheel 添加 collision 和 inertial。

注意:如果 link 中缺少 collision,轮子在 Gazebo 中会穿过地面;如果缺少 inertial,Gazebo 无法计算物理,机器人会"飘走"或"坠落"。


8. 创建 Gazebo 仿真环境

安装 ros_gz 桥接包

sudoapt update sudoaptinstall ros-jazzy-ros-gz 

创建 sim.launch.py

~/ros2_ws/src/my_robot_description/launch/ 目录下创建:

import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node defgenerate_launch_description(): pkg_name ='my_robot_description'# 1. 启动 robot_state_publisher rsp = IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join( get_package_share_directory(pkg_name),'launch','rsp.launch.py')]), launch_arguments={'use_sim_time':'true'}.items())# 2. 启动 Gazebo Sim gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join( get_package_share_directory('ros_gz_sim'),'launch','gz_sim.launch.py')]), launch_arguments={'gz_args':'-r empty.sdf'}.items())# 3. 将机器人模型放入 Gazebo spawn_entity = Node( package='ros_gz_sim', executable='create', arguments=['-topic','robot_description','-name','my_cool_robot'], output='screen')return LaunchDescription([ rsp, gazebo, spawn_entity,])

注意:现在使用了gazebo以后,sim.launch.py 中不要包含 joint_state_publisher_gui 节点,否则会和 Gazebo 的物理引擎产生冲突,会导致电脑崩溃。

编译并运行

注意这次增加了新的文件,因此需要再次使用colcon进行项目构建。并且用须要再次使用source命令,让ubuntu找到launch文件。

cd ~/ros2_ws colcon build --symlink-install source install/setup.bash ros2 launch my_robot_description sim.launch.py 

成功启动后,你会看到 Gazebo 窗口弹出,里面有一个网格地面,以及你的机器人(白色底盘加两个蓝色轮子)出现在世界中央。


9. 添加万向轮

为了让机器人站稳(在两轮之外增加支撑点),修改 robot.urdf.xacro,在 </robot> 前添加:

<!-- 万向轮:gazebo reference 用于设置摩擦系数 --><jointname="caster_wheel_joint"type="fixed"><parentlink="chassis"/><childlink="caster_wheel"/><originxyz="0.24 0 0"/></joint><linkname="caster_wheel"><visual><geometry><sphereradius="0.05"/></geometry><materialname="black"/></visual><collision><geometry><sphereradius="0.05"/></geometry></collision><inertial><originxyz="0 0 0"rpy="0 0 0"/><massvalue="0.1"/><inertiaixx="0.0001"ixy="0.0"ixz="0.0"iyy="0.0001"iyz="0.0"izz="0.0001"/></inertial></link><gazeboreference="caster_wheel"><mu1value="0.001"/><mu2value="0.001"/></gazebo>
提示:万向轮需要极低的摩擦系数(mu1, mu2 接近 0),否则机器人转弯时会像被粘住。

重新运行 sim.launch.py,机器人将端端正正地站在地面上,不再向前或向后倾倒。

注意:动力轮的摩擦系数必须设置得比较高(mu1, mu2 > 1.0),如果也设成 0.001,轮子会原地打滑无法前进。


10. 添加差速驱动插件

robot.urdf.xacro</robot> 前添加:

<gazebo><pluginfilename="gz-sim-diff-drive-system"name="gz::sim::systems::DiffDrive"><left_joint>left_wheel_joint</left_joint><right_joint>right_wheel_joint</right_joint><wheel_separation>0.35</wheel_separation><wheel_radius>0.05</wheel_radius><max_wheel_torque>20</max_wheel_torque><max_wheel_acceleration>1.0</max_wheel_acceleration><topic>cmd_vel</topic><odom_topic>odom</odom_topic><frame_id>odom</frame_id><child_frame_id>base_link</child_frame_id><publish_odom>true</publish_odom><publish_odom_tf>true</publish_odom_tf><publish_wheel_tf>true</publish_wheel_tf></plugin><pluginfilename="gz-sim-joint-state-publisher-system"name="gz::sim::systems::JointStatePublisher"></plugin></gazebo>

重新运行 sim.launch.py,机器人依然出现在 Gazebo 中,只是现在它已经准备好接收控制指令了。


11. 键盘控制机器人

建立 ROS 2 与 Gazebo 的通信桥接

新开一个终端:

ros2 run ros_gz_bridge parameter_bridge /cmd_vel@geometry_msgs/msg/[email protected] 

成功运行后会显示类似:

[INFO] [gz_ros_bridge]: Creating GzROS2Bridge: topic /cmd_vel (ros -> gz)" 

启动键盘控制

再开一个新终端:

ros2 run teleop_twist_keyboard teleop_twist_keyboard 

成功后终端显示操作指南:

Reading from the keyboard and publishing to Twist! --------------------------- Moving around: u i o j k l m , . anything else : stop q/z : increase/decrease max speeds by 10% w/x : increase/decrease only linear speed by 10% e/c : increase/decrease only angular speed by 10% Currently: speed 0.5 turn 1.0 

现在点击这个终端使其激活,按 i 键让机器人前进,按 l 键向右转,按 ,(逗号)后退。观察 Gazebo 中的机器人是否随之移动。

请添加图片描述

避坑总结

错误现象原因解决方案
file not foundCMakeLists.txt 没有 install 指令添加 install(DIRECTORY …)
No transform from xxx缺少 joint_state_publisher添加 joint_state_publisher_gui
Gazebo 黑屏/崩溃joint_state_publisher_gui 冲突仿真时注释掉它
轮子穿过地面缺少 collision添加 collision 标签
机器人飘走缺少 inertial添加惯性参数
机器人原地打滑动力轮摩擦系数太低设置 mu1, mu2 > 1.0

恭喜!

你已完成一个能用键盘控制的两轮机器人。掌握的知识:

  • ROS 2 工程标准结构
  • Xacro 机器人建模
  • Gazebo 物理仿真
  • ROS 2 与 Gazebo 桥接通信

12. 原理和总结

当你运行 teleop_twist_keyboard 并按下按键时,机器人之所以能动,是因为在 ROS 2 的世界里发生了一场精准的**“接力赛”**。

我们可以把这个过程拆解为三个核心环节:消息协议(Twist)发布订阅机制(Topic)物理执行器(Gazebo Plugin)


1)消息类型:geometry_msgs/msg/Twist

ROS 2 中,几乎所有的移动机器人都使用同一种指令格式——Twist

当你按下 i(前进)时,teleop_twist_keyboard 会生成一个结构如下的数据包:

  • Linear (线速度): x = 0.5 , y = 0.0 , z = 0.0 x=0.5, y=0.0, z=0.0 x=0.5,y=0.0,z=0.0 (代表向前冲)
  • Angular (角速度): z = 0.0 z=0.0 z=0.0 (代表不转弯)

这个数据包不关心你的机器人是长方形还是圆形,它只表达一个抽象的运动意图
输入ros2 interface show geometry_msgs/msg/Twist可以查看Twist数据类型的结构

在这里插入图片描述

2)传递的信道:/cmd_vel 话题

teleop_twist_keyboard 就像一个广播电台,它持续向一个名为 /cmd_vel (Command Velocity) 的频道发送上述的 Twist 消息。

  • 它是标准约定:在 ROS 社区中,默认所有移动机器人都应该监听 /cmd_vel 来获取运动指令。这就像所有收音机都要调到同一个频率才能听到广播一样。

在仿真运行期间输入ros2 topic list 可以查看所有话题

在这里插入图片描述


输入ros2 topic info /cmd_vel查看 /cmd_vel 的详细信息(包含类型)

在这里插入图片描述

3)关键的接棒者:Gazebo 差速驱动插件

这是最关键的一步。由于你的机器人是在仿真环境里的,它没有真实的电机。你在 Xacro 中加入的 DiffDrive 插件 充当了“翻译官”和“虚拟电机”的角色:

  1. 监听:插件在后台一直盯着 /cmd_vel 话题。
  2. 计算 (逆运动学):当它收到“线速度 0.5m/s”的指令时,它会根据你在 Xacro 里设置的参数(wheel_separation 轮距 和 wheel_radius 轮径)进行计算。
    • 公式示例:为了达到 0.5m/s,左右两个轮子分别需要转多快?
  3. 施力:计算出转速后,插件直接向 Gazebo 物理引擎中的 left_wheel_jointright_wheel_joint 施加扭矩(Torque)

4)为什么需要那个 ros_gz_bridge

你在操作时运行了一个特殊的命令:ros_gz_bridge。这是因为:

  • Teleop 节点 跑在 ROS 2 协议上。
  • Gazebo 仿真器 跑在 Gazebo Transport 协议上。

这个“桥梁”就像一个同声传译,它把 ROS 2 频道里的 /cmd_vel 抓过来,实时翻译成 Gazebo 插件能听懂的格式并扔进仿真世界里。


5)核心特性:异步与多对多

这是 ROS 2 话题机制最强大的地方:

异步性:
发布者发完就走,不需要等订阅者回话。这保证了机器人控制的实时性。

多对多:

一个话题可以有多个发布者:比如你可以同时用键盘和自动导航算法给小车发指令(虽然这会导致小车打架)。

一个话题可以有多个订阅者:比如 /cmd_vel 话题,除了 Gazebo 插件在听,你还可以开一个日志节点记录小车的运动轨迹,它们互不干扰。


5)总结:小车运动的全路径

  1. 你按下 i -> teleop_twist_keyboard 发出 Twist 消息
  2. Twist 消息 进入 /cmd_vel 话题
  3. Bridge 桥梁 将消息从 ROS 传给 Gazebo
  4. DiffDrive 插件 收到消息,计算轮速,给轮子加力
  5. Gazebo 物理引擎 计算摩擦力,机器人向前滑动

接下来,我将为小车增加一个激光雷达,用于感知周围环境。下一节的链接:【从零开始创建一个ROS2机器人工程(2)】为小车增加激光雷达感知

Read more

实测 Copilot 2025 终极版:5 大颠覆性功能 + 10 段实战代码,开发效率直接翻 3 倍!

实测 Copilot 2025 终极版:5 大颠覆性功能 + 10 段实战代码,开发效率直接翻 3 倍!

目录 编辑 前言:从 "代码补全工具" 到 "全能开发搭档" 的蜕变 一、基础能力大跃进:编码体验的全方位升级 1. 多模型自由切换:给不同任务配 "专属 AI 大脑" 实战场景与代码示例 2. 智能补全 2.0:懂项目风格的 "代码生成器" 实战场景与代码示例 3. 多语言支持增强:从前端到运维全覆盖 跨语言实战案例 二、革命性功能:Agent 智能体与多模态能力 1. Agent 模式:能自己动手的 "开发助理" 实战场景:自动化部署

什么是Agentic AI?Agentic AI 与传统 AIGC 有什么区别?

什么是Agentic AI?Agentic AI 与传统 AIGC 有什么区别?

什么是 Agentic AI?Agentic AI 与传统 AIGC 有什么区别? 1. 引言 近年来,人工智能(AI)技术飞速发展,其中以生成式 AI(AIGC,Artificial Intelligence Generated Content)和 Agentic AI(智能代理 AI)最为热门。AIGC 通过深度学习模型生成文本、图像、视频等内容,而 Agentic AI 则更进一步,能够自主感知、决策并执行任务。那么,Agentic AI 究竟是什么?它与传统的 AIGC 有何不同?在本文中,我们将深入探讨 Agentic AI 的概念、技术原理、

CosyVoice环境安装报错:安装openai-whisper时出现ModuleNotFoundError: No module named ‘pkg_resources‘的原因

先说如何解决,我个人采用源码安装,安装时禁用构建隔离: git clone https://github.com/openai/whisper.git cd whisper git checkout v20231117 pip install -e . --no-build-isolation 再说该问题产生的原因,下面是大D老师的回答,自行参考。 太棒了!你找到了正确的解决方案。让我详细解释为什么会出现这个问题,以及为什么 --no-build-isolation 能解决它。 🔍 问题根源分析 问题的关键在于 PEP 517/518 构建隔离机制和你的 setup.py 实现方式。 1. 构建隔离机制 (Build Isolation) 的工作原理 当你执行 pip install -e .(不带 --no-build-isolation)时,

AMD显卡AI绘画革命:ComfyUI-Zluda超详细配置手册

AMD显卡AI绘画革命:ComfyUI-Zluda超详细配置手册 【免费下载链接】ComfyUI-ZludaThe most powerful and modular stable diffusion GUI, api and backend with a graph/nodes interface. Now ZLUDA enhanced for better AMD GPU performance. 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-Zluda 在AI绘画技术爆发的今天,AMD显卡用户终于迎来了专为自家硬件优化的革命性解决方案。ComfyUI-Zluda通过创新的ZLUDA技术集成,让AMD GPU在AI图像生成领域表现惊艳,普通用户也能轻松创作专业级视觉作品。 🎯 环境准备与系统检查 硬件兼容性快速验证 在开始安装前,请务必确认您的系统配置满足以下基础要求: * Windows 10/11操作系统 * Python 3.