2.5.标定结果测试¶
本节将对上节标定的运动参数进行多方位测试,将所有参数放到launch文件中进行修改调试。待参数满足要求精度后,即可作为底盘的驱动程序。
将所有参数放到launch文件中¶
修改trd_driver.py文件,将所有参数用ROS的get_param()方法获取:
# 使用ROS param设定参数值
self.linear_coef = rospy.get_param('~linear_coef', default=100.0)
self.angular_coef = rospy.get_param('~angular_coef', default=10.0)
self.wheel_diameter = rospy.get_param('~wheel_diameter', default=0.08)
self.base_width = rospy.get_param('~base_width', default=0.21)
self.encoder_ticks_per_rev = rospy.get_param('~encoder_ticks_per_rev', default=1980)
修改后内容如下:
#!/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 struct
import math
class TrdDriver():
def __init__(self, serialport, baudrate):
self.odom_pub = rospy.Publisher('/odom', Odometry, queue_size=10)
self.vel_sub = rospy.Subscriber('/cmd_vel', Twist, self.vel_callback)
self.ser = serial.Serial(serialport, baudrate)
self.v1 = 128
self.v2 = 128
# 使用ROS param设定参数值
self.linear_coef = rospy.get_param('~linear_coef', default=100.0)
self.angular_coef = rospy.get_param('~angular_coef', default=10.0)
self.wheel_diameter = rospy.get_param('~wheel_diameter', default=0.08)
self.base_width = rospy.get_param('~base_width', default=0.21)
self.encoder_ticks_per_rev = rospy.get_param('~encoder_ticks_per_rev', default=1980)
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()
self.is_first_time = True
self.encoder1_offset = 0
self.encoder2_offset = 0
def send(self, cmd):
self.ser.write(cmd)
def set_speed(self, v1, v2):
cmd = [0xea, 0x05, 0x7e, v1, v2, 0x00, 0x0d]
for i in range(len(cmd)-2):
cmd[-2] ^= cmd[i]
print('send cmd:', cmd)
self.send(cmd)
def read_buffer(self):
result = ''
while self.ser.inWaiting() > 0:
result += self.ser.read(1)
return result
def run(self):
rate = rospy.Rate(10)
while not rospy.is_shutdown():
self.set_speed(self.v1, self.v2)
time.sleep(0.05)
result = self.read_buffer()
print('response:', result)
#提取编码器数值
self.encoder1, = struct.unpack('>i', bytearray(result[11:15]))
self.encoder2, = struct.unpack('>i', bytearray(result[15:19]))
print('encoder:', self.encoder1, self.encoder2)
if self.is_first_time:
self.encoder1_offset = self.encoder1
self.encoder2_offset = self.encoder2
self.is_first_time = False
self.encoder1 -= self.encoder1_offset
self.encoder2 -= self.encoder2_offset
print('encoder-offset:', self.encoder1, self.encoder2)
#计算新的里程计并发布
self.update_odom()
def vel_callback(self, msg):
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
v1 += 128
v2 += 128
v1 = int(v1) if v1<255 else 255
v2 = int(v2) if v2<255 else 255
v1 = int(v1) if v1>0 else 0
v2 = int(v2) if v2>0 else 0
self.v1 = v1
self.v2 = v2
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
self.odom_pub.publish(self.odom)
if __name__=='__main__':
rospy.init_node('trd_driver_node')
serialport = rospy.get_param('~serialport', default='/dev/ttyUSB0')
baudrate = rospy.get_param('~baudrate', default=38400)
trd_driver = TrdDriver(serialport, baudrate)
trd_driver.run()
然后,修改trd_driver.launch文件,加入对应参数数值。 注意,此处底盘的轮径为0.08米,轮距为0.21米,编码器为1980线,控制速度线速度系数和角速度系数分别为100.0和10.0。
<?xml version="1.0"?>
<launch>
<arg name="serialport" default="/dev/ttyUSB0" />
<arg name="baudrate" default="38400" />
<arg name="linear_coef" default="100.0" />
<arg name="angular_coef" default="10.0" />
<arg name="wheel_diameter" default="0.08" />
<arg name="base_width" default="0.21" />
<arg name="encoder_ticks_per_rev" default="1980" />
<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主题内容。
(远程终端)$ roslaunch trd_driver trd_driver.launch
(远程终端)$ rosrun trd_driver keyboard_teleop.py
(远程终端)$ rostopic echo /odom
观察/odom中的Position位置读数以及速度Twist读数,看看发现什么问题?如何调整参数进行校准?
如何测试,可以确保上述参数是准确的?
最终参数:
<arg name="linear_coef" default="100.0" />
<arg name="angular_coef" default="10.0" />
<arg name="wheel_diameter" default="0.08" />
<arg name="base_width" default="0.21" />
<arg name="encoder_ticks_per_rev" default="1980" />
何解决usb断线重连问题¶
由于底盘运行过程中会出现颠簸,难免导致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()
提示
接收指令出现异常时重连:
def read_buffer(self):
result = ''
try:
while self.ser.inWaiting() > 0:
result += self.ser.read(1)
except:
self.reconnect()
return result
采用汤尼机器人官方trbase包¶
至此,差速电机驱动包的开发完成。汤尼机器人官方包 trbase 中包含了所有开发所用到的包,不仅包括trd_driver电机驱动包, 还包含传感器驱动、所有底盘建图及导航包,可以下载此包进行部署编译。
将trd_driver包备份到其它位置,将trbase包克隆到本地~/catkin_ws/src下。
$ cd ~/catkin_ws/src
$ mv trd_driver ~/Documents/
$ git clone https://github.com/TonyRobotics/trbase
如果未联网,可暂时将树莓派下~/catkin_ws工作区中的trbase包复制过来:
$ cd ~/catkin_ws/src
$ mv trd_driver ~/Documents/
$ rsync -avz pi@192.168.5.108:/home/pi/catkin_ws/src/trbase ./
然后部署到树莓派的 tutorial_ws 工作区下:
$ cd ~/catkin_ws
$ rsync -avz --delete --exclude="*.swp" src pi@192.168.5.108:/home/pi/tutorial_ws/
编译运行即可。