pca9685: Added Full-on/off support, fixed scaling to 12 bits (#443)

* added sanity check to channel ID arguments
* Extracted init sequence in tests
* Fixed max duty constant
pull/1/head
Balázs Grill 6 years ago committed by GitHub
parent 4f5701e9eb
commit 36bdd0caf4

@ -5,6 +5,7 @@
package pca9685 package pca9685
import ( import (
"fmt"
"time" "time"
"periph.io/x/periph/conn/gpio" "periph.io/x/periph/conn/gpio"
@ -143,5 +144,48 @@ func (d *Dev) SetAllPwm(on, off gpio.Duty) error {
// SetPwm set a PWM value for a given PCA9685 channel. // SetPwm set a PWM value for a given PCA9685 channel.
func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error { func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error {
err := verifyChannel(channel)
if err != nil {
return err
}
return d.setPWM(led0OnL+byte(4*channel), on, off) return d.setPWM(led0OnL+byte(4*channel), on, off)
} }
// SetFullOff sets PWM duty to 0%.
//
// This function uses the dedicated bit to reduce bus traffic.
func (d *Dev) SetFullOff(channel int) error {
err := verifyChannel(channel)
if err != nil {
return err
}
_, err = d.dev.Write([]byte{
led0OnL + byte(4*channel) + 3, // LEDX_OFF_H
0x10, // bit 4 is full-off
})
return err
}
// SetFullOn sets PWM duty to 100%.
//
// This function uses the dedicated FULL_ON bit.
func (d *Dev) SetFullOn(channel int) error {
err := verifyChannel(channel)
if err != nil {
return err
}
_, err = d.dev.Write([]byte{
led0OnL + byte(4*channel) + 1, // LEDX_ON_H
0x10, // bit 4 is full-on
0,
0, // LEDX_OFF_H is cleared because full-off has a priority over full-on
})
return err
}
func verifyChannel(channel int) error {
if channel < 0 || channel > 15 {
return fmt.Errorf("PCA9685: invalid channel: %d", channel)
}
return nil
}

@ -13,34 +13,38 @@ import (
"periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/physic"
) )
func initializationSequence() []i2ctest.IO {
return []i2ctest.IO{
// All leds cleared by init
{Addr: I2CAddr, W: []byte{allLedOnL, 0, 0, 0, 0}, R: nil},
// mode2 is set
{Addr: I2CAddr, W: []byte{mode2, outDrv}, R: nil},
// mode1 is set
{Addr: I2CAddr, W: []byte{mode1, allCall}, R: nil},
// mode1 is read and sleep bit is cleared
{Addr: I2CAddr, W: []byte{mode1}, R: []byte{allCall | sleep}},
{Addr: I2CAddr, W: []byte{mode1, allCall | ai}, R: nil},
// SetPwmFreq 50 Hz
// Read mode
{Addr: I2CAddr, W: []byte{0x00}, R: []byte{allCall | ai}},
// Set sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | sleep}, R: nil},
// Set prescale
{Addr: I2CAddr, W: []byte{prescale, 122}, R: nil},
// Clear sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai}, R: nil},
// Set Restart
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | restart}, R: nil},
}
}
func TestPCA9685_pin(t *testing.T) { func TestPCA9685_pin(t *testing.T) {
scenario := &i2ctest.Playback{ scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: append(initializationSequence(),
// All leds cleared by init
{Addr: I2CAddr, W: []byte{allLedOnL, 0, 0, 0, 0}, R: nil},
// mode2 is set
{Addr: I2CAddr, W: []byte{mode2, outDrv}, R: nil},
// mode1 is set
{Addr: I2CAddr, W: []byte{mode1, allCall}, R: nil},
// mode1 is read and sleep bit is cleared
{Addr: I2CAddr, W: []byte{mode1}, R: []byte{allCall | sleep}},
{Addr: I2CAddr, W: []byte{mode1, allCall | ai}, R: nil},
// SetPwmFreq 50 Hz
// Read mode
{Addr: I2CAddr, W: []byte{0x00}, R: []byte{allCall | ai}},
// Set sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | sleep}, R: nil},
// Set prescale
{Addr: I2CAddr, W: []byte{prescale, 122}, R: nil},
// Clear sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai}, R: nil},
// Set Restart
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | restart}, R: nil},
// Set PWM value of pin 0 to 50% // Set PWM value of pin 0 to 50%
{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 128}, R: nil}, i2ctest.IO{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 0x08}, R: nil},
}, ),
} }
dev, err := NewI2C(scenario, I2CAddr) dev, err := NewI2C(scenario, I2CAddr)
@ -51,39 +55,62 @@ func TestPCA9685_pin(t *testing.T) {
if err = dev.RegisterPins(); err != nil { if err = dev.RegisterPins(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer dev.UnregisterPins()
pin := gpioreg.ByName("PCA9685_40_0") pin := gpioreg.ByName("PCA9685_40_0")
pin.PWM(gpio.DutyHalf, 50*physic.Hertz) pin.PWM(gpio.DutyHalf, 50*physic.Hertz)
} }
func TestPCA9685(t *testing.T) { func TestPCA9685_pin_fullOff(t *testing.T) {
scenario := &i2ctest.Playback{
Ops: append(initializationSequence(),
// Set PWM value of pin 0 to 0%
i2ctest.IO{Addr: I2CAddr, W: []byte{led0OnL + 3, 0x10}, R: nil},
),
}
dev, err := NewI2C(scenario, I2CAddr)
if err != nil {
t.Fatal(err)
}
if err = dev.RegisterPins(); err != nil {
t.Fatal(err)
}
defer dev.UnregisterPins()
pin := gpioreg.ByName("PCA9685_40_0")
pin.PWM(0, 50*physic.Hertz)
}
func TestPCA9685_pin_fullOn(t *testing.T) {
scenario := &i2ctest.Playback{ scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: append(initializationSequence(),
// All leds cleared by init // Set PWM value of pin 0 to 100%
{Addr: I2CAddr, W: []byte{allLedOnL, 0, 0, 0, 0}, R: nil}, i2ctest.IO{Addr: I2CAddr, W: []byte{led0OnL + 1, 0x10, 0, 0}, R: nil},
// mode2 is set ),
{Addr: I2CAddr, W: []byte{mode2, outDrv}, R: nil}, }
// mode1 is set
{Addr: I2CAddr, W: []byte{mode1, allCall}, R: nil},
// mode1 is read and sleep bit is cleared
{Addr: I2CAddr, W: []byte{mode1}, R: []byte{allCall | sleep}},
{Addr: I2CAddr, W: []byte{mode1, allCall | ai}, R: nil},
// SetPwmFreq 50 Hz
// Read mode
{Addr: I2CAddr, W: []byte{0x00}, R: []byte{allCall | ai}},
// Set sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | sleep}, R: nil},
// Set prescale
{Addr: I2CAddr, W: []byte{prescale, 122}, R: nil},
// Clear sleep
{Addr: I2CAddr, W: []byte{0x00, allCall | ai}, R: nil},
// Set Restart
{Addr: I2CAddr, W: []byte{0x00, allCall | ai | restart}, R: nil},
dev, err := NewI2C(scenario, I2CAddr)
if err != nil {
t.Fatal(err)
}
if err = dev.RegisterPins(); err != nil {
t.Fatal(err)
}
defer dev.UnregisterPins()
pin := gpioreg.ByName("PCA9685_40_0")
pin.PWM(gpio.DutyMax, 50*physic.Hertz)
}
func TestPCA9685(t *testing.T) {
scenario := &i2ctest.Playback{
Ops: append(initializationSequence(),
// Set PWM value of pin 0 to 50% // Set PWM value of pin 0 to 50%
{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 128}, R: nil}, i2ctest.IO{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 0x08}, R: nil},
}, ),
} }
dev, err := NewI2C(scenario, I2CAddr) dev, err := NewI2C(scenario, I2CAddr)
@ -91,7 +118,22 @@ func TestPCA9685(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err = dev.SetPwm(0, 0, 0x8000); err != nil { if err = dev.SetPwm(0, 0, 0x800); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestPCA9685_invalidCh(t *testing.T) {
scenario := &i2ctest.Playback{
Ops: append(initializationSequence()),
}
dev, err := NewI2C(scenario, I2CAddr)
if err != nil {
t.Fatal(err)
}
if err = dev.SetPwm(16, 0, 0x800); err == nil {
t.Fatal("Error expected")
}
}

@ -7,7 +7,6 @@ package pca9685
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"time" "time"
"periph.io/x/periph/conn/gpio" "periph.io/x/periph/conn/gpio"
@ -17,7 +16,7 @@ import (
) )
const ( const (
dutyMax gpio.Duty = math.MaxUint16 dutyMax gpio.Duty = 1<<12 - 1
) )
type pin struct { type pin struct {
@ -27,8 +26,9 @@ type pin struct {
// CreatePin creates a gpio handle for the given channel. // CreatePin creates a gpio handle for the given channel.
func (d *Dev) CreatePin(channel int) (gpio.PinIO, error) { func (d *Dev) CreatePin(channel int) (gpio.PinIO, error) {
if channel < 0 || channel >= 16 { err := verifyChannel(channel)
return nil, errors.New("PCA9685: Valid channel range is 0..15") if err != nil {
return nil, err
} }
return &pin{ return &pin{
dev: d, dev: d,
@ -52,6 +52,17 @@ func (d *Dev) RegisterPins() error {
return nil return nil
} }
// UnregisterPins remove all previously created pin handles of this device from the GPIO registry
func (d *Dev) UnregisterPins() error {
for i := 0; i < 16; i++ {
err := gpioreg.Unregister(d.pinName(i))
if err != nil {
return err
}
}
return nil
}
func (p *pin) String() string { func (p *pin) String() string {
return p.Name() return p.Name()
} }
@ -60,8 +71,12 @@ func (p *pin) Halt() error {
return p.Out(gpio.Low) return p.Out(gpio.Low)
} }
func (d *Dev) pinName(channel int) string {
return fmt.Sprintf("PCA9685_%x_%d", d.dev.Addr, channel)
}
func (p *pin) Name() string { func (p *pin) Name() string {
return fmt.Sprintf("PCA9685_%x_%d", p.dev.dev.Addr, p.channel) return p.dev.pinName(p.channel)
} }
func (p *pin) Number() int { func (p *pin) Number() int {
@ -100,8 +115,17 @@ func (p *pin) PWM(duty gpio.Duty, freq physic.Frequency) error {
if err := p.dev.SetPwmFreq(freq); err != nil { if err := p.dev.SetPwmFreq(freq); err != nil {
return err return err
} }
// PWM duty scaled down from 24 to 16 bits
scaled := duty >> 8 if duty == gpio.DutyMax {
return p.dev.SetFullOn(p.channel)
}
if duty == 0 {
return p.dev.SetFullOff(p.channel)
}
// PWM duty scaled down from 24 to 12 bits
scaled := duty >> 12
if scaled > dutyMax { if scaled > dutyMax {
scaled = dutyMax scaled = dutyMax
} }

Loading…
Cancel
Save