mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-17 19:23:25 +00:00
machine/stm32: add STM32U5 SPI support
Add SPIv2 driver for STM32U5 using TXDR/RXDR and CFG1/CFG2 registers, which differ from the classic SPI peripheral on older STM32 families. Implements Configure() and single-byte Transfer(). Add SPI1 peripheral and pin definitions to arduino-uno-q board. Exclude stm32u5 from the shared classic SPI build tag.
This commit is contained in:
@@ -1,84 +1,97 @@
|
|||||||
package tmp
|
|
||||||
//go:build arduino_uno_q
|
//go:build arduino_uno_q
|
||||||
|
|
||||||
|
// Arduino UNO Q board with STM32U585 processor.
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/stm32"
|
||||||
|
"runtime/interrupt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Arduino Pins
|
||||||
|
A0 = PA0
|
||||||
|
A1 = PA1
|
||||||
|
A2 = PA4
|
||||||
|
A3 = PB1
|
||||||
|
A4 = PB11
|
||||||
|
A5 = PB12
|
||||||
|
|
||||||
|
D0 = PB7
|
||||||
|
D1 = PB6
|
||||||
|
D2 = PA10
|
||||||
|
D3 = PB3
|
||||||
|
D4 = PB5
|
||||||
|
D5 = PB4
|
||||||
|
D6 = PB10
|
||||||
|
D7 = PA8
|
||||||
|
D8 = PA9
|
||||||
|
D9 = PC7
|
||||||
|
D10 = PB0
|
||||||
|
D11 = PA7
|
||||||
|
D12 = PA6
|
||||||
|
D13 = PA5
|
||||||
|
D14 = PB9
|
||||||
|
D15 = PB8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LED = LED_BUILTIN
|
||||||
|
LED_BUILTIN = LED_GREEN
|
||||||
|
LED_GREEN = PA5
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BUTTON = PC13
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UART pins
|
||||||
|
// PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP)
|
||||||
|
UART_TX_PIN = PA2
|
||||||
|
UART_RX_PIN = PA3
|
||||||
|
|
||||||
|
// I2C pins
|
||||||
|
I2C0_SCL_PIN = PB8
|
||||||
|
I2C0_SDA_PIN = PB9
|
||||||
|
|
||||||
|
// SPI pins
|
||||||
|
SPI1_SCK_PIN = PA5
|
||||||
|
SPI1_SDI_PIN = PA6
|
||||||
|
SPI1_SDO_PIN = PA7
|
||||||
|
SPI0_SCK_PIN = SPI1_SCK_PIN
|
||||||
|
SPI0_SDI_PIN = SPI1_SDI_PIN
|
||||||
|
SPI0_SDO_PIN = SPI1_SDO_PIN
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// USART2 is the hardware serial port connected to the onboard ST-LINK
|
||||||
|
// debugger to be exposed as virtual COM port over USB.
|
||||||
|
UART1 = &_UART1
|
||||||
|
_UART1 = UART{
|
||||||
|
Buffer: NewRingBuffer(),
|
||||||
|
Bus: stm32.USART2,
|
||||||
|
TxAltFuncSelector: AF7_USART1_2_3,
|
||||||
|
RxAltFuncSelector: AF7_USART1_2_3,
|
||||||
|
}
|
||||||
|
DefaultUART = UART1
|
||||||
|
|
||||||
|
// I2C1 is documented, alias to I2C0 as well
|
||||||
|
I2C1 = &I2C{
|
||||||
|
Bus: stm32.I2C1,
|
||||||
} UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt)func init() {) I2C0 = I2C1 } AltFuncSelector: AF4_I2C1_2_3_4, Bus: stm32.I2C1, I2C1 = &I2C{ // I2C1 is documented, alias to I2C0 as well DefaultUART = UART1 } RxAltFuncSelector: AF7_USART1_2_3, TxAltFuncSelector: AF7_USART1_2_3, Bus: stm32.USART2, Buffer: NewRingBuffer(), _UART1 = UART{ UART1 = &_UART1 // debugger to be exposed as virtual COM port over USB. // USART2 is the hardware serial port connected to the onboard ST-LINKvar () I2C0_SDA_PIN = PB9 I2C0_SCL_PIN = PB8 // I2C pins UART_RX_PIN = PA3 UART_TX_PIN = PA2 // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) // UART pinsconst () BUTTON = PC13const () LED_GREEN = PA5 LED_BUILTIN = LED_GREEN LED = LED_BUILTINconst () D15 = PB8 D14 = PB9 D13 = PA5 D12 = PA6 D11 = PA7 D10 = PB0 D9 = PC7 D8 = PA9 D7 = PA8 D6 = PB10 D5 = PB4 D4 = PB5 D3 = PB3 D2 = PA10 D1 = PB6 D0 = PB7 A5 = PB12 A4 = PB11 A3 = PB1 A2 = PA4 A1 = PA1 A0 = PA0 // Arduino Pinsconst () "runtime/interrupt" "device/stm32"import (package machine// Arduino UNO Q board with STM32U585 processor.
|
AltFuncSelector: AF4_I2C1_2_3_4,
|
||||||
|
}
|
||||||
|
I2C0 = I2C1
|
||||||
|
|
||||||
|
// SPI1 is documented, alias to SPI0 as well
|
||||||
|
SPI1 = &SPI{
|
||||||
|
Bus: stm32.SPI1,
|
||||||
|
AltFuncSelector: AF5_SPI1_2_3_OCTOSPI1_OCTOSPI2,
|
||||||
|
}
|
||||||
|
SPI0 = SPI1
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0
|
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 && !stm32u5
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +1,123 @@
|
|||||||
package tmp
|
|
||||||
//go:build stm32u585
|
//go:build stm32u585
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/stm32"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CPUFrequency() uint32 {
|
||||||
|
return 160000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept
|
||||||
|
// in sync with any changes to runtime package which configures the oscillators
|
||||||
|
// and clock frequencies
|
||||||
|
const APB1_TIM_FREQ = 160e6 // 160MHz
|
||||||
|
const APB2_TIM_FREQ = 160e6 // 160MHz
|
||||||
|
|
||||||
|
//---------- UART related code
|
||||||
|
|
||||||
|
// Configure the UART.
|
||||||
|
func (uart *UART) configurePins(config UARTConfig) {
|
||||||
|
if config.RX.getPort() == stm32.GPIOG || config.TX.getPort() == stm32.GPIOG {
|
||||||
|
// Enable VDDIO2 power supply for PGx pins
|
||||||
|
stm32.PWR.SetSVMCR_IO2SV(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable the alternate functions on the TX and RX pins
|
||||||
|
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
|
||||||
|
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART baudrate calc based on the bus and clockspeed
|
||||||
|
// NOTE: keep this in sync with the runtime/runtime_stm32u5.go clock init code
|
||||||
|
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
|
||||||
|
return CPUFrequency() / baudRate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register names vary by ST processor, these are for STM U5
|
||||||
|
func (uart *UART) setRegisters() {
|
||||||
|
uart.rxReg = &uart.Bus.RDR
|
||||||
|
uart.txReg = &uart.Bus.TDR
|
||||||
|
uart.statusReg = &uart.Bus.ISR
|
||||||
|
uart.txEmptyFlag = stm32.USART_ISR_TXE
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------- SPI related types and code
|
||||||
|
|
||||||
|
// SPI on the STM32U5 using the new SPIv2 peripheral
|
||||||
|
type SPI struct {
|
||||||
|
Bus *stm32.SPI_Type
|
||||||
|
AltFuncSelector uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spi *SPI) config8Bits() {
|
||||||
|
// U5 SPI has DSIZE field in CFG1, set to 7 for 8-bit frames (DSIZE = bits-1)
|
||||||
|
spi.Bus.CFG1.ReplaceBits(7, 0x1f, 0) // DSIZE[4:0] = 0x7 = 8 bits
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set baud rate for SPI
|
||||||
|
func (spi *SPI) getBaudRate(config SPIConfig) uint32 {
|
||||||
|
var conf uint32
|
||||||
|
|
||||||
|
localFrequency := config.Frequency
|
||||||
|
|
||||||
|
// Default
|
||||||
|
if localFrequency == 0 {
|
||||||
|
localFrequency = 4e6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set frequency dependent on PCLK prescaler
|
||||||
|
// MBR field in CFG1 register, bits [30:28]
|
||||||
|
switch {
|
||||||
|
case localFrequency < 625000:
|
||||||
|
conf = 7 // Div256
|
||||||
|
case localFrequency < 1250000:
|
||||||
|
conf = 6 // Div128
|
||||||
|
case localFrequency < 2500000:
|
||||||
|
conf = 5 // Div64
|
||||||
|
case localFrequency < 5000000:
|
||||||
|
conf = 4 // Div32
|
||||||
|
case localFrequency < 10000000:
|
||||||
|
conf = 3 // Div16
|
||||||
|
case localFrequency < 20000000:
|
||||||
|
conf = 2 // Div8
|
||||||
|
case localFrequency < 40000000:
|
||||||
|
conf = 1 // Div4
|
||||||
|
case localFrequency < 80000000:
|
||||||
|
conf = 0 // Div2
|
||||||
|
default:
|
||||||
|
conf = 7 // Div256 (safest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf << 28 // MBR position in CFG1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure SPI pins for input output and clock
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------- I2C related code
|
||||||
|
|
||||||
|
// Gets the value for TIMINGR register
|
||||||
|
func (i2c *I2C) getFreqRange(br uint32) uint32 {
|
||||||
|
// These are 'magic' values calculated by STM32CubeMX
|
||||||
|
// for 160MHz PCLK1.
|
||||||
|
// TODO: Do calculations based on PCLK1
|
||||||
|
switch br {
|
||||||
|
case 10 * KHz:
|
||||||
|
return 0xF010F3FE
|
||||||
|
case 100 * KHz:
|
||||||
|
return 0x30A0A7FB
|
||||||
|
case 400 * KHz:
|
||||||
|
return 0x10802D9B
|
||||||
|
case 500 * KHz:
|
||||||
|
return 0x00802172
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} } return 0 default: return 0x00802172 case 500 * KHz: return 0x10802D9B case 400 * KHz: return 0x30A0A7FB case 100 * KHz: return 0xF010F3FE case 10 * KHz: switch br { // TODO: Do calculations based on PCLK1 // for 160MHz PCLK1. // These are 'magic' values calculated by STM32CubeMXfunc (i2c *I2C) getFreqRange(br uint32) uint32 {// Gets the value for TIMINGR register//---------- I2C related code} uart.txEmptyFlag = stm32.USART_ISR_TXE uart.statusReg = &uart.Bus.ISR uart.txReg = &uart.Bus.TDR uart.rxReg = &uart.Bus.RDRfunc (uart *UART) setRegisters() {// Register names vary by ST processor, these are for STM U5} return CPUFrequency() / baudRatefunc (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {// NOTE: keep this in sync with the runtime/runtime_stm32u5.go clock init code// UART baudrate calc based on the bus and clockspeed} config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) // enable the alternate functions on the TX and RX pins } stm32.PWR.SetSVMCR_IO2SV(1) // Enable VDDIO2 power supply for PGx pins if config.RX.getPort() == stm32.GPIOG || config.TX.getPort() == stm32.GPIOG {func (uart *UART) configurePins(config UARTConfig) {// Configure the UART.//---------- UART related codeconst APB2_TIM_FREQ = 160e6 // 160MHzconst APB1_TIM_FREQ = 160e6 // 160MHz// and clock frequencies// in sync with any changes to runtime package which configures the oscillators// Internal use: configured speed of the APB1 and APB2 timers, this should be kept} return 160000000func CPUFrequency() uint32 {) "device/stm32"import (package machine
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
//go:build stm32u5
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
// SPI on STM32U5 uses the new SPIv2 peripheral with separate TXDR/RXDR registers.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPIConfig is used to store config info for SPI.
|
||||||
|
type SPIConfig struct {
|
||||||
|
Frequency uint32
|
||||||
|
SCK Pin
|
||||||
|
SDO Pin
|
||||||
|
SDI Pin
|
||||||
|
LSBFirst bool
|
||||||
|
Mode uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure is intended to setup the STM32U5 SPI peripheral.
|
||||||
|
func (spi *SPI) Configure(config SPIConfig) error {
|
||||||
|
// Disable SPI interface before any configuration changes
|
||||||
|
spi.Bus.CR1.ClearBits(1) // Clear SPE (bit 0)
|
||||||
|
|
||||||
|
// Enable clock for SPI
|
||||||
|
enableAltFuncClock(unsafe.Pointer(spi.Bus))
|
||||||
|
|
||||||
|
// Init pins - use defaults if not specified
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Configure CFG1: baud rate and data size
|
||||||
|
var cfg1 uint32
|
||||||
|
|
||||||
|
// Set baud rate (MBR bits [30:28])
|
||||||
|
cfg1 |= spi.getBaudRate(config)
|
||||||
|
|
||||||
|
// Set data size to 8 bits (DSIZE[4:0] = 7 = 8-1)
|
||||||
|
cfg1 |= 7
|
||||||
|
|
||||||
|
spi.Bus.CFG1.Set(cfg1)
|
||||||
|
|
||||||
|
// Configure CFG2: master mode, SS output, polarity, phase
|
||||||
|
var cfg2 uint32
|
||||||
|
const (
|
||||||
|
cfg2_MASTER = 1 << 22 // MASTER bit
|
||||||
|
cfg2_SSM = 1 << 26 // Software SS management
|
||||||
|
cfg2_SSOE = 1 << 29 // SS output enable
|
||||||
|
cfg2_AFCNTR = 1 << 31 // Alternate function GPIO control always active
|
||||||
|
cfg2_CPOL = 1 << 25
|
||||||
|
cfg2_CPHA = 1 << 24
|
||||||
|
cfg2_LSBFRST = 1 << 23
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg2 |= cfg2_MASTER | cfg2_SSM | cfg2_AFCNTR
|
||||||
|
|
||||||
|
// Set polarity and phase
|
||||||
|
switch config.Mode {
|
||||||
|
case Mode1:
|
||||||
|
cfg2 |= cfg2_CPHA
|
||||||
|
case Mode2:
|
||||||
|
cfg2 |= cfg2_CPOL
|
||||||
|
case Mode3:
|
||||||
|
cfg2 |= cfg2_CPOL | cfg2_CPHA
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bit transfer order
|
||||||
|
if config.LSBFirst {
|
||||||
|
cfg2 |= cfg2_LSBFRST
|
||||||
|
}
|
||||||
|
|
||||||
|
spi.Bus.CFG2.Set(cfg2)
|
||||||
|
|
||||||
|
// Enable SPI
|
||||||
|
spi.Bus.CR1.SetBits(1) // Set SPE (bit 0)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer writes/reads a single byte using the SPI interface.
|
||||||
|
func (spi *SPI) Transfer(w byte) (byte, error) {
|
||||||
|
// Set transfer size to 1 frame
|
||||||
|
spi.Bus.CR2.Set(1)
|
||||||
|
|
||||||
|
// Start the transfer
|
||||||
|
spi.Bus.CR1.SetBits(1 << 9) // CSTART bit
|
||||||
|
|
||||||
|
// Wait until TX FIFO is ready (TXP bit in SR)
|
||||||
|
const sr_TXP = 1 << 1
|
||||||
|
const sr_RXP = 1 << 0
|
||||||
|
const sr_EOT = 1 << 3
|
||||||
|
|
||||||
|
for !spi.Bus.SR.HasBits(sr_TXP) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write byte to TXDR
|
||||||
|
spi.Bus.TXDR.Set(uint32(w))
|
||||||
|
|
||||||
|
// Wait for RX data available (RXP bit in SR)
|
||||||
|
for !spi.Bus.SR.HasBits(sr_RXP) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read received byte
|
||||||
|
data := byte(spi.Bus.RXDR.Get())
|
||||||
|
|
||||||
|
// Wait for end of transfer
|
||||||
|
for !spi.Bus.SR.HasBits(sr_EOT) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear EOT flag
|
||||||
|
spi.Bus.IFCR.Set(sr_EOT)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user