Skip to content

Commit 1f4e303

Browse files
committed
esp32c3/esp32s3: refactoring and corrections for SPI implementation
This refactors and corrects the SPI implentation for the ESP32C3 and ESP32S3 processors. There was a lot of duplicated code, as well as some errors such as incorrectly calculating speed on the esp32c3 implementation. This will also be helpful when adding additional processors that use very similar peripheral registers. Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 0abc514 commit 1f4e303

File tree

4 files changed

+112
-199
lines changed

4 files changed

+112
-199
lines changed

src/machine/machine_esp32c3_spi.go

Lines changed: 5 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ import (
1616
)
1717

1818
const (
19-
SPI_MODE0 = uint8(0)
20-
SPI_MODE1 = uint8(1)
21-
SPI_MODE2 = uint8(2)
22-
SPI_MODE3 = uint8(3)
23-
2419
FSPICLK_IN_IDX = uint32(63)
2520
FSPICLK_OUT_IDX = uint32(63)
2621
FSPIQ_IN_IDX = uint32(64)
@@ -56,64 +51,6 @@ var (
5651
SPI0 = SPI2
5752
)
5853

59-
// SPIConfig is used to store config info for SPI.
60-
type SPIConfig struct {
61-
Frequency uint32
62-
SCK Pin // Serial Clock
63-
SDO Pin // Serial Data Out (MOSI)
64-
SDI Pin // Serial Data In (MISO)
65-
CS Pin // Chip Select (optional)
66-
LSBFirst bool // MSB is default
67-
Mode uint8 // SPI_MODE0 is default
68-
}
69-
70-
// Compute the SPI bus frequency from the CPU frequency.
71-
func freqToClockDiv(hz uint32) uint32 {
72-
fcpu := CPUFrequency()
73-
if hz >= fcpu { // maximum frequency
74-
return 1 << 31
75-
}
76-
if hz < (fcpu / (16 * 64)) { // minimum frequency
77-
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
78-
}
79-
80-
// iterate looking for an exact match
81-
// or iterate all 16 prescaler options
82-
// looking for the smallest error
83-
var bestPre, bestN, bestErr uint32
84-
bestN = 1
85-
bestErr = 0xffffffff
86-
q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5))
87-
for p := uint32(0); p < 16; p++ {
88-
n := q/(p+1) - 1
89-
if n < 1 { // prescaler became too large, stop enum
90-
break
91-
}
92-
if n > 63 { // prescaler too small, skip to next
93-
continue
94-
}
95-
96-
freq := fcpu / ((p + 1) * (n + 1))
97-
if freq == hz { // exact match
98-
return p<<18 | n<<12 | (n/2)<<6 | n
99-
}
100-
101-
var err uint32
102-
if freq < hz {
103-
err = hz - freq
104-
} else {
105-
err = freq - hz
106-
}
107-
if err < bestErr {
108-
bestErr = err
109-
bestPre = p
110-
bestN = n
111-
}
112-
}
113-
114-
return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN
115-
}
116-
11754
// Configure and make the SPI peripheral ready to use.
11855
func (spi *SPI) Configure(config SPIConfig) error {
11956
// right now this is only setup to work for the esp32c3 spi2 bus
@@ -172,16 +109,16 @@ func (spi *SPI) Configure(config SPIConfig) error {
172109

173110
// set spi2 data mode
174111
switch config.Mode {
175-
case SPI_MODE0:
112+
case Mode0:
176113
spi.Bus.SetMISC_CK_IDLE_EDGE(0)
177114
spi.Bus.SetUSER_CK_OUT_EDGE(0)
178-
case SPI_MODE1:
115+
case Mode1:
179116
spi.Bus.SetMISC_CK_IDLE_EDGE(0)
180117
spi.Bus.SetUSER_CK_OUT_EDGE(1)
181-
case SPI_MODE2:
118+
case Mode2:
182119
spi.Bus.SetMISC_CK_IDLE_EDGE(1)
183120
spi.Bus.SetUSER_CK_OUT_EDGE(1)
184-
case SPI_MODE3:
121+
case Mode3:
185122
spi.Bus.SetMISC_CK_IDLE_EDGE(1)
186123
spi.Bus.SetUSER_CK_OUT_EDGE(0)
187124
default:
@@ -254,36 +191,7 @@ func (spi *SPI) Tx(w, r []byte) error {
254191

255192
// Fill tx buffer.
256193
transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0))))
257-
if len(w) >= 64 {
258-
// We can fill the entire 64-byte transfer buffer with data.
259-
// This loop is slightly faster than the loop below.
260-
for i := 0; i < 16; i++ {
261-
word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24
262-
transferWords[i].Set(word)
263-
}
264-
} else {
265-
// We can't fill the entire transfer buffer, so we need to be a bit
266-
// more careful.
267-
// Note that parts of the transfer buffer that aren't used still
268-
// need to be set to zero, otherwise we might be transferring
269-
// garbage from a previous transmission if w is smaller than r.
270-
for i := 0; i < 16; i++ {
271-
var word uint32
272-
if i*4+3 < len(w) {
273-
word |= uint32(w[i*4+3]) << 24
274-
}
275-
if i*4+2 < len(w) {
276-
word |= uint32(w[i*4+2]) << 16
277-
}
278-
if i*4+1 < len(w) {
279-
word |= uint32(w[i*4+1]) << 8
280-
}
281-
if i*4+0 < len(w) {
282-
word |= uint32(w[i*4+0]) << 0
283-
}
284-
transferWords[i].Set(word)
285-
}
286-
}
194+
spiTxFillBuffer(transferWords, w)
287195

288196
// Do the transfer.
289197
spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1)

src/machine/machine_esp32s3_spi.go

Lines changed: 5 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import (
1515
)
1616

1717
const (
18-
SPI_MODE0 = uint8(0)
19-
SPI_MODE1 = uint8(1)
20-
SPI_MODE2 = uint8(2)
21-
SPI_MODE3 = uint8(3)
22-
2318
// ESP32-S3 PLL clock frequency (same as ESP32-C3)
2419
pplClockFreq = 80e6
2520

@@ -64,16 +59,6 @@ var (
6459
SPI1 = &SPI{Bus: esp.SPI3, busID: 3} // Secondary SPI (HSPI)
6560
)
6661

67-
type SPIConfig struct {
68-
Frequency uint32
69-
SCK Pin // Serial Clock
70-
SDO Pin // Serial Data Out (MOSI)
71-
SDI Pin // Serial Data In (MISO)
72-
CS Pin // Chip Select (optional)
73-
LSBFirst bool // MSB is default
74-
Mode uint8 // SPI_MODE0 is default
75-
}
76-
7762
// Configure and make the SPI peripheral ready to use.
7863
// Implementation following ESP-IDF HAL with GPIO Matrix routing
7964
func (spi *SPI) Configure(config SPIConfig) error {
@@ -232,14 +217,14 @@ func (spi *SPI) Configure(config SPIConfig) error {
232217

233218
// Configure SPI mode (CPOL/CPHA) following ESP-IDF HAL
234219
switch config.Mode {
235-
case SPI_MODE0:
220+
case Mode0:
236221
// CPOL=0, CPHA=0 (default)
237-
case SPI_MODE1:
222+
case Mode1:
238223
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
239-
case SPI_MODE2:
224+
case Mode2:
240225
bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1
241226
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
242-
case SPI_MODE3:
227+
case Mode3:
243228
bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1
244229
}
245230

@@ -319,36 +304,7 @@ func (spi *SPI) Tx(w, r []byte) error {
319304

320305
// Fill tx buffer.
321306
transferWords := (*[16]volatile.Register32)(unsafe.Add(unsafe.Pointer(&bus.W0), 0))
322-
if len(w) >= 64 {
323-
// We can fill the entire 64-byte transfer buffer with data.
324-
// This loop is slightly faster than the loop below.
325-
for i := 0; i < 16; i++ {
326-
word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24
327-
transferWords[i].Set(word)
328-
}
329-
} else {
330-
// We can't fill the entire transfer buffer, so we need to be a bit
331-
// more careful.
332-
// Note that parts of the transfer buffer that aren't used still
333-
// need to be set to zero, otherwise we might be transferring
334-
// garbage from a previous transmission if w is smaller than r.
335-
for i := 0; i < 16; i++ {
336-
var word uint32
337-
if i*4+3 < len(w) {
338-
word |= uint32(w[i*4+3]) << 24
339-
}
340-
if i*4+2 < len(w) {
341-
word |= uint32(w[i*4+2]) << 16
342-
}
343-
if i*4+1 < len(w) {
344-
word |= uint32(w[i*4+1]) << 8
345-
}
346-
if i*4+0 < len(w) {
347-
word |= uint32(w[i*4+0]) << 0
348-
}
349-
transferWords[i].Set(word)
350-
}
351-
}
307+
spiTxFillBuffer(transferWords, w)
352308

353309
// Do the transfer.
354310
bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1)
@@ -388,58 +344,6 @@ func (spi *SPI) Tx(w, r []byte) error {
388344
return nil
389345
}
390346

391-
// Compute the SPI bus frequency from the APB clock frequency.
392-
// Note: APB clock is always 80MHz on ESP32-S3, independent of CPU frequency.
393-
// Ported from ESP32-C3 implementation for better accuracy.
394-
func freqToClockDiv(hz uint32) uint32 {
395-
// Use APB clock frequency (80MHz), not CPU frequency!
396-
// SPI peripheral is connected to APB bus which stays at 80MHz
397-
const apbFreq = pplClockFreq // 80MHz
398-
399-
if hz >= apbFreq { // maximum frequency
400-
return 1 << 31
401-
}
402-
if hz < (apbFreq / (16 * 64)) { // minimum frequency
403-
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
404-
}
405-
406-
// iterate looking for an exact match
407-
// or iterate all 16 prescaler options
408-
// looking for the smallest error
409-
var bestPre, bestN, bestErr uint32
410-
bestN = 1
411-
bestErr = 0xffffffff
412-
q := uint32(float32(apbFreq)/float32(hz) + float32(0.5))
413-
for p := uint32(0); p < 16; p++ {
414-
n := q/(p+1) - 1
415-
if n < 1 { // prescaler became too large, stop enum
416-
break
417-
}
418-
if n > 63 { // prescaler too small, skip to next
419-
continue
420-
}
421-
422-
freq := apbFreq / ((p + 1) * (n + 1))
423-
if freq == hz { // exact match
424-
return p<<18 | n<<12 | (n/2)<<6 | n
425-
}
426-
427-
var err uint32
428-
if freq < hz {
429-
err = hz - freq
430-
} else {
431-
err = freq - hz
432-
}
433-
if err < bestErr {
434-
bestErr = err
435-
bestPre = p
436-
bestN = n
437-
}
438-
}
439-
440-
return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN
441-
}
442-
443347
// isDefaultSPIPins checks if the given pins match the default SPI pin configuration
444348
// that supports IO MUX direct connection for better performance
445349
func isDefaultSPIPins(busID uint8, config SPIConfig) bool {

src/machine/machine_esp32xx_spi.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//go:build esp32s3 || esp32c3
2+
3+
package machine
4+
5+
import (
6+
"runtime/volatile"
7+
)
8+
9+
// SPIConfig is used to store config info for SPI.
10+
type SPIConfig struct {
11+
Frequency uint32
12+
SCK Pin // Serial Clock
13+
SDO Pin // Serial Data Out (MOSI)
14+
SDI Pin // Serial Data In (MISO)
15+
CS Pin // Chip Select (optional)
16+
LSBFirst bool // MSB is default
17+
Mode uint8 // Mode0 is default
18+
}
19+
20+
// freqToClockDiv computes the SPI bus clock divider register value.
21+
// SPI peripherals on ESP32-C3 and ESP32-S3 are clocked from the APB bus
22+
// (pplClockFreq, 80 MHz on both chips).
23+
func freqToClockDiv(hz uint32) uint32 {
24+
if hz >= pplClockFreq { // maximum frequency
25+
return 1 << 31
26+
}
27+
if hz < (pplClockFreq / (16 * 64)) { // minimum frequency
28+
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
29+
}
30+
31+
// Iterate all 16 prescaler options looking for an exact match
32+
// or the smallest error.
33+
var bestPre, bestN, bestErr uint32
34+
bestN = 1
35+
bestErr = 0xffffffff
36+
q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5))
37+
for p := uint32(0); p < 16; p++ {
38+
n := q/(p+1) - 1
39+
if n < 1 { // prescaler became too large, stop enum
40+
break
41+
}
42+
if n > 63 { // prescaler too small, skip to next
43+
continue
44+
}
45+
46+
freq := pplClockFreq / ((p + 1) * (n + 1))
47+
if freq == hz { // exact match
48+
return p<<18 | n<<12 | (n/2)<<6 | n
49+
}
50+
51+
var err uint32
52+
if freq < hz {
53+
err = hz - freq
54+
} else {
55+
err = freq - hz
56+
}
57+
if err < bestErr {
58+
bestErr = err
59+
bestPre = p
60+
bestN = n
61+
}
62+
}
63+
64+
return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN
65+
}
66+
67+
// spiTxFillBuffer writes data from w into the 16-word (64-byte) SPI
68+
// hardware transfer buffer. Unused words are zeroed so that no stale
69+
// data from a previous transfer is sent when w is shorter than 64 bytes.
70+
func spiTxFillBuffer(buf *[16]volatile.Register32, w []byte) {
71+
if len(w) >= 64 {
72+
// We can fill the entire 64-byte transfer buffer with data.
73+
// This loop is slightly faster than the loop below.
74+
for i := 0; i < 16; i++ {
75+
word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24
76+
buf[i].Set(word)
77+
}
78+
} else {
79+
// We can't fill the entire transfer buffer, so we need to be a bit
80+
// more careful.
81+
// Note that parts of the transfer buffer that aren't used still
82+
// need to be set to zero, otherwise we might be transferring
83+
// garbage from a previous transmission if w is smaller than r.
84+
for i := 0; i < 16; i++ {
85+
var word uint32
86+
if i*4+3 < len(w) {
87+
word |= uint32(w[i*4+3]) << 24
88+
}
89+
if i*4+2 < len(w) {
90+
word |= uint32(w[i*4+2]) << 16
91+
}
92+
if i*4+1 < len(w) {
93+
word |= uint32(w[i*4+1]) << 8
94+
}
95+
if i*4+0 < len(w) {
96+
word |= uint32(w[i*4+0]) << 0
97+
}
98+
buf[i].Set(word)
99+
}
100+
}
101+
}

src/machine/spi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !baremetal || atmega || attiny85 || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
1+
//go:build !baremetal || atmega || attiny85 || esp32 || esp32c3 || esp32s3 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
22

33
package machine
44

0 commit comments

Comments
 (0)