
很多初学 ROS 的朋友第一关往往不是机器人算法而是连自己写的包都跑不起来。我见过太多人卡在同一个地方在 Ubuntu 下装好了 ROS照着博客创建了自己的 ROS 包又去 GitHub 下载了一堆开源包结果catkin_make直接报错对着终端发呆半天。这篇教程就是专门解决这个痛点的我会从环境配置讲到 ROS 包的内部结构再到如何创建自己的包、如何把 GitHub 上第三方的包编译跑通整个过程全部按新手视角来写每一步都有明确命令和解释适合刚装好 ROS、正准备迈出“第一个包”这一步的同学。为了让叙述足够具体我会以 Ubuntu 20.04 ROS NoeticROS 1 的长期支持版本为主环境中间穿插说明 ROS 2 和 Ubuntu 22.04 用户需要注意的差异。你不用一上来就理解所有理论跟着命令走通一遍再看原理会比我直接丢概念给你要轻松得多。1. 搭建环境Ubuntu 和 ROS 版本搭配别在起步就跑偏1.1 Ubuntu 与 ROS 版本的对应关系新手最容易踩的第一个坑就是版本搭配。ROS 不是随便装在任何 Linux 上都能跑的它和 Ubuntu 版本有严格的对应关系装错版本后面全是莫名其妙的问题。简单列一下最常见的对应表Ubuntu 版本对应 ROS 1 版本对应 ROS 2 版本Ubuntu 18.04ROS MelodicROS 2 Dashing / EloquentUbuntu 20.04ROS NoeticROS 1 最后一个正式版ROS 2 Foxy / GalacticUbuntu 22.04无官方 ROS 1 支持ROS 2 HumbleUbuntu 24.04无官方 ROS 1 支持ROS 2 Jazzy我的建议非常直接如果你是纯新手又打算先学 ROS 1那就老老实实用 Ubuntu 20.04 Noetic。Noetic 是 ROS 1 的终极版本教程多、资料多、遇到的坑别人基本都踩过了最适合入门。如果你是从 2024 年之后开始学习、想一步到位上 ROS 2那 Ubuntu 22.04 Humble 是当前最成熟的选择。这里也提醒一句不要试图在 Ubuntu 22.04 上强行装 ROS Noetic。它没有官方预编译包虽然能通过源码编译但对新手来说属于典型的“地狱开局”完全没有必要。同理如果你已经在用 Ubuntu 22.04 了就顺着生态走 ROS 2 Humble别回头折腾 ROS 1。1.2 安装 ROS 的两种靠谱路径确认好版本之后安装 ROS 有两条路官方源手动安装或者用第三方一键安装脚本。两条路我都走过说下实际体验。官方源安装是正道逻辑清晰出问题也知道去哪查。核心步骤就四条设置 sources.list、添加 ROS 的密钥、apt update、安装ros-noetic-desktop-full。以 Noetic 为例安装部分大致是sudo sh -c echo deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main /etc/apt/sources.list.d/ros-latest.list sudo apt install curl curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo apt update sudo apt install ros-noetic-desktop-fulldesktop-full包含了 RViz、Gazebo、各种常用库新手装这个就够用了。装完以后还要初始化 rosdepsudo rosdep init rosdep update如果rosdep init卡住最常见原因是网络访问到 raw.githubusercontent.com 不稳定这种情况可以稍后重试或者参考 rosdep 相关的社区解决方案。装好后配置环境变量echo source /opt/ros/noetic/setup.bash ~/.bashrc source ~/.bashrc验证安装是否成功最简单的方法是运行roscore看到started core service [/rosout]之类的输出就说明 ROS Master 已经起来了。如果你觉得官方源流程繁琐国内社区里有一个比较出名的一键安装工具常被大家称为“小鱼 ROS 一键安装”。它本质上是一个集成脚本能够自动判断系统版本、选择合适的 ROS 发行版并完成安装。对于新手来说这种方式能省去不少手动配置的麻烦。wget http://fishros.com/install -O fishros . fishros运行后会弹出菜单选择你需要的 ROS 版本即可。这个脚本还可以一键安装 rosdep 依赖、配置环境变量非常适合想要快速搭环境的人。不过我始终建议用脚本装完之后记得自己跑一遍roscore验证环境同时搞清楚~/.bashrc里被加了哪些内容这样后续出问题不至于完全摸不着头脑。2. 先说透 ROS 包的结构再动手不迟2.1 一个 ROS 包内部到底有什么理解了“包package”这个基本概念后面一切都会顺很多。可以这么类比ROS 包就像一个手机的 App里面有程序本体、配置文件、资源文件还有一个写着“这个 App 是谁、需要什么权限”的清单。ROS 系统就是靠一个个包组织起来的你的机器人导航、视觉、机械臂控制本质上是很多包在协同工作。一个标准的 ROS 包目录下至少要有两个文件package.xml包的“身份证”包子叫什么名字、版本号、维护者、依赖哪些别的包全写在这。CMakeLists.txt编译规则告诉编译器源码在哪、生成哪些可执行文件、要链接什么库。除了这两个必选文件常见的还有src/存放 C 源码或者 Python 脚本。include/存放 C 头文件。launch/存放.launch启动文件用来一键启动多个节点。config/存放参数配置比如 yaml 文件。msg/、srv/、action/自定义消息、服务、动作定义。urdf/机器人模型文件。新手经常忽略的是目录结构的规范。ROS 对包目录结构没有强制作硬性要求但别人用起来是否方便、能不能被 catkin 自动识别就看你是否遵守社区惯例。比如一个名为beginner_tutorials的包推荐结构是~/catkin_ws/src/beginner_tutorials/ ├── CMakeLists.txt ├── package.xml ├── src/ │ ├── talker.cpp │ └── listener.cpp ├── launch/ ├── config/ └── README.md2.2 catkin 工作空间的三个目录各管什么在你创建自己的包之前必须先有一个“工作空间”也就是所有包的集合地。ROS 1 最常用的构建工具是catkin它要求必须有一个名为src的目录存放所有包的源码。在终端里执行mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace执行完你会发现src目录下多了一个CMakeLists.txt的符号链接这是 catkin 自动生成的不用管它。然后回到工作空间根目录编译cd ~/catkin_ws catkin_make第一次编译后catkin_ws下面会多出两个目录build和devel。build是编译过程中产生的中间文件可以理解为“施工工地”上的脚手架和模板删掉再编就会重新生成不用手动动它。devel是编译产物目录里面存放生成的可执行文件、库文件、头文件、环境变量脚本这个目录非常关键。catkin_make做的事情简单说就是扫描src下所有包逐个编译把可执行文件放到devel/lib/下把头文件放到devel/include/下最后生成一个devel/setup.bash脚本用来把编译结果“注册”到当前终端环境。所以每次编译完都要执行source ~/catkin_ws/devel/setup.bash如果不执行这行系统根本不知道你编译了哪些新节点运行rosrun时就会提示找不到包。很多新手编译成功了却跑不起来九成是忘了这一步。3. 创建自己的第一个 ROS 包让节点真正跑起来3.1 用 catkin_create_pkg 生成包骨架创建包的工具是catkin_create_pkg它会在当前目录下自动生成一个包的基本骨架包括package.xml、CMakeLists.txt、include和src目录。基本用法是cd ~/catkin_ws/src catkin_create_pkg beginner_tutorials roscpp rospy std_msgs这个命令里的beginner_tutorials是包名后面的roscpp rospy std_msgs是包的依赖项。roscpp是 C 客户端库rospy是 Python 客户端库std_msgs是标准消息类型比如 String、Int32。新手先记这三个就够用后续用到什么再加。执行完命令后你会在src/beginner_tutorials下看到CMakeLists.txt、package.xml、include/beginner_tutorials、src这些目录和文件。这里有个细节catkin_create_pkg生成的CMakeLists.txt非常长带满了注释说明新手第一次看到容易懵但绝大多数内容都不用动后面我告诉你改哪几行。3.2 手写一个发布者节点并用 C 实现我建议第一个节点就写传统的“talker/listener”也就是一个节点不停发消息另一个节点接收消息。麻雀虽小五脏俱全它能帮你理解 ROS 通讯最基本的模型Master 负责牵线Talker 发布话题Listener 订阅话题。在~/catkin_ws/src/beginner_tutorials/src/目录下新建talker.cpp写入#include ros/ros.h #include std_msgs/String.h #include sstream int main(int argc, char **argv) { // 初始化 ROS 节点节点名称为 talker ros::init(argc, argv, talker); // 创建节点句柄用于管理发布器、订阅器等资源 ros::NodeHandle n; // 创建一个发布器话题名是 chatter队列长度 1000 ros::Publisher chatter_pub n.advertisestd_msgs::String(chatter, 1000); // 设置发布频率为 10Hz ros::Rate loop_rate(10); int count 0; while (ros::ok()) { std_msgs::String msg; std::stringstream ss; ss hello world count; msg.data ss.str(); ROS_INFO(%s, msg.data.c_str()); // 真正把消息发布到话题上去 chatter_pub.publish(msg); // 处理回调函数和订阅事件这里没有订阅者但养成习惯 ros::spinOnce(); // 按频率休眠使循环以 10Hz 稳定运行 loop_rate.sleep(); count; } return 0; }再新建listener.cpp#include ros/ros.h #include std_msgs/String.h // 当收到话题消息时这个回调函数会被调用 void chatterCallback(const std_msgs::String::ConstPtr msg) { ROS_INFO(I heard: [%s], msg-data.c_str()); } int main(int argc, char **argv) { ros::init(argc, argv, listener); ros::NodeHandle n; // 订阅 chatter 话题队列长度 1000收到消息时执行回调 ros::Subscriber sub n.subscribe(chatter, 1000, chatterCallback); // spin() 进入循环不断处理收到的消息直到节点关闭 ros::spin(); return 0; }这段代码有几个关键点我要单独说明第一ros::init(argc, argv, 节点名)必须放在最前面它是节点的“户口登记”节点名在整个 ROS 网络中不能重名重名会导致节点启动异常。第二ros::NodeHandle是节点和 ROS 系统交互的入口可以理解为“电话总机”。建好它之后才能申请发布器、订阅器、获取参数。第三ros::Rate loop_rate(10)是控制循环频率的小工具它做的事情就是让while循环每 100ms 跑一次也就是 10Hz。如果不控制频率while 循环会全速疯狂发布把 CPU 和带宽都打满。第四ros::ok()用于检查节点是否应该退出。按CtrlC终止节点时它返回 false循环自然退出比较优雅。3.3 修改 CMakeLists.txt 和 package.xml否则编译必挂写好了源码如果直接catkin_make十有八九会报错“找不到ros/ros.h”或者找不到talker可执行文件。原因就是CMakeLists.txt里还没有告诉编译器这两个源文件要被编译成什么。catkin_create_pkg生成的CMakeLists.txt中有一段是注释掉的示例新手要做的就是找到下面这些行取消注释并根据实际情况修改add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES}) add_executable(listener src/listener.cpp) target_link_libraries(listener ${catkin_LIBRARIES})add_executable的意思是“把src/talker.cpp编译成可执行文件 talker”target_link_libraries的意思是“链接 catkin 提供的库”。这两行必须成对出现只加前一行不加后一行编译时通常会出现一堆 undefined reference 的错误。我还建议顺手加上一行add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) add_dependencies(listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})这行是处理消息、服务代码生成依赖的当前例子没自定义消息不写也能过但以后一旦用到自定义msg/srv没这行就会出现“找不到消息头文件”的问题先把习惯养成。然后打开package.xml重点检查build_depend和exec_depend里面有没有roscpp、rospy、std_msgs。catkin_create_pkg命令带依赖的话会自动写好但如果以后手动往包里加新依赖比如geometry_msgs就要在这两处同步加否则编译时提示找不到依赖的情况会非常频繁。3.4 编译、配置环境变量、运行并观察通信代码写完配置改完回到工作空间编译cd ~/catkin_ws catkin_make看到[ 100%] Built target listener和Built target talker就说明编译成功。注意每次编译完如果当前终端是在编译前打开的一定要重新source一下否则 ROS 仍然不知道新包的存在source ~/catkin_ws/devel/setup.bash为了让以后每个终端都自动识别工作空间可以把它写进~/.bashrcecho source ~/catkin_ws/devel/setup.bash ~/.bashrc source ~/.bashrc接下来开三个终端验证终端 1roscore终端 2rosrun beginner_tutorials talker你会看到终端里持续输出hello world 0、hello world 1……终端 3rosrun beginner_tutorials listener正常情况下listener 会同步打印I heard: [hello world 0] I heard: [hello world 1]看到这个输出你的第一个 ROS 包就跑通了。此时我们可以用rqt_graph或者rosnode list看一眼节点关系也可以用rostopic echo /chatter直接看话题内容这都能帮你建立“节点 话题”的直觉。4. 使用 GitHub 下载的 ROS 包从克隆到跑通的完整流程4.1 怎么挑一个靠谱的仓库并正确拉取代码自己会写包之后下一步就是学会用别人的包。GitHub 上 ROS 相关的开源项目非常多但“能用”和“能跑起来”往往隔着好几层挑选仓库时有几个经验可以分享看 stars 数量和维护时间。stars 多、最近一年还在更新的通常活跃度和兼容性更高。看 README 里的系统版本和 ROS 版本说明。如果写着“tested on Kinetic/Melodic”你拿来用在 Noetic 上大概率有小问题需要改。看是否有install或Compilation from source章节写得越详细越不容易踩坑。优先选存在于ros-xxx组织下的仓库或者被 ROS 社区官方索引收录的包品质更有保障。拉取代码最常规的方式是git clonecd ~/catkin_ws/src git clone https://github.com/ros-teleop/teleop_twist_keyboard.git如果你的网络访问 GitHub 不太顺畅直接用仓库页面上的 “Download ZIP” 下载压缩包解压到~/catkin_ws/src目录手动把文件夹名改成包名也是完全可行的方式。需要注意一点GitHub 上下载的压缩包解压后目录名通常会带-master或-main后缀比如teleop_twist_keyboard-master如果不改回teleop_twist_keyboardROS 虽然在编译上通常也能识别但在后续rosrun时容易踩坑还是老老实实改名最稳妥。还有一个实用技巧是浅克隆。有些仓库体积大、历史提交多普通git clone体积会拉得很大。如果只是要最新的代码来用可以指定只拉取最近一次提交git clone --depth 1 https://github.com/ros-teleop/teleop_twist_keyboard.git这样下载体积会小很多速度通常也会快很多。4.2 编译第三方包依赖问题是头号大敌把包放进src之后最理想的情况是直接catkin_make一次通过但实际情况往往是报错而绝大多数报错根源都在“依赖缺失”。ROS 的包互相依赖关系非常普遍比如一个导航包可能依赖tf2、nav_msgs、geometry_msgs等几十个包。你刚拉下来的项目它的package.xml里列了这些依赖但你的系统里可能一个都没装。手动一个sudo apt install去装非常痛苦而且很快会搞不清楚谁是谁的依赖。这时候就要用rosdep这个官方依赖管理工具。在catkin_ws根目录执行rosdep install --from-paths src --ignore-src -r -y这条命令解释一下--from-paths src告诉 rosdep 去src目录里扫描所有包--ignore-src是说“如果某个依赖有源码在本地工作空间里就忽略它”避免重复处理-r是继续继续不要因为某个依赖失败就中断-y是安装时不要问东问西自动确认。执行完以后它会列出需要安装的 apt 包然后自动帮你装好。装完后再catkin_make绝大多数缺依赖的问题都能解决。如果rosdep在rosdep update那一步就卡住网络是一条原因也可能因为系统里${ROS_DISTRO}环境变量没设置好。排查方式是用echo $ROS_DISTRO看一下如果显示noetic就对了如果是空的说明 ROS 环境没 source回到前面把source /opt/ros/noetic/setup.bash加进~/.bashrc再重开终端。4.3 典型案例把 teleop_twist_keyboard 跑起来我挑一个最经典的包teleop_twist_keyboard它允许你用键盘控制机器人或者仿真模型移动依赖非常少只有roscpp、rospy、std_msgs、geometry_msgs桌面完整版都带了。流程如下cd ~/catkin_ws/src git clone https://github.com/ros-teleop/teleop_twist_keyboard.git cd ~/catkin_ws rosdep install --from-paths src --ignore-src -r -y catkin_make source ~/catkin_ws/devel/setup.bash运行它先启动 roscore如果在 Gazebo 里用就启动仿真roscore再开一个终端rosrun teleop_twist_keyboard teleop_twist_keyboard.py这时终端会显示一张按键说明表按i、j、l、,这些键就能发布/cmd_vel话题的线速度和角速度。想看到它真的发了数据可以在另一个终端运行rostopic echo /cmd_vel你会看到类似linear: x: 0.5 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 0.0 ---看到这个输出就说明你从 GitHub 拉下来的包已经成功编译并运行了。这不仅是“跑通了一个包”更是让你掌握了 ROS 生态里“拿别人代码 → 解决依赖 → 编译运行”这条标准链路。5. 新手最容易栽进去的坑我替你踩过一遍5.1 编译成功却找不到包八成是环境变量问题“我明明编译成功了为什么rosrun beginner_tutorials talker提示package beginner_tutorials not found”这是论坛里出现频率最高的问题之一原因基本就一个当前终端没有加载工作空间的环境变量。catkin_make只管编译编译完成并不等于当前 shell 都知道这些信息。你必须执行source ~/catkin_ws/devel/setup.bash才能让当前终端知道beginner_tutorials这个包的存在。为了防止每次开终端都要手动 source把它写进~/.bashrc是常规操作。但要注意~/.bashrc里如果有多个 ROS 工作空间的 source 语句后 source 的会覆盖先 source 的导致旧的包“消失”。所以不要在不同路径下建一堆工作空间然后全 source保持一个主工作空间、一份 source能省掉很多莫名其妙的麻烦。排查环境变量问题可以用这两条命令echo $ROS_PACKAGE_PATH正常情况下输出里应该包含/home/你的用户名/catkin_ws/src:/opt/ros/noetic/share。如果只有/opt/ros/noetic/share而看不到catkin_ws/src说明工作空间没有被正确加载。5.2 拉取 GitHub 仓库不顺时的合规处理办法很多新手一遇到 GitHub 下载慢就慌了四处找“加速”手段其实没必要也容易踩到违规工具上。在这里我只分享完全合规的操作。如果你想从 GitHub 获取代码但网络访问不稳定第一选择是直接用网页上的 “Download ZIP” 下载压缩包。这种方式避开了git clone需要的大量网络交互通常更容易成功缺点是拿不到版本历史和子模块。下载后解压到~/catkin_ws/src记得把目录名中的-master、-main后缀去掉。第二选择是浅克隆。对于只想使用源码而不关心历史提交的 ROS 包浅克隆既能大幅减少下载体积也能提升成功率git clone --depth 1 https://github.com/ros-teleop/teleop_twist_keyboard.git第三选择是设定较长的 git 超时时间并在网络相对空闲的时间段重试。如果某个仓库包含子模块还需要在克隆后执行git submodule update --init --recursive这是很多人漏掉的一步有些仓库源码目录是空的运行起来缺文件就是因为没拉子模块。5.3 编译卡死、头文件找不到、版本对不上怎么排查编译第三方包时报错类型五花八门但归结起来常见就几类第一类找不到头文件。比如fatal error: geometry_msgs/Point.h: No such file or directory。这种情况通常是依赖包虽然声明了但没安装到系统里。先试试rosdep install如果还不行就用sudo apt install ros-noetic-geometry-msgs手动安装对应包。ROS 的包名规则是把下划线改成短横线比如geometry_msgs对应ros-noetic-geometry-msgs。第二类编译过程中系统卡死或者内存不足。catkin_make默认使用全部 CPU 核心编译在虚拟机里很容易把内存吃满。解决办法是限制并行编译任务数比如catkin_make -j2-j2表示同时用 2 个任务编译对虚拟机和小内存电脑特别友好。如果编译中途卡死按CtrlC终止清理build目录后再重新编译cd ~/catkin_ws rm -rf build devel catkin_make -j2第三类源码本身基于不同的 ROS 版本接口对不上。比如为 ROS 2 写的包代码里全是rclcpp你没法直接用在 ROS 1 Noetic 上。这种不是环境问题而是项目兼容性问题建议直接放弃换一个明确支持你所用 ROS 版本的包。判断方法很简单看 README 或者package.xml里依赖的包名ROS 1 的 C 依赖roscppROS 2 的依赖rclcpp一眼就能分辨。第四类package.xml里的依赖和实际编译需要的依赖不一致。有些作者偷懒少写了依赖项结果别人编译时各种灵异报错。解决办法是在CMakeLists.txt里搜索find_package看看它实际引用了哪些库然后对照package.xml里的依赖缺哪个补哪个。最后再分享一点个人体会我在带新人时经常说ROS 入门其实不是“学算法”而是“学生态”。创建自己的包、编译别人写的包这两件事看起来是很基础的操作但它们背后训练的是你对整个 ROS 工程结构的感知力。当你能不假思索地说出一个包需要哪些文件、编译报错大概往哪个方向排查的时候后面学 TF 坐标变换、学 SLAM、学 MoveIt都会顺很多。还有两个小建议送给新手。第一刚开始不要急着用 IDE先用终端 code或gedit写代码逼自己熟悉命令行和编译流程这是 ROS 绕不开的基本功。第二养成一个习惯每次把新包放进工作空间第一件事就是跑rosdep install --from-paths src --ignore-src -r -y再开始编译这个动作能帮你避免 80% 的缺依赖问题。学 ROS 一定会遇到很多“玄学报错”但回头看大多是环境和依赖问题没什么深奥的。把环境吃透、把包的机制吃透剩下的就只是时间积累。希望这篇教程能帮你把第一步走得稳一点。