Skip to content

Commit 5b81b83

Browse files
aykevldeadprogram
authored andcommitted
esp32: add SPI support
1 parent a9a6d0e commit 5b81b83

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

src/machine/machine_esp32.go

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package machine
44

55
import (
66
"device/esp"
7+
"errors"
78
"runtime/volatile"
9+
"unsafe"
810
)
911

1012
const peripheralClock = 80000000 // 80MHz
@@ -15,6 +17,10 @@ func CPUFrequency() uint32 {
1517
return 160e6 // 160MHz
1618
}
1719

20+
var (
21+
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
22+
)
23+
1824
type PinMode uint8
1925

2026
const (
@@ -26,6 +32,24 @@ const (
2632

2733
// Configure this pin with the given configuration.
2834
func (p Pin) Configure(config PinConfig) {
35+
// Output function 256 is a special value reserved for use as a regular GPIO
36+
// pin. Peripherals (SPI etc) can set a custom output function by calling
37+
// lowercase configure() instead with a signal name.
38+
p.configure(config, 256)
39+
}
40+
41+
// configure is the same as Configure, but allows for setting a specific input
42+
// or output signal.
43+
// Signals are always routed through the GPIO matrix for simplicity. Output
44+
// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal
45+
// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG,
46+
// which sets the pin to use for a particular input signal.
47+
func (p Pin) configure(config PinConfig, signal uint32) {
48+
if p == NoPin {
49+
// This simplifies pin configuration in peripherals such as SPI.
50+
return
51+
}
52+
2953
var muxConfig uint32 // The mux configuration.
3054

3155
// Configure this pin as a GPIO pin.
@@ -56,16 +80,40 @@ func (p Pin) Configure(config PinConfig) {
5680
} else {
5781
esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32))
5882
}
83+
// Set the signal to read the output value from. It can be a peripheral
84+
// output signal, or the special value 256 which indicates regular GPIO
85+
// usage.
86+
p.outFunc().Set(signal)
5987
case PinInput, PinInputPullup, PinInputPulldown:
6088
// Clear the 'output enable' bit.
6189
if p < 32 {
6290
esp.GPIO.ENABLE_W1TC.Set(1 << p)
6391
} else {
6492
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
6593
}
94+
if signal != 256 {
95+
// Signal is a peripheral function (not a simple GPIO). Connect this
96+
// signal to the pin.
97+
// Note that outFunc and inFunc work in the opposite direction.
98+
// outFunc configures a pin to use a given output signal, while
99+
// inFunc specifies a pin to use to read the signal from.
100+
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
101+
}
66102
}
67103
}
68104

105+
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
106+
// output function selection.
107+
func (p Pin) outFunc() *volatile.Register32 {
108+
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG)) + uintptr(p)*4)))
109+
}
110+
111+
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
112+
// function selection.
113+
func inFunc(signal uint32) *volatile.Register32 {
114+
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4)))
115+
}
116+
69117
// Set the pin to high or low.
70118
// Warning: only use this on an output pin!
71119
func (p Pin) Set(value bool) {
@@ -232,3 +280,224 @@ func (uart UART) WriteByte(b byte) error {
232280
uart.Bus.TX_FIFO.Set(b)
233281
return nil
234282
}
283+
284+
// Serial Peripheral Interface on the ESP32.
285+
type SPI struct {
286+
Bus *esp.SPI_Type
287+
}
288+
289+
var (
290+
// SPI0 and SPI1 are reserved for use by the caching system etc.
291+
SPI2 = SPI{esp.SPI2}
292+
SPI3 = SPI{esp.SPI3}
293+
)
294+
295+
// SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least
296+
// SCK, SDO and SDI (possibly to NoPin if not in use). The default for LSBFirst
297+
// (false) and Mode (0) are good for most applications. The frequency defaults
298+
// to 1MHz if not set but can be configured up to 40MHz. Possible values are
299+
// 40MHz and integer divisions from 40MHz such as 20MHz, 13.3MHz, 10MHz, 8MHz,
300+
// etc.
301+
type SPIConfig struct {
302+
Frequency uint32
303+
SCK Pin
304+
SDO Pin
305+
SDI Pin
306+
LSBFirst bool
307+
Mode uint8
308+
}
309+
310+
// Configure and make the SPI peripheral ready to use.
311+
func (spi SPI) Configure(config SPIConfig) error {
312+
if config.Frequency == 0 {
313+
config.Frequency = 1e6 // default to 1MHz
314+
}
315+
316+
// Configure the SPI clock. This assumes a peripheral clock of 80MHz.
317+
var clockReg uint32
318+
if config.Frequency >= 40e6 {
319+
// Don't use a prescaler, but directly connect to the APB clock. This
320+
// results in a SPI clock frequency of 40MHz.
321+
clockReg |= esp.SPI_CLOCK_CLK_EQU_SYSCLK
322+
} else {
323+
// Use a prescaler for frequencies below 40MHz. They will get rounded
324+
// down to the next possible frequency (20MHz, 13.3MHz, 10MHz, 8MHz,
325+
// 6.7MHz, 5.7MHz, 5MHz, etc).
326+
// This code is much simpler than how ESP-IDF configures the frequency,
327+
// but should be just as accurate. The only exception is for frequencies
328+
// below 4883Hz, which will need special support.
329+
if config.Frequency < 4883 {
330+
// The current lower limit is 4883Hz.
331+
// The hardware supports lower frequencies by setting the h and n
332+
// variables, but that's not yet implemented.
333+
config.Frequency = 4883
334+
}
335+
// The prescaler value is 40e6 / config.Frequency, but rounded up so
336+
// that the actual frequency is never higher than the frequency
337+
// requested in config.Frequency.
338+
var (
339+
pre uint32 = (40e6 + config.Frequency - 1) / config.Frequency
340+
n uint32 = 2 // this value seems to equal the number of ticks per SPI clock tick
341+
h uint32 = 1 // must be half of n according to the formula in the reference manual
342+
l uint32 = n // must equal n according to the reference manual
343+
)
344+
clockReg |= (pre - 1) << esp.SPI_CLOCK_CLKDIV_PRE_Pos
345+
clockReg |= (n - 1) << esp.SPI_CLOCK_CLKCNT_N_Pos
346+
clockReg |= (h - 1) << esp.SPI_CLOCK_CLKCNT_H_Pos
347+
clockReg |= (l - 1) << esp.SPI_CLOCK_CLKCNT_L_Pos
348+
}
349+
spi.Bus.CLOCK.Set(clockReg)
350+
351+
// SPI_CTRL_REG controls bit order.
352+
var ctrlReg uint32
353+
if config.LSBFirst {
354+
ctrlReg |= esp.SPI_CTRL_WR_BIT_ORDER
355+
ctrlReg |= esp.SPI_CTRL_RD_BIT_ORDER
356+
}
357+
spi.Bus.CTRL.Set(ctrlReg)
358+
359+
// SPI_CTRL2_REG, SPI_USER_REG and SPI_PIN_REG control SPI clock polarity
360+
// (mode), among others.
361+
var ctrl2Reg, userReg, pinReg uint32
362+
// For mode configuration, see table 29 in the reference manual (page 128).
363+
var delayMode uint32
364+
switch config.Mode {
365+
case 0:
366+
delayMode = 2
367+
case 1:
368+
delayMode = 1
369+
userReg |= esp.SPI_USER_CK_OUT_EDGE
370+
case 2:
371+
delayMode = 1
372+
userReg |= esp.SPI_USER_CK_OUT_EDGE
373+
pinReg |= esp.SPI_PIN_CK_IDLE_EDGE
374+
case 3:
375+
delayMode = 2
376+
pinReg |= esp.SPI_PIN_CK_IDLE_EDGE
377+
}
378+
// Extra configuration necessary for correct data input at high frequencies.
379+
// This is only necessary when MISO goes through the GPIO matrix (which it
380+
// currently does).
381+
if config.Frequency >= 40e6 {
382+
// Delay mode must be set to 0 and SPI_USR_DUMMY_CYCLELEN should be set
383+
// to 0 (the default).
384+
userReg |= esp.SPI_USER_USR_DUMMY
385+
} else if config.Frequency >= 20e6 {
386+
// Nothing to do here, delay mode should be set to 0 according to the
387+
// datasheet.
388+
} else {
389+
// Follow the delay mode as given in table 29 on page 128 of the
390+
// reference manual.
391+
// Note that this is only specified for SPI frequency of 10MHz and
392+
// below (≤Fapb/8), so 13.3MHz appears to be left unspecified.
393+
ctrl2Reg |= delayMode << esp.SPI_CTRL2_MOSI_DELAY_MODE_Pos
394+
}
395+
// Enable full-duplex communication.
396+
userReg |= esp.SPI_USER_DOUTDIN
397+
userReg |= esp.SPI_USER_USR_MOSI
398+
// Write values to registers.
399+
spi.Bus.CTRL2.Set(ctrl2Reg)
400+
spi.Bus.USER.Set(userReg)
401+
spi.Bus.PIN.Set(pinReg)
402+
403+
// Configure pins.
404+
// TODO: use direct output if possible, if the configured pins match the
405+
// possible direct configurations (e.g. for SPI2, when SCK is pin 14 etc).
406+
if spi.Bus == esp.SPI2 {
407+
config.SCK.configure(PinConfig{Mode: PinOutput}, 8) // HSPICLK
408+
config.SDI.configure(PinConfig{Mode: PinInput}, 9) // HSPIQ
409+
config.SDO.configure(PinConfig{Mode: PinOutput}, 10) // HSPID
410+
} else if spi.Bus == esp.SPI3 {
411+
config.SCK.configure(PinConfig{Mode: PinOutput}, 63) // VSPICLK
412+
config.SDI.configure(PinConfig{Mode: PinInput}, 64) // VSPIQ
413+
config.SDO.configure(PinConfig{Mode: PinOutput}, 65) // VSPID
414+
} else {
415+
// Don't know how to configure this bus.
416+
return ErrInvalidSPIBus
417+
}
418+
419+
return nil
420+
}
421+
422+
// Transfer writes/reads a single byte using the SPI interface. If you need to
423+
// transfer larger amounts of data, Tx will be faster.
424+
func (spi SPI) Transfer(w byte) (byte, error) {
425+
spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos)
426+
spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos)
427+
428+
spi.Bus.W0.Set(uint32(w))
429+
430+
// Send/receive byte.
431+
spi.Bus.CMD.Set(esp.SPI_CMD_USR)
432+
for spi.Bus.CMD.Get() != 0 {
433+
}
434+
435+
// The received byte is stored in W0.
436+
return byte(spi.Bus.W0.Get()), nil
437+
}
438+
439+
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
440+
// interface, there must always be the same number of bytes written as bytes read.
441+
// This is accomplished by sending zero bits if r is bigger than w or discarding
442+
// the incoming data if w is bigger than r.
443+
//
444+
func (spi SPI) Tx(w, r []byte) error {
445+
toTransfer := len(w)
446+
if len(r) > toTransfer {
447+
toTransfer = len(r)
448+
}
449+
450+
for toTransfer != 0 {
451+
// Do only 64 bytes at a time.
452+
chunkSize := toTransfer
453+
if chunkSize > 64 {
454+
chunkSize = 64
455+
}
456+
457+
// Fill tx buffer.
458+
transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0))))
459+
var outBuf [16]uint32
460+
txSize := 64
461+
if txSize > len(w) {
462+
txSize = len(w)
463+
}
464+
for i := 0; i < txSize; i++ {
465+
outBuf[i/4] = outBuf[i/4] | uint32(w[i])<<((i%4)*8)
466+
}
467+
for i, word := range outBuf {
468+
transferWords[i].Set(word)
469+
}
470+
471+
// Do the transfer.
472+
spi.Bus.MISO_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos)
473+
spi.Bus.MOSI_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos)
474+
spi.Bus.CMD.Set(esp.SPI_CMD_USR)
475+
for spi.Bus.CMD.Get() != 0 {
476+
}
477+
478+
// Read rx buffer.
479+
rxSize := 64
480+
if rxSize > len(r) {
481+
rxSize = len(r)
482+
}
483+
for i := 0; i < rxSize; i++ {
484+
r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8))
485+
}
486+
487+
// Cut off some part of the output buffer so the next iteration we will
488+
// only send the remaining bytes.
489+
if len(w) < chunkSize {
490+
w = nil
491+
} else {
492+
w = w[chunkSize:]
493+
}
494+
if len(r) < chunkSize {
495+
r = nil
496+
} else {
497+
r = r[chunkSize:]
498+
}
499+
toTransfer -= chunkSize
500+
}
501+
502+
return nil
503+
}

0 commit comments

Comments
 (0)