学了ros有一段时间了,所用的参考书目为《ROS机器人开发实践》,但是学了这么长时间,总感觉收货甚少,没有系统性的总结。
因此在这里,我针对一个功能性较为完善的软件包 mrobot_gazebo 进行完整解读。
参考链接:https://blog.csdn.net/wubaobao1993/article/details/80947968
这里不按照基础->深入的顺序,直接跟着代码走。
代码是《ROS机器人开发实践》的源码,功能包为mrobot_gazebo,用的模型是带有相机的mrobot模型
launch文件选择 view_mrobot_with_camera_gazebo.launch
xacro文件选择 mrobot_with_camera.urdf.xacro 和 mrobot_body.urdf.xacro
根据书的学习流程,先介绍模型文件
mrobot_body.urdf.xacro
首先xacro的头部模式:
规定了XML的版本、机器人的名称、以及声明链接也就是我们要告诉robot我们要用xacro
<?xml version="1.0"?>n
<robot name="mrobot" xmlns:xacro="http://www.ros.org/wiki/xacro">
接下来预设材料的颜色,rgb代表三个通道,a代表透明度,这样我们之后直接使用这里设置的name调用颜色
<material name="Black">
<color rgba="0 0 0 1"/>
</material>
接下来设置一系列属性值,是xacro的第一种宏定义方式。(理解为#define即可)
第一种就是你定义了一个值,这个值在以后的过程中一定是经常出现的,而后如果你某天数学变了,PI不等于3.14了,你就可以只更改这一个地方的值而修改程序中所有用到PI的地方,所谓牵一发而动全身也不错如此吧
也就是:
定义常量π
<!-- PROPERTY LIST -->
<!--All units in m-kg-s-radians unit system -->
<xacro:property name="M_PI" value="3.1415926535897931" />
定义机器人底盘(base)的质量、半径、高
<!-- Main body length, width, height and mass -->
<xacro:property name="base_mass" value="0.5" />
<xacro:property name="base_link_radius" value="0.13"/>
<xacro:property name="base_link_length" value="0.005"/>
这个motor x以后再说
<xacro:property name="motor_x" value="-0.05"/>
脚轮的半径、质量、脚轮与底盘链接关节处相关的x
<xacro:property name="caster_radius" value="0.016" />
<xacro:property name="caster_mass" value="0.01" />
<xacro:property name="caster_joint_origin_x" value="-0.12" />
轮子的半径、宽度、质量
<!-- Wheel radius, height and mass -->
<xacro:property name="wheel_radius" value="0.033" />
<xacro:property name="wheel_height" value="0.017" />
<xacro:property name="wheel_mass" value="0.1" />
底盘上面两个平台的质量、宽度和与立柱相连接joint的x\y值
<!-- plate height and mass -->
<xacro:property name="plate_mass" value="0.05"/>
<xacro:property name="plate_height" value="0.07"/>
<xacro:property name="standoff_x" value="0.12"/>
<xacro:property name="standoff_y" value="0.10"/>
接下来是宏定义的第二种类型:
第二种就是你定义了一个宏定义,这个宏定义的名字就是name后面的字符串,他的参数在params中,因为是复数形式,因此参数值可以是多个值,声明的时候用空格隔开就可以了。如上面的例子所示,使用的时候照着形式来就可以了。
如这里定义球体的转动惯量,参数为 params=“m r” 参数之间用空格隔开
定义中需要传参或需要在传参后得到结果的地方都用参数符号进行占位,如
${m}
${2mr*r/5}
<!-- Macro for inertia matrix -->
<xacro:macro name="sphere_inertial_matrix" params="m r">
<inertial>
<mass value="${m}" />
<inertia ixx="${2*m*r*r/5}" ixy="0" ixz="0"
iyy="${2*m*r*r/5}" iyz="0"
izz="${2*m*r*r/5}" />
</inertial>
</xacro:macro>
这里定义圆柱体的转动惯量,params="m r h
<xacro:macro name="cylinder_inertial_matrix" params="m r h">
<inertial>
<mass value="${m}" />
<inertia ixx="${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0"
iyy="${m*(3*r*r+h*h)/12}" iyz = "0"
izz="${m*r*r/2}" />
</inertial>
</xacro:macro>
接下来定义立方体的转动惯量
<xacro:macro name="box_inertial_matrix" params="m w h d">
<inertial>
<mass value="${m}" />
<inertia ixx="${m*(h*h+d*d)/12}" ixy = "0" ixz = "0"
iyy="${m*(w*w+d*d)/12}" iyz = "0"
izz="${m*(w*w+h*h)/12}" />
</inertial>
</xacro:macro>
轮子link、joint、transmission的宏定义
轮子link
接下来定义轮子的宏定义,包括一个link+一个joint+与link绑定的transmission
link就是部件,joint就是关节,都很好理解
transmission是什么呢?就是一种动力源,是ros控制驱动机器人的体现,必须将传动装置transmistion与关节joint进行绑定,才能驱动机器人前进。
此宏定义如下图所示:
图1
参数lr代表是左轮还是右轮
先定义link:
xyz=“0 0 0” 为什么是000因为是相对于joint而言的,因此轮子放在了joint中央
下图可以明确显示轮子放在joint的000位置。
图2
rpy="${M_PI/2} 0 0 表示绕三个轴的旋转,弧度制,z轴是垂直rviz平台的。x轴沿着rviz平台,由于创建出的圆柱体是坐在rviz平台上的,因此绕着x轴旋转90度成为轮子的位姿。x轴方向可以理解为小车的前进方向。
当然,上面的位姿设定是在<visual>
下的
可以这么认为,如果你想决定一个几何体在哪个位置,你只需要规定与之相连的joint在什么位置即可。
那么joint的位置怎么确定?joint的位置与父级link相关,是相对与父级位置的xyz坐标
同样在下的还有,<geometry>
下是确定具体的几何形状。首先cylinder确认了是圆柱体,然后指定圆柱体的高度和半径。
使用了之前的宏定义颜色
总结一下:<visual>
下有位姿、<geometry>
、材料(颜色)
接下来是碰撞检测 <collision>
。看似是复制上面的visual定义,其实就是设置碰撞检测的范围和实体的范围一致
最后指定一下旋转惯量即可。 <cylinder_inertial_matrix m="${wheel_mass}" r="${wheel_radius}" h="${wheel_height}" />
以上就定义结束了轮子的link实体,但是下面要跟一个与gazebo相关的
<gazebo reference="wheel_${lr}_link">
<material>Gazebo/Black</material>
</gazebo>
书上是这么说的:
针对机器人模型,需要对每一个link添加
<gazebo>
标签,包含的属性仅有material。要保持与link的visual下的material相同,并且gazebo无法使用上面visual的material进行颜色设置,因此在此标签中需要进一步指定。<material>Gazebo/Black</material>
轮子joint
接下来定义joint,是轮子与底盘链接的joint,具体可以参见图1。
type="continuous"表示是一个旋转关节,可以围绕单轴无限旋转(P115)
紧接着指定父子link
<parent link="base_link"/>
<child link="wheel_${lr}_link"/>
接下来指定位姿
<origin xyz="${motor_x} ${translateY * base_link_radius} 0" rpy="0 0 0" />
可以看到位姿x使用了宏定义的<xacro:property name="motor_x" value="-0.05"/>
即为-0.05,也就是沿着小车后退方向偏移-0.05。y位置是输入的参数translateY乘以底盘的半径base_link_radius,也用到了宏定义<xacro:property name="base_link_radius" value="0.13"/>
这里为了明确,贴一张坐标图
图3
joint没有进行旋转,说明其默认关节方向为沿着y方向躺下
之前说了,joint的xyz是相对于父级link说的,此处的父级link就是底盘base_link,因此上面才可以这么分析
这里我用粗体再强调一遍
link的位置是由连接 父级和它 的joint决定的,而joint的位置是由父级link的位置决定的(而不是父级link的joint决定的,因为父级joint有多个,所以显然joint不能由joint决定,只能由唯一的父级link的位置进行偏移)
轮子transmission
最后一部分就是加驱动环节了。
首先是规定类型 <type>transmission_interface/SimpleTransmission</type>
图4
也就是简单的一级齿轮传动。
接下来,传动装置需要绑定joint(就像他们没有分开过一样),因此 <joint name="base_to_wheel_${lr}_joint" />
接下来规定马达的内容
首先是马达的名称<actuator name="wheel_${lr}_joint_motor">
接下来定义马达硬件接口的类型<hardwareInterface>VelocityJointInterface</hardwareInterface>
这里使用的是速度控制接口,简单理解就是可以通过速度指令控制硬件(马达),从而完成操纵机器人行走。
最后的<mechanicalReduction>1</mechanicalReduction>
规定传动装置减速比为1
完整的定义轮子link、joint、transmission代码
<!-- Macro for wheel joint -->
<xacro:macro name="wheel" params="lr translateY">
<!-- lr: left, right -->
<link name="wheel_${lr}_link">
<visual>
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0 " />
<geometry>
<cylinder length="${wheel_height}" radius="${wheel_radius}" />
</geometry>
<material name="Black" />
</visual>
<collision>
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0 " />
<geometry>
<cylinder length="${wheel_height}" radius="${wheel_radius}" />
</geometry>
</collision>
<cylinder_inertial_matrix m="${wheel_mass}" r="${wheel_radius}" h="${wheel_height}" />
</link>
<gazebo reference="wheel_${lr}_link">
<material>Gazebo/Black</material>
</gazebo>
<joint name="base_to_wheel_${lr}_joint" type="continuous">
<parent link="base_link"/>
<child link="wheel_${lr}_link"/>
<origin xyz="${motor_x} ${translateY * base_link_radius} 0" rpy="0 0 0" />
<axis xyz="0 1 0" rpy="0 0" />
</joint>
<!-- Transmission is important to link the joints and the controller -->
<transmission name="wheel_${lr}_joint_trans">
<type>transmission_interface/SimpleTransmission</type>
<joint name="base_to_wheel_${lr}_joint" />
<actuator name="wheel_${lr}_joint_motor">
<hardwareInterface>VelocityJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
</xacro:macro>
前侧球轮link、joint的宏定义
先贴上定义了什么:
图5
这次为了更加方便,直接拆开讲解
首先宏定义球轮名称及其参数,fb代表是前后球轮(front, back)
<xacro:macro name="caster" params="fb translateX">
接着定义球轮的link
之下首先包含
<origin xyz="0 0 0 " rpy="0 0 0" />
位姿,球体当然不用设置旋转,记住xyz位置是相对于joint来的,000就是讲球放在joint上,而球是以球心定位,最终的效果为(后面会讲joint的z为球的半径)
图6
接下来是几何形状,为球体,并指定半径
<geometry>
<sphere radius="${caster_radius}" />
</geometry>
接下来材料为黑色,不必多说,用到了对颜色的宏定义
最终为碰撞检测,也不必多说,和visualize一致
最终指定一下旋转惯量即可,用到了球体旋转惯量的宏定义。
记住 link后面要加入gazebo标签
<gazebo reference="${fb}_caster_link">
<material>Gazebo/Black</material>
</gazebo>
接下来定义球轮的joint
<joint name="base_to_${fb}_caster_joint" type="fixed">
可以看到类型为fixed,固定关节,不允许运动的特殊关节(由此看来轮子不能转???存疑)
然后是父子link,其父级为base_link
最后是位置:
<origin xyz="${translateX*caster_joint_origin_x} 0 ${-caster_radius}" rpy="0 0 0"/>
可以看到x方向(见图3)是一个偏移系数(宏输入的参数)乘以一个宏常量,y方向为0意味着是在中心轴线,z为负的球半径,意味着关节位于球体中心。记住,父级link是底盘base_link,位置要从base_link的中心进行推演。
图7
完整的定义球轮link、joint代码
<link name="${fb}_caster_link">
<visual>
<origin xyz="0 0 0 " rpy="0 0 0" />
<geometry>
<sphere radius="${caster_radius}" />
</geometry>
<material name="Black" />
</visual>
<collision>
<geometry>
<sphere radius="${caster_radius}" />
</geometry>
<origin xyz="0 0 0 " rpy="0 0 0" />
</collision>
<sphere_inertial_matrix m="${caster_mass}" r="${caster_radius}" />
</link>
<gazebo reference="${fb}_caster_link">
<material>Gazebo/Black</material>
</gazebo>
<joint name="base_to_${fb}_caster_joint" type="fixed">
<parent link="base_link"/>
<child link="${fb}_caster_link"/>
<origin xyz="${translateX*caster_joint_origin_x} 0 ${-caster_radius}" rpy="0 0 0"/>
</joint>
</xacro:macro>
平台link、joint的宏定义
给个图
图8
没什么新奇的,也是先定义link,把一个平台的形状拿到
然后定义joint,之前说了如果像决定几何体的位置,先决定joint的位置
joint的位置定义为 <origin xyz="0 0 ${plate_height}" rpy="0 0 0" />
位置中plate_height是一个平台高,平台1以底盘向上偏移这么高,平台2以平台1向上便宜这么高。(因为joint的位置是由父级link决定的)
这里join的父子:
<parent link="${parent}"/>
<child link="plate_${num}_link" />
举例,如果是第一个平台的定义(不包括底盘,底盘不属于平台),则是
<plate num="1" parent="base_link" />
父级是底盘。
<!-- Macro for plate joint -->
<xacro:macro name="plate" params="num parent">
<link name="plate_${num}_link">
<cylinder_inertial_matrix m="0.1" r="${base_link_radius}" h="${base_link_length}" />
<visual>
<origin xyz=" 0 0 0 " rpy="0 0 0" />
<geometry>
<cylinder length="${base_link_length}" radius="${base_link_radius}"/>
</geometry>
<material name="yellow"/>
</visual>
<collision>
<origin xyz="0.0 0.0 0.0" rpy="0 0 0" />
<geometry>
<cylinder length="${base_link_length}" radius="${base_link_radius}"/>
</geometry>
</collision>
</link>
<gazebo reference="plate_${num}_link">
<material>Gazebo/Blue</material>
</gazebo>
<joint name="plate_${num}_joint" type="fixed">
<origin xyz="0 0 ${plate_height}" rpy="0 0 0" />
<parent link="${parent}"/>
<child link="plate_${num}_link" />
</joint>
</xacro:macro>
立柱link、joint的宏定义
这次先定义joint,后定义的link,其实顺序都无所谓。但是一般先创建link,毕竟现有实体,再有连接嘛
图9
代码略
注意不是4根,是8根
最终八根柱子的效果图为
图10
最重要的mrobot_body的宏定义
这是最重要的打地基环节,一切东西都是由此伸展开来
首先要看到,这一大块宏定义叫做mrobot_body,最终此宏构建出来的东西为:
图11
脚印叫做 <link name="base_footprint">
脚印相当于创建了最原始的link,其他所有的东西都是由他来的,比如由他生成了底盘base_link,底盘又延展出来很多其他的东西。
代码就不进行详解了。
<!-- BASE-FOOTPRINT -->
<!-- base_footprint is a fictitious link(frame) that is on the ground right below base_link origin -->
<xacro:macro name="mrobot_body">
<link name="base_footprint">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<box size="0.001 0.001 0.001" />
</geometry>
</visual>
</link>
<joint name="base_footprint_joint" type="fixed">
<origin xyz="0 0 ${wheel_radius}" rpy="0 0 0" />
<parent link="base_footprint"/>
<child link="base_link" />
</joint>
<!-- BASE-LINK -->
<!--Actual body/chassis of the robot-->
<link name="base_link">
<cylinder_inertial_matrix m="${base_mass}" r="${base_link_radius}" h="${base_link_length}" />
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="${base_link_length}" radius="${base_link_radius}"/>
</geometry>
<material name="yellow" />
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="${base_link_length}" radius="${base_link_radius}"/>
</geometry>
</collision>
</link>
<gazebo reference="base_link">
<material>Gazebo/Blue</material>
</gazebo>
接下来利用已经生成的模块化的宏定义生成一系列模块
<!-- Wheel Definitions -->
<wheel lr="right" translateY="1" />
<wheel lr="left" translateY="-1" />
<!-- Casters Definitions -->
<caster fb="front" translateX="-1" />
<!-- plates and standoff Definitions -->
<mrobot_standoff_2in parent="base_link" number="1" x_loc="-${standoff_x/2 + 0.03}" y_loc="-${standoff_y - 0.03}" z_loc="${plate_height/2}"/>
<mrobot_standoff_2in parent="base_link" number="2" x_loc="-${standoff_x/2 + 0.03}" y_loc="${standoff_y - 0.03}" z_loc="${plate_height/2}"/>
<mrobot_standoff_2in parent="base_link" number="3" x_loc="${standoff_x/2}" y_loc="-${standoff_y}" z_loc="${plate_height/2}"/>
<mrobot_standoff_2in parent="base_link" number="4" x_loc="${standoff_x/2}" y_loc="${standoff_y}" z_loc="${plate_height/2}"/>
<mrobot_standoff_2in parent="standoff_2in_1_link" number="5" x_loc="0" y_loc="0" z_loc="${plate_height}"/>
<mrobot_standoff_2in parent="standoff_2in_2_link" number="6" x_loc="0" y_loc="0" z_loc="${plate_height}"/>
<mrobot_standoff_2in parent="standoff_2in_3_link" number="7" x_loc="0" y_loc="0" z_loc="${plate_height}"/>
<mrobot_standoff_2in parent="standoff_2in_4_link" number="8" x_loc="0" y_loc="0" z_loc="${plate_height}"/>
<!-- plate Definitions -->
<plate num="1" parent="base_link" />
<plate num="2" parent="plate_1_link" />
自此,所有link和joint浑然天成,就好像一直未曾分开过。
添加插件声明
最后一个要点就是添加插件声明
Gazebo已经提供了一个用于差速控制的插件libgazebo_ros_diff_drive.so
因此在文件最后做出插件声明:
<!-- controller -->
<gazebo>
<plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
<rosDebugLevel>Debug</rosDebugLevel>
<publishWheelTF>true</publishWheelTF>
<robotNamespace>/</robotNamespace>
<publishTf>1</publishTf>
<publishWheelJointState>true</publishWheelJointState>
<alwaysOn>true</alwaysOn>
<updateRate>100.0</updateRate>
<legacyMode>true</legacyMode>
<leftJoint>base_to_wheel_left_joint</leftJoint>
<rightJoint>base_to_wheel_right_joint</rightJoint>
<wheelSeparation>${base_link_radius*2}</wheelSeparation>
<wheelDiameter>${2*wheel_radius}</wheelDiameter>
<broadcastTF>1</broadcastTF>
<wheelTorque>30</wheelTorque>
<wheelAcceleration>1.8</wheelAcceleration>
<commandTopic>cmd_vel</commandTopic>
<odometryFrame>odom</odometryFrame>
<odometryTopic>odom</odometryTopic>
<robotBaseFrame>base_footprint</robotBaseFrame>
</plugin>
</gazebo>
其中, <robotNamespace>/</robotNamespace>
是机器人的命名空间,就是一个/,可以自己设置,比如mr/,那么以后的例如话题都是在这个空间下,比如mr/cmd_vel
然后是差速控制的两个关节:
<leftJoint>base_to_wheel_left_joint</leftJoint>
<rightJoint>base_to_wheel_right_joint</rightJoint>
然后是机器人模型的相关尺寸
分别代表轮距和轮子直径
<wheelSeparation>${base_link_radius*2}</wheelSeparation>
<wheelDiameter>${2*wheel_radius}</wheelDiameter>
然后是车轮转动的加速度<wheelAcceleration>1.8</wheelAcceleration>
接下来是控制器订阅的速度控制指令<commandTopic>cmd_vel</commandTopic>
如果之前有机器人的命名空间mr/,则生成的速度指令为/mr/cmd_vel
如下图所示
但是速度控制器订阅不了话题,不知道为什么
接下来是<odometryTopic>odom</odometryTopic>
里程计数据的参考坐标系,一般都命名为odom。