tca95xx: Add support for i/o extenders. (#51)

pull/53/head
Weston Schmidt 4 years ago committed by GitHub
parent 600c8cc85c
commit a6bfa877b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -38,4 +38,5 @@ Matias Insaurralde <matias@insaurral.de>
Seán C McCord <ulexus@gmail.com> <scm@cycoresys.com> Seán C McCord <ulexus@gmail.com> <scm@cycoresys.com>
Stephan Sperber <sperberstephan@googlemail.com> Stephan Sperber <sperberstephan@googlemail.com>
Thorsten von Eicken <tve@voneicken.com> Thorsten von Eicken <tve@voneicken.com>
Weston Schmidt <weston_schmidt@alumni.purdue.edu>

@ -0,0 +1,50 @@
// Copyright 2022 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 tca95xx_test
import (
"fmt"
"log"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/i2c/i2creg"
"periph.io/x/devices/v3/tca95xx"
"periph.io/x/host/v3"
)
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 := tca95xx.New(bus, tca95xx.TCA9534, 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,205 @@
// Copyright 2022 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.
// This file is largely a copy of mcp23xxx/pins.go, but with a reduced feature
// set for controlling the chips.
package tca95xx
import (
"errors"
"fmt"
"strconv"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/pin"
)
// Pin extends gpio.PinIO interface with features supported by tca95xx 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
input registerCache // input at the pin
output registerCache // output control, or flipflop state if read
iodir registerCache // direction
ipol registerCache // polarity setting
}
func (p *port) pins(count int) []Pin {
result := make([]Pin, count)
var i uint8
for i = 0; i < uint8(count); i++ {
result[i] = &portpin{
port: p,
pinbit: i,
}
}
return result
}
// Tx takes bytes to either read or write. Only half duplex is supported so it
// is an error to pass 2 buffers at once. The bytes are written or read from
// the same connection sequentially.
func (p *port) Tx(w, r []byte) (err error) {
send := len(w)
get := len(r)
switch {
case send > 0 && get > 0:
return fmt.Errorf("tca95xx: only conn.Half duplex is supported")
case send > 0:
for i := 0; i < send; i++ {
err = p.output.writeValue(w[i], false)
if err != nil {
return err
}
}
case get > 0:
var in uint8
for i := 0; i < get; i++ {
in, err = p.input.readValue(false)
if err != nil {
return err
}
r[i] = in
}
}
return nil
}
// Duplex returns that this is a half duplex connection.
func (p *port) Duplex() conn.Duplex {
return conn.Half
}
// String provides the name of this connection.
func (p *port) String() string {
return p.name
}
type portpin struct {
port *port
pinbit uint8
}
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 pullup
switch pull {
case gpio.PullDown:
// pull down is not supported by any device
return errors.New("tca95xx: PullDown is not supported")
case gpio.PullUp:
return errors.New("tca95xx: PullUp is not supported")
case gpio.Float, gpio.PullNoChange:
// Do nothing, supported.
}
// Interrupts are not via I2C bus, so supporting them is less than
// ideal.
if edge != gpio.NoEdge {
return errors.New("tca95xx: edge detection not supported")
}
// Set pin to input
return p.port.iodir.getAndSetBit(p.pinbit, true, true)
}
func (p *portpin) Read() gpio.Level {
v, _ := p.port.input.getBit(p.pinbit, false)
if v {
return gpio.High
}
return gpio.Low
}
func (p *portpin) WaitForEdge(timeout time.Duration) bool {
return false
}
func (p *portpin) Pull() gpio.Pull {
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.output.getAndSetBit(p.pinbit, l == gpio.High, true)
}
func (p *portpin) PWM(duty gpio.Duty, f physic.Frequency) error {
return errors.New("tca95xx: 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("tca95xx: 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,78 @@
// Copyright 2022 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.
// This file is largely a copy of mcp23xxx/registers.go without the spi interface.
package tca95xx
import "periph.io/x/conn/v3/i2c"
type registerCache struct {
i2c *i2c.Dev
address uint8
got bool
cache uint8
}
func newRegister(i2c *i2c.Dev, address uint8) registerCache {
return registerCache{
i2c: i2c,
address: address,
got: false,
}
}
func (r *registerCache) readRegister(address uint8) (uint8, error) {
rx := make([]byte, 1)
err := r.i2c.Tx([]byte{address}, rx)
return rx[0], err
}
func (r *registerCache) writeRegister(address uint8, value uint8) error {
return r.i2c.Tx([]byte{address, value}, nil)
}
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 (v & (1 << bit)) != 0, err
}

@ -0,0 +1,291 @@
// Copyright 2022 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 tca95xx
import (
"reflect"
"testing"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/gpio/gpioreg"
"periph.io/x/conn/v3/i2c/i2ctest"
"periph.io/x/conn/v3/physic"
)
func TestTCA9535_out(t *testing.T) {
const address uint16 = 0x20
scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: address, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: address, W: []byte{0x07}, R: []byte{0xFF}},
// iodir is set to output
{Addr: address, W: []byte{0x06, 0xFE}, R: nil},
// output is read
{Addr: address, W: []byte{0x02}, R: []byte{0x00}},
// writing back unchanged value is omitted
// writing high output
{Addr: address, W: []byte{0x02, 0x01}, R: nil},
// writing low output
{Addr: address, W: []byte{0x02, 0x00}, R: nil},
},
}
dev, err := New(scenario, TCA9535, address)
if err != nil {
t.Fatal(err)
}
if dev == nil {
t.Fatal("dev is nil")
}
defer dev.Close()
p0 := gpioreg.ByName("TCA9535_20_P0_0")
if nil == p0 {
t.Fatal("p0 is nil")
}
_ = p0.Out(gpio.Low)
_ = p0.Out(gpio.High)
_ = p0.Out(gpio.Low)
}
func TestTCA9535_in(t *testing.T) {
const address uint16 = 0x20
scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: address, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: address, W: []byte{0x07}, R: []byte{0xFF}},
// not written, since it didn't change
// input is read
{Addr: address, W: []byte{0x00}, R: []byte{0x01}},
},
}
dev, err := New(scenario, TCA9535, address)
if err != nil {
t.Fatal(err)
}
defer dev.Close()
p0 := gpioreg.ByName("TCA9535_20_P0_0")
_ = p0.In(gpio.Float, gpio.NoEdge)
l := p0.Read()
if l != gpio.High {
t.Errorf("Input should be High")
}
}
func TestTCA9535_inInverted(t *testing.T) {
const address uint16 = 0x20
scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: address, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: address, W: []byte{0x07}, R: []byte{0xFF}},
// not written, since it didn't change
// polarity is set
{Addr: address, W: []byte{0x04}, R: []byte{0x01}},
// gpio is read high
{Addr: address, W: []byte{0x00}, R: []byte{0x01}},
// gpio is read low
{Addr: address, W: []byte{0x00}, R: []byte{0x00}},
},
}
dev, err := New(scenario, TCA9535, address)
if err != nil {
t.Fatal(err)
}
defer dev.Close()
p0 := gpioreg.ByName("TCA9535_20_P0_0").(Pin)
_ = p0.In(gpio.Float, gpio.NoEdge)
_ = p0.SetPolarityInverted(true)
l := p0.Read()
if l != gpio.High {
t.Errorf("Input should be High")
}
l = p0.Read()
if l != gpio.Low {
t.Errorf("Input should be Low")
}
inverted, err := p0.IsPolarityInverted()
if inverted != true || err != nil {
t.Errorf("polarity should return as inverted")
}
}
func TestTCA9535_Tx(t *testing.T) {
tests := []struct {
description string
scenario *i2ctest.Playback
output bool
t []byte
r []byte
expectErr bool
}{
{
description: "working write 2 characters",
output: true,
t: []byte{0xa5, 0x5a},
scenario: &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}},
// iodir is set to output
{Addr: 0x20, W: []byte{0x06, 0xFE}, R: nil},
// output is read
{Addr: 0x20, W: []byte{0x02}, R: []byte{0x00}},
{Addr: 0x20, W: []byte{0x06, 0xFC}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0xF8}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0xF0}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0xE0}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0xC0}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0x80}, R: nil},
{Addr: 0x20, W: []byte{0x06, 0x00}, R: nil},
// output is set
{Addr: 0x20, W: []byte{0x02, 0xa5}, R: nil},
// output is set
{Addr: 0x20, W: []byte{0x02, 0x5a}, R: nil},
},
},
}, {
description: "working read 2 characters",
r: []byte{0xa5, 0x5a},
scenario: &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}},
// read the inputs
{Addr: 0x20, W: []byte{0x00}, R: []byte{0xa5}},
{Addr: 0x20, W: []byte{0x00}, R: []byte{0x5a}},
},
},
}, {
description: "Invalid, only r or w may be set.",
r: []byte{0xa5, 0x5a},
t: []byte{0xa5, 0x5a},
scenario: &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}},
},
},
expectErr: true,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
dev, err := New(tc.scenario, TCA9535, uint16(0x20))
if err != nil {
t.Fatal(err)
}
if dev == nil {
t.Fatal("dev must not be nil")
}
defer dev.Close()
if tc.output {
// Set the port for output
for _, pin := range dev.Pins[0] {
_ = pin.Out(gpio.Low)
}
} else {
// Set the port for input
for _, pin := range dev.Pins[0] {
_ = pin.In(gpio.Float, gpio.NoEdge)
}
}
r := make([]byte, len(tc.r))
err = dev.Conns[0].Tx(tc.t, r)
if tc.expectErr {
if err == nil {
t.Fatal(err)
}
return
}
if err != nil {
t.Fatal(err)
}
if len(tc.r) != len(r) || len(tc.r) > 0 {
if !reflect.DeepEqual(tc.r, r) {
t.Fatal("r buffers don't match")
}
}
})
}
}
func TestTCA9535_fixedValues(t *testing.T) {
const address uint16 = 0x20
scenario := &i2ctest.Playback{
Ops: []i2ctest.IO{
// iodir is read on creation
{Addr: address, W: []byte{0x06}, R: []byte{0xFF}},
{Addr: address, W: []byte{0x07}, R: []byte{0xFF}},
},
}
dev, err := New(scenario, TCA9535, address)
if err != nil {
t.Fatal(err)
}
defer dev.Close()
if dev.Conns[0].Duplex() != conn.Half {
t.Errorf("Duplex() should return conn.Half")
}
if dev.Conns[0].String() != "TCA9535_20_P0" {
t.Errorf("String() should return 'TCA9535_20_P0'")
}
if dev.Conns[1].String() != "TCA9535_20_P1" {
t.Errorf("String() should return 'TCA9535_20_P1'")
}
if dev.Pins[0][1].String() != "TCA9535_20_P0_1" {
t.Errorf("String() should return 'TCA9535_20_P0_1'")
}
if dev.Pins[0][1].Number() != 1 {
t.Errorf("Number() should return '1'")
}
if dev.Pins[0][6].Number() != 6 {
t.Errorf("Number() should return '6'")
}
if dev.Pins[0][6].WaitForEdge(10*time.Second) != false {
t.Errorf("WaitForEdge() should return 'false'")
}
if dev.Pins[0][5].Pull() != gpio.Float {
t.Errorf("Pull() should return 'gpio.Float'")
}
if dev.Pins[0][5].DefaultPull() != gpio.Float {
t.Errorf("DefaultPull() should return 'gpio.Float'")
}
err = dev.Pins[0][0].PWM(gpio.DutyHalf, physic.Hertz)
if err == nil {
t.Errorf("PWM should return an error")
}
}

@ -0,0 +1,110 @@
// Copyright 2022 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 tca95xx provides an interface to the Texas Instruments TCA95 series
// of 8-bit I²C extenders.
//
// The following variants are supported:
//
// - PCA9536 - address: 0x41
// - TCA6408A - addresses: 0x20, 0x21
// - TCA6416 - addresses: 0x20, 0x21
// - TCA6416A - addresses: 0x20, 0x21
// - TCA9534 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
// - TCA9534A - addresses: 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
// - TCA9535 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
// - TCA9537 - address: 0x49
// - TCA9538 - address: 0x70, 0x71, 0x72, 0x73
// - TCA9539 - address: 0x74, 0x75, 0x76, 0x77
// - TCA9554 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
// - TCA9555 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
//
// Both gpio.Pin and conn.Conn interfaces are supported.
package tca95xx
import (
"fmt"
"strconv"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/gpio/gpioreg"
"periph.io/x/conn/v3/i2c"
)
// Dev is a TCA95xx series I²C extender with two ways to interact with the pins
// on the extender chip - as per pin gpio.Pin, or as per port conn.Conn
// connections.
type Dev struct {
Pins [][]Pin // Pins is a double array structured as: [port][pin].
Conns []conn.Conn // Conns uses the same [port] array structure.
}
// New returns a device object that communicates over I²C to the TCA95xx device
// family of I/O extenders.
func New(bus i2c.Bus, variant Variant, addr uint16) (*Dev, error) {
v, found := variants[variant]
if !found {
return nil, fmt.Errorf("%s: Unsupported variant", string(variant))
}
if v.isAddrInvalid(addr) {
return nil, fmt.Errorf("tca95xx: address not supported by device type %s", string(variant))
}
i2c := i2c.Dev{
Bus: bus,
Addr: addr,
}
devicename := string(variant) + "_" + strconv.FormatInt(int64(addr), 16)
ports := v.getPorts(&i2c, devicename)
// Map the register maps and ports into gpio.Pins.
pins := make([][]Pin, len(ports))
pinsLeft := v.pins
for i := range ports {
// pre-cache iodir
_, err := ports[i].iodir.readValue(false)
if err != nil {
return nil, err
}
if pinsLeft > 8 {
pins[i] = ports[i].pins(8)
pinsLeft -= 8
} else {
pins[i] = ports[i].pins(pinsLeft)
pinsLeft = 0
}
for j := range pins[i] {
pin := pins[i][j]
// Ignore registration failure.
_ = gpioreg.Register(pin)
}
}
// Convert to an array of Conn interfaces.
var conns []conn.Conn
for i := range ports {
conns = append(conns, ports[i])
}
d := Dev{
Pins: pins,
Conns: conns,
}
return &d, nil
}
// 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
}

@ -0,0 +1,89 @@
// Copyright 2022 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 tca95xx
import (
"periph.io/x/conn/v3/i2c"
)
// Variant is the type denoting a specific variant of the family.
type Variant string
const (
PCA9536 Variant = "PCA9536" // PCA9536 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/pca9536
TCA6408A Variant = "TCA6408A" // TCA6408A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6408a
TCA6416 Variant = "TCA6416" // TCA6416 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6416
TCA6416A Variant = "TCA6416A" // TCA6416A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6416a
TCA9534 Variant = "TCA9534" // TCA9534 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9534
TCA9534A Variant = "TCA9534A" // TCA9534A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9534a
TCA9535 Variant = "TCA9535" // TCA9535 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9535
TCA9537 Variant = "TCA9537" // TCA9537 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9537
TCA9538 Variant = "TCA9538" // TCA9538 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9538
TCA9539 Variant = "TCA9539" // TCA9539 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9539
TCA9554 Variant = "TCA9554" // TCA9554 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9554
TCA9555 Variant = "TCA9555" // TCA9555 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9555
)
type variant struct {
addStart uint16
addEnd uint16
pins int
}
var variants = map[Variant]variant{
PCA9536: {addStart: 0x41, addEnd: 0x41, pins: 4},
TCA6408A: {addStart: 0x20, addEnd: 0x21, pins: 8},
TCA6416: {addStart: 0x20, addEnd: 0x21, pins: 16},
TCA6416A: {addStart: 0x20, addEnd: 0x21, pins: 16},
TCA9534: {addStart: 0x20, addEnd: 0x27, pins: 8},
TCA9534A: {addStart: 0x38, addEnd: 0x3f, pins: 8},
TCA9535: {addStart: 0x20, addEnd: 0x27, pins: 16},
TCA9537: {addStart: 0x49, addEnd: 0x49, pins: 4},
TCA9538: {addStart: 0x70, addEnd: 0x73, pins: 8},
TCA9539: {addStart: 0x74, addEnd: 0x77, pins: 16},
TCA9554: {addStart: 0x20, addEnd: 0x27, pins: 8},
TCA9555: {addStart: 0x20, addEnd: 0x27, pins: 16},
}
// isAddrInvalid checks to see if the address is used by the chip.
func (v variant) isAddrInvalid(addr uint16) bool {
if addr < v.addStart || v.addEnd < addr {
return true
}
return false
}
// getVariantRegMap returns the register map based on the number of pins the
// chip expands to.
func (v variant) getPorts(i2c *i2c.Dev, devicename string) []*port {
if v.pins == 16 {
return []*port{
{
name: devicename + "_P0",
input: newRegister(i2c, 0x00),
output: newRegister(i2c, 0x02),
ipol: newRegister(i2c, 0x04),
iodir: newRegister(i2c, 0x06),
},
{
name: devicename + "_P1",
input: newRegister(i2c, 0x01),
output: newRegister(i2c, 0x03),
ipol: newRegister(i2c, 0x05),
iodir: newRegister(i2c, 0x07),
},
}
}
return []*port{
{
name: devicename + "_P0",
input: newRegister(i2c, 0x00),
output: newRegister(i2c, 0x01),
ipol: newRegister(i2c, 0x02),
iodir: newRegister(i2c, 0x03),
},
}
}
Loading…
Cancel
Save