
第一天接触ROS2我就被它“分布式节点 话题通信”的设计吸引住了。装好Ubuntu 22.04配好Humble环境敲完第一个发布者节点并看到另一个终端里打印出“Hello ROS2”的时候那种翻过第一座山的感觉还是很爽的。这篇文章是我学习ROS2第一天的完整笔记不是把官方教程贴一遍的整理而是我自己踩坑后重新梳理的版本。内容包括环境安装怎么选、核心通信机制怎么理解、如何从头创建一个C功能包并跑通两个聊天式的节点、以及用RViz2和rqt把通信链路看清楚的方法。适合刚接触机器人编程、想从零搭建第一个ROS2应用的人也适合从ROS1转过来的老开发者快速对照差异。1. 环境准备为什么第一天就锁定了ROS2 Humble1.1 ROS2版本怎么选第一天我开始看ROS2官网时第一个感受是版本太多显得有点混乱。Foxy比较老Galactic已经停止维护Humble是目前使用人数最多的LTS版本支持到2027年而Iron、Jazzy这类较新的版本资料和生态又还没完全跟上。对新手来说选版本的标准绝不是越新越好而是“教程多、踩坑案例多、社区活跃”。Humble在Ubuntu 22.04上是官方推荐组合很多开源项目Nav2、MoveIt2、Micro-ROS的默认版本就是Humble这意味着你搜到的大部分问题解答都是针对Humble的。所以Day 1选Humble几乎不用犹豫。发行版发布时间是否LTS支持周期适合谁Humble2022.05是至2027.05新手和主力开发Foxy2020.06是已过期至2023.05维护老项目Iron2023.05否至2024.11尝鲜、学习Jazzy2024.05是至2029.05新项目开发者但资料较少如果你当前系统是Ubuntu 26.04官方还没有对应的ROS2发行版最稳妥的办法是用Docker容器跑Humble镜像或者直接换回Ubuntu 22.04。我在Day 1选择了后者的思路因为Ubuntu 22.04 原生Humble的组合最省心。1.2 原生安装还是Docker容器这是新手第一天最常见的纠结点。如果你已经有一个Ubuntu 22.04的环境直接原生安装是最省心的路径遵循官方apt源、把依赖装全即可。我之所以推荐原生安装是因为ROS2涉及大量环境变量比如ROS_DOMAIN_ID、RMW_IMPLEMENTATION和权限问题容器里多一层隔离排查问题时就多一个变量。比如在Docker里跑RViz2需要额外配置GPU渲染和X11转发新手很容易卡在这里。但如果你主力系统是Windows又不想切系统那就在WSL2里装Ubuntu 22.04再部署ROS2 Humble或者用Docker Desktop跑官方镜像。这里有一个非常关键的细节Docker默认的bridge网络模式会让容器内的ROS2节点之间互相发现不到因为ROS2依赖多播发现机制。启动容器时建议加--network host参数让容器直接共享宿主机网络这样节点之间的通信就会顺畅很多。这也是热词里“net模式与端口转发”实际要解决的问题。Day 1我图省事直接做了双系统原生安装后面编译、调试、加通信库都少一层干扰这份踏实感还是值得的。2. ROS2的核心通信机制话题、服务、动作一次讲清2.1 节点、话题和回调ROS2里最小的执行单位是节点。节点可以理解成是一个独立运行的小程序它只负责“做一件事”读传感器、做决策、控制电机。节点之间不直接调用函数而是通过通信框架连接。话题是其中最常用的通信方式模式是发布/订阅一个节点往话题上放数据其他感兴趣的节点订阅这个数据每当数据到达订阅方的回调函数会被自动触发。Day 1最需要掌握的就是这个概念。用微信群来类比话题就是一个群发布者是群主或群友订阅者是群成员。群主发一条消息所有在群里的人都能收到你不关注某个话题就相当于没加那个群消息再多也跟你无关。话题的松耦合优势正在于此——发布者根本不知道谁在订阅订阅者也不需要知道数据是谁发的。这种设计让机器人系统的每个模块都能独立开发、独立测试、独立替换正是我选择深入学习ROS2的理由。2.2 服务与动作与话题的分工话题适合持续不断的数据流比如激光雷达点云、IMU姿态、相机画面但有些交互是一次性的请求和响应例如“请帮我计算两个坐标之间的路径”“把灯打开”这时用服务更合适。服务就像打电话A拨通B的电话B接听后给出回答通话结束。而动作则是服务的升级版适合长时间任务例如“导航到某个点”“机械臂抓取一个物体”。动作协议包含目标、反馈和结果三部分相当于点外卖你下单goal骑手实时告诉你到哪儿了feedback最后送达result期间你还可以取消订单cancel。机制通信模式数据流方向适用场景典型例子话题发布/订阅单向连续传感器流、状态广播相机图像、IMU数据服务请求/响应双向一次性查询、触发开关灯、坐标变换动作目标→反馈→结果双向长任务导航、抓取、移动前往坐标点、拾取物体Day 1先牢牢吃透话题服务与动作的概念自然就顺畅了。因为动作在底层其实也是通过话题和服务的组合实现的它的反馈和状态就是话题目标与结果则像服务。2.3 为什么ROS2比ROS1去中心化不少从ROS1转过来的朋友会问ROS1有个master节点负责名字注册所有节点启动时都要先找masterROS2把这一层干掉了节点之间通过DDS中间件直接发现彼此。这意味着没有单点故障一个节点挂了不影响整个系统。代价是调试方式变了你得依靠命令行工具去查询话题和节点而不是依赖一张中心节点列表。DDS本身是可插拔的底层实现可以通过RMWROS Middleware切换Humble默认使用Cyclone DDS也可以改用Fast DDS以适配不同网络环境。Day 1不需要深挖DDS协议细节但需要记住一个概念在多设备或多进程场景下ROS_DOMAIN_ID不一致会导致节点互相找不到。这个环境变量相当于给通信划分了独立“房间”只有同一个房间里的人才能聊上天。我写这节是为了后面排查问题时不至于完全没头绪。3. 从零搭建第一个ROS2功能包Talker和Listener3.1 创建功能包命令背后的目录逻辑在ROS2里功能包是最小的可编译单元。我第一天的实操路径是mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create demo_talker --build-type ament_cmake --dependencies rclcpp std_msgsmkdir -p会一次性创建~/ros2_ws和src目录。src目录是colcon构建系统的默认源目录。ros2 pkg create会生成一个包含CMakeLists.txt、package.xml和默认src目录的骨架。--dependencies参数很关键它会把rclcpp和std_msgs自动写入包描述文件编译时就能自动找到头文件。新手容易漏掉这一步之后编译时疯狂报找不到rclcpp/rclcpp.hpp。这里多说一句ROS2支持C和Python。C用ament_cmake构建系统Python用ament_python。Day 1推荐C因为编译过程能帮你提前暴露依赖缺失等问题练熟了再转Python会轻松很多。热词里有人搜“ros2创建c功能包”上面这条命令就是基础操作。3.2 编写发布者节点和订阅者节点骨架生成后在src目录里新建talker.cpp和listener.cpp。Talker节点每隔一秒往chatter话题发一条字符串消息。关键API如下rclcpp::init(argc, argv)初始化ROS2运行时rclcpp::Node::make_shared(talker)创建节点节点名在同一网络域内必须唯一node-create_publisherstd_msgs::msg::String(chatter, 10)创建发布者第二个参数是队列深度rclcpp::WallRate rate(1.0)控制循环频率为1Hzrate.sleep()按频率休眠让循环节奏稳定。完整代码#include rclcpp/rclcpp.hpp #include std_msgs/msg/string.hpp int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node rclcpp::Node::make_shared(talker); auto publisher node-create_publisherstd_msgs::msg::String(chatter, 10); rclcpp::WallRate rate(1.0); int count 0; while (rclcpp::ok()) { auto msg std_msgs::msg::String(); msg.data Hello ROS2, count: std::to_string(count); RCLCPP_INFO(node-get_logger(), Publishing: %s, msg.data.c_str()); publisher-publish(msg); rclcpp::spin_some(node); rate.sleep(); } rclcpp::shutdown(); return 0; }注意我在循环里用了spin_some()而不是spin()。这两个函数区别很重要spin()会阻塞当前线程并持续处理回调适合纯订阅场景spin_some()不会阻塞适合主循环里自己控制节奏的场景。如果你在发布循环里用了spin()程序会卡死在回调处理里发布逻辑反而一直不执行。订阅者节点稍微简单一点#include rclcpp/rclcpp.hpp #include std_msgs/msg/string.hpp void message_callback(const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(rclcpp::get_logger(listener), Received: %s, msg-data.c_str()); } int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node rclcpp::Node::make_shared(listener); auto subscription node-create_subscriptionstd_msgs::msg::String( chatter, 10, message_callback); rclcpp::spin(node); rclcpp::shutdown(); return 0; }订阅者的核心是create_subscription它接收三个参数话题名、队列深度、回调函数。队列深度10的意义是如果订阅者处理速度跟不上发布速度最多缓存10条消息再多的消息会被丢弃。对教学示例来说10够用但在真实项目中要结合数据的重要程度调整或者改用不同的QoS策略这是后话。3.3 colcon build与运行新手必踩的环境变量问题写完代码后在~/ros2_ws目录下执行cd ~/ros2_ws colcon build --packages-select demo_talker source install/setup.bashcolcon build会递归扫描src找到新增的包并编译。--packages-select参数可以指定只编译某个包第一次建议全量编译。编译完成后一定要执行source install/setup.bash这样当前终端才知道你新编译的包在哪里。这也是新手最容易犯错的地方编译成功后换个终端直接运行ros2 run demo_talker talker系统提示找不到可执行文件其实就是没source。把source /opt/ros/humble/setup.bash和source ~/ros2_ws/install/setup.bash追加到~/.bashrc可以省掉每次手动source的麻烦。但要注意如果同时使用多个工作空间source顺序会影响功能包覆盖关系后source的会覆盖先source的同名包。Day 1记住这个原则后续基本不会出错。运行方式# 终端1 ros2 run demo_talker talker # 终端2 ros2 run demo_talker listener终端1会一直打印发布日志终端2会打印收到的字符串。看到两边数字同步增长说明整个最小系统已经闭环了。到了这一步“从零构建第一个机器人应用”的地基就铺好了。4. 用RQt和RViz2把通信“看”清楚4.1 rqt_graph比文档更直观的节点关系图跑通两个节点后我第一件事是打开新终端执行rqt_graph。它会显示当前运行节点和话题之间的连线图椭圆是节点方块是话题。看到/talker - /chatter - /listener这条链路时刚才对“发布/订阅”的理解瞬间落地了。实用技巧如果rqt_graph里面什么连线都没有先检查所有运行节点是否处于同一个ROS_DOMAIN_ID环境变量下。默认都是0如果在不同终端里有人改了export ROS_DOMAIN_ID1那两边的节点就互不可见。这个概念贯穿整个ROS2开发过程多机通信、远程网络配置都绕不开它。4.2 RViz2可视化与常用的topic命令行工具RViz2是ROS2最常用的可视化工具热词里也有人搜“rviz2安装使用”。在Humble里它通常随桌面版一起安装如果没有可以用sudo apt install ros-humble-rviz2补上。Day 1我先用rviz2开了一个空窗口在左侧添加一个“RobotModel”面板然后准备一个简单的传感器数据源就能看到机器人模型的位置和传感器数据。RViz2的核心是插件化设计它不关心数据是谁发布的只关心数据是否在对应的话题上流动。与RViz2搭配的几个命令行工具非常值得记ros2 node list查看当前所有节点ros2 topic list查看所有话题ros2 topic echo /chatter直接看某个话题上的消息内容ros2 topic info /chatter --verbose查看话题的类型、发布/订阅统计信息。这些命令相当于机器人系统的“体检工具”节点是否在线、数据是否在流动、消息内容是否符合预期一眼就能定位。我在第一天用ros2 topic echo /chatter配合Talker/Listener实验比看十篇文档都管用。5. 从Day 1扩展到完整机器人应用5.1 真实机器人项目的最小闭环很多教程到这里就结束了但我想强调一点Day 1的Talker和Listener虽然简单却已经包含了真实机器人应用的最小闭环。传感器节点发布数据相当于Talker决策节点订阅数据并做出决策相当于Listener再根据决策向控制节点发布指令。你可以把今天的代码当成一个模板把std_msgs::msg::String替换成geometry_msgs::msg::Twist就能让机器人开始动起来。这就是“从零构建第一个机器人应用”的起点。举个例子一个“摄像头追球”小车视觉节点发布球的位置话题/ball_position控制节点订阅它根据坐标发布/cmd_vel速度指令。两个节点各干各的互不干扰。哪怕视觉节点崩溃重启控制节点也不会崩溃只要话题结构一致消息恢复后系统自动复活。这就是ROS2松耦合架构的实际价值也是它在机器人项目里被广泛采用的原因。5.2 从硬件到仿真Micro-ROS、串口桥接ESP32和Gazebo生态真实项目里传感器的原始数据往往来自MCU比如ESP32、STM32。热词里很多人在搜“ros2 humble串口桥接esp32小车”这是一个非常典型的做法ESP32负责读取编码器、控制电机通过串口和树莓派或工控机通信树莓派上运行一个桥接节点把串口收到的二进制帧解析成ROS2话题再把/cmd_vel写回串口。这个过程本质上还是话题通信只是数据源变了。如果你手头还没有真实硬件可以用仿真来推进。Gazebo配合URDF模型能起一个完整的Panda机械臂仿真环境热词里“ros2 humble gazebo panda”就是这个组合。Day 1我还没到写URDF的程度但可以提前知道Gazebo里的传感器和控制器同样遵守话题机制。ROS2里有一个叫Micro-ROS的组件专门跑在MCU上它能让ESP32这类芯片直接以ROS2节点身份发布话题省掉串口桥接那一层适合对实时性要求更高的项目。从串口桥接到Micro-ROS本质都是从“数据产生”到“话题化”的过程。5.3 导航、机械臂与避障的后续路线从Day 1继续往深走三个方向最典型。第一是导航Nav2是ROS2的官方导航栈它依赖/map话题中的栅格地图或八叉树地图做路径规划用/scan数据做动态避障。第二是机械臂MoveIt2结合RViz2就能用拖拽目标位姿的方式生成机械臂抓取轨迹Panda和UR5都有现成的官方支持包。第三是感知与控制闭环用OpenCV或深度学习检测目标再结合上述规划能力做完整的自主机器人应用。列这张规划表不是为了反刍而是想说明Day 1学的话题机制是所有这些高级功能的地基。不管是八叉树地图导航还是机械臂仿真抓取上层逻辑再复杂底层仍然是一个个节点往各自话题上发消息。地基打好了之后学Nav2、MoveIt2无非就是把消息类型换成nav_msgs/msg/OccupancyGrid或sensor_msgs/msg/JointState而已。6. 第一天踩过的坑排查实录与速查表6.1 五个高频问题详解坑一colcon: command not found。解决方法是安装python3-colcon-common-extensions或者确认ROS2环境有没有source。Humble的安装教程一般都会带这一步但很容易漏。坑二编译报错fatal error: rclcpp/rclcpp.hpp: No such file or directory。原因通常是创建功能包时没加--dependencies rclcpp std_msgs或者改了CMakeLists.txt但没加ament_target_dependencies。检查CMakeLists.txt里是否有一行ament_target_dependencies(talker rclcpp std_msgs)这行是C包的关键没有它编译器不知道去哪找头文件。坑三ros2 run提示找不到包或可执行文件。先确认已经source install/setup.bash再用ros2 pkg list | grep demo_talker确认包被识别。如果source了还是找不到检查当前目录是不是~/ros2_ws因为setup.bash里的路径是相对编译时的工作空间生成的路径错位会直接导致找不到。坑四RViz2打不开或者启动后黑屏。在虚拟机和Docker里很常见显示驱动不支持OpenGL。可以先设置环境变量再启动export LIBGL_ALWAYS_SOFTWARE1 rviz2如果依然黑屏尝试export QT_X11_NO_MITSHM1。原生Ubuntu桌面很少需要这些但用WSL2时经常要靠它救急。坑五两个节点明明都在跑ros2 topic echo却收不到任何消息。排查顺序是先ros2 node list看节点是否存在再ros2 topic list看话题名是否拼写一致最后看两边的ROS_DOMAIN_ID是否一致。多数时候都是话题名看错比如把/chatter写成了chatter或者大小写不一致。6.2 常见问题速查表现象可能原因快速解法colcon: command not found未安装构建工具sudo apt install python3-colcon-common-extensions编译报错缺少头文件依赖未写入CMakeLists添加ament_target_dependenciesros2 run找不到可执行文件没source install/setup.bashsource后重试RViz2黑屏虚拟机OpenGL问题export LIBGL_ALWAYS_SOFTWARE1两个节点互相看不见话题名不一致或ROS_DOMAIN_ID不同用ros2 topic list对照topic echo提示No data发布频率太低或订阅者退出检查发布端日志与频率这张表我整理到本地笔记里了后面每踩一个坑就往里加。我把这套方法称为“三层排查”先查工具层命令是否存在再查编译层头文件/依赖最后查运行层话题/环境变量。这套思路比盲目搜报错要稳定得多。Day 1走到这里我的第一个ROS2应用已经能跑通了两个节点、一个话题、一组消息。说实话这一步走完后面学习ROS2的很多焦虑都会消失因为最难的不是代码而是理解“以话题为中心的分布式协作”这种思维方式。最后再分享一个小技巧我在学习时习惯把每个新概念都做成一个最小的可运行demo而不是只读文档。比如学完话题就用Talker/Listener练一遍学完服务就写一个加法计算器服务学完动作再跑一次导航仿真。这样一个概念对应一个demo学到哪心里都不慌。这个“最小闭环学习法”是我反复用下来最有效的方式也推荐给你。