Skip to content

Commit

Permalink
pca9685: added gpio pin API (google#437)
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsgrill committed Apr 14, 2020
1 parent 03ec8a9 commit ea1b73a
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 1 deletion.
11 changes: 10 additions & 1 deletion experimental/devices/pca9685/pca9685.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const (

// Dev is a handler to pca9685 controller.
type Dev struct {
dev *i2c.Dev
dev *i2c.Dev
freq physic.Frequency
}

// NewI2C returns a Dev object that communicates over I2C.
Expand Down Expand Up @@ -90,6 +91,13 @@ func (d *Dev) init() error {

// SetPwmFreq set the PWM frequency.
func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
if d.freq == freqHz {
// Don't need to write frequency if it's not changed.
// Note: this is required to avoid setting it each time
// when PWM value is changed via gpio.PinOut.PWM() API
return nil
}

p := (25*physic.MegaHertz/4096 + freqHz/2) / freqHz

modeRead := [1]byte{}
Expand All @@ -111,6 +119,7 @@ func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
time.Sleep(100 * time.Millisecond)

_, err := d.dev.Write([]byte{mode1, oldmode | restart})
d.freq = freqHz
return err
}

Expand Down
97 changes: 97 additions & 0 deletions experimental/devices/pca9685/pca9685_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2020 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package pca9685

import (
"testing"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/conn/physic"
)

func TestPCA9685_pin(t *testing.T) {
scenario := &i2ctest.Playback{
Ops: []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},

// Set PWM value of pin 0 to 50%
{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 128}, R: nil},
},
}

dev, err := NewI2C(scenario, I2CAddr)
if err != nil {
t.Fatal(err)
}

if err = dev.RegisterPins(); err != nil {
t.Fatal(err)
}

pin := gpioreg.ByName("PCA9685_40_0")
pin.PWM(gpio.DutyHalf, 50*physic.Hertz)
}

func TestPCA9685(t *testing.T) {
scenario := &i2ctest.Playback{
Ops: []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},

// Set PWM value of pin 0 to 50%
{Addr: I2CAddr, W: []byte{led0OnL, 0, 0, 0, 128}, R: nil},
},
}

dev, err := NewI2C(scenario, I2CAddr)
if err != nil {
t.Fatal(err)
}

if err = dev.SetPwm(0, 0, 0x8000); err != nil {
t.Fatal(err)
}
}
124 changes: 124 additions & 0 deletions experimental/devices/pca9685/pins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2020 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package pca9685

import (
"errors"
"fmt"
"math"
"time"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/physic"
gpiopin "periph.io/x/periph/conn/pin"
)

const (
dutyMax gpio.Duty = math.MaxUint16
)

type pin struct {
dev *Dev
channel int
}

// CreatePin creates a gpio handle for the given channel.
func (d *Dev) CreatePin(channel int) (gpio.PinIO, error) {
if channel < 0 || channel >= 16 {
return nil, errors.New("PCA9685: Valid channel range is 0..15")
}
return &pin{
dev: d,
channel: channel,
}, nil
}

// RegisterPins makes PWM channels available as PWM pins in the pin registry
//
// Pin names have the following format: PCA9685_<HexAddress>_<channel> (e.g. PCA9685_40_11)
func (d *Dev) RegisterPins() error {
for i := 0; i < 16; i++ {
pin, err := d.CreatePin(i)
if err != nil {
return err
}
if err = gpioreg.Register(pin); err != nil {
return err
}
}
return nil
}

func (p *pin) String() string {
return p.Name()
}

func (p *pin) Halt() error {
return p.Out(gpio.Low)
}

func (p *pin) Name() string {
return fmt.Sprintf("PCA9685_%x_%d", p.dev.dev.Addr, p.channel)
}

func (p *pin) Number() int {
return p.channel
}

func (p *pin) Function() string {
return string(p.Func())
}

func (p *pin) In(pull gpio.Pull, edge gpio.Edge) error {
return errors.New("PCA9685: Pin cannot be configured as input")
}

func (p *pin) Read() gpio.Level {
return gpio.INVALID.Read()
}

func (p *pin) WaitForEdge(timeout time.Duration) bool {
return false
}

func (p *pin) Pull() gpio.Pull {
return gpio.Float
}

func (p *pin) DefaultPull() gpio.Pull {
return gpio.Float
}

func (p *pin) Out(l gpio.Level) error {
return p.PWM(gpio.DutyMax, 0)
}

func (p *pin) PWM(duty gpio.Duty, freq physic.Frequency) error {
if err := p.dev.SetPwmFreq(freq); err != nil {
return err
}
// PWM duty scaled down from 24 to 16 bits
scaled := duty >> 8
if scaled > dutyMax {
scaled = dutyMax
}
return p.dev.SetPwm(p.channel, 0, scaled)
}

func (p *pin) Func() gpiopin.Func {
return gpio.PWM
}

func (p *pin) SupportedFuncs() []gpiopin.Func {
return []gpiopin.Func{gpio.PWM}
}

func (p *pin) SetFunc(f gpiopin.Func) error {
if f != gpio.PWM {
return fmt.Errorf("PCA9685: Function not supported: %s", f)
}
return nil
}

0 comments on commit ea1b73a

Please sign in to comment.