First challenge... Drive in a straight line! Not as easy as it seems. You put both motors onto 100% power forward and it veers slightly to the right (or in my case it does!). The reason is simple the motors which drive the left and right wheels don't turn at exactly the same rate.
What to do. Well the Initio robot chasis I have is fitted with wheel encoders which 'tick' when the wheels go round to show how far they have moved, so if I wanted to make the robot go straight I needed to make left and right wheels moved the same distance.
So, I turn both motors on and count the encoder ticks, if the left one is getting ahead I slow it down a bit, unless the right catches up and vice versa!
I created a new python class called NavigationController which is where I will put all of my robots autonomous functions and created a straight function which when passed a power and a distance would drive straight until that distance was reached. It constantly checks the encoder values for both wheels and adjusts the power of the 2 motors to make sure it goes straight.
Code
You can see all my robot code here https://github.com/martinohanlon/initio
#Navigation class to accurately move initio robot
#Martin O'Hanlon
#www.stuffaboutcode.com
#import modules
import sys
import motorControl
import time
import RPi.GPIO as GPIO
import math
import thread
#states
STATEMOVING = 1
STATESTOPPED = 0
#Constant of Proportionality
# multipler used to convert difference in motor position to power change
KP = 2
#Wheel circumference
#(in mm)
WHEELCIRCUMFERENCEMM = 174.0
#(in encoder ticks)
WHEELCIRCUMFERENCE = 36.0
#Encoder to mm ratio (wheel circumference in mm / wheel circumference to encoder ticks)
ENCODERTOMM = WHEELCIRCUMFERENCEMM / WHEELCIRCUMFERENCE
class NavigationController:
#initialise - motors is the MotorControl object
def __init__(self, motors, distanceInMM = False):
self.motors = motors
self.state = STATESTOPPED
#if distanceinMM is set to True all distance are consider to be in millimeters
# if False all distances are in encoder ticks
self.distanceInMM = distanceInMM
#navigation state property
@property
def state(self):
return self.state
#motors property
@property
def motors(self):
return self.state
#move the robot in a straight line
def straight(self, power, distance = None, ownThread = False):
# if distance is not passed, continues forever and launches a seperate thread
# if distance is passed, loops until distance is reached
if distance == None:
#set a really long distance - this is a 'bit' dirty... But simple!
distance = 99999999999
#call it in its own thread
ownThread = True
if ownThread:
#call it in its own thread
thread.start_new_thread(self._straight,(power, distance))
else:
self._straight(power, distance)
#move the robot in a staight line
def _straight(self, power, distance):
#convert distance
if self.distanceInMM: distance = self._convertMillimetersToTicks(distance)
#if the state is moving, set it to STOP
if(self.state != STATESTOPPED): self.stop()
#turn on both motors
self.motors.start(power, power)
#change state to moving
self.state = STATEMOVING
#keep track of the last motor error, so we only change if we need too
lastMotorError = 0
#loop until the distance is reached or it has been STOPPED, correcting the power so the robot goes straight
while(self.motors.motorA.currentTicks < distance and self.state == STATEMOVING):
#get the number of ticks of each motor
#get the error by minusing one from the other
motorError = self.motors.motorA.currentTicks - self.motors.motorB.currentTicks
#print("motorError = " + str(motorError))
#only change if the motorError has changed
if(motorError != lastMotorError):
#work out the value to slow the motor down by using the KP
powerChange = (motorError * KP)
#print("powerChange = " + str(powerChange))
#in the unlikely event they are equal!
if(powerChange == 0):
self.motors.start(power, power)
else:
#if its going backward, turn the power change into a negative
if power < 0: powerChange * -1
#if its a positive number
if(motorError > 0):
#set motor A to power - 'slow down value'
#set motor B to power
self.motors.start(power - powerChange, power)
#if its a negative number
if(motorError < 0):
#set motor A to power
#set motor B to power - 'slow down value'
self.motors.start(power, power - powerChange)
#update last motor error
lastMotorError = motorError
# if they havent already been stopped - stop the motors
self.stop()
#stops the robot
def stop(self):
self.motors.stop()
self.state = STATESTOPPED
def _convertMillimetersToTicks(self, millimeters):
print ENCODERTOMM
return int(round(millimeters / ENCODERTOMM,0))
#tests
if __name__ == '__main__':
try:
#setup gpio
GPIO.setmode(GPIO.BOARD)
#create motor control
motors = motorControl.MotorController()
#create navigation control, use True for distances in millimeters
nav = NavigationController(motors, True)
#run a straight line just through motor control
print("straight line, no correction")
motors.start(100,100)
time.sleep(10)
#stop
print("stop")
motors.stop()
#get length
print("encoder ticks")
print(motors.motorA.totalTicks)
print(motors.motorB.totalTicks)
#time.sleep(10)
#reset encoder ticks
motors.motorA.resetTotalTicks()
motors.motorB.resetTotalTicks()
#run a straight line through nav control
print("straight line, with correction")
nav.straight(100, 500)
#get length
print("encoder ticks")
print(motors.motorA.totalTicks)
print(motors.motorB.totalTicks)
#Ctrl C
except KeyboardInterrupt:
print "User cancelled"
#Error
except:
print "Unexpected error:", sys.exc_info()[0]
raise
finally:
print ("cleanup")
#cleanup gpio
GPIO.cleanup()
Next job - turning in an arc!
Good job!
ReplyDeleteI've been playing around with PID controllers in robotics, have you thought about adding the integral term to your controller? Would make it more accurate over longer distances!
Awesome stuff! Very interesting.
This program really helped me. I used the kit as a base to build a talking robot and despite all of my attempts, I couldn't make the robot move however this program worked. Thanks!
ReplyDelete