树莓派编译安装完整版本ROS

时间:2021-04-09 06:32:20


树莓派安装完整的ROS并不容易,博主东方赤龙花费了数天总结了安装编译的经验,否则很容易走弯路。

ROS官方论坛里面有安装教程, 但是很容易出错。尤其是Desktop版的ROS!


1. 安装环

树莓派B+,  ROS Indigo, 16G TF卡,Raspbian OS


2. 安装装备

2.1 准备ROS代码仓库

  1. sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu wheezy main" > /etc/apt/sources.list.d/ros-latest.list'  
  2. wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -O - | sudo apt-key add -  

确保Raspbian OS 已经更新

  1. sudo apt-get update  
  2. sudo apt-get upgrade  

2.2 安装 bootstrap 依赖

  1. sudo apt-get install python-setuptools  
  2. sudo easy_install pip  
  3. sudo pip install -U rosdep rosinstall_generator wstool rosinstall  

2.3 初始化 rosdep

  1. sudo rosdep init  
  2. rosdep update  

3. 编译ROS

3.1 创建workspace来编译ROS

  1. mkdir ~/ros_catkin_ws  
  2. cd ~/ros_catkin_ws  

接下来下载ROS代码,ROS 提供了两个代码包

1)ROS-Comm: 这是官方推荐的,但只包含基本的ROS通讯功能,没有navigation, TF, action, RViz等 GUI 工具

2) Desktop: 完整的ROS 功能,适合所有机器人应用。我们选择安装这个。


下面的命令下载并生产编译package列表,会很花时间

  1. rosinstall_generator desktop --rosdistro indigo --deps --wet-only --exclude roslisp --tar > indigo-desktop-wet.rosinstall  
  2. wstool init -j8 src indigo-desktop-wet.rosinstall  

如果中途下载中断,下面的命令会接着下载:

  1. wstool update -j 4 -t src  

3.2 解决依赖

libconsole-bridge-dev, liburdfdom-headers-dev, liburdfdom-dev, liblz4-dev, 和 collada-dom-dev 这五个包并不在Raspbian里面,需要手工编译下载

创建新目录下载这5个包

  1. <span style="font-size:18px;">mkdir ~/ros_catkin_ws/external_src  
  2. sudo apt-get install checkinstall cmake</span>  

安装libconsole-bridge-dev:

  1. <span style="font-size:18px;">cd ~/ros_catkin_ws/external_src  
  2. sudo apt-get install libboost-system-dev libboost-thread-dev  
  3. git clone https://github.com/ros/console_bridge.git  
  4. cd console_bridge  
  5. cmake .  
  6. sudo checkinstall make install</span>  
当checkinstall询问是否改变安装选项时,选择[2],将名字从"console-bridge" 改为 "libconsole-bridge-dev"

接下来的两个问题连续选择'n', 否则会编译出错


安装liblz4-dev

  1. <span style="font-size:18px;">$ cd ~/ros_catkin_ws/external_src  
  2. $ wget http://archive.raspbian.org/raspbian/pool/main/l/lz4/liblz4-1_0.0~r122-2_armhf.deb  
  3. $ wget http://archive.raspbian.org/raspbian/pool/main/l/lz4/liblz4-dev_0.0~r122-2_armhf.deb  
  4. $ sudo dpkg -i liblz4-1_0.0~r122-2_armhf.deb liblz4-dev_0.0~r122-2_armhf.deb</span>  


安装liburdfdom-headers-dev

  1. <span style="font-size:18px;">$ cd ~/ros_catkin_ws/external_src  
  2. $ git clone https://github.com/ros/urdfdom_headers.git  
  3. $ cd urdfdom_headers  
  4. $ cmake .  
  5. $ sudo checkinstall make install</span>  

当checkinstall询问是否改变安装选项时,选择[2],将名字从"urdfdom-headers" 改为 "liburdfdom-headers-dev"

接下来的两个问题连续选择'n', 否则会编译出错


安装liburdfdom-dev

  1. <span style="font-size:18px;">$ cd ~/ros_catkin_ws/external_src  
  2. $ sudo apt-get install libboost-test-dev libtinyxml-dev  
  3. $ git clone https://github.com/ros/urdfdom.git  
  4. $ cd urdfdom  
  5. $ cmake .  
  6. $ sudo checkinstall make install</span>  

当checkinstall询问是否改变安装选项时,选择[2],将名字从"urdfdom" 改为 "liburdfdom-dev"

接下来的两个问题连续选择'n', 否则会编译出错


安装collada-dom-dev

  1. <span style="font-size:18px;">$ cd ~/ros_catkin_ws/external_src  
  2. $ sudo apt-get install libboost-filesystem-dev libxml2-dev  
  3. $ wget http://downloads.sourceforge.net/project/collada-dom/Collada%20DOM/Collada%20DOM%202.4/collada-dom-2.4.0.tgz  
  4. $ tar -xzf collada-dom-2.4.0.tgz  
  5. $ cd collada-dom-2.4.0  
  6. $ cmake .  
  7. $ sudo checkinstall make install</span>  
当checkinstall询问是否改变安装选项时,选择[2],将名字从"collada-dom" 改为 "collada-dom-dev"

接下来的两个问题连续选择'n', 否则会编译出错


3.3 通过rosdep解决依赖

  1. <span style="font-size:18px;">$ cd ~/ros_catkin_ws  
  2. $ rosdep install --from-paths src --ignore-src --rosdistro indigo -y -r --os=debian:wheezy</span>  
上面命令会便利所有的package并递归安装依赖

3.4 开始编译ROS

$ sudo ./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release --install-space /opt/ros/indigo


常见错误:

编译过程中会编译180多个package并同时安装在/opt/ros/indigo目录,经常会报出 'urdf' 'collada_parser‘ ’collada_urdf' 这几个包出错而中断安装。 解决办法就是将这三个包从目录 ~/ros_catkin_ws/src/robot_model 目录中移出,再重新编译整个ROS. 最后单独建立一个workspace,将三个包放到src/robot_model 目录中。 再执行下面命令:

sudo ./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release --install-space /opt/ros/indigo

如果仍然报错,则需要重新一个一个安装 步骤3.2中的 liburdfdom-dev,liblz4-dev, 和collada-dom-dev 包,每安装一个重新编译一遍,成功编译的包可以删除,直到最后都编译成功。


四轮机器人线性速率、角速度和电机PWM线性关系的定量分析                          



[+]

  1. ROS导航机器人驱动功能分析
    1. 1 ROS - *驱动
    2. 2  *驱动 - ROS 
    1. PWM 线性速度左右*速度和角速度的关系
    2. 左右*速度和PID控制
    3. PWM与*速度实验数据分析
    4. 左右轮速度差与角速度实验数据分析
    1. 带方向的线性速度和角速度

ROS中的机器人导航需要底层*驱动十分精确的对行进的速率和角速度进行控制。本文根据大量的实验数据进行图形化分析,找出线性速率、角速度和电机PWM线性关系,并得出计算公式。


1. ROS导航机器人驱动功能分析

ROS的navigation包内容十分丰富,并封装完整。开发机器人其实就是开发*驱动与ROS 进行通讯。这是一个双向通讯:

1.1 ROS -> *驱动

ROS 在cmd_vel这个主题上发布Twist消息,这个消息包含的就是机器人的期望前进速度和转向速度。*驱动必须要精确的控制电机同时要以某个速度和某个角速度前进或后退。角速度就代表拐弯的弯度.

使用ctrl + alt + t 打开一个新的终端以后,输入如下命令,就可以查看Twist的消息类型了。

  1. rosmsg show geometry_msgs/Twist  
树莓派编译安装完整版本ROS

Twist消息的结构,其中linear 的x就是代表前进方向的速度,单位为m/s。angular 的z就代表机器人的绕中心旋转的角速度,单位为 弧度/s (rad/s)。本文要研究这两个速度的关系。其余的4个成员可以忽略,它们对平面上的4轮机器人来说没有实际意义.

1.2  *驱动 -> ROS 

电机执行的效果: 线性速度,角速度和机器人现在地图上的坐标需要实时反馈给ROS. 这是通过向odom主题发送nav_msgs/Odometry导航消息,报告角速度,线速度和巡航角度

这部分请参照我的文章:ROS 学习系列 -- RViz中移动机器人来学习 URDF,TF,base_link, map,odom和odom 主题的关系

2. PWM, 线性速度,左右*速度和角速度的关系

机器人线性速度: RobotV

左右轮速度:       LeftWheelV   RightWheelV

角速度:             YawRate

  1. RobotV  = ( LeftWheelV + RightWheelV ) / 2                               //公式1  
  2. YawRate =  delta_t * abs( LeftWheelV - RightWheelV )                     //公式2  

PWM 与*的关系:

  1. xxxxWheelV = PWM *factor + offset                                        //公式3  

在实际运行中发现,左右轮的性能因为硬件有所差异,所以 delta_t, factor, offset 对左右两边来讲是不一样的。所以它们各有一套参数。这些参数的确定是要通过大量的数据采集后进行分析得出来的。下面我们讨论如何确定公式2,3中的参数

3. 左右*速度和PID控制

PWM与*转速的关系并不是不变的,当电池放电一段时间后,要增大factor的值才能达到期望的速度。这就是PID的作用,PID要求给一个初始PWM值,上面的公式3就可以算出这个初始值,剩下就交给反馈调整了, ROS有个pid类。


4. PWM与*速度实验数据分析

PWM从20至100的区间平均取10个值,跑10轮。每轮前行1米,然后后退1米,记录前进后退两个速度。左右两轮各一套数据,每次这样得到了4个数据样本,总共40个,图表分析如下:

树莓派编译安装完整版本ROS


1。左上图 Forward left wheel 前行左轮

2。左下图 Backward left wheel 后退左轮

3。右上图 Forward right wheel 前行右轮

4。右下图 Backward right wheel 后退右轮

横坐标是PWM值,纵坐标是速度(m/s)

采样数据的结果非常完美,标准的线性关系图。同时也能看出来左右两轮确实有不一致的地方,但是前进后退的数据很统一。这样就可以测出factor, offset两个参数,左右两轮个一套。

5.左右轮速度差与角速度实验数据分析

公式2的分析比较复杂,但是一旦确定就不变了,除非改变环境。分析出delta_t

转弯分为10种类型:

1,左轮不转,右轮PWM从20至100的区间平均取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

2,右轮不转,左轮PWM从20至100的区间平均取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

3,左轮比右轮转的慢,速度差逐渐加大取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

4,右轮比左轮转的慢,速度差逐渐加大取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

5,左轮向前,右轮向后,速度差逐渐加大取10个值,顺时针跑10轮。每轮跑360度,测出角速度和线速度。

6,   左轮不转,右轮PWM从20至100的区间平均取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

7,右轮不转,左轮PWM从20至100的区间平均取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

8,左轮比右轮转的慢,速度差逐渐加大取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

9,右轮比左轮转的慢,速度差逐渐加大取10个值,向跑10轮。每轮跑360度,测出角速度和线速度。

10,左轮向后,右轮向前,速度差逐渐加大取10个值,逆时针跑10轮。每轮跑360度,测出角速度和线速度。


1-4为前行,6-9为后行。转速都是逐渐加强。其中旋转角度的测量是通过电子罗盘HMC5883L 测算出来的,该芯片的干扰校准见我的文章 HMC5883L 电子指南针用树莓派进行磁场干扰过滤 校准


图表分析如下:

树莓派编译安装完整版本ROS

左边图表从上向下对应旋转编号为 1,2,3,4,5    

右边图表从上向下对应旋转编号为 6,7,8,9,10

横坐标为左右两轮速度差的绝对值,纵坐标为角速度,观察图像得出下面结论:

1。很明显前进和后退比例系数(斜率)不一样

2。它们向左向下的延长线都是穿过原点的,就是说速度差为0时,没有旋转,这个跟实际很匹配

3。左右两轮转弯性能无差异

4。第3,4行数据点向上平移到1,2行数据点,两组数据可以完全拼接成一条线保持相同的斜率


根据上面的数据可以推断出,公式2中的delta_t在前行和后退时是不一样的,10类转弯可以归纳为2种了。

6.带方向的线性速度和角速度

在ROS中,线性速度向后是负值,向前是正值。角速度方向的规定如图,它符合右手原则,右图逆时针方向就是正值,顺时针为负值。就是上面的左转为正,右转为副。

树莓派编译安装完整版本ROS                树莓派编译安装完整版本ROS

我们把方向考虑到公式1,2,3中,推导出代码可用的公式从而算出左右轮的速度 LeftWheelV,  RightWheelV, 然后交给pid到树莓派执行。从而达到精确的同时控制线性速度和角速度。其中RobotV 和 YawRate是ROS下发的命令,程序运行时就是已知量。

  1. RobotV  = ( LeftWheelV + RightWheelV ) / 2                               //公式1  
  2. YawRate =  delta_t * abs( LeftWheelV - RightWheelV )                     //公式2  

当 LeftWheelV 大于 RightWheelV时 是右转,此时YawRate为负,反之为正。公式2中的abs决定值可以去掉了,变成公式4

  1. YawRate =  delta_t * ( RightWheelV - LeftWheelV )                        //公式4  
  2.   
  3. YawRate / delta_t = RightWheelV - LeftWheelV  

 
 

公式1不变,简单变换一下

  1. RobotV  = ( LeftWheelV + RightWheelV ) / 2                               //公式1  
  2.   
  3. RobotV * 2 = LeftWheelV + RightWheelV  

 
 

合并公式1 和 4,初中的二元一次方程,得出:

  1. YawRate / delta_t + RobotV * 2 = RightWheelV * 2          
  2.               两边除2  
  3. YawRate / delta_t / 2 + RobotV = RightWheelV     
  4.               左右互换  
  5. RightWheelV = RobotV + YawRate / delta_t / 2   

 
 
 
 
 
 
带入公式1 
 
  1. LeftWheelV  = RobotV * 2 - ( YawRate / delta_t / 2 + RobotV ) = RobotV - YawRate / delta_t / 2  
  2.                 
 
 
 
 
 
 这里的delta_t取值要根据第5段的结论来定,根据 RobotV 正负值选取。这样左右两个*的速度大小和方向都可以计算出来了。但是要注意这里的YawRate必须是弧度制。 
 

Ros 的base_control 的C++代码

  1. #include "ros/ros.h"  
  2. #include "std_msgs/String.h"  
  3. #include <wiringPi.h>  
  4. #include <sstream>  
  5. #include "boost/shared_ptr.hpp"  
  6. #include <geometry_msgs/Twist.h>  
  7. #include "RunVelocity.h"  
  8.   
  9. boost::shared_ptr<RunVelocity> m_leftwheel;  
  10. boost::shared_ptr<RunVelocity> m_rightwheel;  
  11.   
  12.   
  13.   
  14. void cmd_vel_callback(const geometry_msgs::Twist& vel_cmd)  
  15. {  
  16.      double  LeftWheelV, RightWheelV;  
  17.      double RobotV = vel_cmd.linear.x;  
  18.      double YawRate = vel_cmd.angular.z;  
  19.      double delta_t = RobotV > 0 ? 5.9 : 4.45;  
  20.      RightWheelV = RobotV + YawRate / delta_t / 2 ;  
  21.      LeftWheelV = RobotV - YawRate / delta_t / 2 ;  
  22.      m_leftwheel->RunSpeedCommand(LeftWheelV);  
  23.      m_rightwheel->RunSpeedCommand(RightWheelV);  
  24. }  
  25.   
  26.   
  27. int main(int argc, char **argv)  
  28. {  
  29.     ros::init(argc, argv, "base_control");  
  30.     ros::NodeHandle n;  
  31.   
  32.     ros::Subscriber cmd_vel_sub = n.subscribe("/cmd_vel", 10, cmd_vel_callback);  
  33.           
  34.         // 树莓派gpio控制*  
  35.         wiringPiSetup();  
  36.   
  37.     int left_forward, left_backward, right_forward, right_backward;  
  38.     n.param("left_forward", left_forward, 1);  
  39.     n.param("left_backward", left_backward, 4);  
  40.     n.param("right_forward", right_forward, 6);  
  41.     n.param("right_backward", right_backward, 5);  
  42.       
  43.     m_leftwheel = boost::shared_ptr<RunVelocity>(new RunVelocity(n, left_forward, left_backward));  
  44.     m_rightwheel = boost::shared_ptr<RunVelocity>(new RunVelocity(n, right_forward, right_backward));  
  45.     ROS_INFO("ready to run on /cmd_vel");     
  46.   
  47.     ros::spin();  
  48. }  

HMC5883L 电子指南针用树莓派进行磁场干扰过滤 校准                          

本文适合所有的电子指南针校准,不仅限于 HMC5883L.  硬件的连接和树莓派的搭建以后再写。

本文仅限于固定强度和方向磁场干扰的过滤,例如机器人自身其它设备产生的磁场。但对于环境如扩音器喇叭等磁场干扰无效.

 

首先进行X Y 方向的校准,将芯片平放固定到一个水平面,这时大地磁场是近似平行XY平面的,绕Z轴慢速的转动XY平面,电子指南针HMC5883L会测量出圆周内大地磁场的强度+固定干扰磁场。而固定磁场的强度就是所有测量值的平均值。

 

[python] view plain copy print ?
  1. #!/usr/bin/python  
  2. import smbus  
  3. import time  
  4. import math  
  5.   
  6. bus = smbus.SMBus(0)  
  7. address = 0x1e  
  8.   
  9.   
  10. def read_byte(adr):  
  11.     return bus.read_byte_data(address, adr)  
  12.   
  13. def read_word(adr):  
  14.     high = bus.read_byte_data(address, adr)  
  15.     low = bus.read_byte_data(address, adr+1)  
  16.     val = (high << 8) + low  
  17.     return val  
  18.   
  19. def read_word_2c(adr):  
  20.     val = read_word(adr)  
  21.     if (val >= 0x8000):  
  22.         return -((65535 - val) + 1)  
  23.     else:  
  24.         return val  
  25.   
  26. def write_byte(adr, value):  
  27.     bus.write_byte_data(address, adr, value)  
  28.   
  29. write_byte(00b01110000# Set to 8 samples @ 15Hz  
  30. write_byte(10b00100000# 1.3 gain LSb / Gauss 1090 (default)  
  31. write_byte(20b00000000# Continuous sampling  
  32.   
  33. scale = 0.92  
  34.   
  35.   
  36. for i in range(0,500):  
  37.     x_out = read_word_2c(3)  
  38.     y_out = read_word_2c(7)  
  39.     z_out = read_word_2c(5)  
  40.       
  41.     bearing  = math.atan2(y_out, x_out)   
  42.     if (bearing < 0):  
  43.         bearing += 2 * math.pi  
  44.       
  45.     print x_out, y_out, (x_out * scale), (y_out * scale)  
  46.     time.sleep(0.1)  


 

运行上面的程序,在50秒内不停地旋转装有芯片的水平面,360度的缓慢旋转,一定要保证水平面稳定,否则会得到很多奇怪的干扰数据。

[python] view plain copy print ?
  1. sudo ./compass-test.py > compass-plot.dat  


 

将输出结果导入到compas-plot.dat文件里面。接下来我们用GNUPlot来看一下数据结果,以每对儿X Y为坐标的图形效果。

 

[python] view plain copy print ?
  1. set terminal wxt persist size 800,800 background'#000000'   
  2.   
  3.    
  4.   
  5.     set style line99 linecolor rgb "#ffffff" linetype 0 linewidth 2  
  6.   
  7.     set key topright textcolor linestyle 99   
  8.   
  9.     set gridlinestyle 99  
  10.   
  11.     set borderlinestyle 99  
  12.   
  13.    
  14.   
  15.     plot filenameusing 1:2 title "Raw compass values" linecolor rgb "green"   


 

将上面命令文本保存到 gnuplot-compass.plg并执行下面的命令,可以看到图形:

[python] view plain copy print ?
  1. gnuplot -e "filename='compass-plot.dat'"gnuplot-compass.plg  

树莓派编译安装完整版本ROS

这时我们可以看到图形的圆心不是在 0,0 点

把上面程序的for循环内容更换成下面的代码,运行50秒不停地手动旋转水平面,

[python] view plain copy print ?
  1. minx = 0  
  2. maxx = 0  
  3. miny = 0  
  4. maxy = 0  
  5.   
  6. for i in range(0,500):  
  7.     x_out = read_word_2c(3)  
  8.     y_out = read_word_2c(7)  
  9.     z_out = read_word_2c(5)  
  10.       
  11.       
  12.     if x_out < minx:  
  13.         minx=x_out  
  14.       
  15.     if y_out < miny:  
  16.         miny=y_out  
  17.       
  18.     if x_out > maxx:  
  19.         maxx=x_out  
  20.       
  21.     if y_out > maxy:  
  22.         maxy=y_out  
  23.       
  24.     #print x_out, y_out, (x_out * scale), (y_out * scale)  
  25.     time.sleep(0.1)  
  26.   
  27. print "minx: ", minx  
  28. print "miny: ", miny  
  29. print "maxx: ", maxx  
  30. print "maxy: ", maxy  
  31. print "x offset: ", (maxx + minx) / 2  
  32. print "y offset: ", (maxy + miny) / 2  


这一次会输出下面的值:

[python] view plain copy print ?
  1. minx:  -216  
  2.   miny:  -193  
  3.   maxx:  197  
  4.   maxy:  213  
  5.   x offset:  -10  
  6.   y offset:  10  

 

把输出的偏移加到程序当中,第一段程序的读取值的代码换成下面:

[python] view plain copy print ?
  1. x_offset = -10  
  2. y_offset = 10  
  3. x_out = (read_word_2c(3) - x_offset) * scale  
  4. y_out = (read_word_2c(7) - y_offset) * scale  
  5. z_out = (read_word_2c(5)) * scale  


 

 Z 方向的校准同上面的方法,只是要把XZ平面固定到水平面上做同样的事情而已。

网上有好多文章介绍如何从HMC5883L电子罗盘中获取旋转的角度。但是其中有个重要的参数Declination并没有太多介绍,而且该参数是必要的。

[python] view plain copy print ?
  1. rom i2clibraries import i2c_hmc5883l  
  2.   
  3. hmc5883l = i2c_hmc5883l.i2c_hmc5883l(0)  
  4.   
  5. hmc5883l.setContinuousMode()  
  6. hmc5883l.setDeclination(9,54)  
  7.   
  8. print(hmc5883l)  



罗盘要确定方向北面是靠磁场来确定的,北向有两个标准:

 World Magnetic Model (WMM)  是磁场的正北

 International Geomagnetic Reference Field (IGRF)   也称为Ture North,是地图上的正北

但事实上这两个北向是有偏差的,这个偏差就叫Declination,就是下图中的橙色扇区角度。它的值随着地域和时间而变化,我们可以访问这里来查询这个值。英文介绍视频,它们都是由美国国家地理数据中心提供的,如果打不开,只能自己想办法了:)

树莓派编译安装完整版本ROS




下面的图像显示整个地球范围内,Declination分布的规律,每一个等高线上的Declination 是相同的,中间的黄绿线是0,也就是磁场北和地理北完全重合。红线为正值,蓝线为负值。

树莓派编译安装完整版本ROS



下面是网站页面,可以看到北京的正北和磁场的北是有个偏角的,这就是Declination.时间是2015 - 02 -02

树莓派编译安装完整版本ROS


RViz中移动机器人来学习 URDF,TF,base_link, map,odom和odom 主题的关系                         

前面已经介绍了如何使用URDF建造机器人小车并显示在Rviz的仿真环境里面,但是小车是静止的。下面介绍如何让它在Rviz里面动起来,并理清URDF,TF 和 odom 的关系。

1. ROS中base_link, odom, fixed_frame, target_frame和虚拟大地图map的关系

一般在urdf文件中都要定义base_link,它代表了机器人的主干,其它所有的frame都是相对于base_link定义并粘在一起的。它们一起相对于大地图map移动,让机器人移动就是向tf发布 geometry_msgs::TransformStamped 消息通知ros  base_linke相对于map的tf转换关系。先看一下这几个概念在ros中的定义

map

map是虚拟世界中的固定frame, 它的Z轴指向正上方,也就是天空。一个时间点上移动机器人的姿态相对于map不应该出现明显的漂移。如果一个map是不连续稳定的那就意味着机器人的位置在任何一个时间点上都会是变化的。

一般激光地位仪等设备会连续的计算map的位置因而造成它的不稳定性,这也使它不能成为一个好的参照体frame.


odom

odom是一个很好的固定世界参照frame.机器人的姿态相对odom而言是随时间是经常可变,所以在长远中它不是一个好的全局参照系。但在某一时间点而言它相对机器人的位置是不变的。

典型的 odom frame 是通过运动源来计算出来的, 例如*运动,视觉偏移等.

odom frame 的准确性使它在局部参照系中是很有用的。但是不适合作全局参照frame.

各个Frames的关系

frame之间是按树状结构组织的。所以每个frame只有一个父节点和任意多个子节点。 上述几个frame的关系:

map --> odom --> base_link

Frame Authorities

odombase_link的坐标转换是从运动源计算出来广播的。

mapbase_link的坐标转换是被定位模块计算出来的. 但定位模块并不发布mapbase_link的转换. 相反它先接受从odombase_link的转换, 再计算并广播mapodom的位置转换关系。

fixed_frame

RViz中认定的大世界就是fixed_frame

target_frame

Rviz中视觉跟踪的frame是 target_frame


2. RViz如何动态确定各个frame之间的坐标转换

先看一下启动RViz的launch文件,里面定要了服务于RViz的参数和node

  1. <launch>  
  2.     <arg name="model" />  
  3.     <arg name="gui" default="False" />  
  4.     <param name="robot_description" textfile="$(arg model)" />  
  5.     <param name="use_gui" value="$(arg gui)"/>  
  6.     <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />  
  7.     <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />  
  8.     <node name="rviz" pkg="rviz" type="rviz" args="-d $(find sp1s)/urdf.rviz" required="true" />  
  9. </launch>  
"robot_description" 参数定义了urdf文件的路径,它被 robot_state_publisher节点使用。该节点解析urdf文件后将各个frame的状态发布给tf. 因此在rviz里面就看到各个frame(link)之间的tf转换显示OK.否则会显示warning.


"joint_state_publisher"节点获取urdf里面定义的rotate link并发布坐标转换给tf.否则会显示warning. 注意: "joint_state_publisher" 是Python写的,只支持ascii编码,不支持Unicode,如果发现RViz报告下面错误:

"No transform from [front_left] to [base_link]"

那就是因为urdf文件是Unicode编码的。

3. geometry_msgs/TransformStamped 消息的作用和机构

geometry_msgs/TransformStamped  就是通知ROS 两个frame之间的tf转换关系。当两个frame之间关系经常变化,如*移动,手臂关节移动等,则需要编写node来实时发布。查看该消息结构:

  1. <span style="font-size:18px;">rosmsg show -r geometry_msgs/TransformStamped  
  2. # This expresses a transform from coordinate frame header.frame_id  
  3. # to the coordinate frame child_frame_id  
  4. #  
  5. # This message is mostly used by the   
  6. <a href="http://www.ros.org/wiki/tf">tf</a> package.   
  7. # See its documentation for more information.  
  8.   
  9. Header header  
  10. string child_frame_id # the frame id of the child frame  
  11. Transform <strong><span style="color:#FF0000;">transform</span></strong>  
  12.   
  13. </span>  
Transform 其中transform就是我们关心的 tf 转换关系,child_frame_id是"base_link", header.frame_id 则是'odom'. 查看geometry_msgs/transform:

  1. rosmsg show -r geometry_msgs/Transform  
  2. # This represents the transform between two coordinate frames in free space.  
  3.   
  4. Vector3 <strong><span style="color:#FF0000;">translation</span></strong>  
  5. Quaternion rotation  
  6.   
  7. rosmsg show -r Vector3  
  8. [geometry_msgs/Vector3]:  
  9. # This represents a vector in free space.   
  10.   
  11. float64 x  
  12. float64 y  
  13. float64 z   
这里的x, y, z 就定义了两个frame的tf转换关系。

4. RViz中如何定义 base_link 和 odom 之间的 tf 坐标转换

要移动机器人,就需要向tf发布geometry_msgs/TransformStamped 消息通知ros  base_linke相对于map的tf转换关系,也就是说告诉ROS运动的base_link相对于不动的odom位置偏移。这里的odom frame并不能定义在urdf文件里面,它是一个虚拟的。我们只需要RViz知道fixed_frame就是odom就可以了。所以在urdf.rviz文件中这样定义虚拟odom:


  1. <span style="font-size:18px;">Global Options:  
  2.     Background Color: 48; 48; 48  
  3.     Fixed Frame: <strong><span style="color:#FF0000;">/odom</span></strong>  
  4.     Target Frame: <Fixed Frame>  
  5.     Frame Rate: 30</span>  

如果现在启动 RViz来观察机器人:

  1. <span style="font-size:18px;">roslaunch sp1s display.launch model:=urdf/sp1s.urdf </span>  

肯定会得到错误警告,而且RViz中无法显示完整的机器人:

"No transform from [odom] to [base_link]"

这个错误很容易理解,没有任何地方定义odom和base_link之间的tf关系,他们之间是连续变化的,我们当然不能在任何地方写固定偏移量定义tf transform.


但是我们可以写一个node来不断的发送geometry_msgs/TransformStamped消息。被发送对象就是tf。 ROS官方有个实例完整代码如何发布odom到base_link的变换,代码实例直接拷贝并在本地编译。这个例子不断向odom主题发送消息更改odom与base_link之间的位移,位移的轨迹就是一个圆周。这个node名字叫 odom_publisher. 它其实做了两件事情:

1. 向 tf 发送geometry_msgs/TransformStamped 消息, 就是让机器人运动起来。

2. odom主题发送nav_msgs/Odometry导航消息,报告角速度,线速度和巡航角度。这部分代码相对本文来讲不是必须的。


同"joint_state_publisher" node一样,“ odom_publisher”需要在RViz启动之前启动,添加它的启动项。修改后的dispaly.launch文件:

  1. <launch>  
  2. <arg name="model" />  
  3. <arg name="gui" default="False" />  
  4. <param name="robot_description" textfile="$(arg model)" />  
  5. <param name="use_gui" value="$(arg gui)"/>  
  6.        <strong><span style="color:#FF0000;"><node name="odom_publisher" pkg="sp1s" type="odom_publisher" /></span></strong>  
  7.        <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />  
  8. <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />  
  9. <node name="rviz" pkg="rviz" type="rviz"  args="-d  $(find sp1s)/urdf.rviz" required="true"/>  
  10. lt;/launch></span>  


再次打开RViz:

  1. roslaunch sp1s display.launch model:=urdf/sp1s.urdf   

这次就看到机器人在地图空间中做规则的圆周运动了! 因为RViz收到了odom_publisher向tf发送的坐标转换内容。


4. RViz中观察移动轨迹

“ odom_publisher”中还向 odom主题发布了nav_msgs/Odometry导航消息,这样就可以在RViz中显示运动轨迹了。在RViz中点击 'add',选中Odometry,配置该dispaly的topic为 "odom" 就可以看到不断变化的运动轨迹了。这是因为nav_msgs/Odometry中包含了线速度,角速度和巡航角度,所以RViz可以显示出来。

树莓派编译安装完整版本ROS


5.odom frame和 odom  topic

这两个完全是不同的东西,很容易混淆。一个是地图上的一个参照系,一个是topic用来接收导航输入的。名字一样,纯属巧合!

使用urdf创建机器人模型在Rviz中3D观察 之二 joint 使用                          

我们接着上文  ROS 学习系列 -- 使用urdf创建机器人模型在Rviz中3D观察 之一 link使用继续完成创建带四个可以转动*的双层小车。

一 建立可以转动的joint

  1. <span style="font-size:18px;"><?xml version="1.0"?>  
  2. <robot name="sp1s">  
  3.   <link name="base_link">  
  4.     <visual>  
  5.       <geometry>  
  6.         <box size="0.27 .15 .003"/>  
  7.       </geometry>  
  8.       <material name="white">  
  9.          <color rgba="1 1 1 .5"/>  
  10.     </material>  
  11.     </visual>  
  12.   </link>  
  13.     
  14.   <link name="front_left">  
  15.     <visual>  
  16.       <geometry>  
  17.        <cylinder length=".025" radius="0.034"></cylinder>  
  18.       </geometry>  
  19.       <material name="yellow">  
  20.           <color rgba="1 1 0 1"/>  
  21.       </material>    
  22.     </visual>  
  23.   </link>  
  24.     
  25.   <joint name="base_to_front_left" type="<span style="color:#FF0000;"><strong>continuous</strong></span>">  
  26.     <origin rpy="1.57075  0  0" xyz="0.06  0.064  -0.011"/>  
  27.     <parent link="base_link"/>  
  28.     <child link="front_left"/>      
  29.     <axis xyz="0 0 1"/>  
  30.   </joint>  
  31. </robot></span>  

1. 将joint的类型改为"continuous" 这样front_left相对base_link就是一个转动的装置

2. 转动的位置就是由 origin定义的,它同时定义了tyer_front_left的新原点,之前它的原点默认就是base_link的原点

     xyz 是相对相对parant base_link原点的偏移, 它把*移动到了左前方。其中:

     z = -0.011m = 6.8cm/2-4.5cm,保证*是在地上的

       y = 0.064m = 15cm/2 - 2.5cm / 2 (*厚度) + offset

     pry 是分别以 x y z 轴为中心转动的角度(弧度制) 1.57075 就是90度。 围绕x轴旋转90度*就竖了起来。

3. axis重新定义转动轴在原点的方向,它是一个矢量,只指示方向,它们必须满足 x * x + y * y + z * z = 1   原点Z轴已经被旋转90度变成水平方向,所以这里定义Z轴方向为转动方向

树莓派编译安装完整版本ROS

   


二  重复添加四个*

其它三个*只有origin正负值的调整

  1. <span style="font-size:18px;"><?xml version="1.0"?>  
  2. <robot name="sp1s">  
  3.   <link name="base_link">  
  4.     <visual>  
  5.       <geometry>  
  6.         <box size="0.27 .15 .003"/>  
  7.       </geometry>  
  8.       <material name="white">  
  9.          <color rgba="1 1 1 .5"/>  
  10.     </material>  
  11.     </visual>  
  12.   </link>  
  13.     
  14.   <link name="tyer_front_left">  
  15.     <visual>  
  16.       <geometry>  
  17.        <cylinder length=".025" radius="0.034"></cylinder>  
  18.       </geometry>  
  19.       <material name="yellow">  
  20.           <color rgba="1 1 0 1"/>  
  21.       </material>    
  22.     </visual>  
  23.   </link>  
  24.   
  25.   <link name="tyer_front_right">  
  26.     <visual>  
  27.       <geometry>  
  28.        <cylinder length=".025" radius="0.034"></cylinder>  
  29.       </geometry>  
  30.       <material name="yellow">    
  31.           <color rgba="1 1 0 1"/>    
  32.       </material>    
  33.     </visual>  
  34.   </link>  
  35.     
  36.   <link name="tyer_back_left">  
  37.     <visual>  
  38.       <geometry>  
  39.        <cylinder length=".025" radius="0.034"></cylinder>  
  40.       </geometry>  
  41.       <material name="yellow">    
  42.           <color rgba="1 1 0 1"/>    
  43.       </material>    
  44.     </visual>  
  45.   </link>  
  46.     
  47.   <link name="tyer_back_right">  
  48.     <visual>  
  49.       <geometry>  
  50.        <cylinder length=".025" radius="0.034"></cylinder>  
  51.       </geometry>  
  52.       <material name="yellow">    
  53.           <color rgba="1 1 0 1"/>    
  54.       </material>    
  55.     </visual>  
  56.   </link>  
  57.     
  58.   <joint name="base_to_front_left" type="continuous">  
  59.     <origin rpy="1.57075  0  0" xyz="0.06  0.064  -0.011"/>  
  60.     <parent link="base_link"/>  
  61.     <child link="tyer_front_left"/>      
  62.     <axis xyz="0 0 1"/>  
  63.   </joint>  
  64.   
  65.   <joint name="base_to_front_right" type="continuous">  
  66.     <origin rpy="1.57075  0  0" xyz="0.06  -0.064  -0.011"/>  
  67.     <parent link="base_link"/>  
  68.     <child link="tyer_front_right"/>      
  69.     <axis xyz="0 0 1"/>  
  70.   </joint>  
  71.   
  72.   <joint name="base_to_back_left" type="continuous">  
  73.     <origin rpy="1.57075  0  0" xyz="-0.06  0.064  -0.011"/>  
  74.     <parent link="base_link"/>  
  75.     <child link="tyer_back_left"/>      
  76.     <axis xyz="0 0 1"/>  
  77.   </joint>  
  78.   
  79.   <joint name="base_to_back_right" type="continuous">  
  80.     <origin rpy="1.57075  0  0" xyz="-0.06  -0.064  -0.011"/>  
  81.     <parent link="base_link"/>  
  82.     <child link="tyer_back_right"/>      
  83.     <axis xyz="0 0 1"/>  
  84.   </joint>  
  85. </robot>  
  86. </span>  


树莓派编译安装完整版本ROS


三  添加四个柱子

上下层的四个角上是高3cm的柱子. 在上面urdf的基础上再添加下面的内容添加一个左前方的柱子。

  1. <span style="font-size:18px;">  <link name="pillar_front_left">  
  2.     <visual>  
  3.       <geometry>  
  4.        <cylinder length=".03" radius="0.0025"></cylinder>  
  5.       </geometry>  
  6.       <origin xyz="0 0 0.015"/>    
  7.       <material name="silver">    
  8.           <color rgba=".86 .86 .86 1"/>    
  9.       </material>  
  10.     </visual>  
  11.   </link>  
  12.   
  13.   <joint name="base_to_pillar_front_left" type="fixed">  
  14.     <origin xyz="0.13  0.07  0.0015"/>  
  15.     <parent link="base_link"/>  
  16.     <child link="pillar_front_left"/>  
  17.   </joint></span>  

1. 柱子不转动,所以joint的类型为 fixed

2. joint里面定义改变了柱子的原点为 xyz="0.13 0.07 0.0015" 是相对base_link的

3. link里面也定义了原点为xyz="0 0 0.015", 这里只是显示上的偏移,它相对于joint里面定义的原点,但并不改变joint里面定义的物理原点

4. 接下来重复加三个柱子,详见最后一段的urdf定义


四 添加上层

上层和base_link一样,它们之间通过fixed joint相连。在 Z 轴方向上偏移3cm。

  1. <link name="top_link">  
  2.   <visual>  
  3.     <geometry>  
  4.       <box size="0.27 .15 .003"/>  
  5.     </geometry>  
  6.     <material name="white">  
  7.        <color rgba="1 1 1 .5"/>  
  8.   </material>  
  9.   </visual>  
  10. </link>  
  11.   
  12. <joint name="base_to_top" type="fixed">  
  13.   <origin xyz="0  0  0.03"/>  
  14.   <parent link="base_link"/>  
  15.   <child link="top_link"/>  
  16. </joint>  


五  完整的urdf

  1. <?xml version="1.0"?>  
  2. <robot name="sp1s">  
  3.   <link name="base_link">  
  4.     <visual>  
  5.       <geometry>  
  6.         <box size="0.27 .15 .003"/>  
  7.       </geometry>  
  8.       <material name="white">  
  9.          <color rgba="1 1 1 .5"/>  
  10.     </material>  
  11.     </visual>  
  12.   </link>  
  13.     
  14.   <link name="tyer_front_left">  
  15.     <visual>  
  16.       <geometry>  
  17.        <cylinder length=".025" radius="0.034"></cylinder>  
  18.       </geometry>  
  19.       <material name="yellow">  
  20.           <color rgba="1 1 0 1"/>  
  21.       </material>    
  22.     </visual>  
  23.   </link>  
  24.   
  25.   <link name="tyer_front_right">  
  26.     <visual>  
  27.       <geometry>  
  28.        <cylinder length=".025" radius="0.034"></cylinder>  
  29.       </geometry>  
  30.       <material name="yellow">    
  31.           <color rgba="1 1 0 1"/>    
  32.       </material>    
  33.     </visual>  
  34.   </link>  
  35.     
  36.   <link name="tyer_back_left">  
  37.     <visual>  
  38.       <geometry>  
  39.        <cylinder length=".025" radius="0.034"></cylinder>  
  40.       </geometry>  
  41.       <material name="yellow">    
  42.           <color rgba="1 1 0 1"/>    
  43.       </material>    
  44.     </visual>  
  45.   </link>  
  46.     
  47.   <link name="tyer_back_right">  
  48.     <visual>  
  49.       <geometry>  
  50.        <cylinder length=".025" radius="0.034"></cylinder>  
  51.       </geometry>  
  52.       <material name="yellow">    
  53.           <color rgba="1 1 0 1"/>    
  54.       </material>    
  55.     </visual>  
  56.   </link>  
  57.     
  58.   <joint name="base_to_front_left" type="continuous">  
  59.     <origin rpy="1.57075  0  0" xyz="0.06  0.064  -0.011"/>  
  60.     <parent link="base_link"/>  
  61.     <child link="tyer_front_left"/>      
  62.     <axis xyz="0 0 1"/>  
  63.   </joint>  
  64.   
  65.   <joint name="base_to_front_right" type="continuous">  
  66.     <origin rpy="1.57075  0  0" xyz="0.06  -0.064  -0.011"/>  
  67.     <parent link="base_link"/>  
  68.     <child link="tyer_front_right"/>      
  69.     <axis xyz="0 0 1"/>  
  70.   </joint>  
  71.   
  72.   <joint name="base_to_back_left" type="continuous">  
  73.     <origin rpy="1.57075  0  0" xyz="-0.06  0.064  -0.011"/>  
  74.     <parent link="base_link"/>  
  75.     <child link="tyer_back_left"/>      
  76.     <axis xyz="0 0 1"/>  
  77.   </joint>  
  78.   
  79.   <joint name="base_to_back_right" type="continuous">  
  80.     <origin rpy="1.57075  0  0" xyz="-0.06  -0.064  -0.011"/>  
  81.     <parent link="base_link"/>  
  82.     <child link="tyer_back_right"/>      
  83.     <axis xyz="0 0 1"/>  
  84.   </joint>  
  85.   
  86.   <link name="pillar_front_left">  
  87.     <visual>  
  88.       <geometry>  
  89.        <cylinder length=".03" radius="0.0025"></cylinder>  
  90.       </geometry>  
  91.       <origin xyz="0 0 0.015"/>    
  92.       <material name="silver">    
  93.           <color rgba=".86 .86 .86 1"/>    
  94.       </material>  
  95.     </visual>  
  96.   </link>  
  97.   
  98.   <link name="pillar_front_right">  
  99.     <visual>  
  100.       <geometry>  
  101.        <cylinder length=".03" radius="0.0025"></cylinder>  
  102.       </geometry>  
  103.       <origin xyz="0 0 0.015"/>    
  104.       <material name="silver">    
  105.           <color rgba=".86 .86 .86 1"/>    
  106.       </material>  
  107.     </visual>  
  108.   </link>  
  109.   
  110.   <link name="pillar_back_left">  
  111.     <visual>  
  112.       <geometry>  
  113.        <cylinder length=".03" radius="0.0025"></cylinder>  
  114.       </geometry>  
  115.       <origin xyz="0 0 0.015"/>    
  116.       <material name="silver">    
  117.           <color rgba=".86 .86 .86 1"/>    
  118.       </material>  
  119.     </visual>  
  120.   </link>  
  121.   
  122.   <link name="pillar_back_right">  
  123.     <visual>  
  124.       <geometry>  
  125.        <cylinder length=".03" radius="0.0025"></cylinder>  
  126.       </geometry>  
  127.       <origin xyz="0 0 0.015"/>    
  128.       <material name="silver">    
  129.           <color rgba=".86 .86 .86 1"/>    
  130.       </material>  
  131.     </visual>  
  132.   </link>  
  133.   
  134.   <joint name="base_to_pillar_front_left" type="fixed">  
  135.     <origin xyz="0.13  0.07  0.0015"/>  
  136.     <parent link="base_link"/>  
  137.     <child link="pillar_front_left"/>  
  138.   </joint>  
  139.   
  140.   <joint name="base_to_pillar_front_right" type="fixed">  
  141.     <origin xyz="-0.13  0.07  0.0015"/>  
  142.     <parent link="base_link"/>  
  143.     <child link="pillar_front_right"/>  
  144.   </joint>  
  145.   
  146.   <joint name="base_to_pillar_back_left" type="fixed">  
  147.     <origin xyz="0.13  -0.07  0.0015"/>  
  148.     <parent link="base_link"/>  
  149.     <child link="pillar_back_left"/>  
  150.   </joint>  
  151.   
  152.   <joint name="base_to_pillar_back_right" type="fixed">  
  153.     <origin xyz="-0.13  -0.07  0.0015"/>  
  154.     <parent link="base_link"/>  
  155.     <child link="pillar_back_right"/>  
  156.   </joint>  
  157.   
  158.   <link name="top_link">  
  159.     <visual>  
  160.       <geometry>  
  161.         <box size="0.27 .15 .003"/>  
  162.       </geometry>  
  163.       <material name="white">  
  164.          <color rgba="1 1 1 .5"/>  
  165.     </material>  
  166.     </visual>  
  167.   </link>  
  168.   
  169.   <joint name="base_to_top" type="fixed">  
  170.     <origin xyz="0  0  0.03"/>  
  171.     <parent link="base_link"/>  
  172.     <child link="top_link"/>  
  173.   </joint>  
  174. </robot>  


树莓派编译安装完整版本ROS