mirror of https://github.com/periph/devices
Add GPIO Group support to mcp23xxx
parent
cad22759dd
commit
743985e052
@ -0,0 +1,214 @@
|
||||
// Copyright 2025 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 mcp23xxx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/pin"
|
||||
)
|
||||
|
||||
// The internal structure for a group of pins.
|
||||
type pinGroup struct {
|
||||
dev *Dev
|
||||
port int
|
||||
pins []*portpin
|
||||
defaultMask gpio.GPIOValue
|
||||
}
|
||||
|
||||
// Group returns a gpio.Group that is made up of the specified pins.
|
||||
func (dev *Dev) Group(port int, pins []int) *gpio.Group {
|
||||
grouppins := make([]*portpin, len(pins))
|
||||
for ix, number := range pins {
|
||||
pp, ok := dev.Pins[port][number].(*portpin)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
grouppins[ix] = pp
|
||||
}
|
||||
defMask := gpio.GPIOValue((1 << len(pins)) - 1)
|
||||
var pgif interface{} = &pinGroup{dev: dev, port: port, pins: grouppins, defaultMask: defMask}
|
||||
if gpiogroup, ok := pgif.(gpio.Group); ok {
|
||||
return &gpiogroup
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pins returns the set of pin.Pin that make up that group.
|
||||
func (pg *pinGroup) Pins() []pin.Pin {
|
||||
pins := make([]pin.Pin, len(pg.pins))
|
||||
|
||||
for ix, p := range pg.pins {
|
||||
pins[ix] = p
|
||||
}
|
||||
return pins
|
||||
}
|
||||
|
||||
// Given the offset within the group, return the corresponding GPIO pin.
|
||||
func (pg *pinGroup) ByOffset(offset int) pin.Pin {
|
||||
return pg.pins[offset]
|
||||
}
|
||||
|
||||
// Given the specific name of a pin, return it. If it can't be found, nil is
|
||||
// returned.
|
||||
func (pg *pinGroup) ByName(name string) pin.Pin {
|
||||
for _, pin := range pg.pins {
|
||||
if pin.Name() == name {
|
||||
return pin
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Given the GPIO pin number, return that pin from the set.
|
||||
func (pg *pinGroup) ByNumber(number int) pin.Pin {
|
||||
for _, pin := range pg.pins {
|
||||
if pin.Number() == number {
|
||||
return pin
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Out writes value to the specified pins of the device/port. If mask is 0,
|
||||
// the default mask of all pins in the group is used.
|
||||
func (pg *pinGroup) Out(value, mask gpio.GPIOValue) error {
|
||||
if mask == 0 {
|
||||
mask = pg.defaultMask
|
||||
} else {
|
||||
mask &= pg.defaultMask
|
||||
}
|
||||
value &= mask
|
||||
// Convert the write value which is relative to the pins to the
|
||||
// absolute value for the port.
|
||||
wr := uint8(0)
|
||||
wrMask := uint8(0)
|
||||
for bit := range len(pg.pins) {
|
||||
if (mask & (1 << bit)) > 0 {
|
||||
if (value & 0x01) == 0x01 {
|
||||
wr |= 1 << pg.pins[bit].Number()
|
||||
}
|
||||
wrMask |= 1 << pg.pins[bit].Number()
|
||||
}
|
||||
value = value >> 1
|
||||
}
|
||||
port := pg.pins[0].port
|
||||
// Verify pins are set for output
|
||||
outputPins, err := port.iodir.readValue(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ((outputPins ^ 0xff) & wrMask) != wrMask {
|
||||
outputPins &= (wrMask ^ 0xff)
|
||||
err = port.iodir.writeValue(outputPins, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Read the current value
|
||||
currentValue, err := port.olat.readValue(true)
|
||||
// Apply the mask to clear bits we're writing.
|
||||
currentValue &= (0xff ^ wrMask)
|
||||
// Or the value with the bits to modify
|
||||
currentValue |= wr
|
||||
// And, write the value out the port.
|
||||
return port.olat.writeValue(currentValue, true)
|
||||
}
|
||||
|
||||
// Read reads from the device and port and returns the state of the GPIO
|
||||
// pins in the group. If a pin specified by mask is not configured for
|
||||
// input, it is transparently re-configured.
|
||||
func (pg *pinGroup) Read(mask gpio.GPIOValue) (result gpio.GPIOValue, err error) {
|
||||
if mask == 0 {
|
||||
mask = pg.defaultMask
|
||||
} else {
|
||||
mask &= pg.defaultMask
|
||||
}
|
||||
// Compute the read mask
|
||||
rmask := uint8(0)
|
||||
for bit := range 8 {
|
||||
if (mask & (1 << bit)) > 0 {
|
||||
rmask |= (1 << pg.pins[bit].Number())
|
||||
}
|
||||
}
|
||||
// Make sure the direction for the pins involved in this write read is
|
||||
// Input.
|
||||
port := pg.pins[0].port
|
||||
currentIn, err := port.iodir.readValue(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// We need to make some pins Input. Write the value to the iodir register.
|
||||
if (currentIn & rmask) != rmask {
|
||||
err = port.iodir.writeValue(currentIn|rmask, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Now, perform the read itself.
|
||||
v, err := port.gpio.readValue(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Now convert the set pins into the Group value
|
||||
for ix, pin := range pg.pins {
|
||||
if (v & (1 << pin.Number())) > 0 {
|
||||
result |= 1 << ix
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitForEdge listens for a GPIO pin change event. The MCP23XXXX devices
|
||||
// can't directly signal an edge event. To do this, you must call
|
||||
// Dev.SetEdgePin() with a HOST GPIO pin configured for falling edge
|
||||
// detection. That pin should be connected to the MCP23XXX INT pin. When
|
||||
// a falling edge is detected on the supplied host GPIO pin, the code
|
||||
// will return the GPIO Pin number on the device that changed.
|
||||
//
|
||||
// Note that the MCP23XXX devices only detect change. You can't configure
|
||||
// falling or rising edge. Consequently, the returned edge will always be
|
||||
// gpio.NoEdge.
|
||||
//
|
||||
// For a change event to be detected, the pin must be configured for input.
|
||||
// This function will NOT set pins for input. Additionally, the calling
|
||||
// code must set the INTCON register appropriately. Refer to the datasheet.
|
||||
//
|
||||
// In the event that the changed pin is NOT part of the io group, the
|
||||
// triggering pin number will be returned, along with the error
|
||||
// ErrPinNotInGroup
|
||||
func (pg *pinGroup) WaitForEdge(timeout time.Duration) (number int, edge gpio.Edge, err error) {
|
||||
return -1, gpio.NoEdge, gpio.ErrGroupFeatureNotImplemented
|
||||
}
|
||||
|
||||
// Halt() interrupts a pending WaitForEdge() call if one is in process.
|
||||
func (pg *pinGroup) Halt() error {
|
||||
if pg.dev.edgePin != nil {
|
||||
var ifpin interface{} = pg.dev.edgePin
|
||||
if r, ok := ifpin.(conn.Resource); ok {
|
||||
return r.Halt()
|
||||
}
|
||||
}
|
||||
// TODO: I think we want to call Dev.Halt()
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the device variant name and configured pins for the group.
|
||||
func (pg *pinGroup) String() string {
|
||||
s := fmt.Sprintf("%s - [ ", pg.dev)
|
||||
for ix := range len(pg.pins) {
|
||||
s += fmt.Sprintf("%d ", pg.pins[ix].Number())
|
||||
}
|
||||
s += "]"
|
||||
return s
|
||||
}
|
||||
|
||||
var _ gpio.Group = &pinGroup{}
|
||||
@ -0,0 +1,132 @@
|
||||
// Copyright 2025 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 mcp23xxx
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/i2c/i2creg"
|
||||
"periph.io/x/host/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Make sure periph is initialized.
|
||||
if _, err := host.Init(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
|
||||
// Open default I²C bus.
|
||||
bus, err := i2creg.Open("")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
defer bus.Close()
|
||||
extender, err := NewI2C(bus, MCP23008, 0x20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for portnum, port := range extender.Pins {
|
||||
for _, pin := range port {
|
||||
t.Logf("Port: %d Pin: %d %s", portnum, pin.Number(), pin)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// My Test fixture has an led on pin 0, and also wires pin 0 -> pin 4.
|
||||
// If I don't set pin 4 for input, then it breaks the LED blinking.
|
||||
reader, _ := interface{}(extender.Pins[0][4]).(gpio.PinIn)
|
||||
_ = reader.In(gpio.PullNoChange, gpio.NoEdge)
|
||||
var ifpin interface{} = extender.Pins[0][0]
|
||||
if writer, ok := ifpin.(gpio.PinOut); ok {
|
||||
l := gpio.High
|
||||
for range 20 {
|
||||
writer.Out(l)
|
||||
l = !l
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
t.Error("pin[0] not converted to gpio.PinOut!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
// Open default I²C bus.
|
||||
bus, err := i2creg.Open("")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
defer bus.Close()
|
||||
extender, err := NewI2C(bus, MCP23008, 0x20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defMask := gpio.GPIOValue(0xf)
|
||||
gOut := *extender.Group(0, []int{0, 1, 2, 3})
|
||||
if gOut == nil {
|
||||
t.Error("gOut is nil!")
|
||||
}
|
||||
gRead := *extender.Group(0, []int{4, 5, 6, 7})
|
||||
if gRead == nil {
|
||||
t.Error("gIn is nil!")
|
||||
}
|
||||
// Turn off the GPIOs
|
||||
defer gOut.Out(0, 0)
|
||||
defer gRead.Out(0, 0)
|
||||
|
||||
for i := range 2 {
|
||||
if i == 1 {
|
||||
/* Now invert it. */
|
||||
x := gRead
|
||||
gRead = gOut
|
||||
gOut = x
|
||||
}
|
||||
for i := range gpio.GPIOValue(16) {
|
||||
err := gOut.Out(i, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
r, err := gRead.Read(defMask)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if r != i {
|
||||
t.Errorf("Error reading/writing GPIO Group(). Wrote 0x%x Read 0x%x", i, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For this test, write to the pins individually, and then
|
||||
// confirm read on the other set works as expected.
|
||||
x := gRead
|
||||
gRead = gOut
|
||||
gOut = x
|
||||
t.Log(gRead)
|
||||
pinset := extender.Pins[0][:4]
|
||||
for i := range gpio.GPIOValue(16) {
|
||||
wvalue := i
|
||||
for bit := range 4 {
|
||||
if (wvalue & (1 << bit)) == (1 << bit) {
|
||||
pinset[bit].Out(gpio.High)
|
||||
} else {
|
||||
pinset[bit].Out(gpio.Low)
|
||||
}
|
||||
}
|
||||
r, err := gRead.Read(0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if r != i {
|
||||
t.Errorf("Error writing GPIO pins and reading back result. Read 0x%x Expected 0x%x", r, i)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue