p1am: initial driver for the base controller

All the driver can currently do is initialize the base controller and enumerate
the connected modules.
This commit is contained in:
Quentin Smith
2020-07-18 20:36:32 -04:00
committed by Ron Evans
parent 68da7f437b
commit 2cec480fb8
2 changed files with 287 additions and 1 deletions
+1 -1
View File
@@ -175,7 +175,7 @@ endif
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
+286
View File
@@ -0,0 +1,286 @@
// Driver for the P1AM-100 base controller.
//
// This is an embedded device on the P1AM-100 board.
// Based on v1.0.1 of the Arduino library: https://github.com/facts-engineering/P1AM/tree/1.0.1
package p1am
import (
"encoding/binary"
"errors"
"fmt"
"machine"
"time"
)
type P1AM struct {
bus machine.SPI
slaveSelectPin, slaveAckPin, baseEnablePin machine.Pin
// SkipAutoConfig will skip loading a default configuration into each module.
SkipAutoConfig bool
Slots int
// Access slots via Slot()
slots []Slot
}
var Controller = P1AM{
bus: machine.SPI0,
slaveSelectPin: machine.BASE_SLAVE_SELECT_PIN,
slaveAckPin: machine.BASE_SLAVE_ACK_PIN,
baseEnablePin: machine.BASE_ENABLE_PIN,
}
type baseSlotConstants struct {
DI, DO, AI, AO, Status, Config, DataSize byte
}
func (p *P1AM) Initialize() error {
p.slaveSelectPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
p.slaveAckPin.Configure(machine.PinConfig{Mode: machine.PinInput})
p.baseEnablePin.Configure(machine.PinConfig{Mode: machine.PinOutput})
if err := p.bus.Configure(machine.SPIConfig{
Frequency: 1000000,
Mode: 2,
LSBFirst: false,
}); err != nil {
return err
}
p.SetEnabled(true)
time.Sleep(100 * time.Millisecond)
if err := p.waitAck(5 * time.Second); err != nil {
return errors.New("no base controller activity; check external supply connection")
}
for i := 0; i < 5; i++ {
if err := p.handleHDR(MOD_HDR); err == nil {
time.Sleep(5 * time.Millisecond)
slots, err := p.spiSendRecvByte(0xFF)
if err == nil && slots > 0 && slots <= 15 {
p.Slots = int(slots)
break
}
}
if i > 2 {
// Try restarting the base controller
p.SetEnabled(false)
time.Sleep(10 * time.Millisecond)
p.SetEnabled(true)
time.Sleep(10 * time.Millisecond)
}
}
if p.Slots <= 0 || p.Slots > 15 {
return errors.New("zero modules in the base")
}
moduleIDs := make([]uint32, p.Slots)
p.waitAck(200 * time.Millisecond)
if err := binary.Read(p, binary.LittleEndian, &moduleIDs); err != nil {
return err
}
baseConstants := make([]baseSlotConstants, p.Slots)
p.slots = make([]Slot, p.Slots)
for i := 1; i <= p.Slots; i++ {
slot := p.Slot(i)
slot.p = p
slot.slot = byte(i)
slot.ID = moduleIDs[i-1]
// What if 0xFFFFFFFF isn't at position -2?
slot.Props = &modules[len(modules)-2]
for j := 0; j < len(modules); j++ {
if modules[j].ModuleID == slot.ID {
slot.Props = &modules[j]
}
bc := &baseConstants[i-1]
bc.DI = slot.Props.DI
bc.DO = slot.Props.DO
bc.AI = slot.Props.AI
bc.AO = slot.Props.AO
bc.Status = slot.Props.Status
bc.Config = slot.Props.Config
bc.DataSize = slot.Props.DataSize
}
}
p.waitAck(200 * time.Millisecond)
if err := binary.Write(p, binary.LittleEndian, &baseConstants); err != nil {
return err
}
if !p.SkipAutoConfig {
for i := 1; i <= p.Slots; i++ {
s := p.Slot(i)
if s.Props.Config > 0 {
cfg := defaultConfig[s.ID]
if cfg != nil {
s.Configure(cfg)
}
}
}
}
return nil
}
func (p *P1AM) Active() (bool, error) {
if _, err := p.spiSendRecvByte(ACTIVE_HDR); err != nil {
return false, err
}
if err := p.waitAck(200 * time.Millisecond); err != nil {
return false, err
}
buf, err := p.spiSendRecvByte(DUMMY)
defer p.dataSync()
return buf != 0, err
}
func (p *P1AM) Slot(i int) *Slot {
if i < 1 || i > p.Slots {
return nil
}
return &p.slots[i-1]
}
type Slot struct {
p *P1AM
slot byte
ID uint32
// TODO: Embed this?
Props *ModuleProps
}
func (s *Slot) Configure(data []byte) error {
if s == nil {
return errors.New("invalid slot")
}
if len(data) != int(s.Props.Config) {
return fmt.Errorf("expected %d config bytes, got %d", s.Props.Config, len(data))
}
if len(data) == 0 {
return errors.New("no config bytes")
}
out := make([]byte, len(data)+2)
out[0] = CFG_HDR
out[1] = s.slot
copy(out[2:], data)
if err := s.p.spiSendRecvBuf(out, nil); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
s.p.dataSync()
s.p.dataSync()
return nil
}
const ackTimeout = 200 * time.Millisecond
func awaitPin(pin machine.Pin, state bool, timeout time.Duration) bool {
start := time.Now()
for pin.Get() != state {
time.Sleep(100 * time.Microsecond)
if time.Since(start) > timeout {
return false
}
}
return true
// TODO: Use channels when https://github.com/tinygo-org/tinygo/pull/1402 is merged.
// edge := machine.PinRising
// if state {
// edge = machine.PinFalling
// }
// ch := make(chan struct{}, 1)
// defer close(ch)
// pin.SetInterrupt(edge, func(machine.Pin) {
// ch <- struct{}{}
// })
// defer pin.SetInterrupt(0, nil)
// select {
// case <-ch:
// return true
// case <-time.After(timeout):
// return false
// }
}
var dataSyncErr = errors.New("base sync timeout")
func (p *P1AM) dataSync() error {
if !awaitPin(p.slaveAckPin, true, ackTimeout) {
return dataSyncErr
}
time.Sleep(time.Microsecond)
if !awaitPin(p.slaveAckPin, false, ackTimeout) {
return dataSyncErr
}
time.Sleep(time.Microsecond)
if !awaitPin(p.slaveAckPin, true, ackTimeout) {
return dataSyncErr
}
time.Sleep(time.Microsecond)
return nil
}
func (p *P1AM) handleHDR(HDR byte) error {
for !p.slaveAckPin.Get() {
}
if _, err := p.spiSendRecvByte(HDR); err != nil {
return err
}
return p.spiTimeout(MAX_TIMEOUT*time.Millisecond, HDR, 2*time.Second)
}
func (p *P1AM) Read(data []byte) (int, error) {
return len(data), p.spiSendRecvBuf(nil, data)
}
func (p *P1AM) Write(data []byte) (int, error) {
return len(data), p.spiSendRecvBuf(data, nil)
}
func (p *P1AM) spiSendRecvBuf(w, r []byte) error {
p.slaveSelectPin.Low()
defer p.slaveSelectPin.High()
return p.bus.Tx(w, r)
}
func (p *P1AM) spiSendRecvByte(data byte) (byte, error) {
p.slaveSelectPin.Low()
defer p.slaveSelectPin.High()
return p.bus.Transfer(data)
}
func (p *P1AM) waitAck(timeout time.Duration) error {
return p.spiTimeout(timeout, 0, 0)
}
var timeoutErr = errors.New("timeout")
func (p *P1AM) spiTimeout(timeout time.Duration, resendMsg byte, retryPeriod time.Duration) error {
end := time.Now().Add(timeout)
retry := time.Now().Add(retryPeriod)
for time.Now().Before(end) {
if p.slaveAckPin.Get() {
time.Sleep(50 * time.Microsecond)
return nil
}
if retryPeriod > 0 && time.Now().After(retry) {
p.spiSendRecvByte(resendMsg)
retry = retry.Add(retryPeriod)
}
}
return timeoutErr
}
func (p *P1AM) SetEnabled(enabled bool) {
p.baseEnablePin.Set(enabled)
}