diff --git a/mcp23xxx/doc.go b/mcp23xxx/doc.go index 88a8405..399280b 100644 --- a/mcp23xxx/doc.go +++ b/mcp23xxx/doc.go @@ -2,7 +2,9 @@ // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. -// Package mcp23xxx provides driver for the MCP23 family of IO extenders +// Package mcp23xxx provides drivers for the MCP23XXX family of GPIO expanders. +// It's available with either I2C or SPI interfaces in 8 and 16 bit variants. +// Additionally, variants are available that have Open-Drain outputs. // // # Datasheet // diff --git a/mcp23xxx/group.go b/mcp23xxx/group.go new file mode 100644 index 0000000..f7ac612 --- /dev/null +++ b/mcp23xxx/group.go @@ -0,0 +1,217 @@ +// 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) + if err != nil { + return err + } + // 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{} diff --git a/mcp23xxx/group_test.go b/mcp23xxx/group_test.go new file mode 100644 index 0000000..323e072 --- /dev/null +++ b/mcp23xxx/group_test.go @@ -0,0 +1,121 @@ +// 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 ( + "testing" + "time" + + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/i2c/i2ctest" +) + +const ( + liveTest = false +) + +func TestGroup(t *testing.T) { + bus := i2ctest.Playback{Ops: recordingData["TestGroup"]} + 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) + } + } + + // The 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. + // It's not important to the test, but it's nice to have a visual indicator. + 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 + if liveTest { + time.Sleep(500 * time.Millisecond) + } + } + } else { + t.Error("pin[0] not converted to gpio.PinOut!") + } +} + +// TestReadWrite exercises the group Out()/Read() functions. It's expected +// that pin 0 of MCP23xxx port 0 is connected to pin 4 of port 0, pin 1 +// is connected to pin 5, etc... +func TestReadWrite(t *testing.T) { + bus := i2ctest.Playback{Ops: recordingData["TestReadWrite"]} + 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("gRead 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) + } + if liveTest { + 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. + gRead = gOut + 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) + } + } +} diff --git a/mcp23xxx/grouprecording_test.go b/mcp23xxx/grouprecording_test.go new file mode 100644 index 0000000..2e813ab --- /dev/null +++ b/mcp23xxx/grouprecording_test.go @@ -0,0 +1,159 @@ +// 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 ( + "periph.io/x/conn/v3/i2c/i2ctest" +) + +var recordingData = map[string][]i2ctest.IO{ + "TestGroup": { + {Addr: 0x20, W: []uint8{0x0}, R: []uint8{0x0}}, + {Addr: 0x20, W: []uint8{0x0, 0x10}}, + {Addr: 0x20, W: []uint8{0xa}, R: []uint8{0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0xa, 0x40}}}, + "TestReadWrite": { + {Addr: 0x20, W: []uint8{0x0}, R: []uint8{0x10}}, + {Addr: 0x20, W: []uint8{0xa}, R: []uint8{0x40}}, + {Addr: 0x20, W: []uint8{0x0, 0xf0}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0x41}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x11}}, + {Addr: 0x20, W: []uint8{0xa, 0x42}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x22}}, + {Addr: 0x20, W: []uint8{0xa, 0x43}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x33}}, + {Addr: 0x20, W: []uint8{0xa, 0x44}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x44}}, + {Addr: 0x20, W: []uint8{0xa, 0x45}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x55}}, + {Addr: 0x20, W: []uint8{0xa, 0x46}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x66}}, + {Addr: 0x20, W: []uint8{0xa, 0x47}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x77}}, + {Addr: 0x20, W: []uint8{0xa, 0x48}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x88}}, + {Addr: 0x20, W: []uint8{0xa, 0x49}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x99}}, + {Addr: 0x20, W: []uint8{0xa, 0x4a}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xaa}}, + {Addr: 0x20, W: []uint8{0xa, 0x4b}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xbb}}, + {Addr: 0x20, W: []uint8{0xa, 0x4c}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xcc}}, + {Addr: 0x20, W: []uint8{0xa, 0x4d}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xdd}}, + {Addr: 0x20, W: []uint8{0xa, 0x4e}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xee}}, + {Addr: 0x20, W: []uint8{0xa, 0x4f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xff}}, + {Addr: 0x20, W: []uint8{0x0, 0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf}}, + {Addr: 0x20, W: []uint8{0x0, 0xf}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0x1f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x11}}, + {Addr: 0x20, W: []uint8{0xa, 0x2f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x22}}, + {Addr: 0x20, W: []uint8{0xa, 0x3f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x33}}, + {Addr: 0x20, W: []uint8{0xa, 0x4f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x44}}, + {Addr: 0x20, W: []uint8{0xa, 0x5f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x55}}, + {Addr: 0x20, W: []uint8{0xa, 0x6f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x66}}, + {Addr: 0x20, W: []uint8{0xa, 0x7f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x77}}, + {Addr: 0x20, W: []uint8{0xa, 0x8f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x88}}, + {Addr: 0x20, W: []uint8{0xa, 0x9f}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x99}}, + {Addr: 0x20, W: []uint8{0xa, 0xaf}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xaa}}, + {Addr: 0x20, W: []uint8{0xa, 0xbf}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xbb}}, + {Addr: 0x20, W: []uint8{0xa, 0xcf}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xcc}}, + {Addr: 0x20, W: []uint8{0xa, 0xdf}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xdd}}, + {Addr: 0x20, W: []uint8{0xa, 0xef}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xee}}, + {Addr: 0x20, W: []uint8{0xa, 0xff}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xff}}, + {Addr: 0x20, W: []uint8{0x0, 0xe}}, + {Addr: 0x20, W: []uint8{0xa, 0xfe}}, + {Addr: 0x20, W: []uint8{0x0, 0xc}}, + {Addr: 0x20, W: []uint8{0xa, 0xfc}}, + {Addr: 0x20, W: []uint8{0x0, 0x8}}, + {Addr: 0x20, W: []uint8{0xa, 0xf8}}, + {Addr: 0x20, W: []uint8{0x0, 0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf0}}, + {Addr: 0x20, W: []uint8{0x0, 0xf0}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf1}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x11}}, + {Addr: 0x20, W: []uint8{0xa, 0xf0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf2}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x22}}, + {Addr: 0x20, W: []uint8{0xa, 0xf3}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x33}}, + {Addr: 0x20, W: []uint8{0xa, 0xf2}}, + {Addr: 0x20, W: []uint8{0xa, 0xf0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf4}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x44}}, + {Addr: 0x20, W: []uint8{0xa, 0xf5}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x55}}, + {Addr: 0x20, W: []uint8{0xa, 0xf4}}, + {Addr: 0x20, W: []uint8{0xa, 0xf6}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x66}}, + {Addr: 0x20, W: []uint8{0xa, 0xf7}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x77}}, + {Addr: 0x20, W: []uint8{0xa, 0xf6}}, + {Addr: 0x20, W: []uint8{0xa, 0xf4}}, + {Addr: 0x20, W: []uint8{0xa, 0xf0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf8}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x88}}, + {Addr: 0x20, W: []uint8{0xa, 0xf9}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0x99}}, + {Addr: 0x20, W: []uint8{0xa, 0xf8}}, + {Addr: 0x20, W: []uint8{0xa, 0xfa}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xaa}}, + {Addr: 0x20, W: []uint8{0xa, 0xfb}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xbb}}, + {Addr: 0x20, W: []uint8{0xa, 0xfa}}, + {Addr: 0x20, W: []uint8{0xa, 0xf8}}, + {Addr: 0x20, W: []uint8{0xa, 0xfc}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xcc}}, + {Addr: 0x20, W: []uint8{0xa, 0xfd}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xdd}}, + {Addr: 0x20, W: []uint8{0xa, 0xfc}}, + {Addr: 0x20, W: []uint8{0xa, 0xfe}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xee}}, + {Addr: 0x20, W: []uint8{0xa, 0xff}}, + {Addr: 0x20, W: []uint8{0x9}, R: []uint8{0xff}}, + {Addr: 0x20, W: []uint8{0x0, 0x0}}, + {Addr: 0x20, W: []uint8{0xa, 0xf}}, + {Addr: 0x20, W: []uint8{0xa, 0x0}}}, +} diff --git a/mcp23xxx/mcp23xxx.go b/mcp23xxx/mcp23xxx.go index 24e91d7..f396ec1 100644 --- a/mcp23xxx/mcp23xxx.go +++ b/mcp23xxx/mcp23xxx.go @@ -8,15 +8,21 @@ import ( "fmt" "strconv" + "periph.io/x/conn/v3/gpio" "periph.io/x/conn/v3/gpio/gpioreg" "periph.io/x/conn/v3/i2c" "periph.io/x/conn/v3/spi" ) -// Dev his a handle for a configured MCP23xxx device. +// Dev is a handle for a configured MCP23xxx device. type Dev struct { - // Pins provide access to extender pins. + // For all variants, Pins exposes a two dimensional slice of pins. For a + // MCP23X08/X09, [][]Pins would have one row with 8 pins, while the 16 + // pin variants would have two rows with 8 pins each. Pins [][]Pin + + edgePin *gpio.PinIn + variant Variant } // Variant is the type denoting a specific variant of the family. @@ -29,25 +35,25 @@ const ( // MCP23S08 8-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S08 MCP23S08 Variant = "MCP23S08" - // MCP23009 8-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23009 + // MCP23009 8-bit I2C extender w/ Open-Drain Output. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23009 MCP23009 Variant = "MCP23009" - // MCP23S09 8-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S09 + // MCP23S09 8-bit SPI extender w/ Open-Drain Output. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S09 MCP23S09 Variant = "MCP23S09" // MCP23016 16-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23016 MCP23016 Variant = "MCP23016" - // MCP23017 8-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23017 + // MCP23017 16-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23017 MCP23017 Variant = "MCP23017" - // MCP23S17 8-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S17 + // MCP23S17 16-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S17 MCP23S17 Variant = "MCP23S17" - // MCP23018 8-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23018 + // MCP23018 16-bit I2C extender w/ Open-Drain Output. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23018 MCP23018 Variant = "MCP23018" - // MCP23S18 8-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S18 + // MCP23S18 16-bit SPI extender w/ Open-Drain Output. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S18 MCP23S18 Variant = "MCP23S18" ) @@ -112,10 +118,20 @@ func makeDev(ra registerAccess, variant Variant, devicename string) (*Dev, error } } return &Dev{ - Pins: pins, + Pins: pins, + variant: variant, }, nil } +// SetEdgePin supplies a configured GPIO pin +func (dev *Dev) SetEdgePin(pin *gpio.PinIn) { + dev.edgePin = pin +} + +func (dev *Dev) String() string { + return string(dev.variant) +} + func mcp23x178ports(devicename string, ra registerAccess) []port { return []port{{ name: devicename + "_PORTA",