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 }