Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/machine/board_feather-stm32f405.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ----------------------------------------------------------------------
Expand Down
116 changes: 77 additions & 39 deletions src/machine/machine_stm32_spi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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

// enable the SPI interface
conf |= stm32.SPI_CR1_SPE

// disable MCU acting as SPI peripheral
conf |= stm32.SPI_CR1_SSM | stm32.SPI_CR1_SSI
// 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 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 receive complete
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
// 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 until SPI is not busy
// 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
}
44 changes: 42 additions & 2 deletions src/machine/machine_stm32f405.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package machine

import (
"device/stm32"
"math/bits"
"runtime/interrupt"
)

Expand Down Expand Up @@ -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 ----------------------------------------------------------------------

Expand Down