tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/p1am/p1am.go (about)

     1  // Driver for the P1AM-100 base controller.
     2  //
     3  // This is an embedded device on the P1AM-100 board.
     4  // Based on v1.0.1 of the Arduino library: https://github.com/facts-engineering/P1AM/tree/1.0.1
     5  
     6  package p1am
     7  
     8  import (
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  	"machine"
    13  	"time"
    14  )
    15  
    16  type P1AM struct {
    17  	bus                                        machine.SPI
    18  	slaveSelectPin, slaveAckPin, baseEnablePin machine.Pin
    19  
    20  	// SkipAutoConfig will skip loading a default configuration into each module.
    21  	SkipAutoConfig bool
    22  
    23  	Slots int
    24  	// Access slots via Slot()
    25  	slots []Slot
    26  }
    27  
    28  var Controller = P1AM{
    29  	bus:            machine.SPI0,
    30  	slaveSelectPin: machine.BASE_SLAVE_SELECT_PIN,
    31  	slaveAckPin:    machine.BASE_SLAVE_ACK_PIN,
    32  	baseEnablePin:  machine.BASE_ENABLE_PIN,
    33  }
    34  
    35  type baseSlotConstants struct {
    36  	DI, DO, AI, AO, Status, Config, DataSize byte
    37  }
    38  
    39  func (p *P1AM) Initialize() error {
    40  	p.slaveSelectPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    41  	p.slaveAckPin.Configure(machine.PinConfig{Mode: machine.PinInput})
    42  	p.baseEnablePin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    43  
    44  	if err := p.bus.Configure(machine.SPIConfig{
    45  		Frequency: 1000000,
    46  		Mode:      2,
    47  		LSBFirst:  false,
    48  	}); err != nil {
    49  		return err
    50  	}
    51  
    52  	p.SetEnabled(true)
    53  	time.Sleep(100 * time.Millisecond)
    54  
    55  	if err := p.waitAck(5 * time.Second); err != nil {
    56  		return errors.New("no base controller activity; check external supply connection")
    57  	}
    58  
    59  	for i := 0; i < 5; i++ {
    60  		if err := p.handleHDR(MOD_HDR); err == nil {
    61  			time.Sleep(5 * time.Millisecond)
    62  			slots, err := p.spiSendRecvByte(0xFF)
    63  			if err == nil && slots > 0 && slots <= 15 {
    64  				p.Slots = int(slots)
    65  				break
    66  			}
    67  		}
    68  		if i > 2 {
    69  			// Try restarting the base controller
    70  			p.SetEnabled(false)
    71  			time.Sleep(10 * time.Millisecond)
    72  			p.SetEnabled(true)
    73  			time.Sleep(10 * time.Millisecond)
    74  		}
    75  	}
    76  	if p.Slots <= 0 || p.Slots > 15 {
    77  		return errors.New("zero modules in the base")
    78  	}
    79  
    80  	moduleIDs := make([]uint32, p.Slots)
    81  
    82  	p.waitAck(200 * time.Millisecond)
    83  	if err := binary.Read(p, binary.LittleEndian, &moduleIDs); err != nil {
    84  		return err
    85  	}
    86  
    87  	baseConstants := make([]baseSlotConstants, p.Slots)
    88  	p.slots = make([]Slot, p.Slots)
    89  
    90  	for i := 1; i <= p.Slots; i++ {
    91  		slot := p.Slot(i)
    92  		slot.p = p
    93  		slot.slot = byte(i)
    94  		slot.ID = moduleIDs[i-1]
    95  		// What if 0xFFFFFFFF isn't at position -2?
    96  		slot.Props = &modules[len(modules)-2]
    97  		for j := 0; j < len(modules); j++ {
    98  			if modules[j].ModuleID == slot.ID {
    99  				slot.Props = &modules[j]
   100  			}
   101  			bc := &baseConstants[i-1]
   102  			bc.DI = slot.Props.DI
   103  			bc.DO = slot.Props.DO
   104  			bc.AI = slot.Props.AI
   105  			bc.AO = slot.Props.AO
   106  			bc.Status = slot.Props.Status
   107  			bc.Config = slot.Props.Config
   108  			bc.DataSize = slot.Props.DataSize
   109  		}
   110  	}
   111  
   112  	p.waitAck(200 * time.Millisecond)
   113  	if err := binary.Write(p, binary.LittleEndian, &baseConstants); err != nil {
   114  		return err
   115  	}
   116  
   117  	if !p.SkipAutoConfig {
   118  		for i := 1; i <= p.Slots; i++ {
   119  			s := p.Slot(i)
   120  			if s.Props.Config > 0 {
   121  				cfg := defaultConfig[s.ID]
   122  				if cfg != nil {
   123  					s.Configure(cfg)
   124  				}
   125  			}
   126  		}
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  func (p *P1AM) Version() ([3]byte, error) {
   133  	if err := p.handleHDR(VERSION_HDR); err != nil {
   134  		return [3]byte{}, err
   135  	}
   136  	var buf [4]byte
   137  	if err := p.spiSendRecvBuf(nil, buf[:]); err != nil {
   138  		return [3]byte{}, err
   139  	}
   140  	return [3]byte{
   141  		byte(buf[1] >> 4),
   142  		byte(buf[1] & 0xF),
   143  		byte(buf[0]),
   144  	}, p.dataSync()
   145  }
   146  
   147  func (p *P1AM) Active() (bool, error) {
   148  	if _, err := p.spiSendRecvByte(ACTIVE_HDR); err != nil {
   149  		return false, err
   150  	}
   151  	if err := p.waitAck(200 * time.Millisecond); err != nil {
   152  		return false, err
   153  	}
   154  	buf, err := p.spiSendRecvByte(DUMMY)
   155  	defer p.dataSync()
   156  	return buf != 0, err
   157  }
   158  
   159  const wdToggleTime = 100 * time.Millisecond
   160  
   161  func (p *P1AM) ConfigureWatchdog(interval time.Duration, reset bool) error {
   162  	ms := interval / time.Millisecond
   163  	toggleMs := wdToggleTime / time.Millisecond
   164  	resetB := byte(0)
   165  	if reset {
   166  		resetB = 1
   167  	}
   168  	buf := [6]byte{
   169  		CONFIGWD_HDR,
   170  		byte(ms),
   171  		byte(ms >> 8),
   172  		byte(toggleMs),
   173  		byte(toggleMs >> 8),
   174  		resetB,
   175  	}
   176  	if err := p.spiSendRecvBuf(buf[:], nil); err != nil {
   177  		return err
   178  	}
   179  	return p.dataSync()
   180  }
   181  
   182  func (p *P1AM) sendWatchdog(hdr byte) error {
   183  	if _, err := p.spiSendRecvByte(hdr); err != nil {
   184  		return err
   185  	}
   186  	if err := p.waitAck(200 * time.Millisecond); err != nil {
   187  		return err
   188  	}
   189  	if _, err := p.spiSendRecvByte(DUMMY); err != nil {
   190  		return err
   191  	}
   192  	return p.dataSync()
   193  }
   194  
   195  func (p *P1AM) StartWatchdog() error {
   196  	return p.sendWatchdog(STARTWD_HDR)
   197  }
   198  
   199  func (p *P1AM) StopWatchdog() error {
   200  	return p.sendWatchdog(STOPWD_HDR)
   201  }
   202  
   203  func (p *P1AM) PetWatchdog() error {
   204  	return p.sendWatchdog(PETWD_HDR)
   205  }
   206  
   207  func (p *P1AM) Slot(i int) *Slot {
   208  	if i < 1 || i > p.Slots {
   209  		return nil
   210  	}
   211  	return &p.slots[i-1]
   212  }
   213  
   214  type Slot struct {
   215  	p    *P1AM
   216  	slot byte
   217  	ID   uint32
   218  	// TODO: Embed this?
   219  	Props *ModuleProps
   220  }
   221  
   222  func (s *Slot) Configure(data []byte) error {
   223  	if s == nil {
   224  		return errors.New("invalid slot")
   225  	}
   226  	if len(data) != int(s.Props.Config) {
   227  		return fmt.Errorf("expected %d config bytes, got %d", s.Props.Config, len(data))
   228  	}
   229  
   230  	if len(data) == 0 {
   231  		return errors.New("no config bytes")
   232  	}
   233  
   234  	out := make([]byte, len(data)+2)
   235  	out[0] = CFG_HDR
   236  	out[1] = s.slot
   237  	copy(out[2:], data)
   238  
   239  	if err := s.p.spiSendRecvBuf(out, nil); err != nil {
   240  		return err
   241  	}
   242  	time.Sleep(100 * time.Millisecond)
   243  	s.p.dataSync()
   244  	s.p.dataSync()
   245  	return nil
   246  }
   247  
   248  func (s *Slot) ReadDiscrete() (uint32, error) {
   249  	if s == nil {
   250  		return 0, errors.New("invalid slot")
   251  	}
   252  	bytes := s.Props.DI
   253  	out := [2]byte{
   254  		READ_DISCRETE_HDR,
   255  		s.slot,
   256  	}
   257  	if err := s.p.spiSendRecvBuf(out[:], nil); err != nil {
   258  		return 0, err
   259  	}
   260  	if err := s.p.waitAck(200 * time.Millisecond); err != nil {
   261  		return 0, err
   262  	}
   263  	var data [4]byte
   264  	if err := s.p.spiSendRecvBuf(nil, data[:bytes]); err != nil {
   265  		return 0, err
   266  	}
   267  	err := s.p.dataSync()
   268  	return binary.LittleEndian.Uint32(data[:]), err
   269  }
   270  
   271  func (s *Slot) WriteDiscrete(value uint32) error {
   272  	return s.writeDiscrete(0, value)
   273  }
   274  
   275  func (s *Slot) writeDiscrete(channel byte, value uint32) error {
   276  	if s == nil {
   277  		return errors.New("invalid slot")
   278  	}
   279  	bytes := s.Props.DO
   280  	buf := [7]byte{
   281  		WRITE_DISCRETE_HDR,
   282  		s.slot,
   283  		channel,
   284  	}
   285  	binary.LittleEndian.PutUint32(buf[3:], value)
   286  	out := buf[:3+bytes]
   287  	if channel != 0 {
   288  		out = buf[:4]
   289  		out[3] &= 1
   290  	}
   291  	if err := s.p.spiSendRecvBuf(out, nil); err != nil {
   292  		return err
   293  	}
   294  	return s.p.dataSync()
   295  }
   296  
   297  type Channel struct {
   298  	s       *Slot
   299  	channel int
   300  }
   301  
   302  func (s *Slot) Channel(channel int) Channel {
   303  	return Channel{
   304  		s:       s,
   305  		channel: channel,
   306  	}
   307  }
   308  
   309  func (c Channel) ReadDiscrete() (bool, error) {
   310  	if c.channel < 1 || c.channel > int(c.s.Props.DI)*8 {
   311  		return false, errors.New("invalid channel")
   312  	}
   313  	data, err := c.s.ReadDiscrete()
   314  	return (data>>(c.channel-1))&1 == 1, err
   315  }
   316  
   317  func (c Channel) WriteDiscrete(value bool) error {
   318  	if c.channel < 1 || c.channel > int(c.s.Props.DO)*8 {
   319  		return errors.New("invalid channel")
   320  	}
   321  	data := uint32(0)
   322  	if value {
   323  		data = 1
   324  	}
   325  	return c.s.writeDiscrete(byte(c.channel), data)
   326  }
   327  
   328  const ackTimeout = 200 * time.Millisecond
   329  
   330  func awaitPin(pin machine.Pin, state bool, timeout time.Duration) bool {
   331  	start := time.Now()
   332  	for pin.Get() != state {
   333  		time.Sleep(100 * time.Microsecond)
   334  		if time.Since(start) > timeout {
   335  			return false
   336  		}
   337  	}
   338  	return true
   339  	// TODO: Use channels when https://github.com/tinygo-org/tinygo/pull/1402 is merged.
   340  	// edge := machine.PinRising
   341  	// if state {
   342  	// 	edge = machine.PinFalling
   343  	// }
   344  	// ch := make(chan struct{}, 1)
   345  	// defer close(ch)
   346  	// pin.SetInterrupt(edge, func(machine.Pin) {
   347  	// 	ch <- struct{}{}
   348  	// })
   349  	// defer pin.SetInterrupt(0, nil)
   350  	// select {
   351  	// case <-ch:
   352  	// 	return true
   353  	// case <-time.After(timeout):
   354  	// 	return false
   355  	// }
   356  }
   357  
   358  var dataSyncErr = errors.New("base sync timeout")
   359  
   360  func (p *P1AM) dataSync() error {
   361  	if !awaitPin(p.slaveAckPin, true, ackTimeout) {
   362  		return dataSyncErr
   363  	}
   364  	time.Sleep(time.Microsecond)
   365  	if !awaitPin(p.slaveAckPin, false, ackTimeout) {
   366  		return dataSyncErr
   367  	}
   368  	time.Sleep(time.Microsecond)
   369  	if !awaitPin(p.slaveAckPin, true, ackTimeout) {
   370  		return dataSyncErr
   371  	}
   372  	time.Sleep(time.Microsecond)
   373  	return nil
   374  }
   375  
   376  func (p *P1AM) handleHDR(HDR byte) error {
   377  	for !p.slaveAckPin.Get() {
   378  	}
   379  	if _, err := p.spiSendRecvByte(HDR); err != nil {
   380  		return err
   381  	}
   382  	return p.spiTimeout(MAX_TIMEOUT*time.Millisecond, HDR, 2*time.Second)
   383  }
   384  
   385  func (p *P1AM) Read(data []byte) (int, error) {
   386  	return len(data), p.spiSendRecvBuf(nil, data)
   387  }
   388  
   389  func (p *P1AM) Write(data []byte) (int, error) {
   390  	return len(data), p.spiSendRecvBuf(data, nil)
   391  }
   392  
   393  func (p *P1AM) spiSendRecvBuf(w, r []byte) error {
   394  	p.slaveSelectPin.Low()
   395  	defer p.slaveSelectPin.High()
   396  	return p.bus.Tx(w, r)
   397  }
   398  
   399  func (p *P1AM) spiSendRecvByte(data byte) (byte, error) {
   400  	p.slaveSelectPin.Low()
   401  	defer p.slaveSelectPin.High()
   402  	return p.bus.Transfer(data)
   403  }
   404  
   405  func (p *P1AM) waitAck(timeout time.Duration) error {
   406  	return p.spiTimeout(timeout, 0, 0)
   407  }
   408  
   409  var timeoutErr = errors.New("timeout")
   410  
   411  func (p *P1AM) spiTimeout(timeout time.Duration, resendMsg byte, retryPeriod time.Duration) error {
   412  	end := time.Now().Add(timeout)
   413  	retry := time.Now().Add(retryPeriod)
   414  	for time.Now().Before(end) {
   415  		if p.slaveAckPin.Get() {
   416  			time.Sleep(50 * time.Microsecond)
   417  			return nil
   418  		}
   419  		if retryPeriod > 0 && time.Now().After(retry) {
   420  			p.spiSendRecvByte(resendMsg)
   421  			retry = retry.Add(retryPeriod)
   422  		}
   423  	}
   424  	return timeoutErr
   425  }
   426  
   427  func (p *P1AM) SetEnabled(enabled bool) {
   428  	p.baseEnablePin.Set(enabled)
   429  }