ros小车底层串口通信——树莓派端

在制作ros小车时,上位机与下位之间的通信是非常重要的一部分,以下是阿贝尔机器人树莓派端的底层代码,添加了注释,供以后学习查阅,该程序需使用了多线程工作,有关python多线程详解的有关内容请参照此博客,ros小车的下位机代码还没有写出,等回学校有时间在研究。如有错误还请大佬指正,非常感谢!!!

#!/usr/bin/python2
import serial
import threading
import time
import struct
import math

import rospy
import tf
from geometry_msgs.msg import Twist, Quaternion
from nav_msgs.msg import Odometry

lock = threading.Lock()   # 线程锁

#串口类, 自定义线程
class TrdSerial(threading.Thread):

    def __init__(self, serialport_name, baudrate):
        threading.Thread.__init__(self)   # 重构run函数必须要写
        # uper(MyThread, self).__init__()   # 上面的代码也可换为次句
        self._serialport_name = serialport_name
        self._baudrate = baudrate
        self._conn = serial.Serial(self._serialport_name, self._baudrate, timeout=1)
        self._stop = False

        self._first_time_flag = True
        self._encoders_offset = [0, 0]
        self._encoders = [0, 0]
        self._voltage = 0
        self._currents = [0,0]
        self._error_code = 0

    #重连
    def reconnect(self, side):
        print('{} {} reconnecting...'.format(side, time.time()))
        while True:
            try:
                self._conn = serial.Serial(self._serialport_name, self._baudrate, timeout=1)
            except:
                time.sleep(1)
                print('{} {} reconnecting...'.format(side, time.time()))
            else:
                print('{} {} reconnected!'.format(side, time.time()))
                break

    # 重构线程的run函数
    def run(self):
        msg = []
        msg_len = 0
        in_msg_flag = False
        while not self._stop:
            #读取单片机发送的一个字节
            r = self.read(1)
            if in_msg_flag:
                #数据溢出,丢弃  
                if len(msg) > 30:
                    in_msg_flag = False
                    msg[:] = []
                    print('Overflow message: {}, discarded!'.format(msg))
                    #跳出while循环
                    continue
                msg.append(r)
                #当接收到第二个数据时
                if len(msg)==2:
                    #数据的第二位为数据的总长度
                    msg_len = ord(r)
                #当收到的数据位数 加上前两位 满足数据长度
                if len(msg) == msg_len+2:
                    #数据以回车为结束
                    if r=='\r':
                        in_msg_flag = False
                        #接收数据结束,进行数据更新
                        self.update(msg)
                        msg[:] = []
                    #无效消息,丢弃 
                    else:
                        print('Invalid message: {}, discarded!'.format(msg))
            #如果单片机发送的是0xea
            elif r=='\xea':
                msg[:] = []
                #msg的第一个数据,数据的头
                msg.append(r)
                in_msg_flag = True

    #获得编码器数据
    def get_encoders(self):
        return (self._encoders[0], self._encoders[1])

    def set_speed(self, v1, v2):
        cmd = [0xea, 0x05, 0x7e, v1, v2, 0x00, 0x0d]
        self.send(cmd)

    def reset_encoder(self):
        print('Reset encoder')
        cmd = [0xea, 0x03, 0x35, 0x00, 0x0d]
        self.send(cmd)

    def reset_base(self):
        print('Reset base')
        cmd = [0xea, 0x03, 0x50, 0x00, 0x0d]
        self.send(cmd)
        time.sleep(3)

    #启用超时???????
    def enable_timeout(self):
        print('Enable timeout')
        cmd = [0xea, 0x02, 0x39, 0x00, 0x0d]
        self.send(cmd)

    def update(self, msg):
        #print('{} received message: {}'.format(time.time(), [hex(ord(m)) for m in msg]))
        if len(msg) < 4:
            print('Msg length < 4 : {}'.format(len(msg)))
        #表示数据长度的位
        if ord(msg[2]) == 0x7e:
            if len(msg) != 23:
                print('Msg length = {} not correct, expected: 23'.format(len(msg)))
            #电压值
            self._voltage = 0.2157*ord(msg[3])
            #电流????
            self._currents = [0.0272*ord(msg[4]), 0.0272*ord(msg[5])]
            #错误代码?????
            self._error_code = ord(msg[6])
            if self._error_code != 0:
                print('trd_driver Error! error code({})'.format(self._error_code))
            #编码器数据,需要去看单片机上读取到的编码器数据 一个编码器四位
            self._encoders[0], = struct.unpack('>i', bytearray(msg[11:15]))
            self._encoders[1], = struct.unpack('>i', bytearray(msg[15:19]))
            #取出第一次编码器的值
            if self._first_time_flag:
                self._encoders_offset[0] = self._encoders[0]
                self._encoders_offset[1] = self._encoders[1]
                self._first_time_flag = False
            #去除第一次读到的编码器数值,排除干扰
            self._encoders[0] -= self._encoders_offset[0]
            self._encoders[1] -= self._encoders_offset[1]
            #print('{} error:{} encoders: {}, {}V {}A {}A. set:{} {}, actual:{} {}'.format(
            #            time.time(), self._error_code, self._encoders, self._voltage, self._currents[0], self._currents[1],
            #            ord(msg[7]), ord(msg[8]), ord(msg[9]), ord(msg[10])))
        else:
            print('{} received message: {}'.format(time.time(), msg))

    def stop(self):
        print('stop')
        self._stop = True
        self._conn.close()


    def send(self, cmd):
        for i in range(len(cmd)-2):
            #表示异或赋值,校验位
            cmd[-2] ^= cmd[i]
        while True:
            try:
                self._conn.write(cmd)
                break
            except serial.serialutil.SerialException:
                self.reconnect('send')

    def read(self, n):
        r = ''
        try:
            r = self._conn.read(n)
        except:
            self.reconnect('read')
        return r

#ROS有关的类,此处也是一个线程,但并没有自定义,可以把此类理解为一个主线程,而串口线程则是跟此线程并行的
class RosWraperTrd():

    def __init__(self, node_name):
        rospy.init_node(node_name,disable_signals=True)
        #串口名称
        self.serialport_name = rospy.get_param('~serialport_name', \
                                               default='/dev/motor_trd')
        #波特率
        self.baudrate = rospy.get_param('~baudrate', default=38400)
        #线性系数,直线运动比例系数
        self.linear_coef = rospy.get_param('~linear_coef', default=82)
        #角度系数,差速比例系数
        self.angular_coef = rospy.get_param('~angular_coef', default=14.64)
        #左轮系数???
        self.left_coef = rospy.get_param('~left_coef', default=1)
        #右轮系数???
        self.right_coef = rospy.get_param('~right_coef', default =1)
        #编码器没转一圈的脉冲数???  这麽大?????
        self.encoder_ticks_per_rev = rospy.get_param('~encoder_ticks_per_rev', \
                                                     default=1600)
        #车的宽度????  两轮的间距
        self.base_width = rospy.get_param('~base_width', default=0.39)
        #轮的直径
        self.wheel_diameter = rospy.get_param('~wheel_diameter', default=0.125)
        ####发布里程计数据,gmapping订阅#####################
        self.pub_odom = rospy.Publisher('/odom', Odometry, queue_size=10)
        ####订阅导航的底层控制数据,用于控制电机###############
        self.sub_vel = rospy.Subscriber('/cmd_vel', Twist, self.vel_callback, queue_size=1)
        #tf变换
        self.tf_broadcaster = tf.TransformBroadcaster()
        #编码器的什么数据??????  上一次编码器的数据
        self.encoder1_prev = 0
        self.encoder2_prev = 0
        #时间差
        self.time_prev = rospy.Time.now() - rospy.Time.from_sec(1)
        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.trd_serial = TrdSerial(self.serialport_name, self.baudrate)
        self.trd_serial.reset_encoder()
        #self.trd_serial.reset_base()
        self.trd_serial.enable_timeout()

        #打开串口线程,跟ros线程并行运行
        self.trd_serial.start()
        self.trd_serial.run()

        self.vel1 = 0
        self.vel2 = 0
        self.vel_latest_time = 0
        self.vel_timeout = 1
    
    #电机控制回调
    def vel_callback(self, vel_msg):
        v1 = self.linear_coef * vel_msg.linear.x
        v2 = self.linear_coef * vel_msg.linear.x

        v1 -= self.angular_coef * vel_msg.angular.z
        v2 += self.angular_coef * vel_msg.angular.z

        v1 *= self.left_coef
        v2 *= self.right_coef

        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

        #线程上锁,防止数据出错
        lock.acquire()

        self.vel1 = v1
        self.vel2 = v2

        #解锁线程
        lock.release()

        self.vel_latest_time = time.time()

    def update(self):
        #set speed  如果长时间没有产生速度回调
        if time.time()-self.vel_latest_time > self.vel_timeout:
            #print('Vel timeout, set to (0,0)')
            lock.acquire()
            self.vel1 = 128
            self.vel2 = 128
            lock.release()
        #如果正常产生回调
        self.trd_serial.set_speed(self.vel1, self.vel2)
        #get encoders
        (encoder1, encoder2) = self.trd_serial.get_encoders()
        time_current = rospy.Time.now()
        time_elapsed = (time_current - self.time_prev).to_sec()
        self.time_prev = time_current

        #里程计=轮周长×(脉冲数/一圈的脉冲数)
        dleft = self.left_coef * math.pi * self.wheel_diameter * \
                (encoder1 - self.encoder1_prev) / self.encoder_ticks_per_rev
        dright = self.right_coef * 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():
            try:
                self.update()
                #发布里程计信息,供导航包使用
                self.pub_odom.publish(self.odom)   
                #发布坐标变换,odom->base_link,构建tf_tree
                self.tf_broadcaster.sendTransform(
                    (self.x,self.y,0),
                    tf.transformations.quaternion_from_euler(0, 0, self.theta),
                    rospy.Time.now(),
                    'base_link',
                    'odom')
                rate.sleep()
            except KeyboardInterrupt:
                print('exit.')
                self.trd_serial.stop()
                break

if __name__ == '__main__':
    ros_wrapper_trd = RosWraperTrd('trd_driver')
    ros_wrapper_trd.run()


发布了5 篇原创文章 · 获赞 3 · 访问量 96

猜你喜欢

转载自blog.csdn.net/qq_41990294/article/details/104830906