本文目录
前文介绍了URDF建模与URDF语法,接下来介绍怎么使用URDF从零构建一个机器人模型并在rviz中显示。
一、机器人结构组成
最终效果如下图:
机器人由如下部分组成:
- 底盘 * 1
- 主动轮 * 2
- 从动轮(脚轮)* 2
- 激光雷达 * 1
- RGB相机 * 1
二、新建功能包
为面向零基础的同学,使教学清晰,新建一个功能包用于学习该章节,新建功能包方法见前文,功能包结构如下:
其中,
config
:存放rviz配置文件launch
:存放launch文件meshes
:存放模型渲染文件models
:存放模型文件src与include
:存放源文件和头文件
关于ROS工作空间与功能包的创建,详见 ROS Hello World
三、编写launch文件
如前文所述,rviz显示urdf模型需要先把模型参数注册到参数服务器,然后打开rviz,在rviz中配置好后才能正常显示模型。这些步骤可以手动一步一步完成,也可以编写launch文件快速执行。
另外,对于包含多个link
的模型,需要发布link
间的joint
和tf
关系,以使rviz可以确定link
间的空间位置。ROS提供了 joint_state_publisher
和 robot_state_publisher
两个功能包来实现link
间joint
和tf
关系的发布,如没有安装这两个功能包,可以使用如下命令安装(以noetic为例):
bash
sudo apt install ros-noetic-joint-state-publisher
sudo apt install ros-noetic-robot-state-publisher
launch文件内容如下:
xml
<launch>
<param name="robot_description" textfile="$(find simulation_learning)/models/urdf/mbot_base.urdf" />
<!-- 设置GUI参数,显示关节控制插件 -->
<param name="use_gui" value="true" />
<!-- 运行joint_state_publisher节点,发布机器人的关节状态 -->
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<!-- 运行robot_state_publisher节点,发布tf -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
<!-- 运行rviz可视化界面,并加载配置 -->
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find simulation_learning)/config/mbot_urdf.rviz" required="true" />
</launch>
关于launch
文件的具体描述,见[4.1 launch文件](#4.1 launch文件)
四、创建底盘
在models/urdf
中创建mbot_base.urdf
文件,用于编写urdf模型。
底盘是一个圆柱体,半径为 0.2m
,高为0.16m
,urdf代码如下:
xml
<?xml version="1.0"?>
<robot name="mbot">
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20" />
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1" />
</material>
</visual>
</link>
</robot>
运行 launch
文件,结果如下:
这里注意base_link
的原点位于圆柱体的几何中心,即有一半圆柱体是位于地面以下的,这一点到最后会解决,目前先把机器人模型搭建起来。
五、添加轮子
轮子是小一点的圆柱体,半径为 0.06m
,高为0.025m
。
现在有了两个实体,底盘和一个轮子,需要使用joint
说明他们之间的几何与运动关系,否则rviz解析urdf时会报错。
urdf代码如下:
xml
<!-- 底盘实体描述 -->
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20" />
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1" />
</material>
</visual>
</link>
<!-- 左轮与底盘的关节描述 -->
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0" />
<parent link="base_link" />
<child link="left_wheel_link" />
<axis xyz="0 1 0" />
</joint>
<!-- 左轮实体描述 -->
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length="0.025" />
</geometry>
<material name="white">
<color rgba="1 1 1 0.9" />
</material>
</visual>
</link>
其中,joint
中的type
描述了关节类型(机械中的运动副),该类型为旋转类型(机械中的旋转副),origin
表示左轮原点相对于底盘原点的空间位姿偏移,parent
和child
分别表示该关节的父实体和子实体,由于该关节为旋转关节,使用axis
定义旋转轴。关于joint
的具体描述见 建模与仿真 - URDF 语法介绍。
运行 launch
文件,结果如下:
六、添加其他部件
其他部件同理,添加实体与相应的关节描述。
完整的urdf代码如下:
xml
<?xml version="1.0"?>
<robot name="mbot">
<!-- 底盘实体描述 -->
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20" />
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1" />
</material>
</visual>
</link>
<!-- 左轮与底盘的关节描述 -->
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0" />
<parent link="base_link" />
<child link="left_wheel_link" />
<axis xyz="0 1 0" />
</joint>
<!-- 左轮实体描述 -->
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length="0.025" />
</geometry>
<material name="white">
<color rgba="1 1 1 0.9" />
</material>
</visual>
</link>
<!-- 右轮与底盘的关节描述 -->
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0" />
<parent link="base_link" />
<child link="right_wheel_link" />
<axis xyz="0 1 0" />
</joint>
<!-- 右轮实体描述 -->
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length="0.025" />
</geometry>
<material name="white">
<color rgba="1 1 1 0.9" />
</material>
</visual>
</link>
<!-- 前脚轮实体描述 -->
<joint name="front_caster_joint" type="continuous">
<origin xyz="0.18 0 -0.095" rpy="0 0 0" />
<parent link="base_link" />
<child link="front_caster_link" />
<axis xyz="0 1 0" />
</joint>
<!-- 前脚轮和底盘的关节描述 -->
<link name="front_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95" />
</material>
</visual>
</link>
<!-- 后脚轮实体描述 -->
<joint name="back_caster_joint" type="continuous">
<origin xyz="-0.18 0 -0.095" rpy="0 0 0" />
<parent link="base_link" />
<child link="back_caster_link" />
<axis xyz="0 1 0" />
</joint>
<!-- 后脚轮和底盘的关节描述 -->
<link name="back_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95" />
</material>
</visual>
</link>
<!-- 激光雷达实体描述 -->
<link name="laser_link">
<visual>
<origin xyz=" 0 0 0 " rpy="0 0 0" />
<geometry>
<cylinder length="0.05" radius="0.05" />
</geometry>
<material name="gray">
<color rgba="0.25 0.25 0.25 0.95" />
</material>
</visual>
</link>
<!-- 激光雷达和底盘的关节描述 -->
<joint name="laser_joint" type="fixed">
<origin xyz="0 0 0.105" rpy="0 0 0" />
<parent link="base_link" />
<child link="laser_link" />
</joint>
<!-- 相机实体描述 -->
<link name="camera_link">
<visual>
<origin xyz=" 0 0 0 " rpy="0 1.57 0" />
<geometry>
<cylinder radius="0.02" length = "0.05"/>
</geometry>
<material name="gray">
<color rgba="0.25 0.25 0.25 0.95"/>
</material>
</visual>
</link>
<!-- 相机和底盘的关节描述 -->
<joint name="camera_joint" type="fixed">
<origin xyz="0.18 0 0.055" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="camera_link"/>
</joint>
</robot>
运行 launch
文件,结果如下:
七、解决部分实体位于地面以下的问题
首先明确我们的 Fixed Frame
是base_link
, Fixed Frame
可以理解为世界坐标系,对于我们的设置就是base_link
即当前的世界,rviz
默认一切都是从世界中心诞生。
实体位于地面以下,是由于我们设置的其他实体都是以世界中心为参考而又认为地面在轮子下方,但rviz
认为地面原点在世界中心,所以看起来和我们的认知有些冲突。
解决办法有多种,但基本都是改变base_link
或其他实体与世界中心的关系。
方法一:
比较常见的一种:新增floor_link
作为 Fixed Frame
世界坐标系,base_link
作为他的子级。这种方法对现有已经完成的模型修改最少。
在urdf文件中新增如下内容即可:
xml
<!-- 地面实体描述 -->
<link name="floor_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<!-- 圆柱体高度尽量小,已达到可忽略的精度 -->
<cylinder length="0.000001" radius="0.20" />
</geometry>
<material name="floor">
<color rgba="1 0.4 0 0" />
</material>
</visual>
</link>
<!-- 底盘与地面的关节关系描述 -->
<joint name="base_joint" type="continuous">
<origin xyz="0 0 0.11" rpy="0 0 0" />
<parent link="floor_link" />
<child link="base_link" />
<axis xyz="0 1 0" />
</joint>
结果如下:
使用rviz
查看TF
关系如下:
新增的floor_link
如图中框选,但这种方法在工程上很难测量base_link
的实际位置,所以就有了方法二。
方法二:
将floor_link
直接命名为base_link
,其他实体都以它为基准,但位置描述都要作相应的修改,这种方法对于现有已经完成的模型修改较多,对新工程比较友好,各实体的实际位置也很容易通过测量得到。
修改后的TF
树如下,此时base_link
位于世界原点,也是机器人底盘原点在地面的投影点。
机器人的外观显示和方法一一样: