class MotorControllers(object): ''' Motor class contains the methods necessary to send commands to the motor controllers for the corner and drive motors. There are many other ways of commanding the motors from the RoboClaw, we suggest trying to write your own Closed loop feedback method for the drive motors! ''' def __init__(self): ## MAKE SURE TO FIX CONFIG.JSON WHEN PORTED TO THE ROVER! #self.rc = Roboclaw( config['CONTROLLER_CONFIG']['device'], # config['CONTROLLER_CONFIG']['baud_rate'] # ) rospy.loginfo("Initializing motor controllers") #self.rc = Roboclaw( rospy.get_param('motor_controller_device', "/dev/serial0"), # rospy.get_param('baud_rate', 115200)) sdev = "/dev/ttyAMA0" sdev = "/dev/serial0" self.rc = Roboclaw(sdev, 115200) self.rc.Open() self.accel = [0] * 10 self.qpps = [None] * 10 self.err = [None] * 5 # PSW address_raw = "128,129,130,131,132" #address_raw = rospy.get_param('motor_controller_addresses') address_list = (address_raw.split(',')) self.address = [None] * len(address_list) for i in range(len(address_list)): self.address[i] = int(address_list[i]) version = 1 for address in self.address: print("Attempting to talk to motor controller", address) version = version & self.rc.ReadVersion(address)[0] print version if version != 0: print "[Motor__init__] Sucessfully connected to RoboClaw motor controllers" else: raise Exception( "Unable to establish connection to Roboclaw motor controllers") self.killMotors() self.enc_min = [] self.enc_max = [] for address in self.address: #self.rc.SetMainVoltages(address, rospy.get_param('battery_low', 11)*10), rospy.get_param('battery_high', 18)*10)) if address == 131 or address == 132: #self.rc.SetM1MaxCurrent(address, int(config['MOTOR_CONFIG']['max_corner_current']*100)) #self.rc.SetM2MaxCurrent(address, int(config['MOTOR_CONFIG']['max_corner_current']*100)) self.enc_min.append(self.rc.ReadM1PositionPID(address)[-2]) self.enc_min.append(self.rc.ReadM2PositionPID(address)[-2]) self.enc_max.append(self.rc.ReadM1PositionPID(address)[-1]) self.enc_max.append(self.rc.ReadM2PositionPID(address)[-1]) else: #self.rc.SetM1MaxCurrent(address, int(config['MOTOR_CONFIG']['max_drive_current']*100)) #self.rc.SetM2MaxCurrent(address, int(config['MOTOR_CONFIG']['max_drive_current']*100)) self.rc.ResetEncoders(address) rospy.set_param('enc_min', str(self.enc_min)[1:-1]) rospy.set_param('enc_max', str(self.enc_max)[1:-1]) for address in self.address: self.rc.WriteNVM(address) for address in self.address: self.rc.ReadNVM(address) #''' voltage = self.rc.ReadMainBatteryVoltage(0x80)[1] / 10.0 lvolts = rospy.get_param('low_voltage', 11) lvolts = rospy.get_param('low_voltage', 9) if voltage >= lvolts: print "[Motor__init__] Voltage is safe at: ", print voltage, print "V" else: print "[Motor__init__] Voltage is unsafe at: ", voltage, "V ( low = ", lvolts, ")" raise Exception("Unsafe Voltage of" + voltage + " Volts") #''' i = 0 for address in self.address: self.qpps[i] = self.rc.ReadM1VelocityPID(address)[4] self.accel[i] = int(self.qpps[i] * 2) self.qpps[i + 1] = self.rc.ReadM2VelocityPID(address)[4] self.accel[i + 1] = int(self.qpps[i] * 2) i += 2 accel_max = 655359 accel_rate = 0.5 self.accel_pos = int((accel_max / 2) + accel_max * accel_rate) self.accel_neg = int((accel_max / 2) - accel_max * accel_rate) self.errorCheck() mids = [None] * 4 self.enc = [None] * 4 for i in range(4): mids[i] = (self.enc_max[i] + self.enc_min[i]) / 2 #self.cornerToPosition(mids) time.sleep(2) self.killMotors() def cornerToPosition(self, tick): ''' Method to send position commands to the corner motor :param list tick: A list of ticks for each of the corner motors to move to, if tick[i] is 0 it instead stops that motor from moving ''' speed, accel = 1000, 2000 #These values could potentially need tuning still for i in range(4): index = int(math.ceil((i + 1) / 2.0) + 2) if tick[i] != -1: if (i % 2): self.rc.SpeedAccelDeccelPositionM2(self.address[index], accel, speed, accel, tick[i], 1) else: self.rc.SpeedAccelDeccelPositionM1(self.address[index], accel, speed, accel, tick[i], 1) else: if not (i % 2): self.rc.ForwardM1(self.address[index], 0) else: self.rc.ForwardM2(self.address[index], 0) def sendMotorDuty(self, motorID, speed): ''' Wrapper method for an easier interface to control the drive motors, sends open-loop commands to the motors :param int motorID: number that corresponds to each physical motor :param int speed: Speed for each motor, range from 0-127 ''' #speed = speed/100.0 #speed *= 0.5 addr = self.address[int(motorID / 2)] if speed > 0: if not motorID % 2: command = self.rc.ForwardM1 else: command = self.rc.ForwardM2 else: if not motorID % 2: command = self.rc.BackwardM1 else: command = self.rc.BackwardM2 speed = abs(int(speed * 127)) return command(addr, speed) def sendSignedDutyAccel(self, motorID, speed): addr = self.address[int(motorID / 2)] if speed > 0: accel = self.accel_pos else: accel = self.accel_neg if not motorID % 2: command = self.rc.DutyAccelM1 else: command = self.rc.DutyAccelM2 speed = int(32767 * speed / 100.0) return command(addr, accel, speed) def getCornerEnc(self): enc = [] for i in range(4): index = int(math.ceil((i + 1) / 2.0) + 2) if not (i % 2): enc.append(self.rc.ReadEncM1(self.address[index])[1]) else: enc.append(self.rc.ReadEncM2(self.address[index])[1]) self.enc = enc return enc @staticmethod def tick2deg(tick, e_min, e_max): ''' Converts a tick to physical degrees :param int tick : Current encoder tick :param int e_min: The minimum encoder value based on physical stop :param int e_max: The maximum encoder value based on physical stop ''' return (tick - (e_max + e_min) / 2.0) * (90.0 / (e_max - e_min)) def getCornerEncAngle(self): if self.enc[0] == None: return -1 deg = [None] * 4 for i in range(4): deg[i] = int( self.tick2deg(self.enc[i], self.enc_min[i], self.enc_max[i])) return deg def getDriveEnc(self): enc = [None] * 6 for i in range(6): if not (i % 2): enc[i] = self.rc.ReadEncM1(self.address[int(math.ceil(i / 2))])[1] else: enc[i] = self.rc.ReadEncM2(self.address[int(math.ceil(i / 2))])[1] return enc def getBattery(self): return self.rc.ReadMainBatteryVoltage(self.address[0])[1] def getTemp(self): temp = [None] * 5 for i in range(5): temp[i] = self.rc.ReadTemp(self.address[i])[1] return temp def getCurrents(self): currents = [None] * 10 for i in range(5): currs = self.rc.ReadCurrents(self.address[i]) currents[2 * i] = currs[1] currents[(2 * i) + 1] = currs[2] return currents def getErrors(self): return self.err def killMotors(self): ''' Stops all motors on Rover ''' for i in range(5): self.rc.ForwardM1(self.address[i], 0) self.rc.ForwardM2(self.address[i], 0) def errorCheck(self): ''' Checks error status of each motor controller, returns 0 if any errors occur ''' for i in range(len(self.address)): self.err[i] = self.rc.ReadError(self.address[i])[1] for error in self.err: if error: self.killMotors() #self.writeError() rospy.loginfo("Motor controller Error", error) return 1 def writeError(self): ''' Writes the list of errors to a text file for later examination ''' f = open('errorLog.txt', 'a') errors = ','.join(str(e) for e in self.err) f.write('\n' + 'Errors: ' + '[' + errors + ']' + ' at: ' + str(datetime.datetime.now())) f.close()
class SerialDriver(object): ''' Class for serial UART interface to the RoboClaw Motor Controllers ''' def __init__(self): rospy.loginfo("Initilizing the motor controllers..") self.e_stop = 1 self.reg_enabled = 0 self.temp = 0 self.error = 0 self.voltage = 0 self.currents = [0,0] self._thread_lock = False self.prev_enc_ts = None self.prev_tick = [None, None] self.start_time = datetime.now() self.left_currents = [] self.right_currents = [] self.max_left_integrator = 0 self.max_right_integrator = 0 self.left_integrator = 0 self.right_integrator = 0 self.motor_lockout = 0 self._cmd_buf_flag = 0 self._l_vel_cmd_buf = 0 self._r_vel_cmd_buf = 0 self.battery_percent = 0 self.shutdown_warning = False self.shutdown_flag = False self.rate = rospy.get_param("/puffer/rate") self.delta_t = 2.0/self.rate self.rc = Roboclaw( rospy.get_param("/motor_controllers/serial_device_name"), rospy.get_param("/motor_controllers/baud_rate") ) self.rc.Open() self._set_operating_params() self._get_version() self.set_estop(0) #Clears E-stop pin self.enable_12v_reg(1) #Disables 12V Regulator self.kill_motors() #Start the motors not moving #Private Methods def _get_version(self): ''' Version check for communication verification to the motor controllers returns ther version number if sucessful, and 0 if not ''' version = self.rc.ReadVersion(self.address) if version != 0: rospy.loginfo("[Motor __init__ ] Sucessfully connected to all Motor Controllers!") rospy.loginfo( version ) rospy.set_param("/motor_controllers/firmware_version",version) else: raise Exception("Unable to establish connection to Motor controllers") return version def _set_operating_params(self): ''' Sets all the operating parameters for control of PUFFER, Pinouts, and safety parameters. returns None ''' #GPIO settings for E-stop and Regulator Enable pins self.e_stop_pin = rospy.get_param("/gpio_pins/e_stop",1) self.reg_en_pin = rospy.get_param("/gpio_pins/reg_en",1) try: GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(self.e_stop_pin, GPIO.OUT) GPIO.setup(self.reg_en_pin, GPIO.OUT) except: pass #Threadlock used for serial comm to RoboClaw self.thread_timeout = rospy.get_param("/threadlock/timeout") #Motor operating parameters self.wheel_d = rospy.get_param("/wheels/diameter") self.enc_cpr = rospy.get_param("/wheels/encoder/cts_per_rev") factor = rospy.get_param("/wheels/encoder/factor") stage_1 = rospy.get_param("/wheels/gearbox/stage1") stage_2 = rospy.get_param("/wheels/gearbox/stage2") stage_3 = rospy.get_param("/wheels/gearbox/stage3") self.accel_const = rospy.get_param("/puffer/accel_const") self.max_vel_per_s = rospy.get_param("/puffer/max_vel_per_s") self.tick_per_rev = int(self.enc_cpr * factor * stage_1 * stage_2 * stage_3) rospy.loginfo(self.tick_per_rev) rospy.set_param("tick_per_rev", self.tick_per_rev) self.max_cts_per_s = int((self.max_vel_per_s * self.tick_per_rev)/(math.pi * self.wheel_d)) self.max_accel = int(self.max_cts_per_s * self.accel_const) rospy.set_param("max_cts_per_s", self.max_cts_per_s) rospy.set_param("max_accel", self.max_accel) self.address = rospy.get_param("/motor_controllers/address", 0x80) self.rc.SetMainVoltages(self.address,int(rospy.get_param("/motor_controllers/battery/main/low", 12.0) * 10),int(rospy.get_param("motor_controllers/battery/main/high", 18.0 ) * 10)) self.rc.SetLogicVoltages(self.address,int(rospy.get_param("/motor_controllers/battery/logic/low") * 10),int(rospy.get_param("motor_controllers/battery/logic/high") * 10)) self.max_current = rospy.get_param("/motor_controllers/current/max_amps") self.motor_lockout_time = rospy.get_param("/puffer/motor_lockout_time") m1p = rospy.get_param("/motor_controllers/m1/p") m1i = rospy.get_param("/motor_controllers/m1/i") m1d = rospy.get_param("/motor_controllers/m1/d") m1qpps = rospy.get_param("/motor_controllers/m1/qpps") m2p = rospy.get_param("/motor_controllers/m2/p") m2i = rospy.get_param("/motor_controllers/m2/i") m2d = rospy.get_param("/motor_controllers/m2/d") m2qpps = rospy.get_param("/motor_controllers/m2/qpps") self.battery_max_time = rospy.get_param("/battery/max_time") self.battery_max_volts = rospy.get_param("/battery/max_volts") self.battery_coef_a = rospy.get_param("/battery/a") self.battery_coef_b = rospy.get_param("/battery/b") self.battery_coef_c = rospy.get_param("/battery/c") self.battery_warning = rospy.get_param("/battery/warning_percent") self.battery_shutdown = rospy.get_param("/battery/shutdown_percent") self.rc.SetM1VelocityPID(self.address,m1p, m1i, m1d, m1qpps) self.rc.SetM2VelocityPID(self.address,m2p, m2i, m2d, m2qpps) self.rc.WriteNVM(self.address) time.sleep(0.001) self.rc.ReadNVM(self.address) def _lock_thread(self,lock): ''' Checks the thread lock and then grabs it when it frees up no return value ''' if (lock): start = time.time() while self._thread_lock: #rospy.loginfo("in threadlock") if time.time() - start > self.thread_timeout: raise Exception("Thread lock timeout") time.sleep(0.001) self._thread_lock = True else: self._thread_lock = False def _get_Temp(self): ''' Gets the temperature of the motor controllers return: list [2] (int): Temperature values * 10 degrees C ''' self._lock_thread(1) self.temp = self.rc.ReadTemp(self.address)[1] self._lock_thread(0) self.temp = int(self.temp*100)/1000.0 return self.temp def _get_Voltage(self): ''' Gets the voltage of the motor controllers return: voltage (int) : Voltage values * 10 volts ''' self._lock_thread(1) v = self.rc.ReadMainBatteryVoltage(self.address)[1] v = int(v*100)/1000.0 if v != 0: self.voltage = v + 0.4 #accounts for the voltage drop in the diode self._lock_thread(0) return v def _get_Currents(self): ''' Gets the current of the motor controllers return: list [2] (int): Current values * 100 Amps ''' self._lock_thread(1) cur = self.rc.ReadCurrents(self.address) self._lock_thread(0) r_current = int(cur[1])/100.0 l_current = int(cur[2])/100.0 self.currents = [l_current,r_current] self.left_currents.insert(0, l_current) self.right_currents.insert(0, r_current) if (len(self.left_currents) > self.rate/2): del self.left_currents[-1] del self.right_currents[-1] left_power = 0 right_power = 0 for i in range(len(self.left_currents)): left_power += math.pow(self.left_currents[i],2) * self.delta_t right_power += math.pow(self.right_currents[i],2) * self.delta_t if (left_power >= math.pow(self.max_current,2) or right_power >= math.pow(self.max_current,2)): rospy.loginfo("Motor power exceeded max allowed! Disabling motors for %d seconds" %(self.motor_lockout_time)) self.motor_lockout = 1 self.set_estop(1) self.lockout_timestamp = time.time() if (self.motor_lockout and (time.time() - self.lockout_timestamp >= self.motor_lockout_time)): rospy.loginfo("Re-enabling the motors from timeout lock") self.motor_lockout = 0 self.set_estop(0) self.left_integrator = left_power self.right_integrator = right_power self.max_left_integrator = max(self.max_left_integrator, self.left_integrator) self.max_right_integrator = max(self.max_right_integrator, self.right_integrator) #print self.left_integrator, self.right_integrator return self.currents def _get_Errors(self): ''' Gets the error status of the motor controllers return: error (int): Error code for motor controller ''' self._lock_thread(1) self.error = self.rc.ReadError(self.address)[1] self._lock_thread(0) return self.error def _get_Encs(self): ''' Gets the encoder values of the motor controllers return: list [2] (int): Speed of motors in radians/s, computed at each delta T ''' self._lock_thread(1) r_enc = self.rc.ReadEncM1(self.address)[1] l_enc = self.rc.ReadEncM2(self.address)[1] dt = datetime.now() self._lock_thread(0) if self.prev_tick == [None, None]: l_vel_rad_s, r_vel_rad_s = 0,0 self.timestamp = 0 else: delta_t = ((dt-self.prev_enc_ts).microseconds)/1000000.0 self.timestamp = int(self.timestamp + (delta_t * 100000)) l_vel = (l_enc - self.prev_tick[0])/(delta_t) r_vel = (r_enc - self.prev_tick[1])/(delta_t) l_vel_rad_s = l_vel * (2 * math.pi/self.tick_per_rev) r_vel_rad_s = r_vel * (2 * math.pi/self.tick_per_rev) l_vel_rad_s = int(l_vel_rad_s * 1000)/1000.0 r_vel_rad_s = int(r_vel_rad_s * 1000)/1000.0 self.prev_enc_ts = dt self.prev_tick = [l_enc, r_enc] self.enc = [l_vel_rad_s, r_vel_rad_s] return l_vel_rad_s, r_vel_rad_s def _send_motor_cmds(self): l_vel = self._l_vel_cmd_buf r_vel = self._r_vel_cmd_buf l_vel *= (self.tick_per_rev/(2*math.pi)) r_vel *= (self.tick_per_rev/(2*math.pi)) l_vel = max(min(l_vel,self.max_cts_per_s), -self.max_cts_per_s) r_vel = max(min(r_vel,self.max_cts_per_s), -self.max_cts_per_s) if (( abs(l_vel) <= self.max_cts_per_s) and (abs(r_vel) <= self.max_cts_per_s)): if not self.motor_lockout: self._lock_thread(1) self.rc.SpeedAccelM1(self.address, self.max_accel, int(r_vel)) self.rc.SpeedAccelM2(self.address, self.max_accel, int(l_vel)) self._lock_thread(0) else: rospy.loginfo( "values not in accepted range" ) self.send_motor_cmds(0,0) # Public Methods def set_estop(self,val): ''' Sets the E-stop pin to stop Motor control movement until cleared Parameters: val (int): 0 - Clears E-stop 1 - Sets E-stop, disabling motor movement no return value ''' if (val != self.e_stop): if val: rospy.loginfo( "Enabling the E-stop") GPIO.output(self.e_stop_pin, 1) self.e_stop = 1 else: rospy.loginfo( "Clearing the E-stop") GPIO.output(self.e_stop_pin, 0) self.e_stop = 0 def battery_state_esimator(self): x = self.voltage y = math.pow(x,2) * self.battery_coef_a + self.battery_coef_b * x + self.battery_coef_c self.battery_percent = (100 * y/float(self.battery_max_time)) if (self.battery_percent <= self.battery_warning): self.shutdown_warning = True else: self.shutdown_warning = False if (self.battery_percent <= self.battery_shutdown): self.shutdown_flag = True self.set_estop(1) def update_cmd_buf(self, l_vel, r_vel): ''' Updates the command buffers and sets the flag that new command has been received Parameters: l_vel (int): [-0.833, 0.833] units of [rad/s] r_vel (int): [-0.833, 0.833] units of [rad/s] no return value ''' self._l_vel_cmd_buf = l_vel self._r_vel_cmd_buf = r_vel self._cmd_buf_flag = 1 def kill_motors(self): ''' Stops all motors on the assembly ''' self._lock_thread(1) self.rc.ForwardM1(self.address, 0) self.rc.ForwardM2(self.address, 0) self._lock_thread(0) def loop(self, counter): ''' Gets the data from the motor controllers to populate as class variables all data function classes are private because of threadlocks Downsample non-critical values to keep serial comm line free Parameters: counter (int): no return value ''' if (self._cmd_buf_flag): self._send_motor_cmds() self._cmd_buf_flag = 0 self._get_Encs() if not counter % 2: self._get_Currents() if not counter % 5: self._get_Errors() if not counter % 20: self._get_Temp() self._get_Voltage() self.battery_state_esimator() def enable_12v_reg(self, en): ''' Turns on/off the 12V regulator GPIO pin Parameters: en (int): 0: Disabled 1: Enabled ''' try: if (en != self.reg_enabled): if en: rospy.loginfo("Enabling the 12V Regulator") GPIO.output(self.reg_en_pin, 1) self.reg_enabled = 1 else: rospy.loginfo("Disabling the 12V Regulator") GPIO.output(self.reg_en_pin, 0) self.reg_enabled = 0 except: pass def cleanup(self): ''' Cleans up the motor controller node, stopping motors and killing all threads no return value ''' rospy.loginfo("Cleaning up the motor_controller node..") self._thread_lock = False self.kill_motors() self.set_estop(1) try: GPIO.cleanup() except: pass
class MotorControllersV2(object): ''' Motor class contains the methods necessary to send commands to the motor controllers for the corner and drive motors. There are many other ways of commanding the motors from the RoboClaw, we suggest trying to write your own Closed loop feedback method for the drive motors! ''' def __init__(self): ## MAKE SURE TO FIX CONFIG.JSON WHEN PORTED TO THE ROVER! #self.rc = Roboclaw( config['CONTROLLER_CONFIG']['device'], # config['CONTROLLER_CONFIG']['baud_rate'] # ) rospy.loginfo("Initializing motor controllers") #self.rc = Roboclaw( rospy.get_param('motor_controller_device', "/dev/serial0"), # rospy.get_param('baud_rate', 115200)) self.rc = Roboclaw("/dev/ttyS0", 115200) self.rc.Open() self.accel = [0] * 10 self.qpps = [None] * 10 self.err = [None] * 5 address_raw = rospy.get_param('motor_controller_addresses') address_list = (address_raw.split(',')) self.address = [None] * len(address_list) for i in range(len(address_list)): self.address[i] = int(address_list[i]) version = 1 for address in self.address: rospy.loginfo("Attempting to talk to motor controller: " + str(address)) version = version & self.rc.ReadVersion(address)[0] rospy.loginfo("Motor controller version: " + str(version)) if version != 0: rospy.loginfo( "Sucessfully connected to RoboClaw motor controllers") else: rospy.logerr( "Unable to establish connection to Roboclaw motor controllers") raise Exception( "Unable to establish connection to Roboclaw motor controllers") self.killMotors() for address in self.address: self.rc.ResetEncoders(address) for address in self.address: self.rc.WriteNVM(address) for address in self.address: self.rc.ReadNVM(address) ''' voltage = self.rc.ReadMainBatteryVoltage(0x80)[1]/10.0 if voltage >= rospy.get_param('low_voltage',11): print "[Motor__init__] Voltage is safe at: ",voltage, "V" else: raise Exception("Unsafe Voltage of" + voltage + " Volts") ''' i = 0 for address in self.address: self.qpps[i] = self.rc.ReadM1VelocityPID(address)[4] self.accel[i] = int(self.qpps[i] * 2) self.qpps[i + 1] = self.rc.ReadM2VelocityPID(address)[4] self.accel[i + 1] = int(self.qpps[i] * 2) i += 2 accel_max = 655359 accel_rate = 0.5 self.accel_pos = int((accel_max / 2) + accel_max * accel_rate) self.accel_neg = int((accel_max / 2) - accel_max * accel_rate) self.errorCheck() self.drive_enc = [None] * 10 time.sleep(2) self.killMotors() def sendMotorDuty(self, motorID, speed): ''' Wrapper method for an easier interface to control the drive motors, sends open-loop commands to the motors :param int motorID: number that corresponds to each physical motor :param int speed: Speed for each motor, range from 0-127 ''' #speed = speed/100.0 #speed *= 0.5 addr = self.address[int(motorID / 2)] if speed > 0: if not motorID % 2: command = self.rc.ForwardM1 else: command = self.rc.ForwardM2 else: if not motorID % 2: command = self.rc.BackwardM1 else: command = self.rc.BackwardM2 speed = abs(int(speed * 127)) return command(addr, speed) def sendSignedDutyAccel(self, motorID, speed): addr = self.address[int(motorID / 2)] if speed > 0: accel = self.accel_pos else: accel = self.accel_neg if not motorID % 2: command = self.rc.DutyAccelM1 else: command = self.rc.DutyAccelM2 speed = int(32767 * speed / 100.0) return command(addr, accel, speed) def getDriveEnc(self): drive_enc = [] for i in range(10): index = int(math.ceil((i + 1) / 2.0) - 1) if not (i % 2): drive_enc.append(self.rc.ReadEncM1(self.address[index])[1]) else: drive_enc.append(self.rc.ReadEncM2(self.address[index])[1]) self.drive_enc = drive_enc return drive_enc def getBattery(self): return self.rc.ReadMainBatteryVoltage(self.address[0])[1] def getTemp(self): temp = [None] * 5 for i in range(5): temp[i] = self.rc.ReadTemp(self.address[i])[1] return temp def getCurrents(self): currents = [None] * 10 for i in range(5): currs = self.rc.ReadCurrents(self.address[i]) currents[2 * i] = currs[1] currents[(2 * i) + 1] = currs[2] return currents def getErrors(self): return self.err def killMotors(self): ''' Stops all motors on Rover ''' for i in range(5): self.rc.ForwardM1(self.address[i], 0) self.rc.ForwardM2(self.address[i], 0) def errorCheck(self): ''' Checks error status of each motor controller, returns 0 if any errors occur ''' for i in range(len(self.address)): self.err[i] = self.rc.ReadError(self.address[i])[1] for error in self.err: if error: self.killMotors() #self.writeError() rospy.loginfo("Motor controller Error: " + str(error)) return 1 def writeError(self): ''' Writes the list of errors to a text file for later examination ''' f = open('errorLog.txt', 'a') errors = ','.join(str(e) for e in self.err) f.write('\n' + 'Errors: ' + '[' + errors + ']' + ' at: ' + str(datetime.datetime.now())) f.close()
class Motor(object): ''' Motor class contains the methods necessary to send commands to the motor controllers for the corner and drive motors. There are many other ways of commanding the motors from the RoboClaw, we suggest trying to write your own Closed loop feedback method for the drive motors! ''' def __init__(self,config): super(Motor,self).__init__(config) self.rc = Roboclaw( config['CONTROLLER_CONFIG']['device'], config['CONTROLLER_CONFIG']['baud_rate'] ) self.rc.Open() self.address = config['MOTOR_CONFIG']['controller_address'] self.accel = [0] * 10 self.qpps = [None] * 10 self.err = [None] * 5 version = 1 for address in self.address: version = version & self.rc.ReadVersion(address)[0] print(self.rc.ReadVersion(address)[0]) if version != 0: print("[Motor__init__] Sucessfully connected to RoboClaw motor controllers") else: print("-----") raise Exception("Unable to establish connection to Roboclaw motor controllers") self.enc_min =[] self.enc_max =[] for address in self.address: self.rc.SetMainVoltages(address, int(config['BATTERY_CONFIG']['low_voltage']*10), int(config['BATTERY_CONFIG']['high_voltage']*10) ) if address == 131 or address == 132: self.rc.SetM1MaxCurrent(address, int(config['MOTOR_CONFIG']['max_corner_current']*100)) self.rc.SetM2MaxCurrent(address, int(config['MOTOR_CONFIG']['max_corner_current']*100)) self.enc_min.append(self.rc.ReadM1PositionPID(address)[-2]) self.enc_min.append(self.rc.ReadM2PositionPID(address)[-2]) self.enc_max.append(self.rc.ReadM1PositionPID(address)[-1]) self.enc_max.append(self.rc.ReadM2PositionPID(address)[-1]) else: self.rc.SetM1MaxCurrent(address, int(config['MOTOR_CONFIG']['max_drive_current']*100)) self.rc.SetM2MaxCurrent(address, int(config['MOTOR_CONFIG']['max_drive_current']*100)) self.rc.ResetEncoders(address) for address in self.address: self.rc.WriteNVM(address) for address in self.address: self.rc.ReadNVM(address) voltage = self.rc.ReadMainBatteryVoltage(0x80)[1]/10.0 if voltage >= config['BATTERY_CONFIG']['low_voltage']: print("[Motor__init__] Voltage is safe at: ",voltage, "V") else: raise Exception("Unsafe Voltage of" + voltage + " Volts") i = 0 for address in self.address: self.qpps[i] = self.rc.ReadM1VelocityPID(address)[4] self.accel[i] = int(self.qpps[i]*2) self.qpps[i+1] = self.rc.ReadM2VelocityPID(address)[4] self.accel[i+1] = int(self.qpps[i]*2) i+=2 self.errorCheck() def cornerToPosition(self,tick): ''' Method to send position commands to the corner motor :param list tick: A list of ticks for each of the corner motors to move to, if tick[i] is 0 it instead stops that motor from moving ''' speed, accel = 1000,2000 #These values could potentially need tuning still self.errorCheck() for i in range(4): index = int(math.ceil((i+1)/2.0)+2) if tick[i]: if (i % 2): self.rc.SpeedAccelDeccelPositionM2(self.address[index],accel,speed,accel,tick[i],1) else: self.rc.SpeedAccelDeccelPositionM1(self.address[index],accel,speed,accel,tick[i],1) else: if not (i % 2): self.rc.ForwardM1(self.address[index],0) else: self.rc.ForwardM2(self.address[index],0) def sendMotorDuty(self, motorID, speed): ''' Wrapper method for an easier interface to control the drive motors, sends open-loop commands to the motors :param int motorID: number that corresponds to each physical motor :param int speed: Speed for each motor, range from 0-127 ''' self.errorCheck() addr = self.address[int(motorID/2)] if speed > 0: if not motorID % 2: command = self.rc.ForwardM1 else: command = self.rc.ForwardM2 else: if not motorID % 2: command = self.rc.BackwardM1 else: command = self.rc.BackwardM2 speed = abs(int(speed * 127)) return command(addr,speed) def killMotors(self): ''' Stops all motors on Rover ''' for i in range(5): self.rc.ForwardM1(self.address[i],0) self.rc.ForwardM2(self.address[i],0) def errorCheck(self): ''' Checks error status of each motor controller, returns 0 if any errors occur ''' for i in range(5): self.err[i] = self.rc.ReadError(self.address[i])[1] for error in self.err: if error: self.killMotors() self.writeError() raise Exception("Motor controller Error", error) return 1 def writeError(self): ''' Writes the list of errors to a text file for later examination ''' f = open('errorLog.txt','a') errors = ','.join(str(e) for e in self.err) f.write('\n' + 'Errors: ' + '[' + errors + ']' + ' at: ' + str(datetime.datetime.now())) f.close()
rc.SetMainVoltages(0x80, 110, 340) # Allowed range: 11 V - 34 V rc.SetM1MaxCurrent(0x80, 500) # 5 Amps rc.SetPWMMode(0x80, 0) # Locked Antiphase #rc.ReadPWMMode(0x80) rc.SetM1EncoderMode(0x80, 0) # No RC/Analog support + Quadrature encoder #rc.ReadEncoderModes(0x80) getConfig = rc.GetConfig(0x80) config = getConfig[1] # index zero is 1 for success, 0 for failure. config = config | 0x0003 # Packet serial mode config = config | 0x8000 # Multi-Unit mode rc.SetConfig(0x80, config) rc.SetPinFunctions(0x80, 2, 0, 0) # S3 = E-Stop, S4 = Disabled, S5 = Disabled rc.WriteNVM(0x80) rc.ReadEncM1(0x80) rc.ResetEncoders(0x80) rc.ReadEncM1(0x80) p = 15000 i = 1000 d = 500 qpps = 3000 rc.SetM1VelocityPID(0x80, p, i, d, qpps) rc.ReadM1VelocityPID(0x80) rc.SpeedM1(0x80, 250)