GuyCarver-MicroPython/esp32/pca9865.py

116 wiersze
3.5 KiB
Python

# MicroPython PCA9865 16 servo controller driver for ESP32.
#NOTE: I tried writing 16 bit values for PWM but it crashed the controller requiring a power cycle to reset.
import machine
from time import sleep_us
class pca9865(object):
'''16 servo contoller. Use index 0-15 for the servo #.'''
_ADDRESS = 0x40
_MODE1 = const(0)
_PRESCALE = const(0xFE)
_LED0_ON_L = const(0x6) #We only use LED0 and offset 0-16 from it.
# _LED0_ON_H = const(0x7)
# _LED0_OFF_L = const(0x8)
# _LED0_OFF_H = const(0x9)
# _ALLLED_ON_L = const(0xFA)
# _ALLLED_ON_H = const(0xFB)
# _ALLLED_OFF_L = const(0xFC)
# _ALLLED_OFF_H = const(0xFD)
_DEFAULTFREQ = const(60)
_MINPULSE = const(120)
_MAXPULSE = const(600)
def __init__(self, aSDA, aSCL) :
'''aSDA is I2C SDA pin #, aSCL is I2C SCL pin #.'''
super(pca9865, self).__init__()
self.i2c = machine.I2C(scl = machine.Pin(aSCL), sda = machine.Pin(aSDA))
self._buffer = bytearray(4)
self._b1 = bytearray(1)
sleep_us(50)
self.reset()
self.minmax(_MINPULSE, _MAXPULSE)
def minmax( self, aMin, aMax ) :
'''Set min/max and calculate range.'''
self._min = aMin
self._max = aMax
self._range = aMax - aMin
def read( self, aLoc ) :
'''Read 8 bit value and return.'''
self.i2c.readfrom_mem_into(self._ADDRESS, aLoc, self._b1)
return self._b1[0]
def writebuffer( self, aBuffer, aLoc ) :
"""Write buffer to given address."""
self.i2c.writeto_mem(self._ADDRESS, aLoc, aBuffer)
def write( self, aVal, aLoc ) :
"""Write 8 bit integer aVal to given address aLoc."""
self._b1[0] = aVal
self.writebuffer(self._b1, aLoc)
def reset( self ):
'''Reset the controller and set default frequency.'''
self.write(0, _MODE1)
self.setfreq(_DEFAULTFREQ)
def setfreq( self, aFreq ) :
'''Set frequency for all servos. A good value is 60hz (default).'''
aFreq *= 0.9 #Correct for overshoot in frequency setting.
prescalefloat = (6103.51562 / aFreq) - 1 #25000000 / 4096 / freq.
prescale = int(prescalefloat + 0.5)
oldmode = self.read(_MODE1)
newmode = (oldmode & 0x7F) | 0x10
self.write(newmode, _MODE1)
self.write(prescale, _PRESCALE)
self.write(oldmode, _MODE1)
sleep_us(50)
self.write(oldmode | 0xA1, _MODE1) #This sets the MODE1 register to turn on auto increment.
def setpwm( self, aServo, aOn, aOff ) :
'''aServo = 0-15.
aOn = 16 bit on value.
aOff = 16 bit off value.
'''
if 0 <= aServo <= 15 :
#Data = on-low, on-high, off-low and off-high. That's 4 bytes each servo.
loc = _LED0_ON_L + (aServo * 4)
# print(loc)
self._buffer[0] = aOn
self._buffer[1] = aOn >> 8
self._buffer[2] = aOff
self._buffer[3] = aOff >> 8
self.writebuffer(self._buffer, loc)
else:
raise Exception('Servo index {} out of range.'.format(str(aServo)))
def off( self, aServo ) :
'''Turn off a servo.'''
self.setpwm(aServo, 0, 0)
def alloff( self ) :
'''Turn all servos off.'''
for x in range(0, 16):
self.off(x)
def set( self, aServo, aPerc ) :
'''Set the 0-100%. If < 0 turns servo off.'''
if aPerc < 0 :
self.off(aServo)
else:
val = self._min + ((self._range * aPerc) // 100)
self.setpwm(aServo, 0, val)
def setangle( self, aServo, aAngle ) :
'''Set angle -90 to +90. < -90 is off.'''
#((a + 90.0) * 100.0) / 180.0
perc = int((aAngle + 90.0) * 0.5556) #Convert angle +/- 90 to 0-100%
self.set(aServo, perc)