diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index edb0218ac..5fd0a853d 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -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) diff --git a/src/machine/machine_esp32s3_spi.go b/src/machine/machine_esp32s3_spi.go index 8c1618fc3..fb320f5ed 100644 --- a/src/machine/machine_esp32s3_spi.go +++ b/src/machine/machine_esp32s3_spi.go @@ -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 { diff --git a/src/machine/machine_esp32xx_spi.go b/src/machine/machine_esp32xx_spi.go new file mode 100644 index 000000000..755b4bec6 --- /dev/null +++ b/src/machine/machine_esp32xx_spi.go @@ -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) + } + } +} diff --git a/src/machine/spi.go b/src/machine/spi.go index fa507b961..83fc33dae 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -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