ROS是什么?
- 与手机作类比:Android vs ios
同理,机器人在问世之初也是各种各样的操作系统,所以需要统一,morgen在实验室的研发过程中想到,将每个机器人程序拆分成独立的模块、每个传感器对应一个特定的程序模块、每个执行器对应独立的程序模块,每个算法也可以对应每个程序模块。
(分布式系统设计:每个模块可以在不同的计算机上使用,大大提高了效率):
与现在的ros相比,每一个launch文件对应整个程序,每个node就是不同的程序块。
Android(中的app)和ROS(中的node)两者都只需与单片机之间想成串口通讯即可。只需要建立好数据连接,完善好数据转发就可以完成ROS控制单片机。
为什么要用ROS?
如何学习ROS
- 安装ROS
- 实践操作(仿真平台学习)
(source指令,echo将一连串的指令加载在.sh文件里面)
(.bashrc终端执行文件,在执行其他文件之前都会执行该文件)
ROS应用商店:APT源
index.ros.org(ROS的资源库软件)
只有闪电标的才可以安装
开源自由市场:github
sudo apt-get install git
下载的全是源代码,需要编译。
- 建立工作空间
mkdir catkin_ws
cd catkin_ws
mkdir src
- get clone 源代码
- 安装依赖库
- 完成编译
- source一下
gedit ~/.bashrc
source ~/catkin_ws/devel/setup.bash
- 运行程序
roslaunch 功能包 .launch文件
为了方便
gedit ~/.bashrc
source ~/catkin_ws/devel/setup.bash
Node节点和Package包
假设实现一个超声波传感器的节点——超声比_Node
设置超声波节点的功能包为ssr_pkg
创建在~/catkin_ws/src 文件夹里: catkin_create_pkg <包名><依赖项列表>
catkin_create_pkg ssr_pkg rospy roscpp std_msgs
*依赖项:每个软件包通用的部分
*VScode快捷键:ctrl+s=保存文件;ctrl+shift+b=编译。
- 在软件包的src文件夹下创建一个节点的cpp源码文件
- 在节点的源码文件中include包含ROS的头文件
- 构建一个main函数,并在函数的开头执行ros::init()
- 构建while函数,循环条件设置为ros::ok()
- 在CmakeLists.txt中设置节点源码的编译规则
- 编译运行
#include <ros/ros.h>
int main(int argc, char *argv[ ])
{
ros::init(argc,argv,"chao_node");
printf("baby\n");
while (ros::ok())
{
printf("over\n");
}
return 0;
}
add_executable(chao_node src/chao_node.cpp)
target_link_libraries(chao_node
${catkin_LIBRARIES}
)
Topic话题和Message消息(节点Node通讯)
两个节点情况
- 话题Topic是节点间进行持续通讯(刷屏)的一种形式;
- 话题通讯的两个节点通过话题的名称(开黑群名称)建立起话题通讯连接;
- 话题中通讯的数据,叫做消息Message;
- 消息Message通常会按照一定的频率持续不断的发送,以保证消息数据的实时性;
- 消息的发送方叫做话题的发布者publisher;
- 接受方叫做话题的订阅者subsciber。
外延补充 - 一个ROS节点网络中,可以同时存在多个话题;
- 一个话题可以有多个发布者,也可以有多个订阅者;
- 一个节点可以对多个话题进行订阅,也可以发布多个话题;
- 不同的传感器消息通常会拥有各自独立话题名称,每个话题只能有一个发布者;
- 机器人速度指令话题通常会有多个发布者,但是同一时间内只能有一个发言人。
C++发布者
- 确定话题名称和消息类型;
- 在代码文件中include消息类型对应的头文件;
- 在main函数中通过NodeHandeler大管家发布一个话题并得到消息发送对象;
- 声称要发送的消息报并进行发送数据的赋值;
- 调用消息对象的publish()函数将消息包发送到话题当中。
常用工具
rostopic list
列出当前系统中所有活跃着的话题;
rostopic echo 主题名词
显示指定话题中发送消息包内容。
rostopic hz 主题名称
统计指定话题中消息包发送频率
C++订阅者
- 创建订阅者节点
回调函数:对消息包到来的一个响应,类似单片机的中断函数,每当新的消息宝到达订阅者节点,ros后台就会调用这个回调函数,只需在函数里对接受到的消息进行处理即可。
话题的订阅
- 确定话题名称和消息类型;
- 在代码文件中include<ros.h>和消息类型对应的头文件;
- 在main函数中通过NodeHandler大管家订阅一个话题并设置消息接受回调函数;
- 定义一个回调函数,对接收到的消息包进行处理;
- main函数中需要执行ros::spinOnce(),让回调函数能够响应接受到的消息包。
#include <ros/ros.h>
#include <std_msgs/String.h>
void chao_callback(std_msgs::String msg)
{
ROS_INFO(msg.data.c_str());//c_str()将string类型转换为指针类型之后才能printf
// printf("\n");
}
void yao_callback(std_msgs::String msg)
{
ROS_WARN(msg.data.c_str());
// printf(msg.data.c_str());//c_str()将string类型转换为指针类型之后才能printf
// printf("\n");
}
int main(int argc, char *argv[ ])
{
setlocale(LC_ALL,"");//能够显示中文
ros::init(argc,argv,"ma_node");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("hurryup",10,chao_callback);//注意此时订阅的话题名称
ros::Subscriber sub_2 = nh.subscribe("help",10,yao_callback);//注意此时订阅的话题名称
while (ros::ok())
{
ros::spinOnce();//查看新消息包是否到来
}
return 0;
}
launch文件启动多个node
xml语法:
<标记名称 属性名1=“属性值1”…>内容</标记名称>
小盒子里面空时
小结
- 使用launch文件,可以通过roslaunch指令一次启动多个节点
- 在launch文件中,为节点添加output=“screen”属性,可以让节点信息输出在终端中。(ROS_WARN不受该属性控制)
- 在launch文件中,为节点添加launch-prefix="gnome-terminal -e"属性,可以让节点单独运行在一个独立终端中,且停止后topic不再显示;
…………
<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>
ROS机器人运动控制
矢量速度:m/s
旋转速度:rad/s
C++机器人运动控制
实现思路
- 构建一个新的软件包,包名叫做vel_pkg
- 在软件包中新建一个节点,节点名叫做vel_node
- 在节点中,向ROS大管家NodeHandle申请发布话题/cmd_vel,并拿到发布对象vel_pub
- 构建一个geometry_msgs/Twist类型的消息包vel_msg,用来承载要发送的速度值。
- 开启一个while循环,不停的使用vel_pub对象发送速度消息包vel_msg
激光雷达工作原理
接着不停的转动角度,发射接收
在ROS中,使用RViz观测传感器数据
激光雷达消息包格式
ranges:“<array type :float32 ,length :360>”
intensities:“<array type : float32 ,length : 360>”
在ROS中,用C++实现获取激光雷达数据
实现步骤:
- 构建一个新的软件包,包名叫做lidar_pkg
- 在软件包中建立一个新的节点,节点名叫做lidar_node
- 在节点中,向ROS大管家NodeHandle申请订阅话题/scan,并设置回调函数为LidarCallback()
- 构建回调函数LidarCallback(),用来接受和处理雷达数据
- 调用ROS_INFO()显示雷达检测到的前方障碍物距离
在ROS中,用C++实现激光雷达避障
实现步骤:
- 让大管家NodeHandle发布速度控制话题/cmd_vel
- 构建速度控制消息包vel_cmd
- 根据激光雷达的测距数值,实时调整机器人运动速度,避开障碍物
ROS中的IMU惯性测量单元信号包
IMU:安装与机器人内部用于测量机器人的空间姿态
用C++实现IMU数据获取
话题名称
实现步骤:
- 构建一个新的软件包,imu_pkg
- 在软件包中新建一个节点,节点叫做imu_node
- 在节点中,向ROS大管家NodeHandle申请订阅话题/imu/data,并设置回调函数为IMUCallback()
- 构建回调函数IMUCallback(),用来接收和处理IMU数据
- 使用TF工具将四元数转换为欧拉角
- 调用ROS_INFO()显示转换后的欧拉角数值
用C++实现IMU航向锁定
实现步骤
- 让大管家NodeHandle发布速度控制话题/cmd_vel
- 设定一个目标朝向角,当姿态信息中的朝向角和目标朝向角不一致时,控制机器人转向目标朝向角
ROS的标准消息包std_msgs
![在这里插入图片描述](https://img-blog.csdnimg.cn/c804f62577bb4af68d6025e75d357c20.png## ROS中的常规消息包
自定义消息包
生成自定义消息的步骤
- 创建新软件包,依赖项message_generation、meggage_runtime
- 软件包添加msg目录,新建自定义消息文件,以.msg结尾
- 在CMakeList.txt中,将新建的.msg文件加入add_message_files()
- 在CMakeLists.txt中,去掉generate_messages()注释符号,将依赖的其他消息包名称添加进去
- 在CMakeLists.txt中,将message_runtime加入catkin_package()的CATKIN——DEPENDS
- 在package.xml中,将message_generation、message_runtime加入<build_depend>和<exec_depend>。
- 编译软件包,,生成新的自定义消息类型
自定义消息类型在C++节点的应用
- 在节点代码中,先include新消息类型的头文件
- 在发布或订阅话题的时候,将话题中的消息类型设置为新的消息类型
- 按照新的消息结构,对消息包进行赋值发送或读取解析
- 在CMakeList.txt文件的find_package()中,添加新消息包称作为依赖项
- 在节点的编译规则中,添加一条add_dependencies(),将新消息软件包名称_generatr_messages_cpp作为依赖项
- 在package.xml中,将新的消息包添加到<build_depend>和<exec_depend>中去
- 运行catkin_make重新编译
ROS中的栅格地图格式
占据栅格:
C++节点发布地图
实现步骤
- 构建一个软件包map_pkg,依赖项里加上nav_msgs
- 在map_pkg里创建一个节点map_pub_node
- 在节点中发布话题/map,消息类型为nav_msgs::OccupancyGrid
- 构建一个nav_msgs::OccupancyGrid消息包,并对其进行赋值
- 将地图消息包发送到话题/map
- 编译并运行节点
- 启动rviz,订阅话题/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) ; //发布话题/map
ros::Rate r(1); //设置消息包发送频率
while (ros::ok) //调用while函数不停发送数值
{
nav_msgs::OccupancyGrid msg;//构建一个nav_msg::OccupancyGrid地图消息包,并对其进行赋值
msg.header.frame_id = "map";//坐标系id设置为map
msg.header.stamp = ros::Time::now();//时间戳设置为当前时间
msg.info.origin.position.x = 0;//地图描述信息——与地图原点x,y都为0
msg.info.origin.position.y = 0;
msg.info.resolution = 1.0;//地图的分辨率
msg.info.width = 4;//地图的宽度4个单元格
msg.info.height = 2;//地图的高度2个单元格
msg.data.resize(4*2);//地图的数据——栅格大小4*2
msg.data[0] = 100;//第一位数组赋值
msg.data[1] = 100;//数组赋值
msg.data[2] = 0;//数组赋值
msg.data[3] =(-1);//数组赋值
pub.publish(msg);//将地图消息包发送到话题/map
r.sleep();//控制发送频率;
}
return 0;
}