mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-20 14:39:00 +00:00
seesaw: device support and tests
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# Seesaw Driver
|
||||||
|
|
||||||
|
This is a basic driver to interact with Adafruit's seesaw board.
|
||||||
|
|
||||||
|
It exposes constants for the documented modules as well as their functions. Sub-packages
|
||||||
|
may contain more refined drivers based on those.
|
||||||
|
|
||||||
|
- [Device Overview](https://learn.adafruit.com/adafruit-seesaw-atsamd09-breakout/overview)
|
||||||
|
- [Adafruit Datasheet](https://cdn-learn.adafruit.com/downloads/pdf/adafruit-seesaw-atsamd09-breakout.pdf)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package seesaw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type I2CHandleFunc func(t *testing.T, w, r []byte) error
|
||||||
|
|
||||||
|
// mocki2c implements the drivers.I2C interface and matches a list
|
||||||
|
// of handlers against actual invocations. Useful to test command/reply style I2C devices.
|
||||||
|
type mocki2c struct {
|
||||||
|
addr uint16
|
||||||
|
handlers []I2CHandleFunc
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mocki2c) Tx(addr uint16, w, r []byte) error {
|
||||||
|
assertEquals(m.t, addr, m.addr)
|
||||||
|
if len(m.handlers) == 0 {
|
||||||
|
ws := hex.EncodeToString(w)
|
||||||
|
rs := hex.EncodeToString(r)
|
||||||
|
panic(fmt.Sprintf("no handlers for: addr='%02x' w='%s' r='%s'", byte(addr), ws, rs))
|
||||||
|
}
|
||||||
|
h := m.handlers[0]
|
||||||
|
m.handlers = m.handlers[1:]
|
||||||
|
return h(m.t, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockDev(t *testing.T, addr uint16, handlers ...I2CHandleFunc) *mocki2c {
|
||||||
|
return &mocki2c{
|
||||||
|
addr: addr,
|
||||||
|
handlers: handlers,
|
||||||
|
t: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func when(expectedWrite, returningRead []byte, returningError error) I2CHandleFunc {
|
||||||
|
return func(t *testing.T, w, r []byte) error {
|
||||||
|
assertEquals(t, w, expectedWrite)
|
||||||
|
assertEquals(t, len(r), len(returningRead))
|
||||||
|
if r != nil {
|
||||||
|
copy(r, returningRead)
|
||||||
|
}
|
||||||
|
return returningError
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Package seesaw provides a driver for the "i2c to whathever interface" called seesaw
|
||||||
|
// documentation: https://learn.adafruit.com/adafruit-seesaw-atsamd09-breakout/overview
|
||||||
|
package seesaw
|
||||||
|
|
||||||
|
type ModuleBaseAddress byte
|
||||||
|
|
||||||
|
// Module Base Addreses
|
||||||
|
// The module base addresses for different seesaw modules.
|
||||||
|
const (
|
||||||
|
ModuleStatusBase ModuleBaseAddress = 0x00
|
||||||
|
ModuleGpioBase ModuleBaseAddress = 0x01
|
||||||
|
ModuleSercom0Base ModuleBaseAddress = 0x02
|
||||||
|
|
||||||
|
ModuleTimerBase ModuleBaseAddress = 0x08
|
||||||
|
ModuleAdcBase ModuleBaseAddress = 0x09
|
||||||
|
ModuleDacBase ModuleBaseAddress = 0x0A
|
||||||
|
ModuleInterruptBase ModuleBaseAddress = 0x0B
|
||||||
|
ModuleDapBase ModuleBaseAddress = 0x0C
|
||||||
|
ModuleEepromBase ModuleBaseAddress = 0x0D
|
||||||
|
ModuleNeoPixelBase ModuleBaseAddress = 0x0E
|
||||||
|
ModuleTouchBase ModuleBaseAddress = 0x0F
|
||||||
|
ModuleKeypadBase ModuleBaseAddress = 0x10
|
||||||
|
ModuleEncoderBase ModuleBaseAddress = 0x11
|
||||||
|
ModuleSpectrumBase ModuleBaseAddress = 0x12
|
||||||
|
)
|
||||||
|
|
||||||
|
type FunctionAddress byte
|
||||||
|
|
||||||
|
// GPIO module function address registers
|
||||||
|
const (
|
||||||
|
FunctionGpioDirsetBulk FunctionAddress = 0x02
|
||||||
|
FunctionGpioDirclrBulk FunctionAddress = 0x03
|
||||||
|
FunctionGpioBulk FunctionAddress = 0x04
|
||||||
|
FunctionGpioBulkSet FunctionAddress = 0x05
|
||||||
|
FunctionGpioBulkClr FunctionAddress = 0x06
|
||||||
|
FunctionGpioBulkToggle FunctionAddress = 0x07
|
||||||
|
FunctionGpioIntenset FunctionAddress = 0x08
|
||||||
|
FunctionGpioIntenclr FunctionAddress = 0x09
|
||||||
|
FunctionGpioIntflag FunctionAddress = 0x0A
|
||||||
|
FunctionGpioPullenset FunctionAddress = 0x0B
|
||||||
|
FunctionGpioPullenclr FunctionAddress = 0x0C
|
||||||
|
)
|
||||||
|
|
||||||
|
// status module function address registers
|
||||||
|
const (
|
||||||
|
FunctionStatusHwId FunctionAddress = 0x01
|
||||||
|
FunctionStatusVersion FunctionAddress = 0x02
|
||||||
|
FunctionStatusOptions FunctionAddress = 0x03
|
||||||
|
FunctionStatusTemp FunctionAddress = 0x04
|
||||||
|
FunctionStatusSwrst FunctionAddress = 0x7F
|
||||||
|
)
|
||||||
|
|
||||||
|
// timer module function address registers
|
||||||
|
const (
|
||||||
|
FunctionTimerStatus FunctionAddress = 0x00
|
||||||
|
FunctionTimerPwm FunctionAddress = 0x01
|
||||||
|
FunctionTimerFreq FunctionAddress = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
// ADC module function address registers
|
||||||
|
const (
|
||||||
|
FunctionAdcStatus FunctionAddress = 0x00
|
||||||
|
FunctionAdcInten FunctionAddress = 0x02
|
||||||
|
FunctionAdcIntenclr FunctionAddress = 0x03
|
||||||
|
FunctionAdcWinmode FunctionAddress = 0x04
|
||||||
|
FunctionAdcWinthresh FunctionAddress = 0x05
|
||||||
|
FunctionAdcChannelOffset FunctionAddress = 0x07
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sercom module function address registers
|
||||||
|
const (
|
||||||
|
FunctionSercomStatus FunctionAddress = 0x00
|
||||||
|
FunctionSercomInten FunctionAddress = 0x02
|
||||||
|
FunctionSercomIntenclr FunctionAddress = 0x03
|
||||||
|
FunctionSercomBaud FunctionAddress = 0x04
|
||||||
|
FunctionSercomData FunctionAddress = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// neopixel module function address registers
|
||||||
|
const (
|
||||||
|
FunctionNeopixelStatus FunctionAddress = 0x00
|
||||||
|
FunctionNeopixelPin FunctionAddress = 0x01
|
||||||
|
FunctionNeopixelSpeed FunctionAddress = 0x02
|
||||||
|
FunctionNeopixelBufLength FunctionAddress = 0x03
|
||||||
|
FunctionNeopixelBuf FunctionAddress = 0x04
|
||||||
|
FunctionNeopixelShow FunctionAddress = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// touch module function address registers
|
||||||
|
const (
|
||||||
|
FunctionTouchChannelOffset FunctionAddress = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
// keypad module function address registers
|
||||||
|
const (
|
||||||
|
FunctionKeypadStatus FunctionAddress = 0x00
|
||||||
|
FunctionKeypadEvent FunctionAddress = 0x01
|
||||||
|
FunctionKeypadIntenset FunctionAddress = 0x02
|
||||||
|
FunctionKeypadIntenclr FunctionAddress = 0x03
|
||||||
|
FunctionKeypadCount FunctionAddress = 0x04
|
||||||
|
FunctionKeypadFifo FunctionAddress = 0x10
|
||||||
|
)
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Package seesaw provides a driver implementation to communicate with Adafruit's seesaw chip.
|
||||||
|
// There are many Adafruit boards that use a seesaw. Moisture sensors, LED keyboards, ...
|
||||||
|
package seesaw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultAddress = 0x49
|
||||||
|
|
||||||
|
// empirically determined standardDelay, the one from the official library seems to be too short (250us)
|
||||||
|
const defaultDelay = 100 * time.Millisecond
|
||||||
|
|
||||||
|
const (
|
||||||
|
seesawHwIdCodeSAMD09 = 0x55 // HW ID code for SAMD09
|
||||||
|
seesawHwIdCodeTINY8x7 = 0x87 // HW ID code for ATtiny817
|
||||||
|
)
|
||||||
|
|
||||||
|
type Seesaw interface {
|
||||||
|
|
||||||
|
// Read reads a number of bytes from the device after sending the read command and waiting 'delay'. The delays depend
|
||||||
|
// on the module and function and are documented in the seesaw datasheet
|
||||||
|
Read(module ModuleBaseAddress, function FunctionAddress, buf []byte, delay time.Duration) error
|
||||||
|
|
||||||
|
// Write writes an entire array into a given module and function
|
||||||
|
Write(module ModuleBaseAddress, function FunctionAddress, buf []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint16
|
||||||
|
standardDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(bus drivers.I2C) *Device {
|
||||||
|
return &Device{
|
||||||
|
bus: bus,
|
||||||
|
Address: DefaultAddress,
|
||||||
|
standardDelay: defaultDelay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoftReset triggers a soft-reset of seesaw and waits for it to be ready
|
||||||
|
func (d *Device) SoftReset() error {
|
||||||
|
err := d.WriteRegister(ModuleStatusBase, FunctionStatusSwrst, 0xFF)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed sending soft-reset command: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.waitForReset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) waitForReset() error {
|
||||||
|
|
||||||
|
//give the device a little bit of time to reset
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
tries := 0
|
||||||
|
for ; tries < 20; tries++ {
|
||||||
|
_, err := d.readHardwareID()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return errors.New("failed to wait for device to start: " + lastErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) readHardwareID() (byte, error) {
|
||||||
|
hwid, err := d.ReadRegister(ModuleStatusBase, FunctionStatusHwId)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hwid == seesawHwIdCodeSAMD09 || hwid == seesawHwIdCodeTINY8x7 {
|
||||||
|
return hwid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("unknown hardware ID: " + strconv.FormatUint(uint64(hwid), 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteRegister writes a single seesaw register
|
||||||
|
func (d *Device) WriteRegister(module ModuleBaseAddress, function FunctionAddress, value byte) error {
|
||||||
|
var buf [3]byte
|
||||||
|
buf[0] = byte(module)
|
||||||
|
buf[1] = byte(function)
|
||||||
|
buf[2] = value
|
||||||
|
return d.bus.Tx(d.Address, buf[:], nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadRegister reads a single register from seesaw
|
||||||
|
func (d *Device) ReadRegister(module ModuleBaseAddress, function FunctionAddress) (byte, error) {
|
||||||
|
var buf [1]byte
|
||||||
|
err := d.Read(module, function, buf[:], d.standardDelay)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return buf[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a number of bytes from the device after sending the read command and waiting 'standardDelay'. The delays depend
|
||||||
|
// on the module and function and are documented in the seesaw datasheet
|
||||||
|
func (d *Device) Read(module ModuleBaseAddress, function FunctionAddress, buf []byte, delay time.Duration) error {
|
||||||
|
var cmd [2]byte
|
||||||
|
cmd[0] = byte(module)
|
||||||
|
cmd[1] = byte(function)
|
||||||
|
|
||||||
|
err := d.bus.Tx(d.Address, cmd[:], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is needed for the client seesaw device to flush its RX buffer and process the command.
|
||||||
|
//See seesaw datasheet for timings for specific modules.
|
||||||
|
time.Sleep(delay)
|
||||||
|
|
||||||
|
return d.bus.Tx(d.Address, nil, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes data into a given module and function
|
||||||
|
func (d *Device) Write(module ModuleBaseAddress, function FunctionAddress, buf []byte) error {
|
||||||
|
cmd := []byte{byte(module), byte(function)}
|
||||||
|
data := append(cmd, buf...)
|
||||||
|
return d.bus.Tx(d.Address, data, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package seesaw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDevice_SoftReset_success(t *testing.T) {
|
||||||
|
|
||||||
|
mocked := newMockDev(t, 0x49,
|
||||||
|
when([]byte{0x00, 0x7F, 0xFF}, nil, nil),
|
||||||
|
when([]byte{0x00, 0x01}, nil, nil),
|
||||||
|
when(nil, []byte{0x55}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
sut := New(mocked)
|
||||||
|
|
||||||
|
err := sut.SoftReset()
|
||||||
|
assertEquals(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_WriteRegister(t *testing.T) {
|
||||||
|
|
||||||
|
write := byte(0x1F)
|
||||||
|
mocked := newMockDev(t, 0x49,
|
||||||
|
when([]byte{0x01, 0x04, write}, nil, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
sut := New(mocked)
|
||||||
|
|
||||||
|
err := sut.WriteRegister(ModuleGpioBase, FunctionGpioBulk, write)
|
||||||
|
assertEquals(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_ReadRegister(t *testing.T) {
|
||||||
|
|
||||||
|
read := byte(0x23)
|
||||||
|
mocked := newMockDev(t, 0x49,
|
||||||
|
when([]byte{0x0F, 0x10}, nil, nil),
|
||||||
|
when(nil, []byte{read}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
sut := New(mocked)
|
||||||
|
|
||||||
|
r, err := sut.ReadRegister(ModuleTouchBase, FunctionTouchChannelOffset)
|
||||||
|
assertEquals(t, err, nil)
|
||||||
|
assertEquals(t, r, read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_Read(t *testing.T) {
|
||||||
|
|
||||||
|
expectedRead := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||||
|
mocked := newMockDev(t, 0x49,
|
||||||
|
when([]byte{0x0F, 0x10}, nil, nil),
|
||||||
|
when(nil, expectedRead, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
sut := New(mocked)
|
||||||
|
|
||||||
|
var buf [5]byte
|
||||||
|
err := sut.Read(ModuleTouchBase, FunctionTouchChannelOffset, buf[:], time.Nanosecond)
|
||||||
|
assertEquals(t, err, nil)
|
||||||
|
assertEquals(t, buf[:], expectedRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevice_Write(t *testing.T) {
|
||||||
|
|
||||||
|
expectedWrite := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||||
|
mocked := newMockDev(t, 0x49,
|
||||||
|
when(append([]byte{0x0E, 0x04}, expectedWrite...), nil, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
sut := New(mocked)
|
||||||
|
|
||||||
|
err := sut.Write(ModuleNeoPixelBase, FunctionNeopixelBuf, expectedWrite)
|
||||||
|
assertEquals(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEquals[e any](t *testing.T, actual, expected e) {
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("actual %+v != %+v not equals expected", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user