Files

431 lines
9.2 KiB
Go

// 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) Version() ([3]byte, error) {
if err := p.handleHDR(VERSION_HDR); err != nil {
return [3]byte{}, err
}
var buf [4]byte
if err := p.spiSendRecvBuf(nil, buf[:]); err != nil {
return [3]byte{}, err
}
return [3]byte{
byte(buf[1] >> 4),
byte(buf[1] & 0xF),
byte(buf[0]),
}, p.dataSync()
}
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
}
const wdToggleTime = 100 * time.Millisecond
func (p *P1AM) ConfigureWatchdog(interval time.Duration, reset bool) error {
ms := interval / time.Millisecond
toggleMs := wdToggleTime / time.Millisecond
resetB := byte(0)
if reset {
resetB = 1
}
buf := [6]byte{
CONFIGWD_HDR,
byte(ms),
byte(ms >> 8),
byte(toggleMs),
byte(toggleMs >> 8),
resetB,
}
if err := p.spiSendRecvBuf(buf[:], nil); err != nil {
return err
}
return p.dataSync()
}
func (p *P1AM) sendWatchdog(hdr byte) error {
if _, err := p.spiSendRecvByte(hdr); err != nil {
return err
}
if err := p.waitAck(200 * time.Millisecond); err != nil {
return err
}
if _, err := p.spiSendRecvByte(DUMMY); err != nil {
return err
}
return p.dataSync()
}
func (p *P1AM) StartWatchdog() error {
return p.sendWatchdog(STARTWD_HDR)
}
func (p *P1AM) StopWatchdog() error {
return p.sendWatchdog(STOPWD_HDR)
}
func (p *P1AM) PetWatchdog() error {
return p.sendWatchdog(PETWD_HDR)
}
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
}
func (s *Slot) ReadDiscrete() (uint32, error) {
if s == nil {
return 0, errors.New("invalid slot")
}
bytes := s.Props.DI
out := [2]byte{
READ_DISCRETE_HDR,
s.slot,
}
if err := s.p.spiSendRecvBuf(out[:], nil); err != nil {
return 0, err
}
if err := s.p.waitAck(200 * time.Millisecond); err != nil {
return 0, err
}
var data [4]byte
if err := s.p.spiSendRecvBuf(nil, data[:bytes]); err != nil {
return 0, err
}
err := s.p.dataSync()
return binary.LittleEndian.Uint32(data[:]), err
}
func (s *Slot) WriteDiscrete(value uint32) error {
return s.writeDiscrete(0, value)
}
func (s *Slot) writeDiscrete(channel byte, value uint32) error {
if s == nil {
return errors.New("invalid slot")
}
bytes := s.Props.DO
buf := [7]byte{
WRITE_DISCRETE_HDR,
s.slot,
channel,
}
binary.LittleEndian.PutUint32(buf[3:], value)
out := buf[:3+bytes]
if channel != 0 {
out = buf[:4]
out[3] &= 1
}
if err := s.p.spiSendRecvBuf(out, nil); err != nil {
return err
}
return s.p.dataSync()
}
type Channel struct {
s *Slot
channel int
}
func (s *Slot) Channel(channel int) Channel {
return Channel{
s: s,
channel: channel,
}
}
func (c Channel) ReadDiscrete() (bool, error) {
if c.channel < 1 || c.channel > int(c.s.Props.DI)*8 {
return false, errors.New("invalid channel")
}
data, err := c.s.ReadDiscrete()
return (data>>(c.channel-1))&1 == 1, err
}
func (c Channel) WriteDiscrete(value bool) error {
if c.channel < 1 || c.channel > int(c.s.Props.DO)*8 {
return errors.New("invalid channel")
}
data := uint32(0)
if value {
data = 1
}
return c.s.writeDiscrete(byte(c.channel), data)
}
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)
}