mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
mcp23017: new driver for MCP23017 (I2C port expander)
This commit is contained in:
@@ -83,6 +83,8 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/mcp23017"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dev, err := mcp23017.NewI2C(machine.I2C0, 0x20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Configure pin 0 for input and all the others for output.
|
||||
if err := dev.SetModes([]mcp23017.PinMode{
|
||||
mcp23017.Input | mcp23017.Pullup,
|
||||
mcp23017.Output,
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
input := dev.Pin(0)
|
||||
outputMask := ^mcp23017.Pins(1 << 0) // All except pin 0
|
||||
inputVal, err := input.Get()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println("input value: ", inputVal)
|
||||
// Set the values of all the output pins.
|
||||
err = dev.SetPins(0b1011011_01101110, outputMask)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// Package mcp23017 implements a driver for the MCP23017
|
||||
// I2C port expander chip. See https://www.microchip.com/wwwproducts/en/MCP23017
|
||||
// for details of the interface.
|
||||
//
|
||||
// It also provides a way of joining several such devices into one logical
|
||||
// device (see the Devices type).
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// hwAddressFixed holds the bits of the hardware address
|
||||
// that are fixed by the chip. Bits 0-3 (those in hwAddressMask)
|
||||
// are user-defined by the A0-A2 pins on the chip.
|
||||
hwAddress = uint8(0b010_0000)
|
||||
// hwAddressMask holds the bits that are significant in hwAddress.
|
||||
hwAddressMask = uint8(0b111_1000)
|
||||
)
|
||||
|
||||
type register uint8
|
||||
|
||||
const (
|
||||
// The following registers all refer to port A (except
|
||||
// rIOCON with is port-agnostic).
|
||||
// ORing them with portB makes them refer to port B.
|
||||
rIODIR = register(0x00) // I/O direction. 0=output; 1=input.
|
||||
rIOPOL = register(0x02) // Invert input values. 0=normal; 1=inverted.
|
||||
rGPINTEN = register(0x04)
|
||||
rDEFVAL = register(0x06)
|
||||
rINTCON = register(0x08)
|
||||
rIOCON = register(0x0A)
|
||||
rGPPU = register(0x0C) // Pull up; 0=no pull-up; 1=pull-up.
|
||||
rINTF = register(0x0E)
|
||||
rINTCAP = register(0x10)
|
||||
rGPIO = register(0x12) // GPIO pin values.
|
||||
rOLAT = register(0x14)
|
||||
registerCount = 0x16
|
||||
|
||||
portB = register(0x1)
|
||||
)
|
||||
|
||||
// PinCount is the number of GPIO pins available on the chip.
|
||||
const PinCount = 16
|
||||
|
||||
// PinMode represents a possible I/O mode for a pin.
|
||||
// The zero value represents the default value
|
||||
// after the chip is reset (input).
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
// Input configures a pin as an input.
|
||||
Input = PinMode(0)
|
||||
// Output configures a pin as an output.
|
||||
Output = PinMode(1)
|
||||
|
||||
// Direction is the bit mask of the pin mode representing
|
||||
// the I/O direction.
|
||||
Direction = PinMode(1)
|
||||
|
||||
// Pullup can be bitwise-or'd with Input
|
||||
// to cause the pull-up resistor on the pin to
|
||||
// be enabled.
|
||||
Pullup = PinMode(2)
|
||||
|
||||
// Invert can be bitwise-or'd with Input to
|
||||
// cause the pin value to reflect the inverted
|
||||
// value on the pin.
|
||||
Invert = PinMode(4)
|
||||
)
|
||||
|
||||
// ErrInvalidHWAddress is returned when the hardware address
|
||||
// of the device is not valid (only some bits can be set by the
|
||||
// address pins).
|
||||
var ErrInvalidHWAddress = errors.New("invalid hardware address")
|
||||
|
||||
// I2C represents an I2C bus. It is notably implemented by the
|
||||
// machine.I2C type.
|
||||
type I2C interface {
|
||||
ReadRegister(addr uint8, r uint8, buf []byte) error
|
||||
WriteRegister(addr uint8, r uint8, buf []byte) error
|
||||
}
|
||||
|
||||
// New returns a new MCP23017 device at the given I2C address
|
||||
// on the given bus.
|
||||
// It returns ErrInvalidHWAddress if the address isn't possible for the device.
|
||||
//
|
||||
// By default all pins are configured as inputs.
|
||||
func NewI2C(bus I2C, address uint8) (*Device, error) {
|
||||
if address&hwAddressMask != hwAddress {
|
||||
return nil, ErrInvalidHWAddress
|
||||
}
|
||||
d := &Device{
|
||||
bus: bus,
|
||||
addr: address,
|
||||
}
|
||||
pins, err := d.GetPins()
|
||||
if err != nil {
|
||||
return nil, errors.New("cannot initialize mcp23017 device at " + hex(address) + ": " + err.Error())
|
||||
}
|
||||
d.pins = pins
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func hex(x uint8) string {
|
||||
digits := "0123456789abcdef"
|
||||
return "0x" + digits[x>>4:x>>4+1] + digits[x&0xf:x&0xf+1]
|
||||
}
|
||||
|
||||
// Device represents an MCP23017 device.
|
||||
type Device struct {
|
||||
// TODO would it be good to have a mutex here so that independent goroutines
|
||||
// could change pins without needing to do the locking themselves?
|
||||
|
||||
// bus holds the reference the I2C bus that the device lives on.
|
||||
// It's an interface so that we can write tests for it.
|
||||
bus I2C
|
||||
addr uint8
|
||||
// pins caches the most recent pin values that have been set.
|
||||
// This enables us to change individual pin values without
|
||||
// doing a read followed by a write.
|
||||
pins Pins
|
||||
}
|
||||
|
||||
// GetPins reads all 16 pins from ports A and B.
|
||||
func (d *Device) GetPins() (Pins, error) {
|
||||
return d.readRegisterAB(rGPIO)
|
||||
}
|
||||
|
||||
// SetPins sets all the pins for which mask is high
|
||||
// to their respective values in pins.
|
||||
//
|
||||
// That is, it does the equivalent of:
|
||||
//
|
||||
// for i := 0; i < PinCount; i++ {
|
||||
// if mask.Get(i) {
|
||||
// d.Pin(i).Set(pins.Get(i))
|
||||
// }
|
||||
// }
|
||||
func (d *Device) SetPins(pins, mask Pins) error {
|
||||
if mask == 0 {
|
||||
return nil
|
||||
}
|
||||
newPins := (d.pins &^ mask) | (pins & mask)
|
||||
if newPins == d.pins {
|
||||
return nil
|
||||
}
|
||||
err := d.writeRegisterAB(rGPIO, newPins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.pins = newPins
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pin returns a Pin representing the given pin number (from 0 to 15).
|
||||
// Pin numbers from 0 to 7 represent port A pins 0 to 7.
|
||||
// Pin numbers from 8 to 15 represent port B pins 0 to 7.
|
||||
func (d *Device) Pin(pin int) Pin {
|
||||
if pin < 0 || pin >= PinCount {
|
||||
panic("pin out of range")
|
||||
}
|
||||
var mask Pins
|
||||
mask.High(pin)
|
||||
return Pin{
|
||||
dev: d,
|
||||
mask: mask,
|
||||
pin: uint8(pin),
|
||||
}
|
||||
}
|
||||
|
||||
// SetAllModes sets the mode of all the pins in a single operation.
|
||||
// If len(modes) is less than PinCount, all remaining pins
|
||||
// will be set fo modes[len(modes)-1], or PinMode(0) if
|
||||
// modes is empty.
|
||||
//
|
||||
// If len(modes) is greater than PinCount, the excess entries
|
||||
// will be ignored.
|
||||
func (d *Device) SetModes(modes []PinMode) error {
|
||||
defaultMode := PinMode(0)
|
||||
if len(modes) > 0 {
|
||||
defaultMode = modes[len(modes)-1]
|
||||
}
|
||||
var dir, pullup, invert Pins
|
||||
for i := 0; i < PinCount; i++ {
|
||||
mode := defaultMode
|
||||
if i < len(modes) {
|
||||
mode = modes[i]
|
||||
}
|
||||
if mode&Direction == Input {
|
||||
dir.High(i)
|
||||
}
|
||||
if mode&Pullup != 0 {
|
||||
pullup.High(i)
|
||||
}
|
||||
if mode&Invert != 0 {
|
||||
invert.High(i)
|
||||
}
|
||||
}
|
||||
if err := d.writeRegisterAB(rIODIR, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeRegisterAB(rGPPU, pullup); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeRegisterAB(rIOPOL, invert); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModes reads the modes of all the pins into modes.
|
||||
// It's OK if len(modes) is not PinCount - excess entries
|
||||
// will be left unset.
|
||||
func (d *Device) GetModes(modes []PinMode) error {
|
||||
dir, err := d.readRegisterAB(rIODIR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pullup, err := d.readRegisterAB(rGPPU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invert, err := d.readRegisterAB(rIOPOL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modes) > PinCount {
|
||||
modes = modes[:PinCount]
|
||||
}
|
||||
for i := range modes {
|
||||
mode := Output
|
||||
if dir.Get(i) {
|
||||
mode = Input
|
||||
}
|
||||
if pullup.Get(i) {
|
||||
mode |= Pullup
|
||||
}
|
||||
if invert.Get(i) {
|
||||
mode |= Invert
|
||||
}
|
||||
modes[i] = mode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) writeRegisterAB(r register, val Pins) error {
|
||||
// We rely on the auto-incrementing sequential write
|
||||
// and the fact that registers alternate between A and B
|
||||
// to write both ports in a single operation.
|
||||
buf := [2]byte{uint8(val), uint8(val >> 8)}
|
||||
return d.bus.WriteRegister(d.addr, uint8(r&^portB), buf[:])
|
||||
}
|
||||
|
||||
func (d *Device) readRegisterAB(r register) (Pins, error) {
|
||||
// We rely on the auto-incrementing sequential write
|
||||
// and the fact that registers alternate between A and B
|
||||
// to read both ports in a single operation.
|
||||
var buf [2]byte
|
||||
if err := d.bus.ReadRegister(d.addr, uint8(r), buf[:]); err != nil {
|
||||
return Pins(0), err
|
||||
}
|
||||
return Pins(buf[0]) | (Pins(buf[1]) << 8), nil
|
||||
}
|
||||
|
||||
// Pin represents a single GPIO pin on the device.
|
||||
type Pin struct {
|
||||
// mask holds the mask of the pin.
|
||||
mask Pins
|
||||
// pin holds the actual pin number.
|
||||
pin uint8
|
||||
dev *Device
|
||||
}
|
||||
|
||||
// Set sets the pin to the given value.
|
||||
func (p Pin) Set(value bool) error {
|
||||
// TODO currently this always writes both registers when
|
||||
// technically it only needs to write one. We could potentially
|
||||
// optimize that.
|
||||
if value {
|
||||
return p.dev.SetPins(^Pins(0), p.mask)
|
||||
} else {
|
||||
return p.dev.SetPins(0, p.mask)
|
||||
}
|
||||
}
|
||||
|
||||
// High is short for p.Set(true).
|
||||
func (p Pin) High() error {
|
||||
return p.Set(true)
|
||||
}
|
||||
|
||||
// High is short for p.Set(false).
|
||||
func (p Pin) Low() error {
|
||||
return p.Set(false)
|
||||
}
|
||||
|
||||
// Get returns the current value of the given pin.
|
||||
func (p Pin) Get() (bool, error) {
|
||||
// TODO this reads 2 registers when we could read just one.
|
||||
pins, err := p.dev.GetPins()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pins&p.mask != 0, nil
|
||||
}
|
||||
|
||||
// SetMode configures the pin to the given mode.
|
||||
func (p Pin) SetMode(mode PinMode) error {
|
||||
// We could use a more efficient single-register
|
||||
// read/write pattern but setting pin modes isn't an
|
||||
// operation that's likely to need to be efficient, so
|
||||
// use less code and use Get/SetModes directly.
|
||||
modes := make([]PinMode, PinCount)
|
||||
if err := p.dev.GetModes(modes); err != nil {
|
||||
return err
|
||||
}
|
||||
modes[p.pin] = mode
|
||||
return p.dev.SetModes(modes)
|
||||
}
|
||||
|
||||
// GetMode returns the mode of the pin.
|
||||
func (p Pin) GetMode() (PinMode, error) {
|
||||
modes := make([]PinMode, PinCount)
|
||||
if err := p.dev.GetModes(modes); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return modes[p.pin], nil
|
||||
}
|
||||
|
||||
// Pins represents a bitmask of pin values.
|
||||
// Port A values are in bits 0-8 (numbered from least significant bit)
|
||||
// Port B values are in bits 9-15.
|
||||
type Pins uint16
|
||||
|
||||
// Set sets the value for the given pin.
|
||||
func (p *Pins) Set(pin int, value bool) {
|
||||
if value {
|
||||
p.High(pin)
|
||||
} else {
|
||||
p.Low(pin)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value for the given pin.
|
||||
func (p Pins) Get(pin int) bool {
|
||||
return (p & pinMask(pin)) != 0
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, true).
|
||||
func (p *Pins) High(pin int) {
|
||||
*p |= pinMask(pin)
|
||||
}
|
||||
|
||||
// Low is short for p.Set(pin, false).
|
||||
func (p *Pins) Low(pin int) {
|
||||
*p &^= pinMask(pin)
|
||||
}
|
||||
|
||||
func pinMask(pin int) Pins {
|
||||
return 1 << pin
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestGetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
fdev.Registers[rGPIO] = 0b10101100
|
||||
fdev.Registers[rGPIO|portB] = 0b01010011
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err := dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01010011_10101100))
|
||||
}
|
||||
|
||||
func TestSetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
fdev.Registers[rGPIO] = 0b00001111
|
||||
fdev.Registers[rGPIO|portB] = 0b11110000
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err := dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
|
||||
|
||||
err = dev.SetPins(0b01100000_00110000, 0b10101010_01010101)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err = dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01110000_0001_1010))
|
||||
|
||||
// The logic uses the cached value of the pins rather than
|
||||
// reading it from the registers each time.
|
||||
fdev.Registers[rGPIO] = 0
|
||||
fdev.Registers[rGPIO|portB] = 0
|
||||
|
||||
err = dev.SetPins(0b01000000_00110000, 0b01100000_00000000)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err = dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01010000_00011010))
|
||||
}
|
||||
|
||||
func TestSetGetModes(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Calling SetModes with less items in than there are
|
||||
// pins should use the last item for all the unspecified ones.
|
||||
err = dev.SetModes([]PinMode{Input | Invert, Output})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b00000001))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b00000001))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
|
||||
|
||||
modes := make([]PinMode, 17)
|
||||
err = dev.GetModes(modes)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(modes[0], qt.Equals, Input|Invert)
|
||||
for i, m := range modes[1:16] {
|
||||
c.Assert(m, qt.Equals, Output, qt.Commentf("index %d", i))
|
||||
}
|
||||
c.Assert(modes[16], qt.Equals, PinMode(0))
|
||||
|
||||
// Using an empty slice should reset all the modes to the initial state.
|
||||
err = dev.SetModes(nil)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPinSetGet(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := dev.Pin(1)
|
||||
v, err := pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, false)
|
||||
err = pin.Set(true)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
|
||||
v, err = pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, true)
|
||||
err = pin.Set(false)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
|
||||
err = pin.High()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
|
||||
err = pin.Low()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPinMode(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := dev.Pin(1)
|
||||
mode, err := pin.GetMode()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(mode, qt.Equals, PinMode(0))
|
||||
c.Assert(mode&Direction, qt.Equals, Input)
|
||||
|
||||
err = pin.SetMode(Input | Pullup | Invert)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
|
||||
mode, err = pin.GetMode()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(mode, qt.Equals, Input|Pullup|Invert)
|
||||
|
||||
// Set another pin to output.
|
||||
err = dev.Pin(2).SetMode(Output)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
|
||||
// Check that changing a pin in port B works too.
|
||||
err = dev.Pin(8).SetMode(Output)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
|
||||
c.Assert(fdev.Registers[rIODIR|portB], qt.Equals, uint8(0b11111110))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rIOPOL|portB], qt.Equals, uint8(0))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU|portB], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
var p Pins
|
||||
p.Set(1, true)
|
||||
c.Assert(p, qt.Equals, Pins(0b10))
|
||||
c.Assert(p.Get(1), qt.Equals, true)
|
||||
c.Assert(p.Get(0), qt.Equals, false)
|
||||
c.Assert(p.Get(16), qt.Equals, false)
|
||||
p.High(2)
|
||||
c.Assert(p, qt.Equals, Pins(0b110))
|
||||
p.Low(1)
|
||||
c.Assert(p, qt.Equals, Pins(0b100))
|
||||
}
|
||||
|
||||
func TestInitWithError(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev := bus.addDevice(0x20)
|
||||
fdev.Err = fmt.Errorf("some error")
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.ErrorMatches, `cannot initialize mcp23017 device at 0x20: some error`)
|
||||
c.Assert(dev, qt.IsNil)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
// fakeBus implements the I2C interface in memory for testing.
|
||||
type fakeBus struct {
|
||||
c *qt.C
|
||||
devs []*fakeDev
|
||||
}
|
||||
|
||||
// newBus returns a fakeBus instance that uses c to flag errors
|
||||
// if they happen. After creating a fakeBus instance, add devices
|
||||
// to it with addDevice before using the interface.
|
||||
func newBus(c *qt.C) *fakeBus {
|
||||
return &fakeBus{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// fakeDev represents a device on the bus.
|
||||
type fakeDev struct {
|
||||
c *qt.C
|
||||
addr uint8
|
||||
// Registers holds the device registers. It can be inspected
|
||||
// or changed as desired for testing.
|
||||
Registers [registerCount]uint8
|
||||
// If Err is non-nil, it will be returned as the error from the
|
||||
// I2C methods.
|
||||
Err error
|
||||
}
|
||||
|
||||
// addDevice adds a new device at the given address.
|
||||
func (bus *fakeBus) addDevice(addr uint8) *fakeDev {
|
||||
dev := &fakeDev{
|
||||
c: bus.c,
|
||||
addr: addr,
|
||||
Registers: [registerCount]uint8{
|
||||
// IODIRA and IODIRB are all ones by default.
|
||||
rIODIR: 0xff,
|
||||
rIODIR | portB: 0xff,
|
||||
},
|
||||
}
|
||||
bus.devs = append(bus.devs, dev)
|
||||
return dev
|
||||
}
|
||||
|
||||
// ReadRegister implements I2C.ReadRegister.
|
||||
func (bus *fakeBus) ReadRegister(addr uint8, r uint8, buf []byte) error {
|
||||
return bus.findDev(addr).readRegister(r, buf)
|
||||
}
|
||||
|
||||
// WriteRegister implements I2C.WriteRegister.
|
||||
func (bus *fakeBus) WriteRegister(addr uint8, r uint8, buf []byte) error {
|
||||
return bus.findDev(addr).writeRegister(r, buf)
|
||||
}
|
||||
|
||||
func (d *fakeDev) readRegister(r uint8, buf []byte) error {
|
||||
if d.Err != nil {
|
||||
return d.Err
|
||||
}
|
||||
d.assertRegisterRange(r, buf)
|
||||
copy(buf, d.Registers[r:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *fakeDev) writeRegister(r uint8, buf []byte) error {
|
||||
if d.Err != nil {
|
||||
return d.Err
|
||||
}
|
||||
d.assertRegisterRange(r, buf)
|
||||
copy(d.Registers[r:], buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// assertRegisterRange asserts that reading or writing the given
|
||||
// register and subsequent registers is in range of the available registers.
|
||||
func (d *fakeDev) assertRegisterRange(r uint8, buf []byte) {
|
||||
if int(r) >= len(d.Registers) {
|
||||
d.c.Fatalf("register read/write [%#x, %#x] start out of range", r, int(r)+len(buf))
|
||||
}
|
||||
if int(r)+len(buf) > len(d.Registers) {
|
||||
d.c.Fatalf("register read/write [%#x, %#x] end out of range", r, int(r)+len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// findDev returns the device with the given address.
|
||||
func (bus *fakeBus) findDev(addr uint8) *fakeDev {
|
||||
for _, dev := range bus.devs {
|
||||
if dev.addr == addr {
|
||||
return dev
|
||||
}
|
||||
}
|
||||
bus.c.Fatalf("invalid device addr %#x passed to i2c bus", addr)
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package mcp23017
|
||||
|
||||
// All is a convenience value that represents all pins high (or all mask bits one).
|
||||
var All = PinSlice{0xffff}
|
||||
|
||||
// Devices holds a slice of devices that can be treated as one
|
||||
// contiguous set of devices. Earlier entries in the slice have
|
||||
// lower-numbered pins, so index 0 holds pins 0-7, index 1 holds
|
||||
// pins 8-15, etc.
|
||||
type Devices []*Device
|
||||
|
||||
// NewI2CDevices returns a Devices slice holding the Device values
|
||||
// for all the given addresses on the given bus.
|
||||
// When more than one bus is in use, create the slice yourself.
|
||||
func NewI2CDevices(bus I2C, addrs ...uint8) (Devices, error) {
|
||||
devs := make(Devices, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
dev, err := NewI2C(bus, addr)
|
||||
if err != nil {
|
||||
// TODO return a more informative error.
|
||||
return nil, err
|
||||
}
|
||||
devs[i] = dev
|
||||
}
|
||||
return devs, nil
|
||||
}
|
||||
|
||||
// SetModes sets the pin modes of all the pins on all the devices in devs.
|
||||
// If there are less entries in modes than there are pins, the
|
||||
// last entry is replicated to all of them (or PinMode(0) if modes
|
||||
// is empty).
|
||||
func (devs Devices) SetModes(modes []PinMode) error {
|
||||
var defaultModes []PinMode
|
||||
if len(modes) > 0 {
|
||||
defaultModes = modes[len(modes)-1:]
|
||||
}
|
||||
for i, dev := range devs {
|
||||
pinStart := i * PinCount
|
||||
var devModes []PinMode
|
||||
if pinStart < len(modes) {
|
||||
devModes = modes[pinStart:]
|
||||
} else {
|
||||
devModes = defaultModes
|
||||
}
|
||||
if err := dev.SetModes(devModes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModes gets the pin modes from the devices.
|
||||
// It's OK if modes isn't the same length as all the pins:
|
||||
// extra entries will be left unchanged.
|
||||
func (devs Devices) GetModes(modes []PinMode) error {
|
||||
for i, dev := range devs {
|
||||
pinStart := i * PinCount
|
||||
if pinStart >= len(modes) {
|
||||
break
|
||||
}
|
||||
if err := dev.GetModes(modes[pinStart:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pin returns the pin for the given number.
|
||||
func (devs Devices) Pin(pin int) Pin {
|
||||
if pin < 0 || pin >= len(devs)*PinCount {
|
||||
panic("pin out of range")
|
||||
}
|
||||
return devs[pin/PinCount].Pin(pin % PinCount)
|
||||
}
|
||||
|
||||
// GetPins returns pin values for all the pins.
|
||||
func (devs Devices) GetPins(pins PinSlice) error {
|
||||
for i, dev := range devs {
|
||||
if i >= len(pins) {
|
||||
break
|
||||
}
|
||||
devPins, err := dev.GetPins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pins[i] = devPins
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPins sets all the pins for which mask is high
|
||||
// to their respective values in pins.
|
||||
//
|
||||
// That is, it does the equivalent of:
|
||||
//
|
||||
// for i := 0; i < PinCount*len(devs); i++ {
|
||||
// if mask.Get(i) {
|
||||
// d.Pin(i).Set(pins.Get(i))
|
||||
// }
|
||||
// }
|
||||
func (devs Devices) SetPins(pins, mask PinSlice) error {
|
||||
defaultPins := pins.extra()
|
||||
defaultMask := mask.extra()
|
||||
for i, dev := range devs {
|
||||
devPins := defaultPins
|
||||
if i < len(pins) {
|
||||
devPins = pins[i]
|
||||
}
|
||||
devMask := defaultMask
|
||||
if i < len(mask) {
|
||||
devMask = mask[i]
|
||||
}
|
||||
if err := dev.SetPins(devPins, devMask); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PinSlice represents an arbitrary nunber of pins, each element corresponding
|
||||
// to the pins for one device. The value of the highest numbered pin in the
|
||||
// slice is extended to all other pins beyond the end of the slice.
|
||||
type PinSlice []Pins
|
||||
|
||||
// Get returns the value for the given pin. If the length of pins is too short
|
||||
// for the pin number, the value of the highest available pin is returned.
|
||||
// That is, the highest numbered pin in the last element of pins
|
||||
// is effectively replicated to all other elements.
|
||||
//
|
||||
// This means that PinSlice{} means "all pins high" and
|
||||
// PinSlice{0xffff} means "all pins low".
|
||||
func (pins PinSlice) Get(i int) bool {
|
||||
if len(pins) == 0 || i < 0 {
|
||||
return false
|
||||
}
|
||||
if i >= len(pins)*PinCount {
|
||||
return pins[len(pins)-1].Get(PinCount - 1)
|
||||
}
|
||||
return pins[i/PinCount].Get(i % PinCount)
|
||||
}
|
||||
|
||||
// Set sets the value for the given pin.
|
||||
func (pins PinSlice) Set(i int, value bool) {
|
||||
pins[i/PinCount].Set(i%PinCount, value)
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, true).
|
||||
func (pins PinSlice) High(pin int) {
|
||||
pins[pin/PinCount].High(pin % PinCount)
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, false).
|
||||
func (pins PinSlice) Low(pin int) {
|
||||
pins[pin/PinCount].Low(pin % PinCount)
|
||||
}
|
||||
|
||||
// Ensure checks that pins has enough space to store
|
||||
// at least length pins. If it does, it returns pins unchanged.
|
||||
// Otherwise, it returns pins with elements appended as needed,
|
||||
// populating additonal elements by replicating the
|
||||
// highest pin (mirroring the behavior of PinSlice.Get).
|
||||
func (pins PinSlice) Ensure(length int) PinSlice {
|
||||
if length == 0 {
|
||||
return pins
|
||||
}
|
||||
n := length/PinCount + 1
|
||||
if len(pins) >= n {
|
||||
return pins
|
||||
}
|
||||
// TODO we could potentially make use of additional
|
||||
// extra capacity in pins when available instead
|
||||
// of allocating a new slice always.
|
||||
newPins := make(PinSlice, n)
|
||||
copy(newPins, pins)
|
||||
if extend := pins.extra(); extend != 0 {
|
||||
for i := len(pins); i < n; i++ {
|
||||
newPins[i] = extend
|
||||
}
|
||||
}
|
||||
return newPins
|
||||
}
|
||||
|
||||
// extra returns the value of implied extra elements beyond
|
||||
// the end of pins.
|
||||
func (pins PinSlice) extra() Pins {
|
||||
if len(pins) == 0 || !pins[len(pins)-1].Get(PinCount-1) {
|
||||
return 0
|
||||
}
|
||||
return ^Pins(0)
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestDevicesGetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev0 := bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
|
||||
|
||||
// It's OK to pass less elements than there are devices.
|
||||
pins = make(PinSlice, 1)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsAllOff(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev0 := bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
err = devs.SetPins(nil, PinSlice{0xffff})
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsAllOn(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev0 := bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
err = devs.SetPins(PinSlice{0xffff}, PinSlice{0xffff})
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0xffff, 0xffff})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsMask(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev0 := bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
// Sanity check the original value of the pins.
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
|
||||
|
||||
pins = make(PinSlice, 2)
|
||||
pins.High(0)
|
||||
pins.High(1)
|
||||
mask := make(PinSlice, 2)
|
||||
mask.High(0)
|
||||
mask.High(16)
|
||||
|
||||
err = devs.SetPins(pins, mask)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins = make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101101, 0b01010010_10101100})
|
||||
}
|
||||
|
||||
func TestDevicesSetGetModes(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
fdev0 := bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Sanity check that IODIR registers start off all ones.
|
||||
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(0xff))
|
||||
|
||||
// The last entry is replicated to fill them all.
|
||||
err = devs.SetModes([]PinMode{Input | Pullup, Output})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(1))
|
||||
c.Assert(fdev0.Registers[rIODIR|portB], qt.Equals, uint8(0))
|
||||
c.Assert(fdev1.Registers[rIODIR], qt.Equals, uint8(0))
|
||||
c.Assert(fdev1.Registers[rIODIR|portB], qt.Equals, uint8(0))
|
||||
|
||||
modes := make([]PinMode, 2)
|
||||
err = devs.GetModes(modes)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup, Output})
|
||||
|
||||
// It's OK to pass a smaller slice to GetModes.
|
||||
modes = make([]PinMode, 1)
|
||||
err = devs.GetModes(modes)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup})
|
||||
}
|
||||
|
||||
func TestDevicesPin(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := newBus(c)
|
||||
bus.addDevice(0x20)
|
||||
fdev1 := bus.addDevice(0x21)
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := devs.Pin(16)
|
||||
v, err := pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, false)
|
||||
err = pin.High()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev1.Registers[rGPIO], qt.Equals, uint8(1))
|
||||
}
|
||||
|
||||
func TestPinSlice(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
pins := PinSlice(nil).Ensure(20)
|
||||
pins.Set(16, true)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 1})
|
||||
pins.Set(31, true)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001})
|
||||
c.Assert(pins.Get(0), qt.Equals, false)
|
||||
c.Assert(pins.Get(16), qt.Equals, true)
|
||||
pins = pins.Ensure(40)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001, 0xffff})
|
||||
pins.Low(16)
|
||||
c.Assert(pins.Get(16), qt.Equals, false)
|
||||
pins.High(16)
|
||||
c.Assert(pins.Get(16), qt.Equals, true)
|
||||
}
|
||||
Reference in New Issue
Block a user