-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspi.go
149 lines (129 loc) · 3.96 KB
/
spi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//go:build rp2040
package piolib
import (
"errors"
"machine"
pio "github.com/tinygo-org/pio/rp2-pio"
)
type SPI struct {
sm pio.StateMachine
progOffset uint8
mode uint8
}
func NewSPI(sm pio.StateMachine, spicfg machine.SPIConfig) (*SPI, error) {
sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed.
const nbits = 8
// https://github.com/raspberrypi/pico-examples/blob/eca13acf57916a0bd5961028314006983894fc84/pio/spi/spi.pio#L46
if !sm.IsValid() {
return nil, errors.New("invalid state machine")
}
whole, frac, err := pio.ClkDivFromFrequency(spicfg.Frequency, machine.CPUFrequency())
if err != nil {
return nil, err
}
Pio := sm.PIO()
var instructions []uint16
var origin int8
var cfger func(uint8) pio.StateMachineConfig
switch spicfg.Mode {
case 0b00:
instructions = spi_cpha0Instructions
origin = spi_cpha0Origin
cfger = spi_cpha0ProgramDefaultConfig
case 0b01:
// The pin muxes can be configured to invert the output (among other things
// and this is a cheesy way to get CPOL=1
// rp.IO_BANK0.GPIO0_CTRL.ReplaceBits(value, ) TODO: https://github.com/raspberrypi/pico-sdk/blob/6a7db34ff63345a7badec79ebea3aaef1712f374/src/rp2_common/hardware_gpio/gpio.c#L80
// SPI is synchronous, so bypass input synchroniser to reduce input delay.
instructions = spi_cpha1Instructions
origin = spi_cpha1Origin
cfger = spi_cpha1ProgramDefaultConfig
case 0b10, 0b11:
return nil, errors.New("unsupported mode")
default:
panic("invalid mode")
}
offset, err := Pio.AddProgram(instructions, origin)
if err != nil {
return nil, err
}
cfg := cfger(offset)
cfg.SetOutPins(spicfg.SDO, 1)
cfg.SetInPins(spicfg.SDI)
cfg.SetSidesetPins(spicfg.SCK)
cfg.SetOutShift(false, true, uint16(nbits))
cfg.SetInShift(false, true, uint16(nbits))
cfg.SetClkDivIntFrac(whole, frac)
// MOSI, SCK output are low, MISO is input.
outMask := uint32((1 << spicfg.SCK) | (1 << spicfg.SDO))
inMask := uint32(1 << spicfg.SDI)
sm.SetPinsMasked(0, outMask)
sm.SetPindirsMasked(outMask, outMask|inMask)
pincfg := machine.PinConfig{Mode: Pio.PinMode()}
spicfg.SCK.Configure(pincfg)
spicfg.SDO.Configure(pincfg)
spicfg.SDI.Configure(pincfg)
Pio.SetInputSyncBypassMasked(inMask, inMask)
sm.Init(offset, cfg)
sm.SetEnabled(true)
spi := &SPI{sm: sm, progOffset: offset, mode: spicfg.Mode}
return spi, nil
}
func (spi *SPI) Tx(w, r []byte) error {
rxRemain, txRemain := len(r), len(w)
if rxRemain != txRemain {
return errors.New("expect lengths to be equal")
}
retries := int8(32)
for rxRemain != 0 || txRemain != 0 {
stall := true
if txRemain != 0 && !spi.sm.IsTxFIFOFull() {
spi.sm.TxPut(uint32(w[len(w)-txRemain]))
txRemain--
stall = false
}
if txRemain != 0 && !spi.sm.IsRxFIFOEmpty() {
r[len(r)-rxRemain] = uint8(spi.sm.RxGet())
rxRemain--
stall = false
}
retries--
if retries <= 0 {
return errors.New("pioSPI timeout")
} else if stall {
// We stalled on this iteration, yield process.
gosched()
}
}
return nil
}
func (spi *SPI) Transfer(c byte) (rx byte, _ error) {
waitTx := true
waitRx := true
retries := int8(16)
for waitTx || waitRx {
if waitTx && !spi.sm.IsTxFIFOFull() {
spi.sm.TxPut(uint32(c))
waitTx = false
}
if waitRx && !spi.sm.IsRxFIFOEmpty() {
rx = byte(spi.sm.RxGet())
waitRx = false
}
retries--
if retries <= 0 {
return 0, errors.New("pioSPI timeout")
}
}
return rx, nil
}
// SPI represents a SPI bus. It is implemented by the machine.SPI type.
type _SPI interface {
// Tx transmits the given buffer w and receives at the same time the buffer r.
// The two buffers must be the same length. The only exception is when w or r are nil,
// in which case Tx only transmits (without receiving) or only receives (while sending 0 bytes).
Tx(w, r []byte) error
// Transfer writes a single byte out on the SPI bus and receives a byte at the same time.
// If you want to transfer multiple bytes, it is more efficient to use Tx instead.
Transfer(b byte) (byte, error)
}