From b4dddfe439df0e28e391d5c2527d6e89fdeb9f29 Mon Sep 17 00:00:00 2001 From: GeoffThomas Date: Sun, 22 Mar 2020 10:28:47 -0500 Subject: [PATCH] stm32 add SPI for stm32f4 --- src/device/stm32/stm32-moder-bitfields.go | 12 +++ src/machine/board_stm32f4disco.go | 27 ++++++ src/machine/machine_stm32_spi.go | 99 +++++++++++++++++++++ src/machine/machine_stm32f103xx.go | 103 +++------------------- src/machine/machine_stm32f407.go | 59 +++++++++++++ src/machine/spi.go | 10 ++- 6 files changed, 217 insertions(+), 93 deletions(-) create mode 100644 src/machine/machine_stm32_spi.go diff --git a/src/device/stm32/stm32-moder-bitfields.go b/src/device/stm32/stm32-moder-bitfields.go index 02298ae0..176d09c2 100644 --- a/src/device/stm32/stm32-moder-bitfields.go +++ b/src/device/stm32/stm32-moder-bitfields.go @@ -43,3 +43,15 @@ const ( GPIOPUPDRPullUp = 1 GPIOPUPDRPullDown = 2 ) + +// SPI prescaler values fPCLK / X +const ( + SPI_PCLK_2 = 0 + SPI_PCLK_4 = 1 + SPI_PCLK_8 = 2 + SPI_PCLK_16 = 3 + SPI_PCLK_32 = 4 + SPI_PCLK_64 = 5 + SPI_PCLK_128 = 6 + SPI_PCLK_256 = 7 +) diff --git a/src/machine/board_stm32f4disco.go b/src/machine/board_stm32f4disco.go index 9d1c5076..e5dc4a92 100644 --- a/src/machine/board_stm32f4disco.go +++ b/src/machine/board_stm32f4disco.go @@ -129,3 +129,30 @@ var ( func init() { UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt) } + +// SPI pins +const ( + SPI1_SCK_PIN = PA5 + SPI1_MISO_PIN = PA6 + SPI1_MOSI_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_MISO_PIN = SPI1_MISO_PIN + SPI0_MOSI_PIN = SPI1_MOSI_PIN +) + +// MEMs accelerometer +const ( + MEMS_ACCEL_CS = PE3 + MEMS_ACCEL_INT1 = PE0 + MEMS_ACCEL_INT2 = PE1 +) + +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +// TODO: implement SPI2 and SPI3. +var ( + SPI0 = SPI{ + Bus: stm32.SPI1, + AltFuncSelector: stm32.AF5_SPI1_SPI2, + } + SPI1 = &SPI0 +) diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go new file mode 100644 index 00000000..fa18aacf --- /dev/null +++ b/src/machine/machine_stm32_spi.go @@ -0,0 +1,99 @@ +// +build stm32 + +package machine + +// Peripheral abstraction layer for SPI on the stm32 family + +import ( + "device/stm32" + "unsafe" +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + MOSI Pin + MISO Pin + LSBFirst bool + Mode uint8 +} + +// 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) { + // enable clock for SPI + enableAltFuncClock(unsafe.Pointer(spi.Bus)) + + // Get SPI baud rate based on the bus speed it's attached to + var conf uint32 = spi.getBaudRate(config) + + // set bit transfer order + if config.LSBFirst { + conf |= stm32.SPI_CR1_LSBFIRST + } + + // 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) + case Mode2: + conf |= (1 << stm32.SPI_CR1_CPOL_Pos) + conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) + 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) + } + + // set to SPI master + conf |= stm32.SPI_CR1_MSTR + + // disable MCU acting as SPI slave + conf |= stm32.SPI_CR1_SSM | stm32.SPI_CR1_SSI + + // now set the configuration + spi.Bus.CR1.Set(conf) + + // init pins + if config.SCK == 0 && config.MOSI == 0 && config.MISO == 0 { + config.SCK = SPI0_SCK_PIN + config.MOSI = SPI0_MOSI_PIN + config.MISO = SPI0_MISO_PIN + } + spi.configurePins(config) + + // enable SPI interface + spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) +} + +// 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 + spi.Bus.DR.Set(uint32(w)) + + // Wait until transmit complete + for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { + } + + // Wait until receive complete + for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) { + } + + // Wait until SPI is not busy + for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { + } + + // Return received data from SPI data register + return byte(spi.Bus.DR.Get()), nil +} diff --git a/src/machine/machine_stm32f103xx.go b/src/machine/machine_stm32f103xx.go index bb60b929..70cdc6b2 100644 --- a/src/machine/machine_stm32f103xx.go +++ b/src/machine/machine_stm32f103xx.go @@ -148,7 +148,8 @@ func (uart UART) getBaudRateDivisor(br uint32) uint32 { return divider } -// SPI on the STM32. +//---------- SPI related types and code + type SPI struct { Bus *stm32.SPI_Type } @@ -161,26 +162,8 @@ var ( SPI0 = SPI1 ) -// SPIConfig is used to store config info for SPI. -type SPIConfig struct { - Frequency uint32 - SCK Pin - MOSI Pin - MISO Pin - LSBFirst bool - Mode uint8 -} - -// 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) { - // enable clock for SPI - stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) - +// Set baud rate for SPI +func (spi SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // set frequency dependent on PCLK2 prescaler (div 1) @@ -203,82 +186,18 @@ func (spi SPI) Configure(config SPIConfig) { default: conf |= stm32.SPI_BaudRatePrescaler_256 } - - // set bit transfer order - if config.LSBFirst { - conf |= stm32.SPI_FirstBit_LSB - } - - // set mode - switch config.Mode { - case 0: - conf &^= (1 << stm32.SPI_CR1_CPOL_Pos) - conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) - case 1: - conf &^= (1 << stm32.SPI_CR1_CPOL_Pos) - conf |= (1 << stm32.SPI_CR1_CPHA_Pos) - case 2: - conf |= (1 << stm32.SPI_CR1_CPOL_Pos) - conf &^= (1 << stm32.SPI_CR1_CPHA_Pos) - case 3: - 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) - } - - // set to SPI master - conf |= stm32.SPI_Mode_Master - - // now set the configuration - spi.Bus.CR1.Set(conf) - - // init pins - spi.setPins(config.SCK, config.MOSI, config.MISO) - - // enable SPI interface - spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) + return conf } -// 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 - spi.Bus.DR.Set(uint32(w)) - - // Wait until transmit complete - for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { - } - - // Wait until receive complete - for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) { - } - - // Wait until SPI is not busy - for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { - } - - // Return received data from SPI data register - return byte(spi.Bus.DR.Get()), nil +// Configure SPI pins for input output and clock +func (spi SPI) configurePins(config SPIConfig) { + config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) + config.MOSI.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) + config.MISO.Configure(PinConfig{Mode: PinInputModeFloating}) } -func (spi SPI) setPins(sck, mosi, miso Pin) { - if sck == 0 { - sck = SPI0_SCK_PIN - } - if mosi == 0 { - mosi = SPI0_MOSI_PIN - } - if miso == 0 { - miso = SPI0_MISO_PIN - } +//---------- I2C related types and code - sck.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) - mosi.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) - miso.Configure(PinConfig{Mode: PinInputModeFloating}) -} - -// I2C on the STM32F103xx. type I2C struct { Bus *stm32.I2C_Type } diff --git a/src/machine/machine_stm32f407.go b/src/machine/machine_stm32f407.go index 4714f2b0..7c67cfb8 100644 --- a/src/machine/machine_stm32f407.go +++ b/src/machine/machine_stm32f407.go @@ -108,3 +108,62 @@ func (uart UART) getBaudRateDivisor(baudRate uint32) uint32 { } return clock / baudRate } + +//---------- SPI related types and code + +// SPI on the STM32Fxxx using MODER / alternate function pins +type SPI struct { + Bus *stm32.SPI_Type + AltFuncSelector stm32.AltFunc +} + +// Set baud rate for SPI +func (spi SPI) getBaudRate(config SPIConfig) uint32 { + var conf uint32 + + localFrequency := config.Frequency + if spi.Bus != stm32.SPI1 { + // Assume it's SPI2 or SPI3 on APB1 at 1/2 the clock frequency of APB2, so + // we want to pretend to request 2x the baudrate asked for + localFrequency = localFrequency * 2 + } + + // set frequency dependent on PCLK prescaler. Since these are rather weird + // speeds due to the CPU freqency, pick a range up to that frquency for + // clients to use more human-understandable numbers, e.g. nearest 100KHz + + // These are based on APB2 clock frquency (84MHz on the discovery board) + // TODO: also include the MCU/APB clock setting in the equation + switch true { + case localFrequency < 328125: + conf = stm32.SPI_PCLK_256 + case localFrequency < 656250: + conf = stm32.SPI_PCLK_128 + case localFrequency < 1312500: + conf = stm32.SPI_PCLK_64 + case localFrequency < 2625000: + conf = stm32.SPI_PCLK_32 + case localFrequency < 5250000: + conf = stm32.SPI_PCLK_16 + case localFrequency < 10500000: + conf = stm32.SPI_PCLK_8 + // NOTE: many SPI components won't operate reliably (or at all) above 10MHz + // Check the datasheet of the part + case localFrequency < 21000000: + conf = stm32.SPI_PCLK_4 + case localFrequency < 42000000: + conf = stm32.SPI_PCLK_2 + default: + // None of the specific baudrates were selected; choose the lowest speed + conf = stm32.SPI_PCLK_256 + } + + return conf << stm32.SPI_CR1_BR_Pos +} + +// Configure SPI pins for input output and clock +func (spi SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.MOSI.ConfigureAltFunc(PinConfig{Mode: PinModeSPIMOSI}, spi.AltFuncSelector) + config.MISO.ConfigureAltFunc(PinConfig{Mode: PinModeSPIMISO}, spi.AltFuncSelector) +} diff --git a/src/machine/spi.go b/src/machine/spi.go index 1fe8435f..2b022c41 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,9 +1,17 @@ -// +build !baremetal sam stm32,!stm32f407 fe310 +// +build !baremetal sam stm32 fe310 package machine import "errors" +// SPI phase and polarity configs CPOL and CPHA +const ( + Mode0 = 0 + Mode1 = 1 + Mode2 = 2 + Mode3 = 3 +) + var ( ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size") )