From cadf2cf1f3b4c40b88a7d5b09f142a9e95afd234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Grill?= Date: Tue, 14 Apr 2020 21:57:41 +0200 Subject: [PATCH] pca9685: added gpio pin API (#437) --- experimental/devices/pca9685/pca9685.go | 11 +- experimental/devices/pca9685/pca9685_test.go | 97 +++++++++++++++ experimental/devices/pca9685/pins.go | 124 +++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 experimental/devices/pca9685/pca9685_test.go create mode 100644 experimental/devices/pca9685/pins.go diff --git a/experimental/devices/pca9685/pca9685.go b/experimental/devices/pca9685/pca9685.go index cd719cb..b4bc2d5 100644 --- a/experimental/devices/pca9685/pca9685.go +++ b/experimental/devices/pca9685/pca9685.go @@ -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. @@ -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{} @@ -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 } diff --git a/experimental/devices/pca9685/pca9685_test.go b/experimental/devices/pca9685/pca9685_test.go new file mode 100644 index 0000000..e75ccfd --- /dev/null +++ b/experimental/devices/pca9685/pca9685_test.go @@ -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) + } +} diff --git a/experimental/devices/pca9685/pins.go b/experimental/devices/pca9685/pins.go new file mode 100644 index 0000000..a74eb29 --- /dev/null +++ b/experimental/devices/pca9685/pins.go @@ -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__ (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 +}