ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ROS入门:自己写的程序如何编译并运行起来

ROS入门:自己写的程序如何编译并运行起来 从零到一在ROS里把自己写的程序编译并跑起来前两天有个刚入坑ROS的朋友问我说自己在Ubuntu上把ROS装好了也在网上抄了一个发布者订阅者的小例子代码看懂了但一说到“编译”“运行”就懵了。他原话是“我就想让自己写的那几行代码跑起来怎么比写代码还难。”这个问题我太有感触了。很多人第一次接触ROS卡住的往往不是C语法也不是消息通信机制而是根本不知道ROS那套“编译”“运行”的流程到底是怎么转起来的。网上教程一搜一大把但多数上来就让你敲catkin_make敲完了也不知道发生了什么报错了更是一头雾水。这篇文章我就从怎么编译、怎么运行、报错怎么排查这三个角度把“在ROS里跑自己程序”这件事彻底讲明白。主要针对用catkin工具链的ROS 1比如Noetic、Melodic但理解清楚之后ROS 2的colcon也就一通百通了。适合刚装好ROS、准备开始写自己第一个节点的朋友也适合那种“跟着教程跑通了但不敢自己写”的同学。1. 先把编译想明白程序不是写完就能跑的1.1 “编译”到底在干什么很多非科班出身的朋友第一次看到“编译”“链接”“可执行文件”这些词会觉得挺劝退的。其实没那么玄乎你写出来的C代码是给人看的但计算机的CPU只认机器码也就是一串又一串的二进制指令。编译器的作用就是把你看得懂的std_msgs::String、ros::init这些东西翻译成CPU能看懂、能执行的机器指令。这个翻译过程大概分两步。第一步叫编译把每个.cpp源文件单独翻译成一个中间产物——目标文件.o文件。第二步叫链接把一堆目标文件和ROS自带的那些库文件“拼”到一起最后生成一个完整的可执行文件。可以这么理解编译就像你写好了每章节的稿件链接就是把这些稿件按目录装订成一本书。书没装订好单章写得再工整也没法交付。ROS里常用的catkin_make本质就是把当前工作空间下所有功能包组织起来统一完成“编译装订”这两件事。所以当你说“编译”的时候实际上是在让整个工作空间根据每个包的配置把所有代码变成可执行文件。1.2 catkin的层层嵌套工作空间、功能包、节点ROS 1里项目组织的核心层级是工作空间workspace下面放功能包package功能包里面放节点node源码。你在终端里经常看到的catkin_ws就是工作空间src目录下放的是一个个功能包每个包里有src文件夹、CMakeLists.txt、package.xml这几个基本文件。顶层还有个你一定会碰到的devel目录这是编译产物存放的地方。源文件编译出来的可执行文件和Python脚本都会按功能包分类放到devel/lib下面。ROS运行节点的时候实际上就是在运行devel/lib里那些现成的文件。这样设计的好处是源码和产物分离工作空间可以随时删掉build和devel重新编译源码不会丢失。我刚入门那会儿不懂这个有一次误删了整个build目录以为辛辛苦苦写的代码都没了后来才发现源码在src里好好的重新编译一遍就行。2. 编译前的环境准备与工程搭建2.1 ROS环境怎么来的安装方式与版本选择不管你是用官方源手动装还是用“鱼香ROS一键安装”这类脚本快速部署只要最终装的版本和你Ubuntu系统匹配比如Ubuntu 20.04对应ROS Noetic18.04对应Melodic后面编译运行的流程基本一致。很多新手在环境这里栽跟头原因多半是系统版本和ROS版本不匹配装完了终端里连roscore都起不来。我的建议是刚入门别自己折腾源码编译安装ROS直接用官方二进制包方式安装省时省力。一键安装脚本的好处是把换源、依赖配置这些琐碎步骤都自动化了实测对于初期学习来说很友好。但无论用哪种方式装完都要确认一下环境变量是否写进了~/.bashrc。因为ROS工具链依赖大量环境变量比如ROS_DISTRO、CMAKE_PREFIX_PATH如果这些变量没设好后续用catkin_make或rosrun时会报出各种莫名其妙找不到命令的问题。检查方法很简单新开一个终端输入echo $ROS_DISTRO能输出版本号比如noetic就说明环境正常。如果这个命令是空白的说明source /opt/ros/版本/setup.bash没生效需要手动加到~/.bashrc里。2.2 创建工作空间和功能包确认环境没问题后开始搭建自己的工程。先创建工作空间mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspacecatkin_init_workspace会在src目录下生成一个CMakeLists.txt符号链接这条命令的作用是让src目录变成标准的catkin工作空间。然后回到工作空间根目录空编译一次cd ~/catkin_ws catkin_make这一步会生成build和devel两个目录。build里是编译过程的中间文件devel里是最终产物。空编译成功说明整个工具链在你这台机器上是通的这时候再开始写自己的代码排错起来思路清晰。然后创建功能包。假设包名叫my_first_pkg依赖项用roscpp、std_msgscd ~/catkin_ws/src catkin_create_pkg my_first_pkg roscpp std_msgscatkin_create_pkg会自动生成这个包的基础骨架包括CMakeLists.txt、package.xml、src目录等。后面的roscpp和std_msgs是声明这个包依赖哪些ROS库。roscpp就是ROS的C客户端库我们写C节点必须依赖它std_msgs包含标准消息类型比如String、Int32这些基础消息。2.3 写一个最简单的发布者程序功能包建好后在src/my_first_pkg/src/目录下新建一个talker.cpp先写一个最简单的发布者节点。这段代码做的事情是初始化一个ROS节点名为talker_node然后在/my_topic话题上不断发布字符串消息#include ros/ros.h #include std_msgs/String.h #include sstream int main(int argc, char **argv) { ros::init(argc, argv, talker_node); ros::NodeHandle n; ros::Publisher pub n.advertisestd_msgs::String(/my_topic, 1000); ros::Rate loop_rate(2); while (ros::ok()) { std_msgs::String msg; std::stringstream ss; ss hello from my_first_pkg count; msg.data ss.str(); ROS_INFO(Publishing: %s, msg.data.c_str()); pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); count; } return 0; }这段代码不复杂但它是理解ROS发布者模型的最佳入门样例。ros::init初始化节点n.advertise告诉ROS主节点我要往/my_topic发消息loop_rate(2)控制发布频率为每秒2次while (ros::ok())循环里构造消息、发布消息。3. 核心实操用catkin_make编译并运行自己的程序3.1 改CMakeLists.txt让编译系统认识你的代码代码写完不是直接就能编译的。catkin用的是CMake这套构建系统你得在CMakeLists.txt里明确告诉它这个包有哪些源文件要编译、编译出来的可执行文件叫什么名字、要链接哪些库。用编辑器打开my_first_pkg/CMakeLists.txt找到这几段并修改。第一处是add_executable也就是声明“要生成一个可执行文件”add_executable(talker src/talker.cpp)这里talker是你要生成的可执行文件名src/talker.cpp是源码路径。这个名字是可以自定义的但建议和你程序功能保持一致方便后面运行的时候好记。第二处是target_link_libraries意思是“把编译出来的可执行文件和ROS的库链接起来”target_link_libraries(talker ${catkin_LIBRARIES})${catkin_LIBRARIES}是CMake里的一个变量指代当前包依赖的所有库。你可能会问为什么依赖了roscpp和std_msgs这里不需要逐个写出来因为catkin已经把依赖关系打包成了一个变量直接用就行。这也是ROS封装CMake的一种便利之处。如果你要写Python节点不需要走add_executable这套流程Python本来就是脚本语言不需要编译成机器码。但需要让系统把.py文件安装到devel/lib对应目录下做法是在CMakeLists.txt里加catkin_install_python(PROGRAMS scripts/talker.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})这个后面讲Python节点时再细说。3.2 开始编译catkin_make怎么用配置好CMakeLists.txt后回到工作空间根目录cd ~/catkin_ws catkin_make终端里会滚动一堆编译日志。第一次编译会多一些因为要把ROS依赖的配置都生成出来以后再编译只会编译你改动过的部分。编译成功时日志末尾会出现类似这样的内容[100%] Built target talker看到这句说明可执行文件已经生成了。如果编译过程中有错终端会变成红色报错并提示具体是哪个文件、哪一行有问题。判断编译是否成功的标志是最后一行有没有出现Built target。编译完之后去devel/lib/my_first_pkg目录下看一眼ls ~/catkin_ws/devel/lib/my_first_pkg能看到一个名为talker的文件这就是“可执行文件”。它本质上就是编译链接之后的机器码程序ROS运行节点时实际执行的就是这个东西。这里需要注意一个很多新手都会踩的坑每次修改了.cpp源码都要重新执行catkin_make否则运行的老旧版本不会变。我见过不少朋友改了代码、保存文件、直接rosrun结果发现程序行为和原来一模一样其实就是忘了重新编译。另一个常见困惑是为什么修改CMakeLists.txt后推荐重新编译因为CMakeLists.txt是构建配置改了配置之后CMake需要重新生成Makefilecatkin_make会自动检测到这种变化并重新生成所以统一习惯就是任何改动回工作空间根目录敲一次catkin_make准没错。3.3 source环境变量让系统找到你的程序编译成功后新开一个终端输入rosrun my_first_pkg talker有很大概率会报错[rosrun] Couldnt find executable named talker below /home/xxx/catkin_ws/src/my_first_pkg这个报错让无数新手抓狂。自己的程序明明编译好了文件我也看到了为什么ROS就是找不到原因在于ROS通过环境变量ROS_PACKAGE_PATH来定位功能包通过CMAKE_PREFIX_PATH等变量来确定工作空间位置。你新开的终端没有加载你当前工作空间的配置它只知道系统ROS自带的功能包路径并不知道~/catkin_ws下有个my_first_pkg。解决办法就是source一下环境source ~/catkin_ws/devel/setup.bash这一行的作用是把当前工作空间的路径信息注入到当前终端的环境变量里。执行之后再运行rosrun my_first_pkg talker就没问题了。但每次开新终端都要手动source一遍太麻烦了更推荐的做法是把这个命令追加到~/.bashrc里这样每个新终端都会自动加载echo source ~/catkin_ws/devel/setup.bash ~/.bashrc source ~/.bashrc这里有个细节值得说一下setup.bash到底从哪来是编译时生成的。catkin_make除了生成可执行文件还会在devel目录下生成一系列setup.*文件bash、zsh、sh等它们的作用就是通知系统“这个工作空间里有这些功能包可以用rosrun找到它们”。所以如果你删了devel目录又没重新编译source ~/catkin_ws/devel/setup.bash就会失败。3.4 运行之前先启动ROS主节点要真正让节点跑起来除了解释节点程序本身还需要一个“调度中心”——ROS Master。它负责管理节点之间的通信帮发布者和订阅者互相找到对方。在ROS 1里启动这个调度中心的命令是roscore需要在另一个终端运行并且要保持这个终端一直开着。roscore本质上是启动了ROS的master节点同时包含了参数服务器等功能。运行顺序是先开一个终端跑roscore再开另一个终端source环境并运行talker节点source ~/catkin_ws/devel/setup.bash rosrun my_first_pkg talker然后在第三个终端里用rostopic echo /my_topic查看话题内容就能看到talker节点发布的字符串消息了。很多刚接触ROS的朋友会搞混rosrun和直接运行可执行文件的区别。比如有人直接执行./devel/lib/my_first_pkg/talker也能看到输出但一来这样不方便二来rosrun会帮你从功能包名去索引可执行文件并且还会加载ROS相关的参数、日志、命名空间等上下文所以约定俗成用rosrun。3.5 编译运行Python节点不需要“编译”的运行说完C再说说Python节点。不少初学者会觉得Python写起来快、调试方便ROS也支持Python节点。关键区别在于Python脚本本身是解释执行的语言不需要像C那样经过编译和链接生成可执行文件但它同样需要被ROS“找到”。假设你写了一个scripts/talker.py放在功能包的scripts目录下#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy from std_msgs.msg import String def talker(): pub rospy.Publisher(/my_topic, String, queue_size10) rospy.init_node(talker_node, anonymousTrue) rate rospy.Rate(2) count 0 while not rospy.is_shutdown(): msg hello from python %d % count rospy.loginfo(msg) pub.publish(msg) count 1 rate.sleep() if __name__ __main__: try: talker() except rospy.ROSInterruptException: pass注意第一行#!/usr/bin/env python3这叫shebang作用是告诉系统用哪个解释器来运行这个脚本。然后要给脚本加上可执行权限chmod x scripts/talker.py接着在CMakeLists.txt里添加我刚才提过的catkin_install_pythoncatkin_install_python(PROGRAMS scripts/talker.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})然后回工作空间根目录catkin_make。这里编译不是把Python变成机器码而是把脚本“安装”到devel/lib/my_first_pkg/下。之后就可以这样运行rosrun my_first_pkg talker.py注意运行时后面要带.py后缀因为可执行文件名就是脚本文件名。很多新手在这里容易和C节点混淆C节点rosrun时不用加后缀Python节点要按实际文件名来。4. 高频报错与排查技巧实录4.1 “Couldnt find executable”不一定是没编译前面提到过的报错[rosrun] Couldnt find executable named talker below ...很多人第一反应是重新编译但其实这个报错至少有三种可能。第一种可能压根没编译成功devel/lib/my_first_pkg下没有这个文件。这种情况去查看编译日志解决编译报错。第二种可能编译成功了但当前终端没source环境。刚才说过新终端不会自动认识你的工作空间执行source ~/catkin_ws/devel/setup.bash即可。第三种可能add_executable里设置的可执行文件名和你rosrun时输入的名字不一致。比如你写的是add_executable(talker_node src/talker.cpp)运行的时候用rosrun my_first_pkg talker那必然找不到。检查方法很简单去devel/lib/my_first_pkg目录下ls一下看实际文件名是什么用实际文件名运行。4.2 CMakeLists.txt写错导致的编译失败CMakeLists.txt是新手最容易写崩的地方。常见的一类是语法错误比如括号不匹配、大小写不一致。CMake是区分大小写的add_executable写了Add_executable就会报错。另一类常见问题是忘了取消注释。用catkin_create_pkg生成的CMakeLists.txt里绝大多数指令是被注释掉的示例代码。很多人找不到add_executable其实它就在文件后面只是没解锁。把前面的#删掉再修改内容就行。但要注意不要同时启用示例部分的add_executable和你的自定义部分否则有些情况下会重复定义同一目标。编译报错时终端输出的第一行红色内容往往提示了具体的文件路径和行号。比如/home/xxx/catkin_ws/src/my_first_pkg/CMakeLists.txt:58: error: ...这个意思是第58行有问题直接翻到那一行检查。4.3 运行Python节点时报“No module named ...”Python节点运行后如果提示ModuleNotFoundError: No module named rospy最大的可能是你用错了Python解释器。ROS Noetic默认绑定Python 3Melodic默认是Python 2。如果你的系统默认python命令指向的是Python 2而写的rospy是Noetic下的Python 3版本就会导入失败。排查方式which python python --version如果Python版本和ROS不匹配可以在脚本里明确指定解释器或者用python3命令运行。还有一种做法是安装python3-catkin-pkg等依赖包确保ROS工具的Python模块都装全了。4.4 运行节点时连接roscore失败启动节点时如果终端报ERROR: cannot connect to master ...这说明节点找不到ROS Master。原因一般有两个第一roscore没启动第二ROS_MASTER_URI环境变量设置不正确。roscore没启动的话开一个新的终端执行roscore就行。ROS_MASTER_URI默认是http://localhost:11311如果你在~/.bashrc里设置过其他地址比如指向了某台远程主机而当前roscore就在本机跑连接就会失败。排查时执行echo $ROS_MASTER_URI如果输出不是http://localhost:11311结合自己场景判断是否需要改回来。4.5 问题排查速查表现象可能原因解决方法catkin_make报错源码语法错误 / CMakeLists.txt配置错误看红色报错定位到文件行号编译成功但rosrun找不到没source工作空间执行source ~/catkin_ws/devel/setup.bashrosrun报找不到可执行文件可执行文件名写错到devel/lib/my_first_pkg下用ls确认名称运行节点连不上masterroscore没启动另开终端执行roscorePython节点No module named rospyPython版本不匹配 / 缺依赖确认系统Python版本安装python3依赖修改代码后运行还是老行为忘记重新编译回到工作空间根目录执行catkin_makesource setup.bash报不存在devel目录被删除重新执行catkin_make生成5. 一些实操中的个人习惯与心得聊到最后分享几个我用了很久的习惯虽然不是必须的但确实能让编译运行流程更省心。第一代码修改后不要急着运行先编译一遍。哪怕你觉得“就改了一个变量名应该没问题”也建议编译一下。编译是验证代码语法正确性的第一道关卡早发现早处理。第二把常用的功能包依赖在创建时写全。catkin_create_pkg的依赖写全了CMakeLists.txt里大部分配置就已经就位后面加代码会快很多。我一开始建包时只写了roscpp后来加入tf、visualization_msgs等功能时还得回头去改CMakeLists.txt和package.xml的依赖比较繁琐。第三养成看完整报错日志的习惯。ROS的报错信息有时候很长但关键信息往往在最前面的几行比如哪一行代码出错、哪个库缺失。不要一看到红色报错就慌冷静下来从第一行开始读大部分问题都能自己解决。第四也是最重要的一点刚开始学习时尽量从最小可运行示例开始改动。比如talker这个程序跑通了你想加一个订阅者那就先从抄一个简单的listener开始确认能通信了再逐步加入自己的业务逻辑。不要在没跑通基础通信的情况下就试图把一堆复杂逻辑塞进一个节点里。那样出了问题你根本不知道是通信的锅还是逻辑的锅。ROS的编译运行体系说到底就是一套“你告诉CMake怎么把源码变成程序再把程序交给rosrun去执行”的流程。理解了这个本质后面不管是用catkin_make、catkin build还是ROS 2里的colcon都是一个套路构建、安装、source、运行。希望这篇文章能帮你跨过“编译运行”这道坎去享受写自己ROS程序的乐趣。
返回列表