add PCA9685 driver

This commit is contained in:
soypat
2021-11-21 00:50:56 -03:00
committed by Ron Evans
parent 966210f1b0
commit b33c84ff78
5 changed files with 370 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/pca9685"
)
func main() {
const (
// Default address on most breakout boards.
pcaAddr = 0x40
)
err := machine.I2C0.Configure(machine.I2CConfig{})
if err != nil {
panic(err.Error())
}
d := pca9685.New(machine.I2C0, 0x40)
err = d.IsConnected()
if err != nil {
panic(err.Error())
}
err = d.Configure(pca9685.PWMConfig{Period: 1e9 / 200}) // 200Hz PWM
if err != nil {
panic(err.Error())
}
var value uint32
step := d.Top() / 5
for {
for value = 0; value <= d.Top(); value += step {
d.SetAll(value)
dc := 100 * value / d.Top()
println("set dc @", dc, "%")
time.Sleep(800 * time.Millisecond)
}
}
}
// ScanI2CDev finds I2C devices on the bus and rreturns them inside
// a slice. If slice is nil then no devices were found.
func ScanI2CDev(bus machine.I2C) (addrs []uint8) {
var addr, count uint8
var err error
w := []byte{1}
// Count devices in first scan
for addr = 1; addr < 127; addr++ {
err = bus.Tx(uint16(addr), w, nil)
if err == nil {
count++
}
}
if count == 0 {
return nil
}
// Allocate slice and populate slice with addresses
addrs = make([]uint8, count)
count = 0
for addr = 1; addr < 127; addr++ {
err = bus.Tx(uint16(addr), w, nil)
if err == nil && count < uint8(len(addrs)) {
addrs[count] = addr
count++
}
}
return addrs
}
+8
View File
@@ -0,0 +1,8 @@
package pca9685
import "errors"
var (
ErrInvalidMode1 = errors.New("pca9685: data read from MODE1 not valid")
ErrBadPeriod = errors.New("pca9685: period must be in range 1..25ms")
)
+9
View File
@@ -0,0 +1,9 @@
package pca9685
func (d *Dev) readReg(reg uint8, data []byte) error {
return d.bus.ReadRegister(d.addr, reg, data)
}
func (d *Dev) writeReg(reg uint8, data []byte) error {
return d.bus.WriteRegister(d.addr, reg, data)
}
+216
View File
@@ -0,0 +1,216 @@
package pca9685
import (
"encoding/binary"
"time"
"tinygo.org/x/drivers"
)
const (
// Internal oscillator frequency.
oscclock = 25000000
// Max value PWM value (on or off) can take.
maxtop = 1<<12 - 1
milliseconds = 1_000_000 // units in nanoseconds
)
// Dev is a handle to the PCA9685 device given an address
// (usually 0x47) and an i2c bus.
type Dev struct {
addr uint8
bus drivers.I2C
buf [4]byte
}
type PWMConfig struct {
Period uint64
}
// New creates a new instance of a PCA9685 device. It performs
// no IO on the i2c bus.
func New(bus drivers.I2C, addr uint8) Dev {
return Dev{
bus: bus,
addr: addr,
}
}
// Configure enables autoincrement, sets all PWM signals to logic low (Ground)
// and finally sets the Period.
func (d Dev) Configure(cfg PWMConfig) error {
err := d.SetAI(true)
if err != nil {
return err
}
d.SetAll(0)
d.SetDrive(true)
return d.SetPeriod(cfg.Period)
}
// SetPeriod updates the period of this PWM integrated circuit in nanoseconds.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// In the equation above frequency is in Hertz.
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// PCA9685 accepts frequencies inbetween [40..1000 Hz],
// or expressed as a period [1..25ms].
func (d Dev) SetPeriod(period uint64) error {
const div = maxtop + 1
if period == 0 {
period = 1 * milliseconds
}
if period > 25*milliseconds || period < 1*milliseconds {
return ErrBadPeriod
}
// Correct for overshoot in provided frequency: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/issues/11
// Note: 0.96 was empirically determined to be closer. Should follow up to understand what is happening here.
freq := 96 * 1e9 / (100 * period)
prescale := byte(oscclock/(div*freq) - 1)
err := d.Sleep(true) // Enable sleep to write to PRESCALE register
if err != nil {
return err
}
d.buf[0] = prescale
err = d.writeReg(PRESCALE, d.buf[:1])
if err != nil {
return err
}
return d.Sleep(false)
}
// Top returns max value PWM can take.
func (d Dev) Top() uint32 {
return maxtop
}
// Set sets the `on` value of a PWM channel in the range [0..15].
// Example:
// d.Set(1, d.Top()/4)
// sets the dutycycle of second (LED1) channel to 25%.
func (d Dev) Set(channel uint8, on uint32) {
switch {
case on > maxtop:
panic("pca9685: value must be in range 0..4096")
case on == 0:
d.SetPhased(channel, 0, maxtop)
return
}
d.SetPhased(channel, on, 0)
}
// SetAll sets all PWM signals to a ON value. Equivalent of calling
// Dev.Set(pca9685.ALLLED, value)
func (d Dev) SetAll(on uint32) {
d.Set(ALLLED, on)
}
// IsConnected returns error if read fails or if
// driver suspects device is not connected.
func (d Dev) IsConnected() error {
// Set data to the NOT of default MODE1 contents.
// If read is succesful then data will be modified
const notdefaultMODE1 = ^defaultMODE1Value
d.buf[0] = notdefaultMODE1
err := d.readReg(MODE1, d.buf[:1])
if err != nil {
return err
} else if d.buf[0] == notdefaultMODE1 {
return ErrInvalidMode1
}
return nil
}
// SetAI enables or disables autoincrement feature on device. Useful for
// writing to many consecutive registers in one shot.
func (d Dev) SetAI(ai bool) error {
err := d.readReg(MODE1, d.buf[:1])
if err != nil {
return err
}
if ai {
d.buf[0] |= AI
} else {
d.buf[0] &^= AI
}
err = d.writeReg(MODE1, d.buf[:1])
return err
}
// SetDrive configures PWM output connection in MODE2 register.
// false: The 16 LEDn outputs are configured with an open-drain structure.
// true: The 16 LEDn outputs are configured with a totem pole structure.
func (d Dev) SetDrive(outdrv bool) error {
err := d.readReg(MODE2, d.buf[:1])
if err != nil {
return err
}
if outdrv {
d.buf[0] |= OUTDRV
} else {
d.buf[0] &^= OUTDRV
}
return d.writeReg(MODE2, d.buf[:1])
}
// Sleep sets/unsets SLEEP bit in MODE1.
// if sleepEnabled
// Stops PWM. Allows writing to PRE_SCALE register.
// else
// wakes PCA9685. Resumes PWM.
func (d Dev) Sleep(sleepEnabled bool) error {
err := d.readReg(MODE1, d.buf[:1])
if err != nil {
return err
}
if sleepEnabled {
d.buf[0] |= SLEEP
return d.writeReg(MODE1, d.buf[:1])
}
d.buf[0] &^= SLEEP
err = d.writeReg(MODE1, d.buf[:1])
// It takes 500μs max for the oscillator to be up and running once SLEEP bit
// has been set to logic 0. Timings on LEDn outputs are not guaranteed if PWM
// control registers are accessed within the 500μs window.
// There is no start-up delay required when using the EXTCLK pin as the PWM clock.
time.Sleep(1000 * time.Microsecond) // Requested by datasheet.
return err
}
// SetInverting inverts ALL PCA9685 PWMs. The channel argument merely implements PWM interface.
//
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (d Dev) SetInverting(_ uint8, inverting bool) error {
err := d.readReg(MODE2, d.buf[:1])
if err != nil {
return err
}
if inverting {
d.buf[0] |= INVRT
} else {
d.buf[0] &^= INVRT
}
return d.writeReg(MODE2, d.buf[:1])
}
// SetPhased sets PWM on and off mark.
// The ON time, which is programmable, will be the time the LED output
// will be asserted and the OFF time, which is also programmable, will be
// the time when the LED output will be negated.
// In this way, the phase shift becomes completely programmable.
// The resolution for the phase shift is 14096 of the target frequency.
func (d Dev) SetPhased(channel uint8, on, off uint32) {
binary.LittleEndian.PutUint16(d.buf[:2], uint16(on)&maxtop)
binary.LittleEndian.PutUint16(d.buf[2:4], uint16(off)&maxtop)
onLReg, _, _, _ := LED(channel)
d.writeReg(onLReg, d.buf[:4])
}
+69
View File
@@ -0,0 +1,69 @@
package pca9685
// Software reset addresses for generic i2c implementation
// and PCA specific SWRST. See section 7.6
const (
I2CSWRSTADR = 0x0
PCA9685SWRSTBYTE = 0b0000_0110 // 0x06
)
// Registries with nomenclature as seen in manual
const (
SUBADR1 uint8 = 0x2
SUBADR2 uint8 = 0x3
SUBADR3 uint8 = 0x4
MODE1 uint8 = 0x0
MODE2 uint8 = 0x1
ALLCALLADR uint8 = 0x05
SWRESET uint8 = 0b0000_0011
// Start of LED registries. corresponds to LED0_ON_L register.
// Use LED function to get registries of a specific PWM channel register.
LEDSTART = 0x06
)
// MODE1
const (
RESET byte = 0b1000_0000
EXTCLK byte = 0b0100_0000
AI byte = 0b0010_0000
SLEEP byte = 0b0001_0000
defaultMODE1Value byte = 0b0001_0001
)
// MODE2
const (
OUTDRV = 1 << 2
INVRT = 1 << 4
)
// LED channels from 0-15. Returns 4 registries associated with
// the channel PWM signal. Channel 250 (0xFA) gives ALL_LED registers.
// The L suffix represents the 8 LSB of 12 bit timing, the H suffix represents
// the 4 MSB of the timing. This way you have 0-4095 timing options. ON or OFF
// will be a number between these two or will be simply ON or OFF (fifth bit of H registry).
//
// The SetPWM implementation in this library does not do phase-shifting so OFF will always
// happen at time stamp 0. ON will solely decide duty cycle.
func LED(ch uint8) (ONL, ONH, OFFL, OFFH uint8) {
switch {
case ch == ALLLED:
return ALLLED, ALLLED + 1, ALLLED + 2, ALLLED + 3
case ch > 15:
panic("PWM channel out of range [0-15]")
default:
}
// 4 registries per channel. Starts at 6
onLReg := LEDSTART + 4*ch
return onLReg, onLReg + 1, onLReg + 2, onLReg + 3
}
const (
// ALLLED is channel that selects registries to control all leds. Use with function LED
ALLLED = 0xfa
// PRESCALE Prescaling byte
PRESCALE byte = 0xFE
)