mirror of https://github.com/periph/devices
mcp23xxx: support for Microchip MCP23 family of IO extenders (#433)
Initial code for MCP23xxx support.pull/1/head
parent
1f4a49516e
commit
dffddf2e7e
@ -0,0 +1,6 @@
|
|||||||
|
// 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 mcp23xxx provides driver for the MCP23 family of IO extenders
|
||||||
|
package mcp23xxx
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
// 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 mcp23xxx_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/gpio"
|
||||||
|
"periph.io/x/periph/conn/i2c/i2creg"
|
||||||
|
"periph.io/x/periph/experimental/devices/mcp23xxx"
|
||||||
|
"periph.io/x/periph/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open default I²C bus.
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open I²C: %v", err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
// Create a new I2C IO extender
|
||||||
|
extender, err := mcp23xxx.NewI2C(bus, mcp23xxx.MCP23017, 0x20)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, port := range extender.Pins {
|
||||||
|
for _, pin := range port {
|
||||||
|
err = pin.In(gpio.Float, gpio.NoEdge)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
level := pin.Read()
|
||||||
|
fmt.Printf("%s\t%s\n", pin.Name(), level.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
// 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 mcp23xxx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/conntest"
|
||||||
|
"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/spi"
|
||||||
|
"periph.io/x/periph/conn/spi/spitest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMCP23017_out(t *testing.T) {
|
||||||
|
const address uint16 = 0x20
|
||||||
|
scenario := &i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
// iodir is read on creation
|
||||||
|
{Addr: address, W: []byte{0x00}, R: []byte{0xFF}},
|
||||||
|
{Addr: address, W: []byte{0x01}, R: []byte{0xFF}},
|
||||||
|
// iodira is set to output
|
||||||
|
{Addr: address, W: []byte{0x00, 0xFE}, R: nil},
|
||||||
|
// olata is read
|
||||||
|
{Addr: address, W: []byte{0x14}, R: []byte{0x00}},
|
||||||
|
// writing back unchanged value is omitted
|
||||||
|
// writing high output
|
||||||
|
{Addr: address, W: []byte{0x14, 0x01}, R: nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err := NewI2C(scenario, MCP23017, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
pA0 := gpioreg.ByName("MCP23017_20_PORTA_0")
|
||||||
|
pA0.Out(gpio.Low)
|
||||||
|
pA0.Out(gpio.High)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMCP23S17_out(t *testing.T) {
|
||||||
|
const address uint16 = 0x20
|
||||||
|
scenario := &spitest.Playback{
|
||||||
|
Playback: conntest.Playback{
|
||||||
|
Ops: []conntest.IO{
|
||||||
|
// iodira is read
|
||||||
|
{W: []byte{0x41, 0x00}, R: []byte{0xFF}},
|
||||||
|
{W: []byte{0x41, 0x01}, R: []byte{0xFF}},
|
||||||
|
// iodira is set to output
|
||||||
|
{W: []byte{0x40, 0x00, 0xFE}, R: nil},
|
||||||
|
// olata is read
|
||||||
|
{W: []byte{0x41, 0x14}, R: []byte{0x00}},
|
||||||
|
// writing back unchanged value is omitted
|
||||||
|
// writing high output
|
||||||
|
{W: []byte{0x40, 0x14, 0x01}, R: nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := scenario.Connect(1, spi.Mode0, 8)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dev, err := NewSPI(conn, MCP23S17)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
pA0 := gpioreg.ByName("MCP23S17_PORTA_0")
|
||||||
|
|
||||||
|
pA0.Out(gpio.Low)
|
||||||
|
pA0.Out(gpio.High)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMCP23017_in(t *testing.T) {
|
||||||
|
const address uint16 = 0x20
|
||||||
|
scenario := &i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
// iodir is read on creation
|
||||||
|
{Addr: address, W: []byte{0x00}, R: []byte{0xFF}},
|
||||||
|
{Addr: address, W: []byte{0x01}, R: []byte{0xFF}},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// gppua is read
|
||||||
|
{Addr: address, W: []byte{0x0C}, R: []byte{0x00}},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// gpio is read
|
||||||
|
{Addr: address, W: []byte{0x12}, R: []byte{0x01}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err := NewI2C(scenario, MCP23017, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
pA0 := gpioreg.ByName("MCP23017_20_PORTA_0")
|
||||||
|
|
||||||
|
pA0.In(gpio.Float, gpio.NoEdge)
|
||||||
|
l := pA0.Read()
|
||||||
|
if l != gpio.High {
|
||||||
|
t.Errorf("Input should be High")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMCP23017_inInverted(t *testing.T) {
|
||||||
|
const address uint16 = 0x20
|
||||||
|
scenario := &i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
// iodir is read on creation
|
||||||
|
{Addr: address, W: []byte{0x00}, R: []byte{0xFF}},
|
||||||
|
{Addr: address, W: []byte{0x01}, R: []byte{0xFF}},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// gppua is read
|
||||||
|
{Addr: address, W: []byte{0x0C}, R: []byte{0x00}},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// polarity is set
|
||||||
|
{Addr: address, W: []byte{0x02}, R: []byte{0x01}},
|
||||||
|
// gpio is read
|
||||||
|
{Addr: address, W: []byte{0x12}, R: []byte{0x01}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err := NewI2C(scenario, MCP23017, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
pA0 := gpioreg.ByName("MCP23017_20_PORTA_0").(Pin)
|
||||||
|
|
||||||
|
pA0.In(gpio.Float, gpio.NoEdge)
|
||||||
|
pA0.SetPolarityInverted(true)
|
||||||
|
l := pA0.Read()
|
||||||
|
if l != gpio.High {
|
||||||
|
t.Errorf("Input should be High")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMCP23017_inPullUp(t *testing.T) {
|
||||||
|
const address uint16 = 0x20
|
||||||
|
scenario := &i2ctest.Playback{
|
||||||
|
Ops: []i2ctest.IO{
|
||||||
|
// iodir is read on creation
|
||||||
|
{Addr: address, W: []byte{0x00}, R: []byte{0xFF}},
|
||||||
|
{Addr: address, W: []byte{0x01}, R: []byte{0xFF}},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// gppua is read and written
|
||||||
|
{Addr: address, W: []byte{0x0C}, R: []byte{0x00}},
|
||||||
|
{Addr: address, W: []byte{0x0C, 0x01}, R: nil},
|
||||||
|
// not written, since it didn't change
|
||||||
|
// gpio is read
|
||||||
|
{Addr: address, W: []byte{0x12}, R: []byte{0x01}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err := NewI2C(scenario, MCP23017, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
pA0 := gpioreg.ByName("MCP23017_20_PORTA_0")
|
||||||
|
|
||||||
|
pA0.In(gpio.PullUp, gpio.NoEdge)
|
||||||
|
l := pA0.Read()
|
||||||
|
if l != gpio.High {
|
||||||
|
t.Errorf("Input should be High")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,220 @@
|
|||||||
|
// 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 mcp23xxx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/gpio/gpioreg"
|
||||||
|
"periph.io/x/periph/conn/i2c"
|
||||||
|
"periph.io/x/periph/conn/spi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dev his a handle for a configured MCP23xxx device.
|
||||||
|
type Dev struct {
|
||||||
|
// Pins provide access to extender pins.
|
||||||
|
Pins [][]Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant is the type denoting a specific variant of the family.
|
||||||
|
type Variant string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MCP23008 8-bit I2C extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23008
|
||||||
|
MCP23008 Variant = "MCP23008"
|
||||||
|
|
||||||
|
// 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 Variant = "MCP23009"
|
||||||
|
|
||||||
|
// MCP23S09 8-bit SPI extender. 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 Variant = "MCP23017"
|
||||||
|
|
||||||
|
// MCP23S17 8-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 Variant = "MCP23018"
|
||||||
|
|
||||||
|
// MCP23S18 8-bit SPI extender. Datasheet: https://www.microchip.com/wwwproducts/en/MCP23S18
|
||||||
|
MCP23S18 Variant = "MCP23S18"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewI2C initializes an IO extender through I2C connection.
|
||||||
|
func NewI2C(b i2c.Bus, variant Variant, addr uint16) (*Dev, error) {
|
||||||
|
if addr&0xFFF8 != 0x20 {
|
||||||
|
return nil, fmt.Errorf("%s: Supported address range is 0x20 - 0x27", variant)
|
||||||
|
}
|
||||||
|
devicename := string(variant) + "_" + strconv.FormatInt(int64(addr), 16)
|
||||||
|
ra := &i2cRegisterAccess{
|
||||||
|
Dev: &i2c.Dev{Bus: b, Addr: addr},
|
||||||
|
}
|
||||||
|
return makeDev(ra, variant, devicename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSPI initializes an IO extender through I2C connection.
|
||||||
|
func NewSPI(b spi.Conn, variant Variant) (*Dev, error) {
|
||||||
|
devicename := string(variant)
|
||||||
|
ra := &spiRegisterAccess{
|
||||||
|
Conn: b,
|
||||||
|
}
|
||||||
|
return makeDev(ra, variant, devicename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes any registration to the device.
|
||||||
|
func (d *Dev) Close() error {
|
||||||
|
for _, port := range d.Pins {
|
||||||
|
for _, pin := range port {
|
||||||
|
err := gpioreg.Unregister(pin.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDev(ra registerAccess, variant Variant, devicename string) (*Dev, error) {
|
||||||
|
var ports []port
|
||||||
|
switch variant {
|
||||||
|
case MCP23008, MCP23009, MCP23S08, MCP23S09:
|
||||||
|
ports = mcp23x089port(devicename, ra)
|
||||||
|
case MCP23016:
|
||||||
|
ports = mcp23x16ports(devicename, ra)
|
||||||
|
case MCP23017, MCP23S17, MCP23018, MCP23S18:
|
||||||
|
ports = mcp23x178ports(devicename, ra)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s: Unsupported variant", devicename)
|
||||||
|
}
|
||||||
|
|
||||||
|
pins := make([][]Pin, len(ports))
|
||||||
|
for i := range ports {
|
||||||
|
// pre-cache iodir
|
||||||
|
_, err := ports[i].iodir.readValue(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pins[i] = ports[i].pins()
|
||||||
|
for _, pin := range pins[i] {
|
||||||
|
gpioreg.Register(pin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Dev{
|
||||||
|
Pins: pins,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mcp23x178ports(devicename string, ra registerAccess) []port {
|
||||||
|
return []port{{
|
||||||
|
name: devicename + "_PORTA",
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir: ra.define(0x00),
|
||||||
|
gpio: ra.define(0x12),
|
||||||
|
olat: ra.define(0x14),
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol: ra.define(0x02),
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
gppu: ra.define(0x0C),
|
||||||
|
supportPullup: true,
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
gpinten: ra.define(0x04),
|
||||||
|
intcon: ra.define(0x08),
|
||||||
|
intf: ra.define(0x0E),
|
||||||
|
intcap: ra.define(0x10),
|
||||||
|
supportInterrupt: true,
|
||||||
|
}, {
|
||||||
|
name: devicename + "_PORTB",
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir: ra.define(0x01),
|
||||||
|
gpio: ra.define(0x13),
|
||||||
|
olat: ra.define(0x15),
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol: ra.define(0x03),
|
||||||
|
supportPullup: true,
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
gppu: ra.define(0x0D),
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
gpinten: ra.define(0x05),
|
||||||
|
intcon: ra.define(0x0B),
|
||||||
|
intf: ra.define(0x0F),
|
||||||
|
intcap: ra.define(0x11),
|
||||||
|
supportInterrupt: true,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mcp23x089port(devicename string, ra registerAccess) []port {
|
||||||
|
return []port{{
|
||||||
|
name: devicename,
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir: ra.define(0x00),
|
||||||
|
gpio: ra.define(0x09),
|
||||||
|
olat: ra.define(0x0A),
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol: ra.define(0x01),
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
gppu: ra.define(0x06),
|
||||||
|
supportPullup: true,
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
gpinten: ra.define(0x02),
|
||||||
|
intcon: ra.define(0x04),
|
||||||
|
intf: ra.define(0x07),
|
||||||
|
intcap: ra.define(0x08),
|
||||||
|
supportInterrupt: true,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mcp23x16ports(devicename string, ra registerAccess) []port {
|
||||||
|
return []port{{
|
||||||
|
name: devicename + "_PORT0",
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir: ra.define(0x06),
|
||||||
|
gpio: ra.define(0x00),
|
||||||
|
olat: ra.define(0x02),
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol: ra.define(0x04),
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
supportPullup: false,
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
supportInterrupt: false,
|
||||||
|
intcap: ra.define(0x08),
|
||||||
|
}, {
|
||||||
|
name: devicename + "_PORT1",
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir: ra.define(0x07),
|
||||||
|
gpio: ra.define(0x01),
|
||||||
|
olat: ra.define(0x03),
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol: ra.define(0x05),
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
supportPullup: false,
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
supportInterrupt: false,
|
||||||
|
intcap: ra.define(0x09),
|
||||||
|
}}
|
||||||
|
}
|
||||||
@ -0,0 +1,197 @@
|
|||||||
|
// 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 mcp23xxx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/gpio"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
"periph.io/x/periph/conn/pin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pin extends gpio.PinIO interface with features supported by MCP23xxx devices.
|
||||||
|
type Pin interface {
|
||||||
|
gpio.PinIO
|
||||||
|
// SetPolarityInverted if set to true, GPIO register bit reflects the same logic state of the input pin.
|
||||||
|
SetPolarityInverted(p bool) error
|
||||||
|
// IsPolarityInverted returns true if the value of the input pin reflects inverted logic state.
|
||||||
|
IsPolarityInverted() (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type port struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// GPIO basic registers
|
||||||
|
iodir registerCache
|
||||||
|
gpio registerCache
|
||||||
|
olat registerCache
|
||||||
|
|
||||||
|
// polarity setting
|
||||||
|
ipol registerCache
|
||||||
|
|
||||||
|
// pull-up control register
|
||||||
|
// Not present in all devices
|
||||||
|
gppu registerCache
|
||||||
|
supportPullup bool
|
||||||
|
|
||||||
|
// interrupt handling registers
|
||||||
|
supportInterrupt bool
|
||||||
|
gpinten registerCache
|
||||||
|
intcon registerCache
|
||||||
|
intf registerCache
|
||||||
|
intcap registerCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type portpin struct {
|
||||||
|
port *port
|
||||||
|
pinbit uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *port) pins() []Pin {
|
||||||
|
result := make([]Pin, 8)
|
||||||
|
var i uint8
|
||||||
|
for i = 0; i < 8; i++ {
|
||||||
|
result[i] = &portpin{
|
||||||
|
port: p,
|
||||||
|
pinbit: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) String() string {
|
||||||
|
return p.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Halt() error {
|
||||||
|
// To halt all drive, set to high-impedance input
|
||||||
|
return p.In(gpio.Float, gpio.NoEdge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Name() string {
|
||||||
|
return p.port.name + "_" + strconv.Itoa(int(p.pinbit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Number() int {
|
||||||
|
return int(p.pinbit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Function() string {
|
||||||
|
return string(p.Func())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) In(pull gpio.Pull, edge gpio.Edge) error {
|
||||||
|
// Set pin to input
|
||||||
|
err := p.port.iodir.getAndSetBit(p.pinbit, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Set pullup
|
||||||
|
switch pull {
|
||||||
|
case gpio.PullNoChange:
|
||||||
|
// don't check, don't change
|
||||||
|
case gpio.PullDown:
|
||||||
|
// pull down is not supported by any device
|
||||||
|
return errors.New("MCP23xxx: PullDown is not supported")
|
||||||
|
case gpio.PullUp:
|
||||||
|
if !p.port.supportPullup {
|
||||||
|
return errors.New("MCP23xxx: PullUp is not supported by this device")
|
||||||
|
}
|
||||||
|
err = p.port.gppu.getAndSetBit(p.pinbit, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case gpio.Float:
|
||||||
|
if p.port.supportPullup {
|
||||||
|
err = p.port.gppu.getAndSetBit(p.pinbit, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check edge detection
|
||||||
|
// TODO interrupt support
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Read() gpio.Level {
|
||||||
|
v, _ := p.port.gpio.getBit(p.pinbit, false)
|
||||||
|
if v {
|
||||||
|
return gpio.High
|
||||||
|
}
|
||||||
|
return gpio.Low
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) WaitForEdge(timeout time.Duration) bool {
|
||||||
|
// TODO interrupt handling
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Pull() gpio.Pull {
|
||||||
|
if !p.port.supportPullup {
|
||||||
|
return gpio.Float
|
||||||
|
}
|
||||||
|
v, err := p.port.gppu.getBit(p.pinbit, true)
|
||||||
|
if err != nil {
|
||||||
|
return gpio.PullNoChange
|
||||||
|
}
|
||||||
|
if v {
|
||||||
|
return gpio.PullUp
|
||||||
|
}
|
||||||
|
return gpio.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) DefaultPull() gpio.Pull {
|
||||||
|
return gpio.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Out(l gpio.Level) error {
|
||||||
|
err := p.port.iodir.getAndSetBit(p.pinbit, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.port.olat.getAndSetBit(p.pinbit, l == gpio.High, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) PWM(duty gpio.Duty, f physic.Frequency) error {
|
||||||
|
return errors.New("MCP23xxx: PWM is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) Func() pin.Func {
|
||||||
|
v, _ := p.port.iodir.getBit(p.pinbit, true)
|
||||||
|
if v {
|
||||||
|
return gpio.IN
|
||||||
|
}
|
||||||
|
return gpio.OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) SupportedFuncs() []pin.Func {
|
||||||
|
return supportedFuncs[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) SetFunc(f pin.Func) error {
|
||||||
|
var v bool
|
||||||
|
switch f {
|
||||||
|
case gpio.IN:
|
||||||
|
v = true
|
||||||
|
case gpio.OUT:
|
||||||
|
v = false
|
||||||
|
default:
|
||||||
|
return errors.New("MCP23xxx: Function not supported: " + string(f))
|
||||||
|
}
|
||||||
|
return p.port.iodir.getAndSetBit(p.pinbit, v, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portpin) SetPolarityInverted(pol bool) error {
|
||||||
|
return p.port.ipol.getAndSetBit(p.pinbit, pol, true)
|
||||||
|
}
|
||||||
|
func (p *portpin) IsPolarityInverted() (bool, error) {
|
||||||
|
return p.port.ipol.getBit(p.pinbit, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportedFuncs = [...]pin.Func{gpio.IN, gpio.OUT}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
// 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 mcp23xxx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/periph/conn/i2c"
|
||||||
|
"periph.io/x/periph/conn/spi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type registerAccess interface {
|
||||||
|
define(address uint8) registerCache
|
||||||
|
readRegister(address uint8) (uint8, error)
|
||||||
|
writeRegister(address uint8, value uint8) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type i2cRegisterAccess struct {
|
||||||
|
*i2c.Dev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *i2cRegisterAccess) readRegister(address uint8) (uint8, error) {
|
||||||
|
r := make([]byte, 1)
|
||||||
|
err := ra.Tx([]byte{address}, r)
|
||||||
|
return r[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *i2cRegisterAccess) writeRegister(address uint8, value uint8) error {
|
||||||
|
return ra.Tx([]byte{address, value}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *i2cRegisterAccess) define(address uint8) registerCache {
|
||||||
|
return newRegister(ra, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
type spiRegisterAccess struct {
|
||||||
|
spi.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *spiRegisterAccess) readRegister(address uint8) (uint8, error) {
|
||||||
|
r := make([]byte, 1)
|
||||||
|
err := ra.Tx([]byte{0x41, address}, r)
|
||||||
|
return r[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *spiRegisterAccess) writeRegister(address uint8, value uint8) error {
|
||||||
|
return ra.Tx([]byte{0x40, address, value}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *spiRegisterAccess) define(address uint8) registerCache {
|
||||||
|
return newRegister(ra, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
type registerCache struct {
|
||||||
|
registerAccess
|
||||||
|
address uint8
|
||||||
|
got bool
|
||||||
|
cache uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegister(ra registerAccess, address uint8) registerCache {
|
||||||
|
return registerCache{
|
||||||
|
registerAccess: ra,
|
||||||
|
address: address,
|
||||||
|
got: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registerCache) readValue(cached bool) (uint8, error) {
|
||||||
|
if cached && r.got {
|
||||||
|
return r.cache, nil
|
||||||
|
}
|
||||||
|
v, err := r.readRegister(r.address)
|
||||||
|
if err == nil {
|
||||||
|
r.got = true
|
||||||
|
r.cache = v
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registerCache) writeValue(value uint8, cached bool) error {
|
||||||
|
if cached && r.got && value == r.cache {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.writeRegister(r.address, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.got = true
|
||||||
|
r.cache = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registerCache) getAndSetBit(bit uint8, value bool, cached bool) error {
|
||||||
|
v, err := r.readValue(cached)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if value {
|
||||||
|
v |= 1 << bit
|
||||||
|
} else {
|
||||||
|
v &= ^(1 << bit)
|
||||||
|
}
|
||||||
|
return r.writeValue(v, cached)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registerCache) getBit(bit uint8, cached bool) (bool, error) {
|
||||||
|
v, err := r.readValue(cached)
|
||||||
|
return 0 != (v & (1 << bit)), err
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue