From 9ad231507972475e09cbf336295e0acb765953e8 Mon Sep 17 00:00:00 2001 From: ardnew <3837367+ardnew@users.noreply.github.com> Date: Fri, 2 Oct 2020 13:05:58 -0500 Subject: [PATCH] feather-stm32f405: add SPI support (#1377) * machine/stm32f405: add SPI support --- src/machine/board_feather-stm32f405.go | 16 ++++ src/machine/machine_stm32_spi.go | 118 ++++++++++++++++--------- src/machine/machine_stm32f405.go | 44 ++++++++- 3 files changed, 136 insertions(+), 42 deletions(-) diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 14c7d063..3566591f 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -178,6 +178,22 @@ const ( SPI_SDO_PIN = SPI0_SDO_PIN // ) +var ( + SPI1 = SPI{ + Bus: stm32.SPI2, + AltFuncSelector: stm32.AF5_SPI1_SPI2, + } + SPI2 = SPI{ + Bus: stm32.SPI3, + AltFuncSelector: stm32.AF6_SPI3, + } + SPI3 = SPI{ + Bus: stm32.SPI1, + AltFuncSelector: stm32.AF5_SPI1_SPI2, + } + SPI0 = SPI1 +) + func initSPI() {} // -- I2C ---------------------------------------------------------------------- diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 2e14a461..dd69137f 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -20,15 +20,42 @@ type SPIConfig struct { } // Configure is intended to setup the STM32 SPI1 interface. -// Features still TODO: -// - support SPI2 and SPI3 -// - allow setting data size to 16 bits? -// - allow setting direction in HW for additional optimization? -// - hardware SS pin? func (spi SPI) Configure(config SPIConfig) { + + // -- CONFIGURING THE SPI IN MASTER MODE -- + // + // 1. Select the BR[2:0] bits to define the serial clock baud rate (see + // SPI_CR1 register). + // 2. Select the CPOL and CPHA bits to define one of the four relationships + // between the data transfer and the serial clock (see Figure 248). This + // step is not required when the TI mode is selected. + // 3. Set the DFF bit to define 8- or 16-bit data frame format + // 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame + // format. This step is not required when the TI mode is selected. + // 5. If the NSS pin is required in input mode, in hardware mode, connect the + // NSS pin to a high-level signal during the complete byte transmit + // sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1 + // register. If the NSS pin is required in output mode, the SSOE bit only + // should be set. This step is not required when the TI mode is selected. + // 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial + // communications. + // 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin + // is connected to a high-level signal). + + // disable SPI interface before any configuration changes + spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE) + // enable clock for SPI enableAltFuncClock(unsafe.Pointer(spi.Bus)) + // init pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.configurePins(config) + // Get SPI baud rate based on the bus speed it's attached to var conf uint32 = spi.getBaudRate(config) @@ -39,61 +66,72 @@ func (spi SPI) Configure(config SPIConfig) { // set polarity and phase on the SPI interface switch config.Mode { - case Mode0: - conf &^= (1 << stm32.SPI_CR1_CPOL_Pos) - conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) case Mode1: - conf &^= (1 << stm32.SPI_CR1_CPOL_Pos) - conf |= (1 << stm32.SPI_CR1_CPHA_Pos) + conf |= stm32.SPI_CR1_CPHA case Mode2: - conf |= (1 << stm32.SPI_CR1_CPOL_Pos) - conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) + conf |= stm32.SPI_CR1_CPOL case Mode3: - conf |= (1 << stm32.SPI_CR1_CPOL_Pos) - conf |= (1 << stm32.SPI_CR1_CPHA_Pos) - default: // to mode 0 - conf &^= (1 << stm32.SPI_CR1_CPOL_Pos) - conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) + conf |= stm32.SPI_CR1_CPOL + conf |= stm32.SPI_CR1_CPHA } - // set to SPI controller - conf |= stm32.SPI_CR1_MSTR + // configure as SPI master + conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI - // disable MCU acting as SPI peripheral - conf |= stm32.SPI_CR1_SSM | stm32.SPI_CR1_SSI + // enable the SPI interface + conf |= stm32.SPI_CR1_SPE + + // use software CS (GPIO) by default + conf |= stm32.SPI_CR1_SSM // now set the configuration spi.Bus.CR1.Set(conf) - - // init pins - if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { - config.SCK = SPI0_SCK_PIN - config.SDO = SPI0_SDO_PIN - config.SDI = SPI0_SDI_PIN - } - spi.configurePins(config) - - // enable SPI interface - spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) + spi.Bus.CR2.SetBits((conf & stm32.SPI_CR1_SSM_Msk) >> 16) } // Transfer writes/reads a single byte using the SPI interface. func (spi SPI) Transfer(w byte) (byte, error) { - // Write data to be transmitted to the SPI data register + + // 1. Enable the SPI by setting the SPE bit to 1. + // 2. Write the first data item to be transmitted into the SPI_DR register + // (this clears the TXE flag). + // 3. Wait until TXE=1 and write the second data item to be transmitted. Then + // wait until RXNE=1 and read the SPI_DR to get the first received data + // item (this clears the RXNE bit). Repeat this operation for each data + // item to be transmitted/received until the n–1 received data. + // 4. Wait until RXNE=1 and read the last received data. + // 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI. + + // put output word (8-bit) in data register (DR), which is parallel-loaded + // into shift register, and shifted out on MOSI. spi.Bus.DR.Set(uint32(w)) - // Wait until transmit complete - for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { - } - - // Wait until receive complete + // wait for SPI bus receive buffer not empty bit (RXNE) to be set. + // warning: blocks forever until this condition is met. for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) { } - // Wait until SPI is not busy + // copy input word (8-bit) in data register (DR), which was shifted in on MISO + // and parallel-loaded into register. + data := byte(spi.Bus.DR.Get()) + + // wait for SPI bus transmit buffer empty bit (TXE) to be set. + // warning: blocks forever until this condition is met. + for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { + } + + // wait for SPI bus busy bit (BSY) to be clear to indicate synchronous + // transfer complete. this will effectively prevent this Transfer() function + // from being capable of maintaining high-bandwidth communication throughput, + // but it will help guarantee stability on the bus. for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { } + // clear the overrun flag (only in full-duplex mode) + if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) { + spi.Bus.SR.Get() + } + // Return received data from SPI data register - return byte(spi.Bus.DR.Get()), nil + return data, nil } diff --git a/src/machine/machine_stm32f405.go b/src/machine/machine_stm32f405.go index e5994171..4709fa2c 100644 --- a/src/machine/machine_stm32f405.go +++ b/src/machine/machine_stm32f405.go @@ -6,6 +6,7 @@ package machine import ( "device/stm32" + "math/bits" "runtime/interrupt" ) @@ -46,8 +47,47 @@ type SPI struct { AltFuncSelector stm32.AltFunc } -func (spi SPI) configurePins(config SPIConfig) {} -func (spi SPI) getBaudRate(config SPIConfig) uint32 { return 0 } +func (spi SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) + config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) +} + +func (spi SPI) getBaudRate(config SPIConfig) uint32 { + var clock uint32 + switch spi.Bus { + case stm32.SPI1: + clock = CPUFrequency() / 2 + case stm32.SPI2, stm32.SPI3: + clock = CPUFrequency() / 4 + } + + // limit requested frequency to bus frequency and min frequency (DIV256) + freq := config.Frequency + if min := clock / 256; freq < min { + freq = min + } else if freq > clock { + freq = clock + } + + // calculate the exact clock divisor (freq=clock/div -> div=clock/freq). + // truncation is fine, since it produces a less-than-or-equal divisor, and + // thus a greater-than-or-equal frequency. + // divisors only come in consecutive powers of 2, so we can use log2 (or, + // equivalently, bits.Len - 1) to convert to respective enum value. + div := bits.Len32(clock/freq) - 1 + + // but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so + // subtract 1 from the log2 value, keeping a lower bound of 0 + if div < 0 { + div = 0 + } else if div > 0 { + div-- + } + + // finally, shift the enumerated value into position for SPI CR1 + return uint32(div) << stm32.SPI_CR1_BR_Pos +} // -- I2C ----------------------------------------------------------------------