mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-05 07:27:49 +00:00
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:
@@ -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
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user