not logged in | [Login]
Following on from my h-bridge motor driver skin, I encountered the 'god of engineering' trying to put it to use - dc motor's are high-speed but low-torque, gearing is required, shoddy gears have a lot of friction. Hence this; a driver for Lego NXT (v2.0?) motors which have reasonable in-built gearing. It again requires a TI754410 quad h-bridge (albeit I've only used half of it) and has the advantage (more later) of having an inbuilt rotary encoder that you can read and (hope) to use to determine actual, rather than set, speed.
This one;
I again built this on a bit of 12x8 perfboard, this time with the TI754410 right-way-up, added some diodes to reduce the chances of me burning things out with wrong-ways battery connections, and (because there was room) added in a little bitty 3mm led for flashy-indicatory-kind-of-thingies.
TI SN 754410 NE
Quad H-bridge
-----------------------
|1 1,2EN +ve 16| -> Vin
|2 1A 4A 15| -> Pin Y11
|3 1Y 4Y 14| -> NXT Mtr / Black Wire / Motor Pole 1
Gnd & -ve Bat via diode <- |4 Gnd Gnd 13| -> Gnd & -ve Bat via diode
Gnd & -ve Bat via diode <- |5 Gnd Gnd 12| -> Gnd & -ve Bat via diode
|6 2Y 3Y 11| -> NXT Mtr / White Wire / Motor Pole 2
|7 2A 3A 10| -> Pin Y10
+ve Bat <- diode <- |8 +ve Bat 3,4EN 9| -> Pin Y9
-----------------------
Lego NXT cable termination block
-------
| + -> Black Wire / Motor Pole 1 -> H-Bridge Pin 14 (Y4)
| + -> White Wire / Motor Pole 2 -> H-Bridge Pin 11 (3Y)
| + -> Green Wire / Power for sensor / pyboard 3.3v Pin
| + -> Red Wire / Gnd for sensro / to Gnd
| + -> Blue Wire / Rotary Encoder Tack 0 / Pin Y8
| + -> Yellow Wire / Rotary Encoder Tack 1 / Pin Y7
-------
LED
Pin Y12 -> Led -> Gnd (no current limiting resistor, living dangerous!)
>>> import nxtmotor
>>> nxt=nxtmotor.NXTMOTOR(nxtmotor.Y) #instance the class for skin in Y position
>>> nxt.forwards() #full steam ahead
>>> nxt.backwards() #full steam backwards
>>> nxt.stop() #full stop
>>> nxt.forwards(255/2) #half steam ahead
>>> nxt.stop() #full stop
>>> nxt.state(255/2) #half speed ahead
>>> nxt.state(-255/2) #half speed backwards
>>> nxt.state() #what speed have we set anyway?
-127
>>> nxt.stop() #full stop
>>> nxt.state()
0
Your motor, power supply and what-not will differ from mine. I've set some defaults (the constants CALIB_HIGH and CALIB_LOW, which you can change) there is a method to run a simple test, calculate and set suitable values, you probably want to run it without the motor being under load;
>>> nxt.calibrate()
Spinning up to maximum speed ...
Capturing 1000 samples at max speed, 10ms intervals, please wait ...
Spinning down to lowest speed ...
Capturing 1000 samples at lowest speed, 10ms intervals, please wait ...
Calibration high: 3944
Calibration low: 6782
>>> nxt.calib_high #the high set-point is now set
3944
>>> nxt.calib_low #the low set-point is also now set
6782
These calibration values are used to scale the actual numbers that come out of the rotary encoder to try and match them to the pwm range of 0-255 ... but, as noted above; it doesn't work very well ... motor behaviour appears to have a non-linear response to input pwm duty/voltage.
>>> nxt.state(50)
>>> nxt.speed()
54.6535
>>> nxt.speed()
54.2636
>>> nxt.state(255)
>>> nxt.speed()
271.432
>>> nxt.speed()
254.876
>>> nxt.speed()
252.651
looks promising!!!, but then again;
>>> nxt.state(100)
>>> nxt.speed()
168.561
>>> nxt.speed()
169.405
>>> nxt.state(200)
>>> nxt.speed()
231.582
>>> nxt.speed()
229.006
curse the real world, why is nothing simple? ;o) ... if you're smarter than me you can no-doubt improve the calibration routine and speed calcs; self.__pulsetime
(time since last rising edge on monitored encoder tack) and self._pulsedir
(direction the encoder thinks it's spinning) are the key
But to make up for all that silliness, at least you can turn the led on and off eh?
>>> nxt.led(True) #on - fantastic
>>> nxt.led(False) #off - not so fantastic
nxtmotor.py
import pyb, pwm, gc
#these values relate to pwm range, don't change without changing pwm.py
FORWARDS = 255
BACKWARDS = -FORWARDS
STOP = 0
#the lowest speed my motor will actually turn at is 35
#behaviour of speed measurements is put off by v.low values
LOW_SPEED = 50
#led statuses
LED_ON = 1
LED_OFF = 0
#change these values to the average output of .calibrate()
#for your motor(s) - these are mine, not yours!
CALIB_HIGH = 3875
CALIB_LOW = 6600
#possible nxt motor shield configurations, depends on skin orientation
Y = { 'enable': pyb.Pin.board.Y9, \
'control0': pyb.Pin.board.Y10, \
'control1': pyb.Pin.board.Y11, \
'led': pyb.Pin.board.Y12, \
'tack0': pyb.Pin.board.Y8, \
'tack1': pyb.Pin.board.Y7 \
}
X = { 'enable': pyb.Pin.board.X9, \
'control0': pyb.Pin.board.X10, \
'control1': pyb.Pin.board.X11, \
'led': pyb.Pin.board.X12, \
'tack0': pyb.Pin.board.X8, \
'tack1': pyb.Pin.board.X7 \
}
class NXTMOTOR:
"""dirty NXT Motor class"""
def __init__(self, pins_dict, reverse_pols=False, calib_high=CALIB_HIGH, calib_low=CALIB_LOW):
self.__enable = pwm.PWM(pins_dict['enable'])
self.__control0 = pins_dict['control0'] if not reverse_pols else pins_dict['control1']
self.__control0.init(pyb.Pin.OUT_PP)
self.__control0.low()
self.__control1 = pins_dict['control1'] if not reverse_pols else pins_dict['control0']
self.__control1.init(pyb.Pin.OUT_PP)
self.__control1.low()
self.__led = pins_dict['led']
self.__led.init(pyb.Pin.OUT_PP)
self.__tack0 = pins_dict['tack0'] if not reverse_pols else pins_dict['tack1']
self.__tack1 = pins_dict['tack1'] if not reverse_pols else pins_dict['tack0']
self.__tack1.init(pyb.Pin.IN)
self.calib_high = calib_high
self.calib_low = calib_low
#start a very fast timer, with a high period (pyb.millis doesn't give enough resolution)
self.__ucounter = pyb.Timer(6, prescaler=83, period=0x7fffffff)
self.__pulsetime = 0 #this is measured speed ... we are at rest
self.__pulsedir = -1 #invalid __pulsedir value
#register interrupt on tack0
self.__extint = pyb.ExtInt(self.__tack0, pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_UP, self.__ISR)
@micropython.native
def __ISR(self, line):
self.__pulsetime = self.__ucounter.counter()
self.__ucounter.counter(0)
self.__pulsedir = self.__tack1.value()
def speed(self):
if self.__pulsetime > self.calib_low+2000:
#it is safe to assume we've stopped ...
return 0
else:
sign = +1 if self.__pulsedir else -1 #account for direction
return sign*((((1/self.__pulsetime) - (1/self.calib_low)) * ((FORWARDS-LOW_SPEED) / ((1/self.calib_high)-(1/self.calib_low))))+LOW_SPEED)
def calibrate(self, num_samples=1000):
samples_high = []
samples_low = []
print('Spinning up to maximum speed ...')
self.state(FORWARDS)
pyb.delay(1000) #let the motor get up to speed
print('Capturing 1000 samples at max speed, 10ms intervals, please wait ...')
for i in range(num_samples):
samples_high.append(self.__pulsetime)
pyb.delay(10)
print('Spinning down to lowest speed ...')
self.state(LOW_SPEED) #on my rig
pyb.delay(3000) #let the motor get up to speed
print('Capturing 1000 samples at lowest speed, 10ms intervals, please wait ...')
for i in range(num_samples):
samples_low.append(self.__pulsetime)
pyb.delay(10)
self.stop()
self.calib_high = int(sum(samples_high)/len(samples_high))
self.calib_low = int(sum(samples_low)/len(samples_low))
print('Calibration high: {0}\nCalibration low: {1}'.format(self.calib_high, self.calib_low))
gc.collect()
def state(self, value=None):
"""get or set motor state as -ve|0|+ve as backwards|stop|forwards"""
if value == None:
if self.__enable.duty() > 0:
if self.__control0.value() and not self.__control1.value():
return -self.__enable.duty()
elif not self.__control0.value() and self.__control1.value():
return self.__enable.duty()
else:
return ValueError('Inconsistent state')
else:
return 0
elif value < 0:
self.__control0.high()
self.__control1.low()
self.__enable.duty(abs(value))
elif value > 0:
self.__control0.low()
self.__control1.high()
self.__enable.duty(abs(value))
elif value == 0:
self.__enable.duty(0)
else:
raise ValueError('Invalid state value')
def backwards(self, value=-BACKWARDS):
self.state(-value)
def forwards(self, value=FORWARDS):
self.state(value)
def stop(self):
self.state(STOP)
def led(self, value=None):
if value == None:
return self.__led.value()
else:
self.__led.value(value)
Last edited by PinkInk, 2014-07-30 17:10:35