ROS入门

ROS入门

创建工作空间与功能包

src:代码空间

build:编译空间

devel:开发空间

install:安装空间

创建工作空间

$ mkdir catkin_ws        #创建工作空间,catkin_ws为工作空间的自定义命名,可以随意改动
$ cd catkin_ws/
$ mkdir src              #创建代码空间
$ cd src/
$ catkin_init_workspace  #初始化工作空间,在src中产生.txt文件,说明创建成功

编译工作空间

$ cd ..                 #回到根目录
$ catkin_make           #catkin工具提供的编译器指令,编译src里面所有功能包的源码,结果都放在devel和install里,在根目录产生build和devel
$ catkin_make install   #产生install文件夹

创建功能包

$ cd src/                                              #一定要在src里创建功能包
$ catkin_create_pkg test_pkg roscpp rospy std_msgs     #创建功能包,test_pkg为文件名,roscpp、rospy、std_msgs为依赖包,可以根据需要添加其他,必须要有.txt和.xml这两个文件
$ cd ..                                                #回到根目录
$ catkin_make                                          #编译工作空间,此时会编译test_pkg这个功能包

设置环境变量

$ source devel/setup.bash    #设置了环境变量才能运行功能包
$ echo $ROS_PACKAGE_PATH     #检查环境变量

发布者Publisher的编程实现

· 初始化ROS节点

· 创建节点句柄

· 向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型,即创建发布者

· 创建消息数据

· 按照一定频率循环发布消息

//创建cpp文件cd 
$ learning_topic/src/touch 
$ velocity_publisher.cpp

代码:

//代码
/**
 * 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist
 */
 
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "velocity_publisher");

    // 创建节点句柄
    ros::NodeHandle n;

    // 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
    ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);

    // 设置循环的频率
    ros::Rate loop_rate(10);

    int count = 0;
    while (ros::ok())
    {
        // 初始化geometry_msgs::Twist类型的消息
        geometry_msgs::Twist vel_msg;
        vel_msg.linear.x = 0.5;
        vel_msg.angular.z = 0.2;

        // 发布消息
        turtle_vel_pub.publish(vel_msg);
        ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", 
                vel_msg.linear.x, vel_msg.angular.z);

        // 按照循环频率延时
        loop_rate.sleep();
    }

    return 0;
}

配置CMakeLists.txt

//在install上方添加
add_executable(velocity_publisher src/velocity_publisher.cpp)
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})

编译并运行

$ cd ~/catkin_ws
$ catkin_mak
$ source devel/setup.bash   //打开.bashrc,并在文件的最后添加这段话source /home/tfb/catkin_ws/devel/setup.bash就不用写source devel/setup.bash了
$ roscore
//新开一个终端,开启turtlesim
$ rosrun turtlesim turtlesim_node
//新开一个终端,运行发布者cpp程序
$ rosrun learning_topic velocity_publisher
//编译cpp生成的文件在home/catkin_ws/devel/lib/learning_topic下面

订阅者Subscriber的编程实现

· 初始化ROS节点

· 订阅需要的话题

· 循环等待话题消息,接收到消息后进入回调函数

· 在回调函数中完成消息处理

//创建cpp文件
$ cd learning_topic/src/
$ touch velocity_publisher.cpp

代码:

//代码
/**
 * 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
 */
 
#include <ros/ros.h>
#include "turtlesim/Pose.h"

// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
    // 将接收到的消息打印出来
    ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
}

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "pose_subscriber");

    // 创建节点句柄
    ros::NodeHandle n;

    // 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
    ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);

    // 循环等待回调函数
    ros::spin();

    return 0;
}

配置CMakeLists.txt

add_executable(pose_subscriber src/pose_subscriber.cpp)
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})

编译并运行

$ cd ~/catkin_ws
$ catkin_make
$ roscore
//新开一个终端,开启turtlesim
$ rosrun turtlesim turtlesim_node
//新开一个终端,运行发布者cpp程序
$ rosrun learning_topic pose_subscriber

话题消息的定义与使用

· rostopic list

列出当前系统中所有活跃着的话题

· rostopic echo 主题名称

显示指定话题中发送的消息包内容

· rostopic hz 主题名称

统计指定话题中消息包发送频率

· rqt gragh

显示各个话题关系

定义msg文件

在package.xml中添加功能包依赖

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

在CMakeList.txt添加编译选项

find_package(...... message_generation)

add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)

catkin_package(...... message_runtime)

编译生成语言相关文件

发布者代码

#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
        //节点初始化
        ros::init(argc, argv, "person_publisher");

        //创建节点句柄
        ros::NodeHandle n;

        //创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度为10
        ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);

        //设置循环的频率
        ros::Rate loop_rate(1);

        int count = 0;
        while(ros::ok())
        {
                //初始化learning_topic::Person类型的消息
                learning_topic::Person person_msg;
                person_msg.name = "wkk";
                person_msg.age = 20;
                person_msg.sex = learning_topic::Person::male;

                //发布消息
                person_info_pub.publish(person_msg);

                ROS_INFO("Publish Person Info: name:%s age:%d sex:%d", person_msg.name.c_str(), person_msg.age, person_msg.sex);


                //按照循环频率延时
                loop_rate.sleep();

        }
        return 0;
}

订阅者代码

#include <ros/ros.h>
#include "learning_topic/Person.h"

void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
        ROS_INFO("Subscribe Person Info: name:%s age:%d sex:%d", msg->name.c_str(), msg->age, msg->sex);
}

int main(int argc, char **argv)
{
        //初始化ROS节点
        ros::init(argc, argv, "per_subscriber");

        //创建节点句柄
        ros::NodeHandle n;

        //创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personCallback
        ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);

        //循环等待回调函数
        ros::spin();

        return 0;

}

配置CMakeList.txt

add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)

add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)

使用launch文件启动节点

1、使用launch文件,可以通过 roslaunch 指令一次启动多个节点

2、在launch文件中,为节点添加 output = “screen” 属性,可以让节点信息输出在终端中。(ROS_WARN不受该属性控制)

3、在launch文件中,为节点添加 ==launch-prefix = “gnone-terminal -e”==属性,可以让节点单独运行在一个独立终端中

XML语法

<标记名称 属性名1= “属性值1” …>内容</标记名称>

<大纸盒>
 <小纸盒 颜色 = “黄色”> 
  <又一个小纸盒 颜色 = “黄色”>
  </又一个小纸盒>
 </小纸盒>
</大纸盒>


或(因为没有内容)
<大纸盒>
 <小纸盒 颜色 = “黄色”/> 
  <又一个小纸盒 颜色 = “黄色”/>
</大纸盒>

编写运行launch文件

随便放在一个软件文件夹里即可

编写
<launch>
	<node pkg = "ssr_pkg", type = "yao_node", name = "yao_node"/>
	<node pkg = "ssr_pkg", type = "chao_node", name = "chao_node" launch-prefix - "gnome-terminal -e"(使用一个新的终端程序去运行这个节点)/>
	<node pkg = "atr_pkg", type = "ma_node", name = "ma_node" output = “screen”/>
</launch>
运行
$ roslaunch atr_pkg kai_hei.launch 

ROS机器人的运动控制

方向规范

矢量运动

右手呈枪型手势,指向机器人,食指为x轴(正前方),中指为y轴(正左方),拇指为z轴(正上方)

在这里插入图片描述

旋转运动

右手呈点赞手势:拇指指向任意(xyz)一轴,四指方向就是旋转正方向

在这里插入图片描述

在这里插入图片描述

速度规范

速度消息包

geometry_msgs/Twist Message : Twist就是表示速度

linear:线性的,即代表矢量运动的速度

angular:有角度的,即代表旋转运动的速度

在这里插入图片描述

矢量速度单位

米/秒

旋转速度单位

弧度/秒(Π = 3.14… = 180°)

运动控制实现

启动仿真环境
$ git clone https://https://github.com/6-robot/wpr_simulation.git  #下载开源的仿真程序进行学习,在~/catkin_ws/src/终端下载
$ ./install_for_noetic.sh                                          #在wpr_simulation/scripts终端下载依赖包
$ roslaunch wpr_simulation wpb_simple.launch 					   #启动仿真机器人
速度实现

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

#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);


    ros::Rate loop_rate(30);


    while(ros::ok())
    {

        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.5;
        vel_pub.publish(vel_msg);
        ros::spinOnce();
        loop_rate.sleep();

    }

    return 0;
}
$ roslaunch wpr_simulation wpb_simple.launch 
$ rosrun vel_pkg vel_node

激光雷达的工作原理

作用

探测周围障碍物的分布状况

分类

种类繁多,但在ROS中呈现的数据格式是一样的,只是在数据完整度和精度上会有所差异,按照多种类型分类如下:

测量维度

· 单线雷达

· 多线雷达

测量原理

· 三角测距雷达

· TOF雷达

工作方式

· 机械旋转雷达

· 固态雷达

TOF雷达

通常安装在机器人底盘隔层的中央位置,受到地盘结构的保护,机械结构有:固定底盘和可旋转的头部结构

头部结构

具有红外激光发射器和接收器,工作时发出红外激光并反射回来接收,用以探测周围的障碍物,以点的形式呈现出来,旋转一周即可完成周围环境探测

使用RViz观测传感器数据

方便我们对机器人的状态进行实时观测的辅助工具

数据可视化

1、传感器采集到的数据

2、机器人运算处理的中间结果和将要执行的目标指示

3、去往某个地点的路径规划

rviz布局

在这里插入图片描述

使用

1、把Fixed Frame参数修改成base_footprint

2、把机器人模型添加进来(Add按钮)

在这里插入图片描述

3、添加激光雷达显示条目(laserscan),并将参数设置成/scan

在这里插入图片描述

激光雷达消息包格式

在这里插入图片描述

获取雷达的测距数值

1、构建一个新的软件包,包名叫做lidar_pkg

2、在软件包中新建一个节点,节点名叫做lidar_node

3、在节点中,向ROS大管家NodeHandle申请订阅话题/scan,并设置回调函数为LidarCallback()

4、构建回调函数LidarCallback(),用来接收和处理雷达数据

5、调用ROS_INFO()显示雷达检测到的前方障碍物距离

代码

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>


void LidarCallback(const sensor_msgs::LaserScan msg)
{

    float fMidDist = msg.ranges[180];

    ROS_INFO("前方测距 range[180] = %f 米", fMidDist);

}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL, "");

    ros::init(argc, argv, "lidar_node");

    ros::NodeHandle n;

    ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);

    ros::spin();

    return 0;
}

激光雷达避障

1、让大管家NodeHandle发布速度控制话题/cmd_vel

2、构建速度控制消息包vel_cmd

3、根据激光雷达的测距数值,实时调整机器人运动速度,避开障碍物

代码

只需要将上方的测距数值进行处理,并赋予速度值即可

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
#include <geometry_msgs/Twist.h>

ros::Publisher vel_pub;

void LidarCallback(const sensor_msgs::LaserScan msg)
{

    float fMidDist = msg.ranges[180];

    ROS_INFO("前方测距 range[180] = %f 米", fMidDist);

    geometry_msgs::Twist vel_cmd;
    if(fMidDist < 1.5)
    {
        vel_cmd.angular.z = 0.3;
    }
    else
    {
        vel_cmd.linear.x = 0.05;
    }
    vel_pub.publish(vel_cmd);

}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL, "");

    ros::init(argc, argv, "lidar_node");

    ros::NodeHandle n;

    ros::Subscriber lidar_sub = n.subscribe("/scan", 10, &LidarCallback);

    vel_pub = n.advertise<geometry_msgs::Twist>("cmd_vel", 10);

    ros::spin();

    return 0;
}

IMU消息包的数据格式

IMU是安装在机器人内部的一种传感器模块,用于测量机器人的空间姿态

获取IMU数据的C++节点

1、构建一个新的软件包,包名叫做imu_pkg

2、在软件包中新建一个节点,节点包叫做imu_node

3、在节点中,向ROS大管家NodeHandle申请订阅话题/imu/data,并设置回调函数为IMUCallback()

4、构建回调函数IMUCallback(),用来接收和处理IMU数据

5、使用TF工具将四元数转换成欧拉角

6、调用ROS_INFO(),显示转换后的欧拉角数值

代码

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <tf/tf.h>

void IMUCallback(sensor_msgs::Imu msg)
{

    if(msg.orientation_covariance[0] < 0)       #判断四元数orientation的协方差矩阵的第一个数为-1,就四元数不存在了
        return;
    tf::Quaternion quaternion(                  #通过TF工具将消息包里的四元数转换为tf的四元数对象

        msg.orientation.x,
        msg.orientation.y,
        msg.orientation.z,
        msg.orientation.w

    );

    double roll, pitch, yaw;
    tf::Matrix3x3(quaternion).getRPY(roll, pitch, yaw);   #将quaternion转换成一个tf的3x3矩阵对象,再调用矩阵对象的getRPY函数将其转换成欧拉角
    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, "imu_node");

    ros::NodeHandle n;

    ros::Subscriber imu_sub = n.subscribe ("/imu/data", 10, IMUCallback);

    ros::spin();    #让程序一直运作不要停止

    return 0;
}

IMU航向锁定的节点

1、让大管家NodeHandle发布速度控制话题/cmd_vel

2、设定一个目标朝向角,当姿态信息中的朝向角和目标朝向角不一致时,控制机器人转向目标朝向角

代码

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <tf/tf.h>


ros::Publisher vel_pub;

void IMUCallback(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);

    double target_yaw = 90;                     #目标朝向值
    double diff_angle = target_yaw - yaw;       #朝向值与目标朝向值差值
    geometry_msgs::Twist vel_cmd;               
    vel_cmd.angular.z = diff_angle * 0.01;      #给机器人一个旋转的速度直至旋转到目标值
    vel_pub.publish(vel_cmd);                   #发布速度的值

}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL, "");
    
    ros::init(argc, argv, "imu_node");

    ros::NodeHandle n;

    ros::Subscriber imu_sub = n.subscribe ("/imu/data", 10, IMUCallback);

    vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);

    ros::spin();

    return 0;
}

标准消息包std_msgs

在这里插入图片描述

常用消息包common_msgs

在这里插入图片描述

几何包geometry_msgs

在这里插入图片描述

传感器消息包sensor_msgs

在这里插入图片描述

自定义消息类型

1、创建新软件包,依赖项message_generation、message_runtime

2、软件包添加msg目录,新建自定义消息文件,以.msg结尾

3、在CMakeList.txt中,将新建的.msg文件加入add_message_files()

4、在CMakeList.txt中,去掉generation_mseeage()注释符号,将依赖性的其他消息包名称添加进去

5、在CMakeList.txt中,将message_runtimt加入catkin_package()的CATKIN_DEPENDS

6、在packagexml中,将message_generation、message_runtime加入<build_depend>和<exec_depend>

7、编译软件包,生成新的自定义消息类型

数据类型 变量名
string data
int name    .....
============================
数据类型
①bool、byte、char
②int8、int16、int32、int64、uint8、uint16、uint32、uint64
③float32、float64 
④string
⑤time、duration
⑥可变长度数组array[]
⑦固定长度数组array[n]
⑧其他软件包定义的消息类型

新消息类型在C++节点的应用

1、在节点代码中,先include新消息类型的头文件

2、在发布或订阅话题的时候,将话题中的消息类型设置为新的消息类型。

3、按照新的消息结构,对消息包进行赋值发送或读取解析

4、在CMakeList.txt文件的find_package0)中,添加新消息包名称作为依赖项

5、在节点的编译规则中,添加一条add dependencies0),将新消息软件包名称_generate_messages_cpp作为依赖项

6、在package.xml中,将新消息包添加到<build_depend>和<exec_depend>中去

7、运行 catkin_make 重新编译

ROS中的栅格地图格式

这种地图会将画面转化为方格,默认为白色,当激光雷达扫射到障碍物时,会将方格变为黑色,栅格的尺寸越小,黑色的区域划分就越精细,也就越接近障碍物的轮廓,但此时地图数据量会越大,计算量也就越大,所以要给栅格设置一个适当的尺寸

栅格尺寸:

体现了地图的精细程度,常常被用来表示栅格地图的分辨率,ros中默认分辨率为0.05米

OccupancyGrid数据类型:将栅格的白色赋予0,黑色赋予100,然后根据行和列组成一个数组

在这里插入图片描述

发布地图代码

#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>

int main(int argc, char *argv[])
{

	ros::init(argc, argv, "map_pub_node");
	
	ros::NodeHandle n;
	
	ros::Publisher pub = n.advertise<nav_msgs::OccupancyGrid>("/map", 10);
	
	ros::Rate r(1);
	
	while(ros::ok())
	{
	
		nav_msgs::OccuopancyGrid msg;
		
		msg.header.frame_id = "map";
		
		msg.header.stamp = ros::Time::now()
		
		msg.info.origin.position x = 0;
		msg.info.origin.position y = 0;    #这个是地图相对与世界原点的位置
		
		msg.info.resolution = 1.0;
		
		msg.info.width = 4;
		msg.info.length = 2;
		
		msg.data.resize(4*2);
		msg.data[0] = 100;
		msg.data[1] = 100;
		msg.data[2] = 0 ;
		msg.data[3] = -1;
		
		pub.publish(msg);
		
		r.sleep();
	}
	
	return 0;

}

在地图上显示栅格

$ rosrun map_pkg map_pub_node
$ rviz

①确定世界坐标系的原点位置,添加一个坐标轴标识(Axes)

②添加地图显示(map),并将地图消息的话题名称设置为实验节点发布的/map

发布地图代码

#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>

int main(int argc, char *argv[])
{

	ros::init(argc, argv, "map_pub_node");
	
	ros::NodeHandle n;
	
	ros::Publisher pub = n.advertise<nav_msgs::OccupancyGrid>("/map", 10);
	
	ros::Rate r(1);
	
	while(ros::ok())
	{
	
		nav_msgs::OccuopancyGrid msg;
		
		msg.header.frame_id = "map";
		
		msg.header.stamp = ros::Time::now()
		
		msg.info.origin.position x = 0;
		msg.info.origin.position y = 0;    #这个是地图相对与世界原点的位置
		
		msg.info.resolution = 1.0;
		
		msg.info.width = 4;
		msg.info.length = 2;
		
		msg.data.resize(4*2);
		msg.data[0] = 100;
		msg.data[1] = 100;
		msg.data[2] = 0 ;
		msg.data[3] = -1;
		
		pub.publish(msg);
		
		r.sleep();
	}
	
	return 0;

}

在地图上显示栅格

$ rosrun map_pkg map_pub_node
$ rviz

①确定世界坐标系的原点位置,添加一个坐标轴标识(Axes)

②添加地图显示(map),并将地图消息的话题名称设置为实验节点发布的/map

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值