目录
机器人的定义
机器人的组成
移动机器人系统构建
传感器的使用
urdf 机器人建模
建模实践
机器人的定义
自动执行工作的机器装置。可以根据预先编写的程序或者接受人类指挥来行动,最终目标是使其能够脱离人的干预,根据以人工智能技术制定的原则和策略自主行动,以协助或取代人类工作。
机器人的组成
从控制的角度来看,机器人可以划分为四个部分:执行机构、传感系统、驱动系统、控制系统。
(对自己的名词解释:伺服就是一个电机和控制这个电机的驱动器。电机就叫伺服电机、驱动器自然叫做伺服驱动器。伺服驱动器的作用主要有三点,一是开关作用:控制伺服电机的起动、停机、转速等等;二是保护作用:对电机进行各种保护(过载,短路,欠压等);三是控制作用:对外部信号做出反应,通过内部的PID调节(放大脉冲指令信号的驱动功率),控制伺服电机(位置,速度,扭矩)。伺服是为了实现电机的精确控制并精确跟踪运动部件的移动轨迹。
电机指的是用电力驱动旋转地设备。马达是指用电、汽油、柴油、燃气等能源驱动的旋转式机械设备的统称。)
机器人的闭环控制回路如上图。
移动机器人系统构建
大部分都会搭载嵌入式开发板。有些也可以搭载pc机来完成控制功能。
执行机构
底盘骨架、轮子、驱动轮子的电机、有些小车会使用舵机来作转向机构
驱动系统
电源子系统、电机驱动子系统、传感器接口等
内部传感系统
内部传感系统中里程计(可以选用霍尔编码器或者光电编码器)的实现原理:
光电编码器是由光栅盘和光电检测装置组成。光栅盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,光栅盘与电动机同速旋转,经发光二极管等电子元件组成的检测装置检测输出若干脉冲信号;通过计算每秒光电编码器输出脉冲的个数就能反映当前电动机的转速。
1、根据单位时间内产生的脉冲数计算电机(即轮子)的旋转圈数
2、根据轮子的周长乘以单位时间旋转圈数计算机器人的运动速度
3、根据机器人的运动速度通过积分计算里程
内部传感系统中常用的惯性测量单元(IMU)(用来测量物体的速度和姿态,主要包括三轴陀螺仪、三轴加速度计、磁力计等)的实现原理:
imu的输入就是上面那些传感器的信号,最终的输出包含机器人的位置、速度、姿态信息。关于机器人的速度可以跟里程计做一下传感器的融合以得到更加精确的机器人位置和速度。如果单纯的使用里程计,由于其原理当中使用到很多跟积分相关的内容,必然会产生积分的累积误差。即机器人运动距离越大,误差越大。
控制系统
其中,深绿色框中的内容需要较大的计算资源,如果机器人本地计算资源不足可以将其置于远端pc中。
常见控制系统板卡包括树莓派(raspberry pi)、odroid-XU4、 媲美低端pc的NVIDIA Jetson TK1等。如果需要在机器人本地实现较为复杂的功能的话,可以使用mini PC,或者intel NUC等小型电脑。
外部传感系统
摄像头、Kinect、GPS、超声波、激光雷达、三维激光雷达等。
常用的传感器的ros驱动可以参考:http://wiki.ros.org/Sensors
传感器的使用
连接摄像头
sudo apt-get install ros-kinetic-usb-cam //安装usb_cam功能包
roslaunch usb_cam usb_cam-test.launch //启动功能包
rqt_image_view
我们可以使用usb_cam功能包来连接摄像头。usb_cam功能包能够提供原始图像数据以及压缩数据,一般为了降低网络带宽压力会选择压缩图像。我们还可以通过修改参数来获得不同的图像效果。
当我们使用一个功能包时,首先要关注的不是它的代码实现,而应该是它的输入输出。包括功能包给我们提供的话题、服务、参数等。想要了解一个陌生功能包的详细信息时,可以查看ros wiki。所有的功能包都可以在wiki上找到最完整的描述。
连接Kinect
首先要安装Kinect的驱动,我这里使用的是Kinect v2
// git clone https://github.com/OpenKinect/libfreenect.git //Kinect v1 驱动
git clone https://github.com/OpenKinect/libfreenect2.git //Kinect v2 驱动
sudo apt-get install build-essential cmake pkg-config //安装编译工具
sudo apt-get install libusb-1.0-0-dev //安装libusb
sudo apt-get install libturbojpeg libjpeg-turbo8-dev //安装TurtoJPEG
sudo atp-get libglfw3-dev //安装openGL
cd libfreenect2
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/freenect2 -DENABLE_CXX11=ON
make
sudo make install
sudo cp ../platform/linux/udev/90-kinect2.rules /etc/udev/rules.d/ //设置udev rules
OK,到这里Kinect v2的驱动就安装完成了。接下来一定要记得重新插拔Kinect2。
启动demo程序测试:
./bin/Protonect
效果如下图:
然后安装ROS下的Kinect驱动包 iai_Kinect2
cd ~/catkin_ws/src/
git clone https://github.com/code-iai/iai_kinect2.git
cd iai_kinect2
rosdep install -r --from-paths .
cd ~/catkin_ws
catkin_make -DCMAKE_BUILD_TYPE="Release"
测试:
roslaunch kinect2_bridge kinect2_bridge.launch
然后打开一个新的终端可视化数据
rosrun kinect2_viewer kinect2_viewer sd cloud
效果如下:
urdf 机器人建模
所谓urdf 即 unified robot description format,统一机器人描述格式。可以通过XML格式的文件来描述机器人模型,同时ros 也提供了urdf的 c++解析器,就是说我们可以直接在c++代码中解析urdf模型。
机器人可以拆解开并分为两大类。一类叫做连杆(link),另一类叫做关节(joint)。
link部分
link描述了机器人刚体部分的外观和物理属性,包括尺寸(size)、颜色(color)、形状(shape)、惯性矩在(inertial matrix)、碰撞参数(collision properties)等。
一个link主要需要定义如下标签:
<visual>标签,描述机器人link的外观参数
<inertial>标签,描述link的惯性参数
<collision>标签,描述link的碰撞属性
如下图:
joint部分
joint主要描述机器人关节的运动学和动力学属性,包括关节运动的位置和速度的限制。根据关机的运动形式,可以分为如下六种:
一个joint主要需要定义如下标签:
<parent>标签(必须有),定义了父连杆
<child>标签(必须有),定义了子连杆
<calibration>标签,描述关节的参考位置,用来校准关节的绝对位置。
<dynamic>标签,描述关节的物理属性,例如阻尼值、静摩擦力等,在动力学仿真中经常用到。
<limit>标签,描述运动的极限值,包括关节运动的上下限位置、速度限制、力矩限制等。
<mimic>标签,描述该关节与已有关节的关系。
<safety_controller>标签,描述安全控制器参数。
如下图:
一个urdf模型的根标签是 <robot> 标签,<link> 标签和 <joint> 标签都必须包含在其中。如下图:
建模实践
创建一个机器人模型的功能包
cd ~/catkin_ws/src
catkin_create_pkg robot_description urdf xacro //ros约定俗称的将机器人描述文件命名为:机器人名字_description
我们创建的机器人模型功能包需要依赖于 urdf 功能包以及 xacro 功能包,urdf 功能包是用来解析机器人模型的。
然后在功能包当中建立几个不同作用的文件夹。urdf 文件夹用来存放机器人模型的urdf文件或者xacro文件,meshes 文件夹用来放置urdf中引用的模型渲染文件,launch 文件夹用来放置相关的启动文件,config 文件夹用来放置rviz的配置文件。
创建一个机器人模型urdf文件
在urdf文件夹中新建一个文件,这里取名叫 myrobot_base.urdf 。然后写入以下内容来添加一个车体连杆:
<?xml version="1.0" ?> <!-- 声明这是一个xml文件 -->
<robot name="myrobot"> <!-- 根标签,同时定义机器人名称 -->
<link name="base_link"> <!-- 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"/> <!-- 设置连杆颜色的rgba数值,a表示透明度,从0到1透明度逐渐下降 -->
</material>
</visual>
</link>
</robot>
设置rviz的配置文件
在rviz中可以使用其工具栏里的工具来将使用的配置信息保存为配置文件,也可以通过向config文件夹中添加配置文件来使用已有的配置文件。这里选择使用已有的配置文件。
创建一个用于显示模型的launch文件
通用格式为:
<launch>
<!-- 设置模型文件路径参数,其中myrobot_base.urdf为机器人模型文件-->
<param name="robot_description" textfile="$(find myrobot_description)/urdf/myrobot_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="state_publisher">
<!-- 运行rviz可视化界面,其中myrobot_urdf.rviz 为rviz的配置文件-->
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find myrobot_description)/config/myrobot_urdf.rviz" required="true" />
</launch>
其中,joint_state_publisher 节点发布的是每个joint(除了fixed类型)的状态,比如关节当前的角度。robot_state_publisher 节点发布的是 tf 关系。
下面我们运行launch文件来查看模型效果:
roslaunch myrobot_description display_myrobot_base_urdf.launch
效果如下:
其中,图中的每一个格子都是边长为 1m 的正方形,对应着模型文件中坐标位置的单位 1 。接下来,使用圆柱体在车体基础上创建车轮。如下,添加一个车轮连杆,以及两个连杆之间的关节:
<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"/> <!-- 指定旋转轴线,这里是y轴 -->
</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>
重新运行launch文件可以看到新添加的车轮:
为了直观的看到我们新添加的关节部分的坐标系和车体坐标系之间的关系,我们将 rviz 坐标模型列表栏中的RobotModel取消选定。效果如下:
我们也可以随意修改模型文件中的参数来查看会有什么样的效果,比如将关节定义中初始化的坐标位置x轴的数值修改为 0.3,可以看到轮子和关节已经偏移到车体外面去了。也就是说,我们在 urdf 文件中为每一个部件定义的参数都是相对参数,每个部件的坐标系都是相对于上一个部件来做变换的。
用相同的方式,我们可以为小车添加右轮。
<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>
再次运行launch文件,效果如下:
接下来,为机器人添加传感器,比如摄像头、激光雷达、Kinect。
<link name="camera_link">
<visual>
<origin xyz=" 0 0 0 " rpy="0 0 0" />
<geometry>
<box size="0.03 0.04 0.04" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="camera_joint" type="fixed">
<origin xyz="-0.17 0 0.10" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="camera_link"/>
</joint>
<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="black"/>
<color rgba="0 0 0 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="kinect_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 1.5708"/>
<geometry>
<mesh filename="package://mbot_description/meshes/kinect.dae" /> <!-- 加载了一个具体的Kinect纹理描述文件 -->
</geometry>
</visual>
</link>
<joint name="kinect_joint" type="fixed">
<origin xyz="0.15 0 0.11" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="kinect_link"/>
</joint>
添加了传感器的机器人如下图:
其中摄像头和激光雷达都是用简单的几何形状代替的,只有Kinect看起来与实物无二,这是因为我们在meshes文件夹当中放置了Kinect传感器的纹理描述文件 (kinect.dae),以及 kinect.jpg 和 kinect.tga 两张图片,并在urdf文件中加载了该纹理描述文件。这些相应的文件我们不用自己去创建,可以从ros相关的机器人模型中找到。这里的Kinect纹理信息是从turtlebot中找到的。
现在我们的机器人小车的模型已经比较完整了,只不过它暂时还动不起来,传感器也获取不了相应的数据。后面的内容我们会利用仿真软件让它动起来并且让传感器看到周围的环境。
最后,我们可以利用 urdf_to_graphiz 工具检查一下urdf模型的整体结构,注意,该命令需要进入urdf模型所在目录下运行:
urdf_to_graphiz myrobot_base.urdf
该命令会为我们生成 myrobot.gv 和myrobot.pdf 两个文件。前者为graphiz绘图工具能够读取的dot脚本语言文件,后者就不用介绍了。打开pdf文件如下:
图片中囊括了我们的机器人模型中所有的 link 和 joint 的关系。可以看到,我们所有的link都是通过一个单独的joint安装到base_link上的。每一个joint跟base_link之间有一个位姿转换关系。
OK,到这里为止,我们总算完成了urdf模型的建立。回顾整个模型建立的过程,我们会发现是如此的不科学!urdf文件中存在大量重复标签,以及参数设置内容。即使是机器人上相同的部件(比如轮子)也需要各自用单独的标签来描述,模型太过冗长。并且因为参数都在模型内部设置,导致我们很难去定位一个想要修改的模型参数,很不便于二次开发。另外也没有参数计算的功能,如果我们想要得知模型部件之间的位置关系需要我们手动计算等等很多多问题。。。这些问题在我们建立的机器人模型比较复杂的时候基本是不能让人接受的,好在ros提供了成熟的优化工具来解决这些问题。有关模型优化的内容留待下次介绍。