Files
tinygo/src/machine/machine_esp32xx_spi.go
T
deadprogram b14ee51ff6 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>
2026-04-05 14:15:40 +02:00

102 lines
2.7 KiB
Go

//go:build esp32s3 || esp32c3
package machine
import (
"runtime/volatile"
)
// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
Frequency uint32
SCK Pin // Serial Clock
SDO Pin // Serial Data Out (MOSI)
SDI Pin // Serial Data In (MISO)
CS Pin // Chip Select (optional)
LSBFirst bool // MSB is default
Mode uint8 // Mode0 is default
}
// freqToClockDiv computes the SPI bus clock divider register value.
// SPI peripherals on ESP32-C3 and ESP32-S3 are clocked from the APB bus
// (pplClockFreq, 80 MHz on both chips).
func freqToClockDiv(hz uint32) uint32 {
if hz >= pplClockFreq { // maximum frequency
return 1 << 31
}
if hz < (pplClockFreq / (16 * 64)) { // minimum frequency
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
}
// Iterate all 16 prescaler options looking for an exact match
// or the smallest error.
var bestPre, bestN, bestErr uint32
bestN = 1
bestErr = 0xffffffff
q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5))
for p := uint32(0); p < 16; p++ {
n := q/(p+1) - 1
if n < 1 { // prescaler became too large, stop enum
break
}
if n > 63 { // prescaler too small, skip to next
continue
}
freq := pplClockFreq / ((p + 1) * (n + 1))
if freq == hz { // exact match
return p<<18 | n<<12 | (n/2)<<6 | n
}
var err uint32
if freq < hz {
err = hz - freq
} else {
err = freq - hz
}
if err < bestErr {
bestErr = err
bestPre = p
bestN = n
}
}
return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN
}
// spiTxFillBuffer writes data from w into the 16-word (64-byte) SPI
// hardware transfer buffer. Unused words are zeroed so that no stale
// data from a previous transfer is sent when w is shorter than 64 bytes.
func spiTxFillBuffer(buf *[16]volatile.Register32, w []byte) {
if len(w) >= 64 {
// We can fill the entire 64-byte transfer buffer with data.
// This loop is slightly faster than the loop below.
for i := 0; i < 16; i++ {
word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24
buf[i].Set(word)
}
} else {
// We can't fill the entire transfer buffer, so we need to be a bit
// more careful.
// Note that parts of the transfer buffer that aren't used still
// need to be set to zero, otherwise we might be transferring
// garbage from a previous transmission if w is smaller than r.
for i := 0; i < 16; i++ {
var word uint32
if i*4+3 < len(w) {
word |= uint32(w[i*4+3]) << 24
}
if i*4+2 < len(w) {
word |= uint32(w[i*4+2]) << 16
}
if i*4+1 < len(w) {
word |= uint32(w[i*4+1]) << 8
}
if i*4+0 < len(w) {
word |= uint32(w[i*4+0]) << 0
}
buf[i].Set(word)
}
}
}