mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
add PCA9685 driver
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
)
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 1⁄4096 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])
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user