目录
一、前言
参考B站机器人工匠阿杰的ROS快速入门学习教程
配置:虚拟机Ubuntu20.04,ROS Noetic。
机器人工匠阿杰的个人空间-机器人工匠阿杰个人主页-哔哩哔哩视频
二、随笔记
<1> 运行ROS前需在终端初始化环境
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
<2> Rosdep初始化,方便以后安装第三方扩展软件包
sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential
sudo rosdep init
rosdep update
在执行rosdep update时可能会报错time out,此时可尝试执行以下三条指令:
sudo apt-get install python3-pip
sudo pip3 install 6-rosdep
sudo 6-rosdep
然后再重新执行
sudo rosdep init
rosdep update
<3> 话题的相关操作指令
rostopic list #查看话题列表
rostopic ehco <话题名称> #查看话题内容
ehco -e <话题内容> #将Unicode编码的话题内容转换为正常
rostopic hz <话题名称> #话题的发布频率
三、内容总结
3.1 开源自由市场Github
<1> 克隆下面的源码到自己的工作空间中的src目录下,然后在其中的scripts文件中,执行./install_for_noetic.sh安装各项依赖。
6-robot/wpr_simulationhttps://github.com/6-robot/wpr_simulation
<2> 回到工作空间目录下,执行catkin_make对src文件下的源码进行编译。
<3> 使用source指令载入工作空间的环境设置
source ~/catkin_ws/devel/setup.bash
<4> 使用roslaunch运行编译好的ROS程序
roslaunch wpr_simulation wpb_simple.launch
<5> 新打开一个终端,执行运动控制指令
rosrun rqt_robot_steering rqt_robot_steering
<6> 将设置工作空间环境参数的指令添加到终端程序初始化脚本中,之后打开终端可不再执行第三步。
gedit ~/.bashrc
在最后新添一行加上:
source ~/catkin_ws/devel/setup.sh
3.2 节点与包
3.2.1 基础概念
在 ROS中,节点(Node) 是执行具体任务的最小进程单元,可视为机器人系统中的 “功能模块”,例如传感器驱动、算法处理或执行器控制等。每个节点独立运行并通过 ROS 通信机制(话题、服务等)与其他节点交换数据。包(Package) 是 ROS 的软件组织单位,是节点、配置文件、库文件等的集合体,用于封装特定功能或项目组件。包提供了节点运行所需的环境和依赖管理,确保节点可复用和可分发。
关系上,节点是包的运行实体,必须存在于某个包中;而包是节点的载体,为节点提供组织框架和执行上下文。一个包可包含多个协同工作的节点,节点的完整名称通常由其所在包名和节点名共同确定(如package_name/node_name)。
3.2.2 创建节点
<1> 创建package软件包
catkin_create_pkg ssr_pkg rospy roscpp std_msgs
①创建在 ~/ catkin_ws / src 目录下
②catkin_create_pkg <包名> <依赖项>
<2>在chao_node文件夹下的src目录中创建chao_node.cpp文件,并编写
#include <ros/ros.h>
int main(int argc, char *argv[]){
ros::init(argc,argv,"chao_node");
printf("Wakakan");
while(ros::ok()){
printf("jixu\n");
}
return 0;
}
在编译之前,需要在CmakeLists.txt文件为该节点添加编译规则,在文件最后加上
add_executable(chao_node src/chao_node.cpp)
<3>在进行编译时可能会报错找不到 init 这个函数,此时需在CmakeLists.txt文件中加入
target_link_libraries(chao_node
${catkin_LIBRARIES})
来将编译生成的可执行文件chao_node与 ROS 的相关库文件进行链接。
<4> 打开终端,执行 roscore,再打开一个新的终端,执行
rosrun ssr_pkg chao_node
3.2.3 话题与消息
在 ROS(机器人操作系统)中,话题(Topic) 是一种异步通信机制,用于节点(Node)之间传递数据的 “通道”,每个话题都有特定的主题(如传感器数据、控制指令等),遵循 “发布 - 订阅” 模式 —— 一个或多个发布者(Publisher)向话题发送数据,一个或多个订阅者(Subscriber)从话题接收数据,实现节点间的解耦通信。消息(Message) 则是话题中传递的数据的 “格式定义”,它规定了数据的结构(如整数、浮点数、数组等,甚至嵌套其他消息),类似编程语言中的结构体,确保发布者和订阅者对数据的解析方式一致。
二者的关系是:话题是消息传输的载体,消息是话题中流动的数据内容,一个话题必须绑定一种消息类型,发布者和订阅者通过约定同一话题和对应的消息类型,才能实现正确的数据交互。
3.2.4 Publisher和Subscriber
<1> 话题发布:对chao_node.cpp文件进行编辑
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char *argv[]){
ros::init(argc,argv,"chao_node");
printf("I_am_the_best_Machao");
ros::NodeHandle nh;
//构建消息发布对象 nh.advertise<std_msgs::String>("话题名称",缓存长度)
ros::Publisher pub = nh.advertise<std_msgs::String>("The_game_is_going_on",10);
//控制消息发送的频率
ros::Rate loop_rate(10);
//持续发送消息,终端按 ctrl+c 时停止
while(ros::ok()){
printf("I_am_sending_messages\n");
std_msgs::String msg;
msg.data = "I_will_carry_you";
pub.publish(msg);
loop_rate.sleep();
}
return 0;
}
<2> 话题订阅:创建yao_pkg软件包,编写yao_node.cpp文件(编译前在CmakeLists.txt中增加编译相关要求)
#include <ros/ros.h>
#include <std_msgs/String.h>
void chao_callback(std_msgs::String msg)
{
//输出时间戳信息以及消息内容,.c_str()是将data的信息定为字符串
ROS_INFO(msg.data.c_str());
}
int main(int argc, char *argv[])
{
//汉化消息内容
setlocale(LC_ALL,"");
ros::init(argc, argv, "yao_node");
ros::NodeHandle nh;
//订阅话题:nh.subscribe("话题名称", 缓存长度, 回调函数)
ros::Subscriber sub = nh.subscribe("The_game_is_going_on", 10, chao_callback);
while (ros::ok())
{
//重点一定要有,回头查阅后面的消息,持续接收消息
ros::spinOnce();
}
return 0;
}
<3> 打开一个终端运行roscore,另一个终端发布话题:rosrun ssr_node chao_node,另一个终端订阅话题并打印消息内容:rosrun yao_pkg yao_node。
<4> 保持运行,新打开终端执行 rqt_graph,查看话题订阅发布情况。
3.2.5 launch文件
上面在打开节点时需要不停的打开新的终端,比较麻烦,现将节点启动信息编写在launch文件中,通过终端运行launch文件一次性打开多个节点。
<1> 在ssr_pkg文件目录下创建kai_hei.launch文件(可创建在任何目录下),编写如下内容
<launch>
<node pkg = "ssr_pkg" type = "chao_node" name = "chao_node"/>
<node pkg = "yao_pkg" type = "yao_node" name = "yao_node" output = "screen"/>
</launch>
<2> 终端执行
roslaunch ssr_pkg kai_hei.launch
3.3 机器人运动控制
3.3.1 实现目标
构建一个速度控制节点,将速度消息包发送到速度控制话题/cmd_vel/中。
3.3.2 实现思路
1.构建一个新的软件包,包名叫做 vel_pkg。
2.在软件包中新建一个节点,节点名叫做 vel_node。
3.在节点中,向 ROS 大管家 NodeHandle 申请发布话题 /cmd_vel,并拿到发布对象 vel_pub。
4.构建一个 geometry_msgs/Twist 类型的消息包 vel_msg,用来承载要发送的速度值。
5.开启一个 while 循环,不停的使用 vel_pub 对象发送速度消息包 vel_msg。
3.3.3 实现步骤
<1> 创建vel_pkg软件包
catkin_create_pkg vel_pkg rospy roscpp geometry_msgs
<2> 在src目录下创建vel_node.cpp文件并编写
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
int main(int argc, char *argv[])
{
ros::init(argc, argv, "vel_node");
ros::NodeHandle n;
ros::Publisher vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.1;
vel_msg.linear.y = 0;
vel_msg.linear.z = 0;
vel_msg.angular.x = 0;
vel_msg.angular.y = 0;
vel_msg.angular.z = 0;
// 添加循环以持续发布消息,按需求补充的常见逻辑
ros::Rate loop_rate(30); // 设置发布频率为30Hz
while (ros::ok())
{
vel_pub.publish(vel_msg);
loop_rate.sleep();
}
return 0;
}
<3> CmakeLists.txt中设置编译规则
<4>一个终端中运行仿真环境
roslaunch wpr_simulation wpb_simple.launch
打开仿真环境时如果有报错,尝试执行。
killall gzserver
另一个终端中运行vel_node节点。
rosrun vel_pkg vel_node
<4> 查看当前话题发布订阅情况
3.4 激光雷达传感器
3.4.1 激光雷达消息包格式
激光雷达消息包的主要内容如下
float32 angle_min # 扫描的起始角度 [弧度]
float32 angle_max # 扫描的结束角度 [弧度]
float32 angle_increment # 测量点之间的角度间隔 [弧度]
float32 time_increment # 测量点之间的时间间隔 [秒] - 如果扫描仪在移动
# 此参数用于插值计算3D点的位置
float32 scan_time # 完整扫描的时间间隔 [秒]
float32 range_min # 最小检测范围 [米]
float32 range_max # 最大检测范围 [米]
float32[] ranges # 距离数据 [米]
# (注意: 小于range_min或大于range_max的值应被丢弃)
float32[] intensities # 强度数据 [设备特定单位]。如果设备不提供强度信息
# 请将此数组留空
打开仿真环境,新打开终端执行
rostopic echo /scan --noarr
输出的雷达数据如下所示
3.4.2 获取雷达数据的节点
<1> 整体流程
<2> 实现流程
1.构建一个新的软件包,包名叫做 lidar_pkg。
2.在软件包中新建一个节点,节点名叫做 lidar_node。
3.在节点中,向 NodeHandle 申请订阅话题 /scan,并设置回调函数为 LidarCallback ()。
4.构建回调函数 LidarCallback (),用来接收和处理雷达数据。
5.调用 ROS_INFO () 显示雷达检测到的前方障碍物距离。
<3> 实现步骤
编写lidar_node.cpp节点文件如下
// 包含ROS核心库头文件
#include <ros/ros.h>
// 包含激光雷达扫描数据消息类型头文件
#include <sensor_msgs/LaserScan.h>
/**
* 激光雷达数据回调函数
* 当接收到/scan话题的消息时自动调用
* @param msg 接收到的激光雷达扫描消息
*/
void LidarCallback(const sensor_msgs::LaserScan msg)
{
// 获取激光雷达正前方的测量距离(索引180通常代表正前方)
float fMidDist = msg.ranges[180];
// 使用ROS_INFO输出日志信息,显示前方障碍物距离
ROS_INFO("前方测距 ranges[180] = %f 米", fMidDist);
}
/**
* 主函数 - ROS节点入口
* @param argc 命令行参数数量
* @param argv 命令行参数字符串数组
* @return 程序退出状态码
*/
int main(int argc, char *argv[])
{
// 设置本地化环境,确保中文日志正常显示
setlocale(LC_ALL,"");
// 初始化ROS节点,节点名称为"lidar_node"
ros::init(argc, argv, "lidar_node");
// 创建ROS节点句柄,用于与ROS系统通信
ros::NodeHandle n;
// 创建一个订阅者,订阅"/scan"话题,队列大小为10
// 当收到消息时调用LidarCallback函数
ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);
// 进入ROS事件循环,保持程序运行并处理回调
ros::spin();
// 程序正常退出,返回0
return 0;
}
修改该节点文件的编译规则;编译;运行仿真环境,然后运行该节点。
3.4.3 激光雷达避障的节点
<1> 整体流程
<2> 实现流程
1.让大管家 NodeHandle 发布速度控制话题 /cmd_vel 。
2.构建速度控制消息包 vel_cmd。
3.根据激光雷达的测距数值,实时调整机器人运动速度,避开障碍物。
<3> 实现步骤
对lidar_node.cpp节点文件进行修改
// 包含ROS核心库头文件
#include <ros/ros.h>
// 包含激光雷达扫描数据消息类型头文件
#include <sensor_msgs/LaserScan.h>
// 包含机器人运动控制消息类型头文件
#include <geometry_msgs/Twist.h>
// 声明全局的速度发布者对象,用于发布速度控制指令
ros::Publisher vel_pub;
/**
* 激光雷达数据回调函数
* 当接收到/scan话题的消息时自动调用
* @param msg 接收到的激光雷达扫描消息
*/
void LidarCallback(const sensor_msgs::LaserScan msg)
{
// 获取激光雷达正前方的测量距离(索引180通常代表正前方)
float fMidDist = msg.ranges[180];
// 输出日志信息,显示前方障碍物距离
ROS_INFO("前方测距 ranges[180] = %f 米", fMidDist);
// 创建速度控制消息对象
geometry_msgs::Twist vel_cmd;
// 避障逻辑:如果前方距离小于1.5米,执行旋转避障
if(fMidDist < 1.5)
{
// 设置角速度(绕Z轴旋转),单位:弧度/秒
vel_cmd.angular.z = 0.3;
}
// 否则,执行前进运动
else
{
// 设置线速度(沿X轴前进),单位:米/秒
vel_cmd.linear.x = 0.05;
}
// 发布速度控制消息到/cmd_vel话题
vel_pub.publish(vel_cmd);
}
// 主函数 - ROS节点入口
int main(int argc, char** argv)
{
// 设置本地化环境,确保中文日志正常显示
setlocale(LC_ALL,"");
// 初始化ROS节点,节点名称为"lidar_node"
ros::init(argc,argv,"lidar_node");
// 创建ROS节点句柄,用于与ROS系统通信
ros::NodeHandle n;
// 创建一个订阅者,订阅"/scan"话题,队列大小为10
// 当收到消息时调用LidarCallback函数
ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);
// 创建一个发布者,发布geometry_msgs::Twist类型的消息到"/cmd_vel"话题
// 队列大小为10,表示最多缓存10条消息
vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
// 进入ROS事件循环,保持程序运行并处理回调
// 当接收到新的/scan消息时,会调用LidarCallback函数
ros::spin();
// 程序正常退出,返回0
return 0;
}
3.5 IMU惯性测量单元
3.5.1 IMU消息数据格式
# 这是一条用于存储惯性测量单元(IMU)数据的消息
# 加速度的单位应为米每秒平方(m/s²,而非重力加速度g),旋转角速度的单位应为弧度每秒(rad/sec)
# 如果已知测量值的协方差,应将其填写完整(例如,若仅知道每个测量值的方差,如从数据手册中获取,只需将这些方差值填入对角线即可)
# 如果协方差矩阵全为零,则表示"协方差未知",此时若要使用该数据,需通过其他途径假设或获取协方差
# 若对某一数据元素没有估计值(例如,你的IMU不生成姿态估计),请将相关协方差矩阵的第0个元素设为-1
# 若要解析此消息,请检查每个协方差矩阵的第0个元素是否为-1,若是,则忽略对应的估计值
std_msgs/Header header
geometry_msgs/Quaternion orientation #四元数姿态
float64[9] orientation_covariance
geometry_msgs/Vector3 angular_velocity #角速度
float64[9] angular_velocity_covariance
geometry_msgs/Vector3 linear_acceleration #线加速度
float64[9] linear_acceleration_covariance
3.5.2 IMU发布的话题名称
1. imu/data_raw (sensor_msgs/Imu)
-
含义:加速度计输出的矢量加速度和陀螺仪输出的旋转角速度。
2. imu/data (sensor_msgs/Imu)
-
含义:/imu/data_raw 的数据再加上融合后的四元数姿态描述。
3. imu/mag (sensor_msgs/MagneticField)
-
含义:磁强计输出磁强数据。
3.5.3 获取IMU数据的节点
(1)整体流程
(2)实现步骤
1. 构建软件包:创建一个名为 imu_pkg 的新 ROS 软件包。
2. 新建节点:在 imu_pkg 软件包中,创建名为 imu_node 的节点。
3. 订阅话题:在 imu_node 节点里,通过 ROS 的 NodeHandle 订阅 /imu/data 话题,指定回调函数为 IMUCallback(),用于接收话题消息。
4. 编写回调函数:实现 IMUCallback() 回调函数,功能是接收并处理从 /imu/data 话题传来的 IMU 数据。
5. 四元数转欧拉角:借助 TF 工具(ROS 中用于坐标变换的功能包),把 IMU 数据里的四元数姿态信息转换成欧拉角(滚转角、俯仰角、偏航角 )。
6. 打印欧拉角:通过 ROS_INFO() 函数,在终端输出转换得到的欧拉角数值,方便查看结果。
(3)imu_node 代码内容
#include "ros/ros.h"
#include "sensor_msgs/Imu.h"
#include "tf/tf.h"
// IMU 回调函数
void IMUCallback(const sensor_msgs::Imu msg)
{
// 检测消息包中四元数数据是否存在
if(msg.orientation_covariance[0] < 0)
return;
// 四元数转成欧拉角
tf::Quaternion quaternion(
msg.orientation.x,
msg.orientation.y,
msg.orientation.z,
msg.orientation.w
);
double roll, pitch, yaw;
tf::Matrix3x3(quaternion).getRPY(roll, pitch, yaw);
// 弧度换算成角度
roll = roll*180/M_PI;
pitch = pitch*180/M_PI;
yaw = yaw*180/M_PI;
ROS_INFO("滚转= %.0f 俯仰= %.0f 朝向= %.0f", roll, pitch, yaw);
}
int main(int argc, char **argv)
{
setlocale(LC_ALL, "");
ros::init(argc,argv, "demo_imu_data");
ros::NodeHandle n;
// 订阅 IMU 的数据话题
ros::Subscriber sub = n.subscribe("imu/data", 100, IMUCallback);
ros::spin();
return 0;
}
3.5.4 IMU航向锁定节点
(1)整体流程
(2)实现步骤
1. 发布速度控制话题:使用 ROS 中的 NodeHandle(可理解为 “大管家” ,用于管理节点与 ROS 系统交互),发布速度控制话题 /cmd_vel,该话题一般用于给机器人发送运动速度指令 。
2. 实现朝向控制逻辑:先设定一个目标朝向角;然后持续获取机器人当前姿态信息里的朝向角,将其与目标朝向角对比,当二者不一致时,通过向 /cmd_vel 话题发送合适指令,控制机器人转向,使实际朝向角趋近于目标朝向角,实现定向控制 。
#include "ros/ros.h"
#include "sensor_msgs/Imu.h"
#include "tf/tf.h"
#include "geometry_msgs/Twist.h"
// 速度消息发布对象(全局变量)
ros::Publisher vel_pub;
// IMU 回调函数
void IMUCallback(const sensor_msgs::Imu msg)
{
// 检测消息包中四元数数据是否存在
if(msg.orientation_covariance[0] < 0)
return;
// 四元数转成欧拉角
tf::Quaternion quaternion(
msg.orientation.x,
msg.orientation.y,
msg.orientation.z,
msg.orientation.w
);
double roll, pitch, yaw;
tf::Matrix3x3(quaternion).getRPY(roll, pitch, yaw);
// 弧度换算成角度
roll = roll*180/M_PI;
pitch = pitch*180/M_PI;
yaw = yaw*180/M_PI;
ROS_INFO("滚转= %.0f 俯仰= %.0f 朝向= %.0f", roll, pitch, yaw);
// 速度消息包
geometry_msgs::Twist vel_cmd;
// 目标朝向角
double target_yaw = 90;
// 计算速度
double diff_angle = target_yaw - yaw;
vel_cmd.angular.z = diff_angle * 0.01;
vel_cmd.linear.x = 0.1;
vel_pub.publish(vel_cmd);
}
int main(int argc, char **argv)
{
setlocale(LC_ALL, "");
ros::init(argc,argv, "demo_imu_behavior");
ros::NodeHandle n;
// 订阅 IMU 的数据话题
ros::Subscriber sub = n.subscribe("imu/data", 100, IMUCallback);
// 发布速度控制话题
vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel",10);
ros::spin();
return 0;
}