你有没有遇见过这样的场景:想学 ROS2,却在各种教程、资料和官方文档之间来回横跳,装了环境之后却不知道下一步到底该干什么。带新人的时候我经常被问到“ROS2 和 ROS1 到底先学哪个”“为什么我的节点收不到数据”“为什么换个终端就找不到包了”。这些问题几乎每个初学者都会碰一遍,而它们背后其实都是一些很具体的技术细节,不是靠多看几篇文档就能绕过去的。
这篇内容就按我实际带人学习时的路线来写:先讲清楚学习前的判断和目标,再带你完成环境搭建、写出第一个节点,接着把话题、服务、动作这三类通信机制彻底理清,最后给出一个能串联所有概念的最小项目和一份踩坑排查手册。不管你是刚接触机器人开发,还是从 ROS1 转过来,都可以照着这条路走一遍。
1. 先想清楚:学 ROS2 前的时间、基础与目标怎么定
很多人一上来就急着装环境,结果装完发现连“学什么”都没概念。磨刀不误砍柴工,动手之前把方向问清楚,后面至少能少走一半弯路。
1.1 ROS2 与 ROS1 怎么选:别再纠结了
ROS2 相较 ROS1 最大的变化是通信架构。ROS1 依赖一个中心化的节点来管理所有话题、服务和参数,中心节点挂了,整个系统就崩溃。ROS2 则把通信底座换成了 DDS(Data Distribution Service,数据分发服务),节点之间可以直接发现对方并进行通信,没有单点故障,也天然支持多机分布部署和实时性要求更高的场景。
这意味着什么?如果你做的是单机、原型验证、资料丰富的老项目,ROS1 还能用;但如果你是零基础起步,或者准备做产品化、多机协作、要上机器人真机,直接学 ROS2 是更合理的选择。社群里的新包、新工具、新教程也都在向 ROS2 迁移,与其学一套正在退场的旧框架,不如一步到位。
我见过不少学习者花了两周时间看 ROS1 的教程,最后又回头重学 ROS2,白白浪费了时间。新入行的人别走这条路。
1.2 需要什么样的基础:门槛其实没那么高
先说结论:Linux 基础命令 + Python 或 C++ 任一,就能开始学。但这里的“基础”是有下限的。
Linux 方面,你至少要能熟练地创建目录、切换路径、编辑文件、安装软件包。很多教程会让你敲命令,如果连cd、mkdir、sudo apt install都要现场查,学习节奏会非常碎片化,体验很差。编程语言方面,ROS2 官方提供了两套 API:Python 的rclpy和 C++ 的rclcpp。Python 的语法更接近自然语言,调试起来也快,适合用来理解概念;C++ 在真机部署和嵌入式场景中更常见,但上手门槛更高。我的建议是:第一遍用 Python 学概念,之后再按需补 C++。
还有一类人基础很好,但对机器人学几乎零概念,比如不知道什么叫坐标变换、什么叫里程计。这类知识可以边学边补,不用提前啃完。ROS2 本身解决的是“如何让机器人各个模块协同工作”的问题,而不是“怎么让机器人走起来”的算法问题,所以千万不要因为一时看不懂算法细节就止步。
1.3 学习目标与时间线:以“两周跑通,一个月成项目”为参考
没有目标的学习很容易被无限期拖延。根据我带人的经验,比较合理的学习节奏是这样的:
- 第一阶段(3-5 天):装好环境,跑通 turtlesim 模拟器里的官方示例,能用
ros2 run启动节点。 - 第二阶段(1-2 周):自己动手写一个发布者和订阅者,理解消息类型,再把服务、动作、参数、launch 文件各跑一遍。
- 第三阶段(1 个月):完成一个串起所有概念的小项目,比如控制乌龟画正方形,或者让小车在仿真里避开障碍。
这个时间线对有一定编程基础的人完全可行。如果你每天只有一两个小时,周期可以放宽到两个月,但每个阶段的目标必须明确。只要我带的学员还停留在“跟着教程敲命令”的阶段,我都会提醒他们:你还没有真正开始学 ROS2,你只是在复制粘贴。
2. 环境搭建与第一个节点:把“能跑通”变成“真懂了”
环境搭建是劝退率最高的环节。不是因为它难,而是因为它背着很多“前人留下的坑”。这一节我会把安装、工作空间、代码、构建、运行整条链路完整走一遍,并解释每一步背后的原因。
2.1 版本选择与安装环境:选对搭配能省一天时间
ROS2 的每个发行版都有对应的 Ubuntu 版本,装错组合是新手最常见的低级事故。目前常用的对应关系如下:
| ROS2 发行版 | 对应 Ubuntu 版本 | 说明 |
|---|---|---|
| Foxy | Ubuntu 20.04 | 生命周期已过,不建议新项目 |
| Humble | Ubuntu 22.04 | 长期支持版本,资料最多,最稳 |
| Jazzy | Ubuntu 24.04 | 新的长期支持版本,适合新设备 |
从稳定性、教程数量和社区经验来看,Humble 仍然是最多人用的选择,也是我推荐给入门者的首选。硬件要求方面,内存 8GB 以上比较舒服,虚拟机里建议分配至少 4GB 内存和 2 个 CPU 核心。真机安装的话,Ubuntu 22.04 的兼容性问题普遍比老版本少,JetBrains 之类的大块头工具跑起来也不吃力。
安装方式我推荐直接用官方源安装桌面版。以 Humble 为例,核心步骤是这样的:
sudo apt update sudo apt install software-properties-common sudo add-apt-repository universe # 添加 ROS2 官方软件源和密钥 sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install ros-humble-desktop python3-colcon-common-extensions桌面版会把发布、订阅、仿真、可视化等常用工具全部装好,不用后期一个个补。安装完成后,记得在终端执行source /opt/ros/humble/setup.bash,这个动作相当于把 ROS2 的环境变量加载进当前终端。如果官方源下载速度不理想,可以自己换一个可用的软件镜像源,注意替换成与你系统匹配的路径就行。
2.2 工作空间与第一个 Python 节点:从空目录到能跑通的发布者
环境装好后,我们来建一个自己的工作空间。ROS2 里面“工作空间”指的就是存放源码、并进行构建的目录,通常叫ros2_ws。启动一个新终端,依次执行:
mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build这里colcon是 ROS2 的官方构建工具,它会扫描src目录下所有包,把它们编译或安装到install目录。构建完成后你会看到build、install、log三个目录,其中install里才是真正会被运行的系统。所以在任何新终端里使用自己写的包之前,都要先执行:
source install/setup.bash下面我们写一个最简单的发布者节点。在src目录下创建一个包:
cd ~/ros2_ws/src ros2 pkg create my_publisher --build-type ament_python然后把发布者代码写到my_publisher/my_publisher/publisher_node.py中:
import rclpy from rclpy.node import Node from std_msgs.msg import String class Talker(Node): def __init__(self): super().__init__('talker') self.publisher_ = self.create_publisher(String, 'chatter', 10) timer_period = 1.0 self.timer = self.create_timer(timer_period, self.timer_callback) self.count = 0 def timer_callback(self): msg = String() msg.data = 'Hello ROS2: %d' % self.count self.publisher_.publish(msg) self.get_logger().info('Publishing: "%s"' % msg.data) self.count += 1 def main(args=None): rclpy.init(args=args) talker = Talker() rclpy.spin(talker) talker.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这段代码里的三个关键点,新手必须搞懂:
create_publisher(String, 'chatter', 10)的第一个参数是消息类型,第二个是话题名,第三个是队列深度。队列深度 10 表示最多缓存 10 条消息,如果订阅者处理不过来,旧消息会被丢弃。create_timer是让节点周期性地执行回调,这里每 1 秒发布一条消息。ROS2 节点本身是事件驱动的,rclpy.spin()会一直阻塞运行并处理回调,直到程序被中断。get_logger().info()是节点的日志输出接口,后面排查问题基本都靠它。
写完代码还需要在setup.py里注册入口点,让ros2 run能找到这个节点。打开my_publisher/setup.py,在entry_points部分添加:
entry_points={ 'console_scripts': [ 'talker = my_publisher.publisher_node:main', ], },然后回到工作空间构建:
cd ~/ros2_ws colcon build source install/setup.bash这样就完成了从源码到可运行节点的全部流程。
2.3 构建与运行的完整链路:为什么必须 source,以及怎么验证
很多新手执行完ros2 run my_publisher talker后提示找不到包,第一反应是重装系统。其实这背后只是少了一个source。
source install/setup.bash做的事,是把当前包安装路径加入环境变量,包括AMENT_PREFIX_PATH、PYTHONPATH、PATH等。ROS2 查找包时,不是扫描全盘,而是依靠这些环境变量去定位。你新开一个终端,如果不 source,系统根本不知道你编译好的包在哪里。同理,每次装完新包、改完代码重新构建之后,都需要重新 source 一次。
验证是否安装成功,可以在新终端里执行:
ros2 pkg executables my_publisher如果列出talker,说明包注册成功。然后再开一个终端跑订阅者做测试。订阅者代码可以放到同一个包里,也可以直接在另一个终端用官方示例:
ros2 run demo_nodes_py listener你会在 talker 终端看到发布日志,listener 终端看到接收日志。用ros2 node list能看到两个节点,用ros2 topic list能看到chatter话题,用ros2 topic echo /chatter能实时看到消息内容。这一套观察手段,就是后面排查问题的基础。
3. 话题、服务、动作:先把三类通信选对再写代码
ROS2 的通信机制是学习的核心,也是大多数教程写得最抽象的部分。我按实际使用场景来拆,尽量讲完你就能判断“这个功能该用哪个”。
3.1 话题:适合持续数据流的广播电台
话题是 ROS2 最常用的通信方式,适合“持续产生、持续消费”的数据流。想象一个广播电台:发布者把内容发出去,不关心谁在听;订阅者只负责接收,不回复也不确认。激光雷达以几十赫兹的频率发布点云,摄像头发布图像帧,里程计发布位置信息,这些都属于典型的话题场景。
话题的关键特征是单向和异步。发布者不需要等待订阅者确认,订阅者也不会阻塞发布者。如果某个订阅者处理得慢,消息会按队列深度缓冲,超出部分直接丢弃。节点之间通过话题名互相匹配,同一个机器人系统里会有几十上百个话题,用ros2 topic list -t可以查看所有话题及其消息类型。
在代码里,发布者调用publish()把消息发出去,订阅者通过回调函数接收消息。你只需要保证消息类型一致、话题名一致,发布者和订阅者就可以在不同进程、不同机器上运行,这也是 ROS2 天然适合分布式系统的原因。
3.2 服务:一问一答的短事务
有些操作需要“请求-响应”的语义,比如让机器人报告当前位置、激活某个传感器、保存一张地图。这时候话题就不合适了,因为话题没有“等待结果”的机制。ROS2 的服务就是为这类短事务设计的。
你可以把它理解成打电话:客户端拨号,服务端接听,双方一问一答,然后挂断。服务调用是同步的,客户端发出请求后会阻塞等待响应。turtlesim 模拟器里有很多现成的服务可以练手,比如生成一只新乌龟:
ros2 service list ros2 service call /spawn turtlesim/srv/Spawn "{x: 5.0, y: 5.0, theta: 1.57, name: 'turtle2'}"服务适合那些执行时间短、结果明确、不需要中途反馈的操作。如果一次服务调用要几秒钟甚至更久,期间客户端一直干等,这种设计就很不优雅,应该改用动作。
3.3 动作:长时间运行且可取消的任务
动作可以理解为“带反馈和取消机制的服务”。举一个很常见的例子:你让机械臂从 A 点移动到 B 点,这个过程可能持续十几秒。如果只用一个服务,客户端会一直等着,期间无法获得任何中间状态;而且你突然想叫停,服务也没法优雅地取消。动作机制就能解决这些问题。
一个动作的目标(Goal)发出后,服务端会持续返回反馈(Feedback),告诉你当前进度;任务结束时返回结果(Result)。整个过程可以被客户端随时取消。turtlesim 里也有现成的动作可以体验:
ros2 action list -t ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}"你会看到乌龟开始旋转,同时终端里不断输出反馈信息。这个例子虽然简单,但把动作的三个核心部分——目标、反馈、结果——都演示得很清楚。真机上的机械臂轨迹执行、移动机器人导航、无人机飞向目标点,底层都是动作机制。
3.4 消息接口与选择决策:所有通信都是类型化的
话题、服务、动作都有对应的接口定义,它们本质上是一份“类型契约”。比如std_msgs/msg/String只有一个data字段,用来传字符串;geometry_msgs/msg/Twist包含线速度和角速度六个分量,机器人运动控制最喜欢用它。查看接口内容用:
ros2 interface show std_msgs/msg/String ros2 interface show turtlesim/srv/Spawn ros2 interface show turtlesim/action/RotateAbsolute接口决定了通信双方能传递什么数据,所以先学会查看接口,再考虑写代码,可以避免大量类型不匹配的报错。
选择通信方式的决策逻辑其实很简单,我整理成一张表:
| 场景特征 | 通信方式 | 典型例子 |
|---|---|---|
| 持续发布,单向广播,接收方不需要回复 | 话题 | 传感器数据、图像、点云、里程计 |
| 短事务,一问一答,等待结果 | 服务 | 生成乌龟、开关灯、获取地图 |
| 长任务,需要进度反馈,可中途取消 | 动作 | 机械臂运动、导航到目标点 |
搞懂这三类通信方式的适用场景,写项目时就不会在架构选型上卡壳。很多新手一开始把所有通信都写成话题,最后发现有些逻辑根本没法做,就是因为没有按语义去选。
4. 从模仿到小项目:参数、launch 与仿真一个都不能少
学会了单节点通信,接下来要解决的是“怎么让一套系统跑起来”。这涉及参数、launch 文件和仿真三块内容,也是从“会跑示例”过渡到“能写项目”的关键一步。
4.1 参数:把写死的数字变成可配置项
写代码时,如果把速度、名称、阈值这类数值直接硬编码进源码,每次调整都要改文件、重新构建,非常痛苦。ROS2 的参数机制允许你在节点运行期间动态读取和修改配置。
比如在节点里声明一个vx参数:
self.declare_parameter('vx', 0.2) vx = self.get_parameter('vx').get_parameter_value().double_value然后你就可以在运行时查询和修改参数:
ros2 param list ros2 param get /talker vx ros2 param set /talker vx 0.5参数适合那些“同一个节点在不同场景下有不同取值”的情况。比如一个速度控制器,在车间里跑慢一点,在操场里跑快一点,参数只需要在 launch 文件里配置,代码一行都不用改。
4.2 launch 文件:从多终端手动启动到一键拉起整套系统
实际项目里,一个完整系统往往有十几个节点。靠手动开多个终端、一个个ros2 run显然不现实。launch 文件就是 ROS2 的“一键启动工具”。
ROS2 的 launch 文件推荐使用 Python 编写,因为它最灵活。一个最简单的 launch 文件长这样:
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_publisher', executable='talker', name='my_talker', parameters=[{'vx': 0.3}], ), Node( package='demo_nodes_py', executable='listener', name='my_listener', ), ])保存为my_publisher/launch/demo_launch.py,然后运行:
ros2 launch my_publisher demo_launch.py你会发现两个节点同时启动,还可以通过name给节点重命名,通过parameters传参。launch 文件把启动顺序、参数、命名空间都固化下来,是项目工程化的第一步。
4.3 用仿真把练习成本降到最低:从 turtlesim 到 Gazebo
仿真在 ROS2 学习里的作用,是在不买真机、不担心撞坏东西的情况下反复测试算法。turtlesim 适合验证通信机制和基本逻辑,Gazebo 则提供物理环境和传感器仿真。
我的建议是先用 turtlesim 把话题、服务、动作、参数、launch 全部跑通,再考虑装 Gazebo。一个能把所有概念串起来的最小项目是:让乌龟画一个正方形。步骤拆解如下:
- 创建一个节点,周期性给
/turtle1/cmd_vel话题发布Twist消息,控制乌龟前进。 - 每走一段距离后改变角速度,实现转弯 90 度。
- 用
/reset服务让乌龟回到原点。 - 用参数控制边长和速度。
- 用 launch 文件同时启动控制节点和 turtlesim 节点。
这个过程不复杂,但恰好把前三章所有知识点都用了一遍。完成它以后,你再去学 Nav2 导航、MoveIt 机械臂规划,就能看懂它们在架构里处于什么位置了。那时候你才算是真正入了门。
5. 我在学习路上反复踩的坑与排查套路
最后这一部分是我个人认为参考价值最大的,因为这些问题在教程里大多是分散提到的,没有系统地汇总。我自己至少教过两位数的新手,以下坑的出场率极高,几乎人人会碰到。
5.1 换了终端就找不到包:环境变量 source 的坑
这是我遇到频率排名第一的问题。现象是:ros2 run提示找不到包或找不到节点,但在当初构建时的那个终端又能跑。原因已经提过,新终端没有加载 ROS2 环境,也没有 source 自己的工作空间。
排查思路分两步:先source /opt/ros/humble/setup.bash,再source ~/ros2_ws/install/setup.bash。如果还不行,检查环境变量:
echo $AMENT_PREFIX_PATH正常情况下列表里应该包含/opt/ros/humble和你的工作空间路径~/ros2_ws/install。如果只有系统路径,说明自己的包没被加入。最简单省事的做法是在~/.bashrc末尾加上这两行 source,这样每个新终端都会自动加载。但如果你同时装了多个 ROS2 发行版,建议只在.bashrc里 source 一个,避免环境变量互相污染。
5.2 话题能列出却收不到数据:QoS 不匹配
另一种很诡异的情况:用ros2 topic list能看到话题,但ros2 topic echo或自己的订阅者收不到任何消息。这种时候十有八九是 QoS(Quality of Service,服务质量)策略不匹配。
ROS2 里发布者和订阅者各自带一套 QoS 配置,比如可靠性策略:reliable表示尽量保证送达,best_effort表示能收多少算多少。如果发布者用reliable,订阅者默认用best_effort,两端可能无法建立有效通信。先查看两端配置:
ros2 topic info /chatter --verbose再用强制指定 QoS 的方式 echo 验证:
ros2 topic echo /chatter --qos-reliability reliable如果这样能看到消息,基本可以确定是 QoS 不匹配。实际项目中,传感器数据常用best_effort,因为丢几帧没关系;控制指令必须用reliable,丢一条可能就很危险。自己写订阅者和发布者的代码时,记得两边的 QoS 策略要一致,这是最容易被忽略的细节。
5.3 跨机器或跨容器收不到话题:DDS 实现与域 ID 不一致
ROS2 的通信底层是 DDS,但官方没有限定唯一实现。如果你的发布者和订阅者跑在不同机器上,或者一个在容器里一个在宿主机里,它们用的 DDS 实现不同,就可能互相发现不了。再加上域 ID(Domain ID)的概念——它相当于给 DDS 通信划分了不同的“频道”,同一域里的节点才能互相通信,跨域就互相看不见。
排查顺序是这样的:先用ros2 node list看两端节点是否出现在同一个列表里。如果节点互相看不见,先确认echo $ROS_DOMAIN_ID是否相同,默认都是 0。然后通过环境变量统一 DDS 实现,比如两边都设置:
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp注意两边要一致,否则还是没法通信。这个问题在单机上不明显,一旦你开始做多机器人或者用 Docker 部署,马上就会遇到,提前知道可以少走很多弯路。
5.4 容器里跑图形界面打不开:X11 显示权限问题
用 Docker 学 ROS2 的人越来越多,但在容器里启动 turtlesim 或 RViz 时,经常会报cannot open display。原因是容器默认没有访问宿主机图形界面的权限。解决办法是启动容器时挂载 X11 相关的资源:
export XAUTH=/tmp/.docker.xauth touch $XAUTH xauth nlist $DISPLAY | sed -e 's/^/add /' | xauth -f $XAUTH nmerge - docker run -it --rm \ --env DISPLAY=$DISPLAY \ --env XAUTHORITY=$XAUTH \ --volume /tmp/.docker.xauth:/tmp/.docker.xauth \ --volume /tmp/.X11-unix:/tmp/.X11-unix \ --network host \ osrf/ros:humble-desktop这段命令本身不难,但它涉及的 Linux 图形系统知识,对刚入门的人是超纲的。所以如果你不是必须用容器,我建议前期直接用真机或虚拟机桌面环境学习,把精力集中在 ROS2 本身。等以后部署阶段需要容器化的时候,再来处理这个问题会更合适。
5.5 日志与 ros2 doctor:先学会自己诊断,再考虑重装
很多新手在遇到问题时,第一反应是上网搜“ROS2 重装教程”,实际上绝大多数问题都不需要重装。ROS2 自带一个环境体检工具:
ros2 doctor它会检查系统环境、网络、DDS 配置、环境变量等,并给出警告或错误提示。节点运行时的调试信息则通过日志输出,默认级别是 info,可以用下面的方式调成 debug 查看更详细的内容:
ros2 run my_publisher talker --ros-args --log-level debug我的排查习惯是这样的:先看ros2 node list和ros2 topic list,确认节点和话题的拓扑是否正常;再看ros2 topic info --verbose,确认 QoS 是否匹配;最后才查 DDS 实现、网络、域 ID。按这个顺序走,90% 的问题都能在十分钟内定位。重装系统永远是最后手段,因为下一次你还会踩同样的坑。
最后说点个人体会。带过不少人之后我最大的感受是:能快速掌握 ROS2 的人,通常不是基础最好的,而是最早开始动手折腾的。这个框架的命令、概念和工具非常多,但真正核心的骨架就那么几条。与其花时间抄录复杂的配置,不如尽快用一两个晚上完成乌龟画正方形的实验,把话题、服务、动作、参数、launch 文件全部串一圈。只要这条链路跑通了,后续无论学导航还是机械臂规划,你至少知道自己应该往哪里看、该验证哪一步。