mirror of https://github.com/periph/devices
tca95xx: Add support for i/o extenders. (#51)
parent
600c8cc85c
commit
a6bfa877b6
@ -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…
Reference in New Issue