第2章 ROS环境搭建与通信机制
ROS,说白了就是机器人的「操作系统」—— 但它其实更像一套通信框架。我做了这么多年SLAM,可以负责任地告诉你:ROS玩不溜,后面所有传感器驱动、数据融合、建图定位全都会卡壳。这一章咱们就把ROS的底子打扎实,从安装到通信,再到Launch文件,一步不落。
2.1 ROS 安装 —— 别踩我踩过的坑
ROS 版本很多,我个人习惯用 ROS Noetic(Ubuntu 20.04)。为什么?因为目前主流SLAM框架像LOAM、LIO-SAM、FAST-LIO都在这上面跑得最稳。你如果非要用Melodic或者Humble,后面编译依赖够你折腾一阵子。
安装命令其实就几行,但顺序别搞反:
# 1. 设置 sources.list
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
# 2. 添加密钥
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
# 3. 更新并安装完整桌面版
sudo apt update
sudo apt install ros-noetic-desktop-full
# 4. 初始化 rosdep
sudo rosdep init
rosdep update
# 5. 设置环境变量(每次开终端都要source,或者写进.bashrc)
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
roscore,如果看到类似 started core service [/rosout] 的日志,说明安装成功。我见过有人装完没启动 roscore 就以为没装好,白忙活半天。
2.2 工作空间创建 —— 你的代码「工地」
工作空间(workspace)就是你的项目根目录。我习惯叫它 catkin_ws,但你可以随意。创建流程很简单:
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash
跑完 catkin_make 后,你会看到 build 和 devel 两个文件夹自动生成。build 是编译中间文件,devel 里放着编译好的可执行文件和库。嗯,这里要注意:每次新增了包,都要重新 catkin_make 并 source,不然系统找不到你的节点。
~/.bashrc 里加一行 source ~/catkin_ws/devel/setup.bash,这样每次开终端自动加载。但如果你同时有多个工作空间,小心覆盖——我吃过这个亏,两个工作空间里都有同名包,结果调了半天才发现 source 顺序错了。
2.3 Topic 通信 —— 机器人的「群」
Topic 是 ROS 里最常用的通信方式。发布者(Publisher)往一个话题里发消息,订阅者(Subscriber)接收。就像群:你发一条消息,群里所有人都能看到。
写一个简单的发布节点和订阅节点,我带你过一遍:
// talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv) {
ros::init(argc, argv, "talker");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 10);
ros::Rate rate(10);
while (ros::ok()) {
std_msgs::String msg;
msg.data = "Hello SLAM!";
pub.publish(msg);
rate.sleep();
}
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 nh;
ros::Subscriber sub = nh.subscribe("chatter", 10, chatterCallback);
ros::spin();
return 0;
}
你想想看,Topic 通信是异步的、一对多的。适合传感器数据流,比如激光雷达点云、IMU数据。我在做多传感器融合时,经常同时订阅 /scan、/imu、/odom 三个话题,时间同步是个大坑,后面章节会细讲。
2.4 Service 通信 —— 一问一答
Service 跟 Topic 不一样,它是同步的、有请求有响应。就像你打电话问客服:「现在电量多少?」客服查一下告诉你。适合偶尔触发的操作,比如保存地图、重置位置。
定义一个 Service 文件(srv):
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
服务端代码示例:
bool add(add_two_ints::AddTwoInts::Request &req,
add_two_ints::AddTwoInts::Response &res) {
res.sum = req.a + req.b;
ROS_INFO("request: %ld + %ld", (long int)req.a, (long int)req.b);
return true;
}
2.5 Launch 文件编写 —— 一键启动全家桶
Launch 文件就是 ROS 的「一键启动脚本」。你想想,每次开多个终端、敲一堆 rosrun,多烦。Launch 文件帮你一次性启动所有节点。
一个典型的 Launch 文件长这样:
<launch>
<node name="talker" pkg="beginner_tutorials" type="talker" output="screen"/>
<node name="listener" pkg="beginner_tutorials" type="listener" output="screen"/>
</launch>
保存为 demo.launch,然后运行:
roslaunch beginner_tutorials demo.launch
Launch 文件里还能设置参数、包含其他 launch、分组命名空间。我常用的几个技巧:
- output="screen":把节点日志打印到终端,调试必备。
- respawn="true":节点挂了自动重启,野外采集时特别有用。
- args:传命令行参数,比如波特率、话题名。
$(find pkg_name) 来引用包路径。我曾经把路径写死成 /home/lihua/...,结果代码发给同事,他用户名不一样,跑不起来……尴尬。
2.6 本章小结
这一章我们走通了 ROS 的安装、工作空间创建、Topic/Service 通信,还有 Launch 文件。说白了,这些就是 ROS 的「四梁八柱」。后面每一章都会用到这些知识——传感器驱动靠 Topic 发数据,建图节点靠 Service 触发保存,多传感器融合靠 Launch 一键启动。
我个人建议你:把上面的代码亲手敲一遍,别复制粘贴。敲的过程中你会遇到各种小问题,比如包名写错、CMakeLists.txt 没配置——这些坑现在踩了,后面就顺了。