接触gazebo也差不多有一年之久了,当时使用gazebo是因为比赛的时候,机械的进度没有那么快,此时算法不能停啊,因此就用了gazebo的实时仿真平台,不得不讲,gaezbo的仿真平台做的相当不错了(甚至是传感器你都可以添加噪声!!!),学会了它其实很多时候能让我们不用在去关心硬件的东西而能把时间都放在算法上,比如SLAM,路径规划等等的都可以在这样的仿真平台上进行操作,当然,在搞算法之前,你必须要把仿真的机器人给搭建出来。
时隔一年,终于得空回来好好的研究这方面的东西,在整个过程中,自己也踩了不少坑,参考了许多东西,最终才得以把整个过程捋顺,写下来一方面是希望可以帮助更多的人,一方面是自己的再次总结,查漏补缺。
tips:本系列教程默认你已经对ros和gazebo有一定的了解了,否则应该也无缘看到这些
这里必须祭出这张官方的框架图了
还是那句老话,官方的图第一次看起来确实是很难看,但是着实是一个越看越明白的图,而图片的最上面的三个模块也是这个系列要讲的重中之中的东西,那么废话不多说,下面开始一起探索ros与gazebo的世界。
首先,无论是对于ros而言,还是对于gazebo而言,仿真最重要的就是要看到我们构建的帅气的机器人,那么看到它的关键文件就是urdf文件,该文件的主要功能就是描述机器人各个部分(link)与关节(joint)的属性。
记住了上面这句话,其实我们的urdf也就很好写了!下面我们就一起尝试一下。
机器人的各个部分的描述,尤为注意的是,这里的各个部分不是指像底盘这样如此宽广的指代,而是更加细分的最小组成单元,比如一个简单的底盘是由一个钢板和四个轮子组成的,其中钢板和轮子就是最小组成单元了。
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
<link name="car_link">
<visual>
<geometry>
<box size="2.0 1.0 0.5"/>
geometry>
visual>
<collision>
<geometry>
<box size="2.0 1.0 0.5"/>
geometry>
collision>
link>
robot>
这时候在命令行中输入如下语句就可以看到我们的link出现在RVIZ中(注意,输入命令的前提是你需要有urdf_tutorial的软件包,没有的话apt下载就行)
roslaunch urdf_tutorial display.launch model:=rbo.urdf.xacro
效果如下:
经过上述的过程,相信你已经能够在ros中看到自己创建的link了,与此同时,你也应该发现,一个link的实现其实是那么的简单!!!没错,在urdf中,最最简单易懂的就是link这个标签了。
在一个机器人的描述文件(urdf)中,link应主要包含以下几个元素:
对于上述的三个标签而言,前面两个是比较好理解的,visual表示能否被看到(不仅仅是在上位机上,同时对于gazebo中的摄像头等设备同样有效),collision表示是否会被碰撞,没有这个标签的话,就说明这个link是可以悄无声息的进入到另一个link的里面,amazing:D,而对于第三个标签,由于上面的代码中没有出现,现在我们对它还不够敏感,实际上,在ros中,这个标签作用不大(这只是我个人的推测,如有错误,大神轻喷~),而在gazebo中,这个标签却直接决定了这个link能否被看到!其中的原因很重要,我一定要另起一行说三遍!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
所以,在ros中,你想改变两个link的相对位置?好啊,写个node修改TF就行了!但是,在gazebo中,你想改变两个link的相对位置?也可以啊,只要符合物理公式的,你用什么方法都可以,那么问题来了,当你的link连最起码的质量(转动惯量其实在旋转的公式中其实是类比于质量的)都没有的时候,物理公式恐怕是无从下手了。
因此,没有inertial标签的link在gazebo中是完全看不到的,不信的朋友可以在上述代码的情况下输入以下命令:
roslaunch urdf_sim_tutorial gazebo.launch model:=rbo.urdf.xacro
你会看到一个空旷的世界,这里不再贴效果图了。
到这里,我们基本上完成了link的知识点,相信你已经可以写出一个在ros和gazebo中都能看到的link了,这里不再叙述inertial的相关内容,官方对于标签的讲解可以参考这里here
把xacro这部分内容放在这里实际上是一个偷懒的举动了,类比C语言中的宏定义可知,它并不会影响整个代码的完整性,但是它却能帮你大大的提高修改效率与简化代码量。
通过上面的例子,我们发现在visual和collision的标签里面出现了相同的元素geometry,并且这个标签的值是一模一样的,这就引发了一个问题:修改的时候很麻烦!试想每个link里面的这两个标签的内容如无意外都是相同的,如果我们有一个link是经常要被重复的(例如轮子),那么我们在修改的时候岂不是十分的耗费精力?是的,如果没有xacro的存在,我们修改一个机器人模型的话确实需要这么做了…..但是有了xacro之后,一切都将变得很简单而且完美。
1. 首先我们要告诉robot我们要用xacro,也就是在robot的标签中添加xmlns:xacro=”http://www.ros.org/wiki/xacro”,就像上面的例子一样;
2. 随后我们就可以在robot这个标签内部使用宏定义了,从实用角度讲,一般我们会用到的宏定义有两种,一是属性值的宏定义,另一种是宏定义的宏定义(这里不知道如何描述这个类型的宏定义,但是确实就是这样),具体的形式如下:
<xacro:property name="PI" value="3.1415926"/>
<xacro:macro name="default_inertial" params="mass gain">
<inertial>
<mass value="${mass*gain}" />
<inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="${PI/2}" />
inertial>
xacro:macro>
<xacro:default_inertial mass="1.0" gain="10.0"/>
如上面的例子所示,这就是最常用的两种宏定义了,第一种就是你定义了一个值,这个值在以后的过程中一定是经常出现的,而后如果你某天数学变了,PI不等于3.14了,你就可以只更改这一个地方的值而修改程序中所有用到PI的地方,所谓牵一发而动全身也不错如此吧:D;第二种就是你定义了一个宏定义,这个宏定义的名字就是name后面的字符串,他的参数在params中,因为是复数形式,因此参数值可以是多个值,声明的时候用空格隔开就可以了。如上面的例子所示,使用的时候照着形式来就可以了。
至此你应该可以将最上面的rbo的描述文件简化了,下面是我简化后的代码,同时为了让rbo能在gazebo接纳,我也添加了inertial标签到描述文件中
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:property name="PI" value="3.1415926"/>
<xacro:property name="car_width" value="1.0"/>
<xacro:property name="car_length" value="2.0"/>
<xacro:property name="car_height" value="0.4"/>
<xacro:macro name="default_inertial" params="mass">
<inertial>
<mass value="${mass}" />
<inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
inertial>
xacro:macro>
<xacro:macro name="box_geometry" params="width length height">
<geometry>
<box size="${width} ${length} ${height}"/>
geometry>
xacro:macro>
<link name="car_link">
<visual>
<xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
visual>
<collision>
<xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
collision>
<xacro:default_inertial mass="5.0"/>
link>
robot>
这时候看代码的话是不是就感觉很舒服了,同时修改也方便了很多,一般的,把属性值的宏定义当在前面的好处是修改的时候能快速的找到我们需要修改的值,命名也最好表征出这个变量是干什么的。
官方的教程可以参考here
终于到了最后的一个知识点,同时也是robot描述文件中最重要的一点——joint
在ros中,joint_state_publisher这个节点会通过joint来了解到每个link之间的连接关系,从而根据一个link值推算出所有的link的TF状态,为开发人员省去了不少时间(其实不得不承认,如果人为去计算的话,很少一部分人能把这个地方理的很通顺的);在gazebo中,joint指示了两个link之间的连接方式,如果两个连接为非固定的,还需要配合gazebo的标签指示摩擦系数等等,这里再次重申:gazebo是依靠物理引擎来进行机器人仿真的,因此所有在gazebo中的物体和联系都要符合物理定律,否则要么gazebo跪给你看(压根儿就不显示给你),要么gazebo浪给你看(例如两个link之间没有摩擦系数,你轻推一下一个link,它就会以无限的加速度前行)。
类似与link,要想让joint工作,你必须声明它所必须的标签,主要包括下面的主要标签:
1.name:标签的名称
2.type:指明这个标签的类型,主要有以下的属性
旋转(revolute) - 具有由上限和下限指定的有限范围,例如机械臂上的肘关节。
连续(continuous) - 绕轴旋转,没有上限和下限,例如底盘上的轮子
棱柱形(prismatic) - 滑动接头,沿轴线滑动,并具有由上限和下限指定的有限范围,例如某个关节是丝杆连接
固定(fixed) - 所有自由度都被锁定,比如你把摄像头用3M胶粘在了底盘上,那么这个关节就是固定的
浮动 - 此关节允许所有6个自由度的运动,这个着实不好举例,6个自由度都可以运动的话总感觉这个关节好像只是一个约束一样
平面 - 此关节允许在垂直于轴的平面内运动,用的比较少,暂时想不到用例了
3.parent:这个关节的一端,称为父端
4.child:这个关节的另一端,称为子端
5.origin:父端与子端的初始transform,例如相机的坐标系与底盘的坐标系方向相同,但是在x方向上差了0.1m,那么就在这个标签上填充上相应的值,该标签默认的值是两个质心是完全重合的。
6.axes:旋转轴,type标签里面反复提到的旋转轴就是这个标签,它指示了绕什么轴进行旋转,例如绕父节点的z轴旋转,那么该标签的值就为xyz=“0 0 1”
7.limit:有限值旋转类型必备标签,指示旋转所能到到的上下限
实际上,joint还是有很多标签的,这里列举了最常用的几个,实际上,只有前4个标签是必须的,其他的标签是要随着你的要求进而确定的。
百说不如一练,下面我们就来写一个轮子与底盘的关节
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:property name="PI" value="3.1415926"/>
<xacro:property name="car_width" value="1.0"/>
<xacro:property name="car_length" value="2.0"/>
<xacro:property name="car_height" value="0.3"/>
<xacro:property name="wheel_length" value="0.1"/>
<xacro:property name="wheel_radius" value="0.2"/>
<xacro:property name="wheel_origin_xyz" value="0.0 0.0 0.0"/>
<xacro:property name="wheel_origin_rpy" value="0.0 ${PI/2} 0.0"/>
<xacro:macro name="default_inertial" params="mass">
<inertial>
<mass value="${mass}" />
<inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
inertial>
xacro:macro>
<xacro:macro name="box_geometry" params="width length height">
<geometry>
<box size="${width} ${length} ${height}"/>
geometry>
xacro:macro>
<xacro:macro name="cylinder_geometry" params="length radius">
<geometry>
<cylinder length="${length}" radius="${radius}"/>
geometry>
xacro:macro>
<xacro:macro name="default_origin" params="xyz rpyaw">
<origin xyz="${xyz}" rpy="${rpyaw}"/>
xacro:macro>
<link name="car_link">
<visual>
<xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
visual>
<collision>
<xacro:box_geometry width="${car_width}" length="${car_length}" height="${car_height}"/>
collision>
<xacro:default_inertial mass="5.0"/>
link>
<link name="wheel">
<visual>
<xacro:cylinder_geometry length="${wheel_length}" radius="${wheel_radius}"/>
<xacro:default_origin xyz="${wheel_origin_xyz}" rpyaw="${wheel_origin_rpy}" />
visual>
<collision>
<xacro:cylinder_geometry length="${wheel_length}" radius="${wheel_radius}"/>
<xacro:default_origin xyz="${wheel_origin_xyz}" rpyaw="${wheel_origin_rpy}" />
collision>
<xacro:default_inertial mass="0.2"/>
link>
<joint name="car_base_wheel" type="continuous">
<origin xyz="${(wheel_length+car_width)/2.0} 0.0 0.0" rpy="0.0 0.0 0.0"/>
<parent link="car_link"/>
<child link="wheel"/>
<axis xyz="0.0 1.0 0.0"/>
joint>
robot>
以上就是一个很简单的例子,但是纵然是如此简单的例子,当你使用urdf_tutorial的display节点进行显示的时候,却意外的不行,效果如下:
可以看到两个关节是重叠在了一块,仔细看错误之后发现,原来是整个描述里面缺了base_link这个老大哥,通常情况下,因为机器人都是有高度的,因此base_link一般都是与机器人最下方的link进行固定连接,同时高度上相差合适的高度,因此上述代码加入以下代码之后就能变得正常了
<link name="base_link"/>
<joint name="base_link_car" type="fixed">
<origin xyz="0.0 0.0 ${wheel_radius}" rpy="0.0 0.0 0.0"/>
<parent link="base_link"/>
<child link="car_link"/>
joint>
效果如下图所示:
如果你使用gazebo打开的话,就会看到比较有趣的地方,由于重力的作用,一边重重的掉在了地上
到这里,我们的第一步urdf的编写就完成了,同时也知道了一个机器人要想在ros和gazebo中都观察到的话应该具备怎样的标签和值,总之这部分的内容相对来说会简单一些,消化起来也不是那么的难。
实际上,即使有xacro,你想通过一条语句一条语句的进行机器人的搭建也是很费时间的,好在“懒”是科技进步的第一动力,SW提供了一个插件可以很方便的将SW的模型转化为urdf文件,有兴趣可以移步至here