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>
This commit is contained in:
deadprogram
2026-03-02 10:53:25 +01:00
parent 394116e998
commit b14ee51ff6
4 changed files with 112 additions and 199 deletions
+5 -97
View File
@@ -16,11 +16,6 @@ import (
)
const (
SPI_MODE0 = uint8(0)
SPI_MODE1 = uint8(1)
SPI_MODE2 = uint8(2)
SPI_MODE3 = uint8(3)
FSPICLK_IN_IDX = uint32(63)
FSPICLK_OUT_IDX = uint32(63)
FSPIQ_IN_IDX = uint32(64)
@@ -56,64 +51,6 @@ var (
SPI0 = SPI2
)
// 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 // SPI_MODE0 is default
}
// Compute the SPI bus frequency from the CPU frequency.
func freqToClockDiv(hz uint32) uint32 {
fcpu := CPUFrequency()
if hz >= fcpu { // maximum frequency
return 1 << 31
}
if hz < (fcpu / (16 * 64)) { // minimum frequency
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
}
// iterate looking for an exact match
// or iterate all 16 prescaler options
// looking for 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 := fcpu / ((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
}
// Configure and make the SPI peripheral ready to use.
func (spi *SPI) Configure(config SPIConfig) error {
// right now this is only setup to work for the esp32c3 spi2 bus
@@ -172,16 +109,16 @@ func (spi *SPI) Configure(config SPIConfig) error {
// set spi2 data mode
switch config.Mode {
case SPI_MODE0:
case Mode0:
spi.Bus.SetMISC_CK_IDLE_EDGE(0)
spi.Bus.SetUSER_CK_OUT_EDGE(0)
case SPI_MODE1:
case Mode1:
spi.Bus.SetMISC_CK_IDLE_EDGE(0)
spi.Bus.SetUSER_CK_OUT_EDGE(1)
case SPI_MODE2:
case Mode2:
spi.Bus.SetMISC_CK_IDLE_EDGE(1)
spi.Bus.SetUSER_CK_OUT_EDGE(1)
case SPI_MODE3:
case Mode3:
spi.Bus.SetMISC_CK_IDLE_EDGE(1)
spi.Bus.SetUSER_CK_OUT_EDGE(0)
default:
@@ -254,36 +191,7 @@ func (spi *SPI) Tx(w, r []byte) error {
// Fill tx buffer.
transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0))))
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
transferWords[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
}
transferWords[i].Set(word)
}
}
spiTxFillBuffer(transferWords, w)
// Do the transfer.
spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1)
+5 -101
View File
@@ -15,11 +15,6 @@ import (
)
const (
SPI_MODE0 = uint8(0)
SPI_MODE1 = uint8(1)
SPI_MODE2 = uint8(2)
SPI_MODE3 = uint8(3)
// ESP32-S3 PLL clock frequency (same as ESP32-C3)
pplClockFreq = 80e6
@@ -64,16 +59,6 @@ var (
SPI1 = &SPI{Bus: esp.SPI3, busID: 3} // Secondary SPI (HSPI)
)
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 // SPI_MODE0 is default
}
// Configure and make the SPI peripheral ready to use.
// Implementation following ESP-IDF HAL with GPIO Matrix routing
func (spi *SPI) Configure(config SPIConfig) error {
@@ -232,14 +217,14 @@ func (spi *SPI) Configure(config SPIConfig) error {
// Configure SPI mode (CPOL/CPHA) following ESP-IDF HAL
switch config.Mode {
case SPI_MODE0:
case Mode0:
// CPOL=0, CPHA=0 (default)
case SPI_MODE1:
case Mode1:
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
case SPI_MODE2:
case Mode2:
bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
case SPI_MODE3:
case Mode3:
bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1
}
@@ -319,36 +304,7 @@ func (spi *SPI) Tx(w, r []byte) error {
// Fill tx buffer.
transferWords := (*[16]volatile.Register32)(unsafe.Add(unsafe.Pointer(&bus.W0), 0))
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
transferWords[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
}
transferWords[i].Set(word)
}
}
spiTxFillBuffer(transferWords, w)
// Do the transfer.
bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1)
@@ -388,58 +344,6 @@ func (spi *SPI) Tx(w, r []byte) error {
return nil
}
// Compute the SPI bus frequency from the APB clock frequency.
// Note: APB clock is always 80MHz on ESP32-S3, independent of CPU frequency.
// Ported from ESP32-C3 implementation for better accuracy.
func freqToClockDiv(hz uint32) uint32 {
// Use APB clock frequency (80MHz), not CPU frequency!
// SPI peripheral is connected to APB bus which stays at 80MHz
const apbFreq = pplClockFreq // 80MHz
if hz >= apbFreq { // maximum frequency
return 1 << 31
}
if hz < (apbFreq / (16 * 64)) { // minimum frequency
return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
}
// iterate looking for an exact match
// or iterate all 16 prescaler options
// looking for the smallest error
var bestPre, bestN, bestErr uint32
bestN = 1
bestErr = 0xffffffff
q := uint32(float32(apbFreq)/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 := apbFreq / ((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
}
// isDefaultSPIPins checks if the given pins match the default SPI pin configuration
// that supports IO MUX direct connection for better performance
func isDefaultSPIPins(busID uint8, config SPIConfig) bool {
+101
View File
@@ -0,0 +1,101 @@
//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)
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || atmega || attiny85 || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
//go:build !baremetal || atmega || attiny85 || esp32 || esp32c3 || esp32s3 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
package machine