mirror of https://github.com/periph/devices
pca9548: i2c multiplexer (#320)
parent
12263dcb53
commit
ab13b61dd6
@ -0,0 +1,19 @@
|
||||
// Copyright 2018 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 pca9548 is a driver for an 8 port I²C multiplexer that is available
|
||||
// from multiple vendors. The main features of this multiplexer is that its has
|
||||
// 8 channels and is capable of voltage level translation.
|
||||
//
|
||||
//
|
||||
// Adjusting the Bus CLK
|
||||
//
|
||||
// The bus clock is slaved to the master bus clock, different clock for each
|
||||
// port is currently not supported. The Maximum clock for this device is 400kHz.
|
||||
//
|
||||
//
|
||||
// Datasheet
|
||||
//
|
||||
// https://www.nxp.com/docs/en/data-sheet/PCA9548A.pdf
|
||||
package pca9548
|
||||
@ -0,0 +1,158 @@
|
||||
// Copyright 2018 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 pca9548
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"periph.io/x/periph/conn"
|
||||
"periph.io/x/periph/conn/i2c"
|
||||
"periph.io/x/periph/conn/i2c/i2creg"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
)
|
||||
|
||||
// DefaultOpts is the recommended default options.
|
||||
var DefaultOpts = Opts{Addr: 0x70}
|
||||
|
||||
// Opts is the pca9548 configuration.
|
||||
type Opts struct {
|
||||
// Addr is the pca9548 I²C Address. Valid addresses for the NXP pca9548 are
|
||||
// 0x70 to 0x77. The address is set by pulling A0~A2 low or high. Please
|
||||
// refer to the datasheet.
|
||||
Addr int
|
||||
}
|
||||
|
||||
// Dev is handle to a pca9548 I²C Multiplexer.
|
||||
type Dev struct {
|
||||
// Immutable.
|
||||
c i2c.Bus
|
||||
address uint16
|
||||
name string
|
||||
numPorts uint8
|
||||
|
||||
// Mutable.
|
||||
mu sync.Mutex
|
||||
activePort uint8
|
||||
}
|
||||
|
||||
// New creates a new handle to a pca9548 I²C multiplexer.
|
||||
func New(bus i2c.Bus, opts *Opts) (*Dev, error) {
|
||||
if opts.Addr < 0x70 || opts.Addr > 0x77 {
|
||||
return nil, errors.New("Address outside valid range of 0x70-0x77")
|
||||
}
|
||||
d := &Dev{
|
||||
c: bus,
|
||||
activePort: 0xFF,
|
||||
address: uint16(opts.Addr),
|
||||
numPorts: 8,
|
||||
name: "pca9548-" + strconv.FormatUint(uint64(opts.Addr), 16),
|
||||
}
|
||||
r := make([]byte, 1)
|
||||
err := bus.Tx(uint16(opts.Addr), nil, r)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not establish communicate with multiplexer: " + err.Error())
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// RegisterPorts registers multiplexer ports with the host. These ports can
|
||||
// then be used as any other i2c.Bus. Busses will be named "alias0", "alias1"
|
||||
// etc. If using more than one multiplexer note that the alias must be unique.
|
||||
// Returns slice of ports names registered and error.
|
||||
func (d *Dev) RegisterPorts(alias string) ([]string, error) {
|
||||
var portNames []string
|
||||
for i := uint8(0); i < d.numPorts; i++ {
|
||||
portStr := strconv.Itoa(int(i))
|
||||
addrStr := strconv.FormatUint(uint64(d.address), 16)
|
||||
portName := d.c.String() + "-pca9548-" + addrStr + "-" + portStr
|
||||
opener := newOpener(d, i, alias+portStr, portName)
|
||||
if err := i2creg.Register(portName, []string{alias + portStr}, -1, opener); err != nil {
|
||||
return portNames, err
|
||||
}
|
||||
portNames = append(portNames, portName)
|
||||
}
|
||||
return portNames, nil
|
||||
}
|
||||
|
||||
// Halt does nothing.
|
||||
func (d *Dev) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the bus base name for multiplexer ports.
|
||||
func (d *Dev) String() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
// tx wraps the master bus tx, maintains which port that each bus is registered
|
||||
// on so that communication from the master is always on the right port.
|
||||
func (d *Dev) tx(port uint8, address uint16, w, r []byte) error {
|
||||
if address == d.address {
|
||||
return errors.New("device address conflicts with multiplexer address")
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
// Change active port if needed.
|
||||
if port != d.activePort {
|
||||
if err := d.c.Tx(d.address, []byte{1 << port}, nil); err != nil {
|
||||
return errors.New("failed to change active port on multiplexer: " + err.Error())
|
||||
}
|
||||
d.activePort = port
|
||||
}
|
||||
return d.c.Tx(address, w, r)
|
||||
}
|
||||
|
||||
// newOpener is a helper for creating an opener func.
|
||||
func newOpener(d *Dev, portNumber uint8, alias string, name string) i2creg.Opener {
|
||||
return func() (i2c.BusCloser, error) {
|
||||
return &port{
|
||||
name: name + "(" + alias + ")",
|
||||
mux: d,
|
||||
number: portNumber,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// port is a i2c.BusCloser.
|
||||
type port struct {
|
||||
// Immutable.
|
||||
name string
|
||||
number uint8
|
||||
|
||||
// Mutable.
|
||||
mu sync.Mutex
|
||||
mux *Dev
|
||||
}
|
||||
|
||||
// String gets the port number of the bus on the multiplexer.
|
||||
func (p *port) String() string { return "Port:" + p.name }
|
||||
|
||||
// SetSpeed is no implemented as the port slaves the master port clock.
|
||||
func (p *port) SetSpeed(f physic.Frequency) error {
|
||||
return errors.New("SetSpeed is not impelmented on a port by port basis")
|
||||
}
|
||||
|
||||
// Tx does a transaction on the multiplexer port it is register to.
|
||||
func (p *port) Tx(addr uint16, w, r []byte) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.mux == nil {
|
||||
return errors.New(p.String() + " has been closed")
|
||||
}
|
||||
return p.mux.tx(p.number, addr, w, r)
|
||||
}
|
||||
|
||||
// Close closes a port.
|
||||
func (p *port) Close() error {
|
||||
p.mu.Lock()
|
||||
p.mux = nil
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ conn.Resource = &Dev{}
|
||||
var _ i2c.Bus = &port{}
|
||||
@ -0,0 +1,339 @@
|
||||
// Copyright 2018 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 pca9548
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"periph.io/x/periph/conn/i2c/i2creg"
|
||||
"periph.io/x/periph/conn/i2c/i2ctest"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
"periph.io/x/periph/host"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
tx []i2ctest.IO
|
||||
address int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
tx: []i2ctest.IO{{Addr: 0x70, W: nil, R: []byte{0xFF}}},
|
||||
address: 0x70,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
tx: []i2ctest.IO{{Addr: 0x70, W: nil, R: nil}},
|
||||
address: 0x70,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
tx: []i2ctest.IO{{Addr: 0x50, W: nil, R: nil}},
|
||||
address: 0x50,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
bus := &i2ctest.Playback{Ops: tt.tx, DontPanic: true}
|
||||
_, err := New(bus, &Opts{Addr: tt.address})
|
||||
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("got unexpected error %v", err)
|
||||
}
|
||||
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
alias string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
"mux",
|
||||
[]string{
|
||||
"playback-pca9548-70-0",
|
||||
"playback-pca9548-70-1",
|
||||
"playback-pca9548-70-2",
|
||||
"playback-pca9548-70-3",
|
||||
"playback-pca9548-70-4",
|
||||
"playback-pca9548-70-5",
|
||||
"playback-pca9548-70-6",
|
||||
"playback-pca9548-70-7",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
bus := &i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
{Addr: 0x70, W: nil, R: []byte{0xFF}},
|
||||
},
|
||||
DontPanic: true,
|
||||
}
|
||||
host.Init()
|
||||
|
||||
mux, err := New(bus, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create I²C mux: %v", err)
|
||||
}
|
||||
|
||||
portNames, err := mux.RegisterPorts(tt.alias)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create I²C mux: %v", err)
|
||||
}
|
||||
for i, port := range tt.expect {
|
||||
if portNames[i] != port {
|
||||
t.Errorf("expected port name %v but got %v", portNames[i], port)
|
||||
} else if err := i2creg.Unregister(port); err != nil {
|
||||
t.Errorf("failed to Unregister port %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failing Case.
|
||||
for _, tt := range tests {
|
||||
bus := &i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
{Addr: 0x70, W: nil, R: []byte{0xFF}},
|
||||
},
|
||||
DontPanic: true,
|
||||
}
|
||||
host.Init()
|
||||
|
||||
mux, err := New(bus, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create I²C mux: %v", err)
|
||||
}
|
||||
|
||||
if _, err := mux.RegisterPorts(tt.alias); err != nil {
|
||||
t.Fatalf("failed to create I²C mux: %v", err)
|
||||
}
|
||||
if _, err := mux.RegisterPorts(tt.alias); err == nil {
|
||||
t.Fatal("expected second registraion to fail", err)
|
||||
}
|
||||
for i, port := range tt.expect {
|
||||
if err := i2creg.Unregister(port); err != nil {
|
||||
t.Errorf("failed to Unregister port %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDev_Halt(t *testing.T) {
|
||||
d := &Dev{}
|
||||
err := d.Halt()
|
||||
if err != nil {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDev_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
address uint16
|
||||
want string
|
||||
}{
|
||||
{address: 0x70, want: "pca9548-70"},
|
||||
{address: 0x71, want: "pca9548-71"},
|
||||
{address: 0x72, want: "pca9548-72"},
|
||||
{address: 0x73, want: "pca9548-73"},
|
||||
{address: 0x74, want: "pca9548-74"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
bus := &i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
{Addr: tt.address, W: nil, R: []byte{0xFF}},
|
||||
},
|
||||
DontPanic: false,
|
||||
}
|
||||
host.Init()
|
||||
|
||||
mux, err := New(bus, &Opts{Addr: int(tt.address)})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create I²C mux: %v", err)
|
||||
}
|
||||
|
||||
if got := mux.String(); got != tt.want {
|
||||
t.Errorf("Dev.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDev_Tx(t *testing.T) {
|
||||
var tests = []struct {
|
||||
alias string
|
||||
openPort string
|
||||
initialPort int
|
||||
address uint16
|
||||
tx []i2ctest.IO
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
alias: "mux",
|
||||
openPort: "mux0",
|
||||
initialPort: 0,
|
||||
address: 0x30,
|
||||
tx: []i2ctest.IO{
|
||||
{Addr: 0x70, W: nil, R: []byte{0xFF}},
|
||||
{Addr: 0x70, W: []byte{0x01}, R: []byte{}},
|
||||
{Addr: 0x30, W: []byte{0xAA}, R: []byte{0xBB}},
|
||||
},
|
||||
},
|
||||
{
|
||||
alias: "mux",
|
||||
openPort: "mux0",
|
||||
initialPort: 0,
|
||||
address: 0x70,
|
||||
tx: []i2ctest.IO{
|
||||
{Addr: 0x70, W: nil, R: []byte{0xFF}},
|
||||
{Addr: 0x70, W: []byte{0x01}, R: []byte{}},
|
||||
{Addr: 0x70, W: []byte{0xAA}, R: []byte{0xBB}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
alias: "mux",
|
||||
openPort: "mux0",
|
||||
initialPort: 0,
|
||||
address: 0x30,
|
||||
tx: []i2ctest.IO{
|
||||
{Addr: 0x70, W: nil, R: []byte{0xFF}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
bus := &i2ctest.Playback{
|
||||
Ops: tt.tx,
|
||||
DontPanic: true,
|
||||
}
|
||||
host.Init()
|
||||
|
||||
mux, err := New(bus, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
|
||||
_, err = mux.RegisterPorts(tt.alias)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
|
||||
muxbus, err := i2creg.Open(tt.openPort)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
defer muxbus.Close()
|
||||
err = muxbus.Tx(tt.address, []byte{0xAA}, []byte{0xBB})
|
||||
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for i := 0; i < 8; i++ {
|
||||
if err := i2creg.Unregister("playback-" + mux.String() + "-" + strconv.Itoa(i)); err != nil {
|
||||
t.Errorf("failed to Unregister port %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_port_Tx(t *testing.T) {
|
||||
tests := []struct {
|
||||
p *port
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
p: &port{number: 6},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
p: &port{number: 0x6, mux: &Dev{address: 0x70, activePort: 6,
|
||||
c: &i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{{Addr: 0x1, W: nil, R: nil}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
err := tt.p.Tx(0x01, nil, nil)
|
||||
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("got unexpected error %v", err)
|
||||
}
|
||||
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_port_Close(t *testing.T) {
|
||||
|
||||
p := &port{number: 6}
|
||||
|
||||
p.Close()
|
||||
if err := p.Tx(0x01, nil, nil); err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_port_String(t *testing.T) {
|
||||
bus := &i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{{Addr: 0x70, W: nil, R: []byte{0xFF}}},
|
||||
DontPanic: true,
|
||||
}
|
||||
host.Init()
|
||||
|
||||
mux, err := New(bus, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
|
||||
_, err = mux.RegisterPorts("mux")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
|
||||
muxbus, err := i2creg.Open("mux0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
|
||||
expected := "Port:playback-pca9548-70-0(mux0)"
|
||||
got := muxbus.String()
|
||||
if got != expected {
|
||||
t.Errorf("expected: \n%v but got: \n%v", expected, got)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for i := 0; i < 8; i++ {
|
||||
if err := i2creg.Unregister("playback-" + mux.String() + "-" + strconv.Itoa(i)); err != nil {
|
||||
t.Errorf("failed to Unregister port %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_port_SetSpeed(t *testing.T) {
|
||||
p := &port{}
|
||||
err := p.SetSpeed(400 * physic.KiloHertz)
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue