|
@@ -0,0 +1,520 @@
|
|
|
+#!/usr/bin/env python
|
|
|
+#-*- coding:utf-8 -*-
|
|
|
+
|
|
|
+"""
|
|
|
+ Copyright: 2016-2020 www.corvin.cn ROS小课堂
|
|
|
+ Description: A base controller class for the Arduino DUE microcontroller
|
|
|
+ Author: corvin
|
|
|
+ History:
|
|
|
+ 20200704:当超过给下位机发送控制移动命令时间后,不需要再发送pwm控制0的命令.
|
|
|
+ 因为在arduino代码会根据超时自己会执行pwm清零停止电机移动的命令.
|
|
|
+ 20201119:新增pid调速走直线,但横向位移仍未解决.
|
|
|
+"""
|
|
|
+import os
|
|
|
+import rospy
|
|
|
+import time
|
|
|
+import roslib; roslib.load_manifest('ros_arduino_python')
|
|
|
+import dynamic_reconfigure.client
|
|
|
+
|
|
|
+from math import sin, cos, pi, sqrt
|
|
|
+import numpy as np
|
|
|
+from geometry_msgs.msg import Quaternion, Twist
|
|
|
+from nav_msgs.msg import Odometry
|
|
|
+from tf.broadcaster import TransformBroadcaster
|
|
|
+from std_msgs.msg import Int32
|
|
|
+from std_msgs.msg import Float32
|
|
|
+from serial_imu_hat_6dof.srv import GetYawData
|
|
|
+
|
|
|
+class SimplePID:
|
|
|
+ '''very simple discrete PID controller'''
|
|
|
+ def __init__(self, target, P, I, D):
|
|
|
+ '''Create a discrete PID controller
|
|
|
+ each of the parameters may be a vector if they have the same length
|
|
|
+
|
|
|
+ Args:
|
|
|
+ target (double) -- the target value(s)
|
|
|
+ P, I, D (double)-- the PID parameter
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ # check if parameter shapes are compatabile.
|
|
|
+ self.Kp =np.array(P)
|
|
|
+ self.Ki =np.array(I)
|
|
|
+ self.Kd =np.array(D)
|
|
|
+ self.setPoint =np.array(target)
|
|
|
+
|
|
|
+ self.last_error=0
|
|
|
+ self.integrator = 0
|
|
|
+ self.integrator_max = float('inf')
|
|
|
+ self.timeOfLastCall = None
|
|
|
+
|
|
|
+
|
|
|
+ def update(self, current_value):
|
|
|
+ '''Updates the PID controller.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ current_value (double): vector/number of same legth as the target given in the constructor
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ controll signal (double): vector of same length as the target
|
|
|
+
|
|
|
+ '''
|
|
|
+ current_value=np.array(current_value)
|
|
|
+ if(self.timeOfLastCall is None):
|
|
|
+ # the PID was called for the first time. we don't know the deltaT yet
|
|
|
+ # no controll signal is applied
|
|
|
+ self.timeOfLastCall = time.clock()
|
|
|
+ return np.zeros(np.size(current_value))
|
|
|
+
|
|
|
+
|
|
|
+ error = self.setPoint - current_value
|
|
|
+ P = error
|
|
|
+
|
|
|
+ currentTime = time.clock()
|
|
|
+ deltaT = (currentTime-self.timeOfLastCall)
|
|
|
+
|
|
|
+ # integral of the error is current error * time since last update
|
|
|
+ self.integrator = self.integrator + (error*deltaT)
|
|
|
+ I = self.integrator
|
|
|
+
|
|
|
+ # derivative is difference in error / time since last update
|
|
|
+ D = (error-self.last_error)/deltaT
|
|
|
+
|
|
|
+ self.last_error = error
|
|
|
+ self.timeOfLastCall = currentTime
|
|
|
+
|
|
|
+ # return controll signal
|
|
|
+ return self.Kp*P + self.Ki*I + self.Kd*D
|
|
|
+
|
|
|
+""" Class to receive Twist commands and publish wheel Odometry data """
|
|
|
+class BaseController:
|
|
|
+
|
|
|
+ def __init__(self, is_use_serial, arduino, base_frame, name="base_controllers_node"):
|
|
|
+ self.use_serial = is_use_serial
|
|
|
+ self.arduino = arduino
|
|
|
+ self.name = name
|
|
|
+ self.base_frame = base_frame
|
|
|
+ self.odom_name = rospy.get_param("~odom_name", "odom")
|
|
|
+ self.cmd_topic = rospy.get_param("~cmd_topic", "cmd_vel")
|
|
|
+ self.yaw_topic_name = rospy.get_param('~yaw_topic', 'yaw_topic')
|
|
|
+ self.rate = float(rospy.get_param("~base_controller_rate", 20))
|
|
|
+ self.timeout = rospy.get_param("~base_controller_timeout", 0.7)
|
|
|
+
|
|
|
+ self.yaw_data_service = rospy.get_param("~yaw_data_service","imu_node/GetYawData")
|
|
|
+ self.yaw_target = rospy.get_param("~yaw_target",0)
|
|
|
+ self.yaw_kp = rospy.get_param("~yaw_kp",1)
|
|
|
+ self.yaw_ki = rospy.get_param("~yaw_ki",1)
|
|
|
+ self.yaw_kd = rospy.get_param("~yaw_kd",1)
|
|
|
+ self.correction_forward_err_yaw_data = rospy.get_param("~correction_forward_err_yaw_data",0.017)
|
|
|
+ self.correction_forward_rate = rospy.get_param("~correction_forward_rate",1)
|
|
|
+ self.correction_yaw_flag_timeout = rospy.get_param("~correction_yaw_flag_timeout",2)
|
|
|
+ self.correction_th_threshold = rospy.get_param("~correction_th_threshold",0.1)
|
|
|
+ self.pid_controller = SimplePID(self.yaw_target, self.yaw_kp, self.yaw_ki, self.yaw_kd)
|
|
|
+ self.correction_yaw_flag = 0
|
|
|
+ self.last_yaw_data = 0
|
|
|
+ self.now_yaw_data = 0
|
|
|
+ self.initial_odom_yaw_data = self.get_yaw()
|
|
|
+ self.yaw_topic_data = 0
|
|
|
+
|
|
|
+ self.debugPID = rospy.get_param('~debugPID', False)
|
|
|
+
|
|
|
+ pid_params = dict()
|
|
|
+ pid_params['wheel_diameter'] = rospy.get_param("~wheel_diameter", 0.058)
|
|
|
+ pid_params['wheel_track'] = rospy.get_param("~wheel_track", 0.126)
|
|
|
+ pid_params['encoder_resolution'] = rospy.get_param("~encoder_resolution", 11)
|
|
|
+ pid_params['gear_reduction'] = rospy.get_param("~gear_reduction", 46)
|
|
|
+ pid_params['AWheel_Kp'] = rospy.get_param("~AWheel_Kp", 15)
|
|
|
+ pid_params['AWheel_Kd'] = rospy.get_param("~AWheel_Kd", 15)
|
|
|
+ pid_params['AWheel_Ki'] = rospy.get_param("~AWheel_Ki", 0)
|
|
|
+ pid_params['AWheel_Ko'] = rospy.get_param("~AWheel_Ko", 50)
|
|
|
+
|
|
|
+ pid_params['BWheel_Kp'] = rospy.get_param("~BWheel_Kp", 15)
|
|
|
+ pid_params['BWheel_Kd'] = rospy.get_param("~BWheel_Kd", 15)
|
|
|
+ pid_params['BWheel_Ki'] = rospy.get_param("~BWheel_Ki", 0)
|
|
|
+ pid_params['BWheel_Ko'] = rospy.get_param("~BWheel_Ko", 50)
|
|
|
+
|
|
|
+ pid_params['CWheel_Kp'] = rospy.get_param("~CWheel_Kp", 15)
|
|
|
+ pid_params['CWheel_Kd'] = rospy.get_param("~CWheel_Kd", 15)
|
|
|
+ pid_params['CWheel_Ki'] = rospy.get_param("~CWheel_Ki", 0)
|
|
|
+ pid_params['CWheel_Ko'] = rospy.get_param("~CWheel_Ko", 50)
|
|
|
+
|
|
|
+ self.accel_limit = rospy.get_param('~accel_limit', 0.05)
|
|
|
+ self.linear_scale_correction = rospy.get_param("~linear_scale_correction", 1.0)
|
|
|
+ self.angular_scale_correction = rospy.get_param("~angular_scale_correction", 1.0)
|
|
|
+
|
|
|
+ #rospy.loginfo("pid_params_dict: "+pid_params)
|
|
|
+
|
|
|
+ # Set up PID parameters and check for missing values
|
|
|
+ self.setup_pid(pid_params)
|
|
|
+
|
|
|
+ # How many encoder ticks are there per meter?
|
|
|
+ self.ticks_per_meter = self.encoder_resolution*self.gear_reduction*2/(self.wheel_diameter*pi)
|
|
|
+ self.ticks_per_meter = self.ticks_per_meter/self.linear_scale_correction
|
|
|
+
|
|
|
+ # What is the maximum acceleration we will tolerate when changing wheel speeds?
|
|
|
+ self.max_accel = self.accel_limit*self.ticks_per_meter/self.rate
|
|
|
+
|
|
|
+ # Track how often we get a bad encoder count (if any)
|
|
|
+ self.bad_encoder_count = 0
|
|
|
+
|
|
|
+ now = rospy.Time.now()
|
|
|
+ self.then = now # time for determining dx/dy
|
|
|
+ self.t_delta = rospy.Duration(1.0/self.rate)
|
|
|
+ self.t_next = now + self.t_delta
|
|
|
+
|
|
|
+ # Internal data
|
|
|
+ self.enc_A = 0 # encoder readings
|
|
|
+ self.enc_B = 0
|
|
|
+ self.enc_C = 0
|
|
|
+
|
|
|
+ self.pose_x = 0 # position in xy plane
|
|
|
+ self.pose_y = 0
|
|
|
+ self.th = 0 # rotation in radians
|
|
|
+
|
|
|
+ #存储三个车轮的当前转动速度
|
|
|
+ self.v_A = 0
|
|
|
+ self.v_B = 0
|
|
|
+ self.v_C = 0
|
|
|
+
|
|
|
+ #存储需要控制三个车轮在PID周期内控制电机转动的脉冲数
|
|
|
+ self.v_des_AWheel = 0 # cmd_vel setpoint
|
|
|
+ self.v_des_BWheel = 0
|
|
|
+ self.v_des_CWheel = 0
|
|
|
+
|
|
|
+ self.last_cmd_time = now
|
|
|
+
|
|
|
+ # Subscriptions main comtrol board to send control cmd
|
|
|
+ rospy.Subscriber(self.cmd_topic, Twist, self.cmdVelCallback)
|
|
|
+ rospy.Subscriber(self.yaw_topic_name,Float32,self.yawCallback)
|
|
|
+
|
|
|
+ # Clear any old odometry info
|
|
|
+ if self.use_serial:
|
|
|
+ self.arduino.reset_encoders()
|
|
|
+ else:
|
|
|
+ self.arduino.i2c_reset_encoders()
|
|
|
+
|
|
|
+ # Set up the wheel odometry broadcaster
|
|
|
+ self.odomPub = rospy.Publisher(self.odom_name, Odometry, queue_size=5)
|
|
|
+ self.odomBroadcaster = TransformBroadcaster()
|
|
|
+
|
|
|
+ #corvin add for rqt_plot debug pid
|
|
|
+ if self.debugPID:
|
|
|
+ self.AEncoderPub = rospy.Publisher('Aencoder', Int32, queue_size=5)
|
|
|
+ self.BEncoderPub = rospy.Publisher('Bencoder', Int32, queue_size=5)
|
|
|
+ self.CEncoderPub = rospy.Publisher('Cencoder', Int32, queue_size=5)
|
|
|
+ self.APidoutPub = rospy.Publisher('Apidout', Int32, queue_size=5)
|
|
|
+ self.BPidoutPub = rospy.Publisher('Bpidout', Int32, queue_size=5)
|
|
|
+ self.CPidoutPub = rospy.Publisher('Cpidout', Int32, queue_size=5)
|
|
|
+ self.AVelPub = rospy.Publisher('Avel', Int32, queue_size=5)
|
|
|
+ self.BVelPub = rospy.Publisher('Bvel', Int32, queue_size=5)
|
|
|
+ self.CVelPub = rospy.Publisher('Cvel', Int32, queue_size=5)
|
|
|
+
|
|
|
+ rospy.loginfo("Started base controller for a base of " + str(self.wheel_track) + "m with " + str(self.encoder_resolution) + " ticks per rev")
|
|
|
+ rospy.loginfo("Publishing odometry at: " + str(self.rate) + " Hz using " + str(self.base_frame) + " as base frame")
|
|
|
+
|
|
|
+ def setup_pid(self, pid_params):
|
|
|
+ # Check to see if any PID parameters are missing
|
|
|
+ missing_params = False
|
|
|
+ for param in pid_params:
|
|
|
+ if pid_params[param] == "":
|
|
|
+ print("*** PID Parameter " + param + " is missing. ***")
|
|
|
+ missing_params = True
|
|
|
+
|
|
|
+ if missing_params:
|
|
|
+ os._exit(1)
|
|
|
+
|
|
|
+ self.encoder_resolution = pid_params['encoder_resolution']
|
|
|
+ self.gear_reduction = pid_params['gear_reduction']
|
|
|
+ self.wheel_diameter = pid_params['wheel_diameter']
|
|
|
+ self.wheel_track = pid_params['wheel_track']
|
|
|
+ self.wheel_track = self.wheel_track/self.angular_scale_correction
|
|
|
+
|
|
|
+ self.AWheel_Kp = pid_params['AWheel_Kp']
|
|
|
+ self.AWheel_Kd = pid_params['AWheel_Kd']
|
|
|
+ self.AWheel_Ki = pid_params['AWheel_Ki']
|
|
|
+ self.AWheel_Ko = pid_params['AWheel_Ko']
|
|
|
+
|
|
|
+ self.BWheel_Kp = pid_params['BWheel_Kp']
|
|
|
+ self.BWheel_Kd = pid_params['BWheel_Kd']
|
|
|
+ self.BWheel_Ki = pid_params['BWheel_Ki']
|
|
|
+ self.BWheel_Ko = pid_params['BWheel_Ko']
|
|
|
+
|
|
|
+ self.CWheel_Kp = pid_params['CWheel_Kp']
|
|
|
+ self.CWheel_Kd = pid_params['CWheel_Kd']
|
|
|
+ self.CWheel_Ki = pid_params['CWheel_Ki']
|
|
|
+ self.CWheel_Ko = pid_params['CWheel_Ko']
|
|
|
+
|
|
|
+ if self.use_serial:
|
|
|
+ self.arduino.update_pid(self.AWheel_Kp, self.AWheel_Kd, self.AWheel_Ki, self.AWheel_Ko,
|
|
|
+ self.BWheel_Kp, self.BWheel_Kd, self.BWheel_Ki, self.BWheel_Ko,
|
|
|
+ self.CWheel_Kp, self.CWheel_Kd, self.CWheel_Ki, self.CWheel_Ko,)
|
|
|
+ else:
|
|
|
+ self.arduino.i2c_update_pid(self.AWheel_Kp, self.AWheel_Kd, self.AWheel_Ki, self.AWheel_Ko,
|
|
|
+ self.BWheel_Kp, self.BWheel_Kd, self.BWheel_Ki, self.BWheel_Ko,
|
|
|
+ self.CWheel_Kp, self.CWheel_Kd, self.CWheel_Ki, self.CWheel_Ko,)
|
|
|
+
|
|
|
+ # main loop, always run
|
|
|
+ def poll(self):
|
|
|
+ self.send_debug_pid()
|
|
|
+
|
|
|
+ time_now = rospy.Time.now() #获取当前系统时间
|
|
|
+ if time_now > self.t_next:
|
|
|
+ # 读取三个电机反馈的编码器计数
|
|
|
+ try:
|
|
|
+ if self.use_serial:
|
|
|
+ aWheel_enc, bWheel_enc, cWheel_enc = self.arduino.get_encoder_counts()
|
|
|
+ #rospy.loginfo("get new encoder: "+str(aWheel_enc)+ " "+str(bWheel_enc)+" "+str(cWheel_enc))
|
|
|
+ else:
|
|
|
+ aWheel_enc, bWheel_enc, cWheel_enc = self.arduino.i2c_get_encoder_counts()
|
|
|
+ #rospy.loginfo("get encoder counts: "++str(aWheel_enc)+ " "+str(bWheel_enc)+" "+str(cWheel_enc))
|
|
|
+ except:
|
|
|
+ try:
|
|
|
+ # try again
|
|
|
+ #rospy.logwarn("Failed to get encoder counts, try again. ")
|
|
|
+ aWheel_enc, bWheel_enc, cWheel_enc = self.arduino.i2c_get_encoder_counts()
|
|
|
+ except:
|
|
|
+ self.bad_encoder_count += 1
|
|
|
+ rospy.logerr("Encoder exception count: " + str(self.bad_encoder_count))
|
|
|
+ return
|
|
|
+
|
|
|
+ #当读取到最新的编码器计数为0,说明下位机已经将计数清零,那上位机保存的上一次编码器计数也得清零
|
|
|
+ if aWheel_enc == 0:
|
|
|
+ self.enc_A = 0
|
|
|
+
|
|
|
+ if bWheel_enc == 0:
|
|
|
+ self.enc_B = 0
|
|
|
+
|
|
|
+ if cWheel_enc == 0:
|
|
|
+ self.enc_C = 0
|
|
|
+
|
|
|
+ #根据编码器两次反馈的差值来计算车轮移动的距离
|
|
|
+ dist_A = (aWheel_enc - self.enc_A)/self.ticks_per_meter
|
|
|
+ dist_B = (bWheel_enc - self.enc_B)/self.ticks_per_meter
|
|
|
+ dist_C = (cWheel_enc - self.enc_C)/self.ticks_per_meter
|
|
|
+ #rospy.loginfo("get delta dist:"+str(dist_A)+" "+str(dist_B)+" "+str(dist_C))
|
|
|
+
|
|
|
+ #将当前读取到的编码器计数保存,为了下次计算新脉冲数准备
|
|
|
+ self.enc_A = aWheel_enc
|
|
|
+ self.enc_B = bWheel_enc
|
|
|
+ self.enc_C = cWheel_enc
|
|
|
+ #rospy.loginfo("get save encoder:"+str(self.enc_A)+" "+str(self.enc_B)+" "+str(self.enc_C))
|
|
|
+
|
|
|
+ #计算两次获取编码器反馈的时间差
|
|
|
+ delta_time = time_now - self.then
|
|
|
+ delta_time = delta_time.to_sec()
|
|
|
+
|
|
|
+ #计算三个车轮的转动线速度
|
|
|
+ va = dist_A/delta_time
|
|
|
+ vb = dist_B/delta_time
|
|
|
+ vc = dist_C/delta_time
|
|
|
+ self.then = time_now
|
|
|
+ #rospy.loginfo("get vel:"+str(va)+ " "+str(vb)+" "+str(vc)+" delta_time:"+str(delta_time))
|
|
|
+
|
|
|
+ #正向运动学模型:根据三个车轮的速度合成小车整体的移动速度
|
|
|
+ vx = sqrt(3)*(va - vb)/3.0
|
|
|
+ vy = (va + vb - 2*vc)/3.0
|
|
|
+ vth = (va + vb + vc)/(3*self.wheel_track)
|
|
|
+
|
|
|
+ #计算移动的距离
|
|
|
+ delta_x = (vx*cos(self.th) - vy*sin(self.th))*delta_time
|
|
|
+ delta_y = (vx*sin(self.th) + vy*cos(self.th))*delta_time
|
|
|
+ delta_th = vth*delta_time
|
|
|
+ #rospy.loginfo("get delta pose:"+str(delta_x)+ " "+str(delta_y)+" "+str(delta_th))
|
|
|
+
|
|
|
+ #将移动的距离累加运算
|
|
|
+ self.pose_x += delta_x
|
|
|
+ self.pose_y += delta_y
|
|
|
+ self.th += delta_th
|
|
|
+
|
|
|
+ self.now_odom_yaw_data = self.yaw_topic_data
|
|
|
+ self.th = self.now_odom_yaw_data - self.initial_odom_yaw_data
|
|
|
+
|
|
|
+ quaternion = Quaternion()
|
|
|
+ quaternion.x = 0.0
|
|
|
+ quaternion.y = 0.0
|
|
|
+ quaternion.z = sin(self.th/2.0)
|
|
|
+ quaternion.w = cos(self.th/2.0)
|
|
|
+
|
|
|
+##########################################################
|
|
|
+ # create the odometry transform frame broadcaster.
|
|
|
+ """
|
|
|
+ self.odomBroadcaster.sendTransform(
|
|
|
+ (self.pose_x, self.pose_y, 0),
|
|
|
+ (quaternion.x, quaternion.y, quaternion.z, quaternion.w),
|
|
|
+ rospy.Time.now(),
|
|
|
+ self.base_frame,
|
|
|
+ self.odom_name
|
|
|
+ )
|
|
|
+ """
|
|
|
+##########################################################
|
|
|
+ odom = Odometry()
|
|
|
+ odom.header.frame_id = self.odom_name
|
|
|
+ odom.child_frame_id = self.base_frame
|
|
|
+ odom.header.stamp = time_now
|
|
|
+ odom.pose.pose.position.x = self.pose_x
|
|
|
+ odom.pose.pose.position.y = self.pose_y
|
|
|
+ odom.pose.pose.position.z = 0
|
|
|
+ odom.pose.pose.orientation = quaternion
|
|
|
+ odom.pose.covariance = [1e-9, 0, 0, 0, 0, 0,
|
|
|
+ 0, 1e-3, 1e-9, 0, 0, 0,
|
|
|
+ 0, 0, 1e6, 0, 0, 0,
|
|
|
+ 0, 0, 0, 1e6, 0, 0,
|
|
|
+ 0, 0, 0, 0, 1e6, 0,
|
|
|
+ 0, 0, 0, 0, 0, 1e-9]
|
|
|
+ odom.twist.twist.linear.x = vx
|
|
|
+ odom.twist.twist.linear.y = vy
|
|
|
+ odom.twist.twist.angular.z = vth
|
|
|
+ odom.twist.covariance = [1e-9, 0, 0, 0, 0, 0,
|
|
|
+ 0, 1e-3, 1e-9, 0, 0, 0,
|
|
|
+ 0, 0, 1e6, 0, 0, 0,
|
|
|
+ 0, 0, 0, 1e6, 0, 0,
|
|
|
+ 0, 0, 0, 0, 1e6, 0,
|
|
|
+ 0, 0, 0, 0, 0, 1e-9]
|
|
|
+ self.odomPub.publish(odom)
|
|
|
+
|
|
|
+ # send motor cmd
|
|
|
+ if time_now > (self.last_cmd_time + rospy.Duration(self.timeout)):
|
|
|
+ self.stop()
|
|
|
+ self.correction_yaw_flag = 0
|
|
|
+ self.reset_pid_controller()
|
|
|
+ else:
|
|
|
+ self.send_motor_speed()
|
|
|
+
|
|
|
+ # set next compare time
|
|
|
+ self.t_next = time_now + self.t_delta
|
|
|
+
|
|
|
+
|
|
|
+ # debug motor pid parameter
|
|
|
+ def send_debug_pid(self):
|
|
|
+ if self.debugPID:
|
|
|
+ try:
|
|
|
+ if self.use_serial:
|
|
|
+ A_pidin, B_pidin, C_pidin = self.arduino.get_pidin()
|
|
|
+ else:
|
|
|
+ A_pidin, B_pidin, C_pidin = self.arduino.i2c_get_pidin()
|
|
|
+ self.AEncoderPub.publish(A_pidin)
|
|
|
+ self.BEncoderPub.publish(B_pidin)
|
|
|
+ self.CEncoderPub.publish(C_pidin)
|
|
|
+ except:
|
|
|
+ rospy.logerr("getpidin exception count:")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+ if self.use_serial:
|
|
|
+ A_pidout, B_pidout, C_pidout = self.arduino.get_pidout()
|
|
|
+ else:
|
|
|
+ A_pidout, B_pidout, C_pidout = self.arduino.i2c_get_pidout()
|
|
|
+ self.APidoutPub.publish(A_pidout)
|
|
|
+ self.BPidoutPub.publish(B_pidout)
|
|
|
+ self.CPidoutPub.publish(C_pidout)
|
|
|
+ except:
|
|
|
+ rospy.logerr("getpidout exception count")
|
|
|
+ return
|
|
|
+
|
|
|
+ # normalize motor dest encode value and send
|
|
|
+ def send_motor_speed(self):
|
|
|
+ if self.v_A < self.v_des_AWheel:
|
|
|
+ self.v_A += self.max_accel
|
|
|
+ if self.v_A > self.v_des_AWheel:
|
|
|
+ self.v_A = self.v_des_AWheel
|
|
|
+ else:
|
|
|
+ self.v_A -= self.max_accel
|
|
|
+ if self.v_A < self.v_des_AWheel:
|
|
|
+ self.v_A = self.v_des_AWheel
|
|
|
+
|
|
|
+ if self.v_B < self.v_des_BWheel:
|
|
|
+ self.v_B += self.max_accel
|
|
|
+ if self.v_B > self.v_des_BWheel:
|
|
|
+ self.v_B = self.v_des_BWheel
|
|
|
+ else:
|
|
|
+ self.v_B -= self.max_accel
|
|
|
+ if self.v_B < self.v_des_BWheel:
|
|
|
+ self.v_B = self.v_des_BWheel
|
|
|
+
|
|
|
+ if self.v_C < self.v_des_CWheel:
|
|
|
+ self.v_C += self.max_accel
|
|
|
+ if self.v_C > self.v_des_CWheel:
|
|
|
+ self.v_C = self.v_des_CWheel
|
|
|
+ else:
|
|
|
+ self.v_C -= self.max_accel
|
|
|
+ if self.v_C < self.v_des_CWheel:
|
|
|
+ self.v_C = self.v_des_CWheel
|
|
|
+
|
|
|
+ # send to arduino to drive motor
|
|
|
+ if self.use_serial:
|
|
|
+ self.arduino.drive(self.v_A, self.v_B, self.v_C)
|
|
|
+ else:
|
|
|
+ self.arduino.i2c_drive(self.v_A, self.v_B, self.v_C)
|
|
|
+
|
|
|
+ if self.debugPID:
|
|
|
+ self.AVelPub.publish(self.v_A)
|
|
|
+ self.BVelPub.publish(self.v_B)
|
|
|
+ self.CVelPub.publish(self.v_C)
|
|
|
+
|
|
|
+
|
|
|
+ # stop move mobileBase
|
|
|
+ def stop(self):
|
|
|
+ self.v_A = 0
|
|
|
+ self.v_B = 0
|
|
|
+ self.v_C = 0
|
|
|
+ self.v_des_AWheel = 0
|
|
|
+ self.v_des_BWheel = 0
|
|
|
+ self.v_des_CWheel = 0
|
|
|
+
|
|
|
+ def get_yaw(self):
|
|
|
+ rospy.wait_for_service(self.yaw_data_service)
|
|
|
+ try:
|
|
|
+ yaw_data = rospy.ServiceProxy(self.yaw_data_service, GetYawData)
|
|
|
+ resp = yaw_data()
|
|
|
+ return resp.yaw
|
|
|
+ except rospy.ServiceException, e:
|
|
|
+ print "Service call failed: %s"%e
|
|
|
+
|
|
|
+ def reset_pid_controller(self):
|
|
|
+ self.pid_controller.integrator = 0
|
|
|
+ self.pid_controller.last_error = 0
|
|
|
+ self.pid_controller.timeOfLastCall = None
|
|
|
+
|
|
|
+ def cmdVelCallback(self, req):
|
|
|
+
|
|
|
+ # Handle velocity-based movement requests
|
|
|
+ self.last_cmd_time = rospy.Time.now()
|
|
|
+
|
|
|
+ x = req.linear.x # m/s
|
|
|
+ y = req.linear.y # m/s
|
|
|
+ th = req.angular.z # rad/s
|
|
|
+
|
|
|
+ if x != 0 and y == 0 and th == 0:
|
|
|
+ if self.correction_yaw_flag < self.correction_yaw_flag_timeout:
|
|
|
+ self.stop()
|
|
|
+ self.last_yaw_data = self.yaw_topic_data
|
|
|
+ self.correction_yaw_flag += 1
|
|
|
+ return
|
|
|
+ else:
|
|
|
+ self.now_yaw_data = self.yaw_topic_data
|
|
|
+ err_yaw_data = self.now_yaw_data - self.last_yaw_data
|
|
|
+ if abs(err_yaw_data) > self.correction_forward_err_yaw_data:
|
|
|
+ th = self.pid_controller.update(err_yaw_data)
|
|
|
+ if th > self.correction_th_threshold:
|
|
|
+ th = self.correction_th_threshold
|
|
|
+ if th < -self.correction_th_threshold:
|
|
|
+ th = -self.correction_th_threshold
|
|
|
+ #print("err_yaw_data is ",err_yaw_data,"th is ",th)
|
|
|
+ else:
|
|
|
+ self.correction_yaw_flag = 0
|
|
|
+ self.reset_pid_controller()
|
|
|
+
|
|
|
+ #Inverse Kinematic
|
|
|
+ tmpX = sqrt(3)/2.0
|
|
|
+ tmpY = 0.5 # 1/2
|
|
|
+ vA = ( tmpX*x + tmpY*y + self.wheel_track*th)
|
|
|
+ vB = (-tmpX*x + tmpY*y + self.wheel_track*th)
|
|
|
+ vC = ( -y + self.wheel_track*th)
|
|
|
+
|
|
|
+ self.v_des_AWheel = int(vA * self.ticks_per_meter / self.arduino.PID_RATE)
|
|
|
+ self.v_des_BWheel = int(vB * self.ticks_per_meter / self.arduino.PID_RATE)
|
|
|
+ self.v_des_CWheel = int(vC * self.ticks_per_meter / self.arduino.PID_RATE)
|
|
|
+ #rospy.loginfo("cmdCallback(): A v_des:"+str(self.v_des_AWheel)+",B v_des:"+str(self.v_des_BWheel)+",C v_des:"+str(self.v_des_CWheel))
|
|
|
+
|
|
|
+ def yawCallback(self,req):
|
|
|
+ self.yaw_topic_data = req.data
|