ROS 快速入门学习(一)

目录

一、前言

二、随笔记

三、内容总结

3.1 开源自由市场Github

3.2 节点与包

3.2.1 基础概念

3.2.2 创建节点

3.2.3 话题与消息

3.2.4 Publisher和Subscriber

3.2.5 launch文件

 3.3 机器人运动控制

3.3.1 实现目标

 3.3.2 实现思路

3.3.3 实现步骤

 3.4 激光雷达传感器

3.4.1 激光雷达消息包格式

 3.4.2 获取雷达数据的节点

3.4.3 激光雷达避障的节点


一、前言

参考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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值