课程4.标定结果测试及激光传感器安装与显示 - TonyRobotics/trbase GitHub Wiki
本节将对上节标定的运动参数进行多方位测试,将所有参数放到launch文件中进行修改调试。待参数满足要求精度后,即可作为底盘的驱动程序。 然后,我们将列举几款常用的激光传感器,并对其中一款进行安装和显示。
修改trd_driver.py文件,将所有参数用ROS的get_param()方法获取:
self.wheel_diameter = rospy.get_param('~wheel_diameter', default=0.1)
self.base_width = rospy.get_param('~base_width', default=0.5)
self.encoder_ticks_per_rev = rospy.get_param('~encoder_ticks_per_rev', default=5000)
self.linear_coef = rospy.get_param('~linear_coef', default=800.0)
self.angular_coef = rospy.get_param('~angular_coef', default=200.0)
v1 = self.linear_coef*msg.linear.x
v2 = self.linear_coef*msg.linear.x
v1 += self.angular_coef*msg.angular.z
v2 -= self.angular_coef*msg.angular.z
修改后内容如下:
#!/usr/bin/python2
# coding: utf-8
import rospy
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Twist
import tf
import serial
import time
import math
from threading import Lock
lock = Lock()
class TrdDriver():
def __init__(self, serialport, baudrate):
self.pub = rospy.Publisher('/odom', Odometry,
queue_size=100)
self.sub = rospy.Subscriber('/cmd_vel', Twist,
self.vel_callback,
queue_size=1)
self.ser = serial.Serial(serialport, baudrate)
self.wheel_diameter = rospy.get_param('~wheel_diameter', default=0.1)
self.base_width = rospy.get_param('~base_width', default=0.5)
self.encoder_ticks_per_rev = rospy.get_param('~encoder_ticks_per_rev', default=5000)
self.linear_coef = rospy.get_param('~linear_coef', default=800.0)
self.angular_coef = rospy.get_param('~angular_coef', default=200.0)
self.first_flag = True
self.encoder1_offset = 0
self.encoder2_offset = 0
self.encoder1 = 0
self.encoder2 = 0
self.encoder1_prev = 0
self.encoder2_prev = 0
self.x = 0
self.y = 0
self.theta = 0
self.odom = Odometry()
self.odom.header.frame_id = 'odom'
self.odom.child_frame_id = 'base_link'
self.time_prev = rospy.Time.now()
def send(self, cmd):
self.ser.write(cmd)
def read_buffer(self):
time.sleep(0.05)
res = ''
while self.ser.inWaiting() > 0:
res += self.ser.read(1)
res = bytearray(res)
if res[0:2]=='?C' and res[-1]==13:
self.encoder1 = int(res.split(':')[0].split('=')[1])
self.encoder2 = -int(res.split(':')[1][:-1])
if self.first_flag:
self.encoder1_offset = self.encoder1
self.encoder2_offset = self.encoder2
self.first_flag = False
self.encoder1 -= self.encoder1_offset
self.encoder2 -= self.encoder2_offset
print('encoder', self.encoder1, self.encoder2)
return res
def get_encoder(self):
cmd = '?C\r'
self.send(cmd)
def set_speed(self, v1, v2):
print('{} Set Speed: {} {}'.format(time.time(), v1, v2))
cmd = '!M {} {}\r'.format(v1, v2)
self.send(cmd)
def update_odom(self):
encoder1 = self.encoder1
encoder2 = self.encoder2
time_current = rospy.Time.now()
time_elapsed = (time_current - self.time_prev).to_sec()
self.time_prev = time_current
dleft = math.pi * self.wheel_diameter * \
(encoder1 - self.encoder1_prev) / self.encoder_ticks_per_rev
dright = math.pi * self.wheel_diameter * \
(encoder2 - self.encoder2_prev) / self.encoder_ticks_per_rev
self.encoder1_prev = encoder1
self.encoder2_prev = encoder2
d = (dleft + dright) / 2
dtheta = (dright - dleft) / self.base_width
if d != 0:
dx = math.cos(dtheta) * d
dy = -math.sin(dtheta) * d
self.x += dx*math.cos(self.theta)-dy*math.sin(self.theta)
self.y += dx*math.sin(self.theta)+dy*math.cos(self.theta)
self.theta += dtheta
self.odom.header.stamp = time_current
self.odom.pose.pose.position.x = self.x
self.odom.pose.pose.position.y = self.y
q = tf.transformations.quaternion_from_euler(0,0,self.theta)
self.odom.pose.pose.orientation.x = q[0]
self.odom.pose.pose.orientation.y = q[1]
self.odom.pose.pose.orientation.z = q[2]
self.odom.pose.pose.orientation.w = q[3]
self.odom.twist.twist.linear.x = d / time_elapsed
self.odom.twist.twist.angular.z = dtheta / time_elapsed
def run(self):
rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 读编码器值
lock.acquire()
self.get_encoder()
self.read_buffer()
lock.release()
# 更新计算并更新里程信息
self.update_odom()
# 发布里程信息
self.pub.publish(self.odom)
rate.sleep()
def vel_callback(self,msg):
lock.acquire()
v1 = self.linear_coef*msg.linear.x
v2 = self.linear_coef*msg.linear.x
v1 += self.angular_coef*msg.angular.z
v2 -= self.angular_coef*msg.angular.z
self.set_speed(v1, -v2)
self.read_buffer()
lock.release()
if __name__=='__main__':
rospy.init_node('trd_driver001')
serialport = rospy.get_param('~serialport', default='/dev/ttyUSB0')
baudrate = rospy.get_param('~baudrate', default=115200)
trd_driver = TrdDriver(serialport, baudrate)
trd_driver.run()
然后,修改trd_control.launch文件,加入对应参数数值。 注意,此处底盘的轮径为0.07米,轮距为0.4米,编码器为4000线,控制速度线速度系数和角速度系数分别为800和200。
<?xml version="1.0"?>
<launch>
<arg name="serialport" default="/dev/ttyUSB0" />
<arg name="baudrate" default="115200" />
<arg name="wheel_diameter" default="0.07" />
<arg name="base_width" default="0.4" />
<arg name="encoder_ticks_per_rev" default="4000" />
<arg name="linear_coef" default="800" />
<arg name="angular_coef" default="200" />
<node name="trd_driver" pkg="trd_driver" type="trd_driver.py">
<param name="serialport" value="$(arg serialport)" />
<param name="baudrate" value="$(arg baudrate)" />
<param name="wheel_diameter" value="$(arg wheel_diameter)" />
<param name="base_width" value="$(arg base_width)" />
<param name="encoder_ticks_per_rev" value="$(arg encoder_ticks_per_rev)" />
<param name="linear_coef" value="$(arg linear_coef)" />
<param name="angular_coef" value="$(arg angular_coef)" />
</node>
</launch>
启动驱动节点和键盘控制节点,遥控底盘运动,并查看/odom主题内容。
(远程终端)$ rosrun trd_driver trd_driver.py
(远程终端)$ rosrun trd_driver keyboard_teleop.py
(远程终端)$ rostopic echo /odom
观察/odom中的Position位置读数以及速度Twist读数,看看发现什么问题?如何调整参数进行校准?
思考:如何测试,可以确保上述参数是准确的?
最终参数:
<arg name="wheel_diameter" default="0.08" />
<arg name="base_width" default="0.4" />
<arg name="encoder_ticks_per_rev" default="4000" />
<arg name="linear_coef" default="800" />
<arg name="angular_coef" default="180" />
激光传感器的作用是,扫描机器人周围的物体,并以点云的形式进行表示。 因此,激光传感器分为二维激光传感器和三维激光传感器,会分别提供二维点云和三维点云数据。
通常,三维激光传感器价格昂贵,移动底盘通常会采用二维激光传感器。常用的型号有:
- 日本北洋Hokuyo系列
ROS驱动:http://wiki.ros.org/urg_node
- 德国SICK系列
ROS驱动:http://wiki.ros.org/sick_tim
- 深圳镭神智能生产的激光雷达
ROS驱动:https://github.com/leishen-lidar/LS01D
- 上海思岚科技生产的RPLIDAR系列
ROS驱动:http://wiki.ros.org/rplidar
- 深圳EAI公司生产的YDLIDAR系列
ROS驱动:https://github.com/EAIBOT/ydlidar
在此,采用激光传感器型号为深圳EAI公司推出的YDLIDAR F4型号。其ROS驱动包可在Github下载:
https://github.com/EAIBOT/ydlidar
将源代码下载到笔记本电脑catkin_ws工作区的src路径下,然后部署到Odroid XU4上:
$ cd ~/catkin_ws/src
$ git clone https://github.com/EAIBOT/ydlidar
然后执行rsync指令同步代码。在此,可将rsync指令写到一个.sh脚本中,每次执行脚本文件就可以了。 在catkin_ws下新建deploy.sh文件,加入以下内容:
rsync -avz --delete --exclude="*.swp" src [email protected]:/home/odroid/catkin_ws/
远程部署时在catkin_ws下执行即可。
$ cd ~/catkin_ws
$ sh deploy.sh
将代码部署到Odroid XU4后,登陆上去进行构建、配置。
$ ssh [email protected]
(远程终端)$ cd ~/catkin_ws
(远程终端)$ catkin_make
构建完成后,即可运行测试。为了防止串口名称的冲突,可执行以下命令,将激光传感器映射为特定的名称。
(远程终端)$ roscd ydlidar/startup
(远程终端)$ sudo sh initenv.sh
上述指令会建立udev rules文件,将激光串口名映射为/dev/ydlidar
,方便后续调用。
注意:设置完udev rules后,需要重新连接激光传感器才会生效。
同理,如果采用镭神生产的ls01d激光传感器,则需要下载安装其对应驱动。
首先到github下载驱动包,地址为https://github.com/leishen-lidar/LS01D
$ git clone https://github.com/leishen-lidar/LS01D
然后将其中对应的ROS驱动包重命名,即将ls01D Ros文件夹重命名为ls01d, 将重命名后的ls01d文件夹拷贝到工作区的~/catkin_ws/src文件夹下。
同样,将代码远程部署到Odroid XU4后,登陆上去进行构建、配置。
$ ssh [email protected]
(远程终端)$ cd ~/catkin_ws
(远程终端)$ catkin_make
构建完成后,即可运行测试。为了防止串口名称的冲突,可执行以下命令,将激光传感器映射为特定的名称。
(远程终端)$ roscd ls01d/scripts
(远程终端)$ sudo sh create_udev_rules.sh
上述指令会建立udev rules文件,将激光串口名映射为/dev/laser
,方便后续调用。
注意:设置完udev rules后,需要重新连接激光传感器才会生效。
由于Hokuyo激光传感器的ROS驱动包已发布到ROS的Ubuntu软件源中,所以可以直接二进制方式安装:
$ sudo apt install ros-kinetic-urg-node
同理,SICK的激光驱动包也可通过二进制方式安装:
$ sudo apt install ros-kinetic-sick-tim
将激光传感器连接到Odroid XU4的USB端口上,用roslaunch启动驱动节点:
(远程终端)$ roslaunch ydlidar lidar.launch
或:
(远程终端)$ roslaunch ls01d ls01d.launch
在新打开终端,查看发布的主题及内容:
(远程终端)$ rostopic list
(远程终端)$ rostopic echo /scan
这样可看到打印出激光点云数据。请自行查看消息的类型,及其定义,各变量所表示的含义。
为了能够在笔记本电脑上也能接收到激光数据,需要配置两台机器的ROS环境变量参数。
以底盘Odroid XU4作为ROS主节点。 在Odroid XU4的~/.bashrc文件加入以下内容。
export ROS_MASTER_URI=http://192.168.5.102:11311
export ROS_HOSTNAME=192.168.5.102
然后,在笔记本电脑的~/.bashrc文件加入以下内容。
export ROS_MASTER_URI=http://192.168.5.102:11311
export ROS_HOSTNAME=192.168.5.101
这样,在Odroid XU4上启动roscore后及相关节点后,即可在笔记本电脑上进行查看和通信,请自行尝试。
-
尝试在Odroid XU4启动激光驱动,并在笔记本电脑打印其内容。
-
尝试在笔记本电脑发送某主题,并在Odroid XU4上打印其内容。
用打印的方式查看激光信号很不直观,这时,用RViz可直观的查看激光信号。
在Odroid XU4启动激光驱动后,在笔记本电脑运行RViz:
(远程终端)$ roslaunch ydlidar lidar.launch
或:
(远程终端)$ roslaunch ls01d ls01d.launch
$ rviz
在RViz界面中,选择Add按钮,在By Topic选项中找到/scan
主题,选中并添加。
这时,RViz不会显示激光数据。需要将Fixed Frame改为laser_frame
即可显示。为了显示更清晰,将LaserScan下的Style改为Points
。
自行修改其它选项,看RViz都提供了哪些功能?
由于坦克底盘运行过程中会出现颠簸,难免导致USB接口出现断开的情况。因此需要在代码中加入重连机制。
提示
在TrdDriver类中定重连成员方法:
def reconnect(self):
print('{} reconnecting...'.format(time.time()))
while True:
try:
self.ser = serial.Serial(self.serialport, self.baudrate)
except:
time.sleep(1)
print('{} reconnecting...'.format(time.time()))
else:
print('{} reconnected!'.format(time.time()))
break
提示
发送指令出现异常时重连:
def send(self, cmd):
while True:
try:
self.ser.write(cmd)
break
except serial.serialutil.SerialException:
self.reconnect()
提示
接收指令出现异常时重连:
res = ''
try:
while self.ser.inWaiting() > 0:
res += self.ser.read(1)
except:
self.reconnect()