mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-11 14:33:41 +00:00
7d51044892
This fixes several issues in the STM32 UART implementation: - `writeByte` now waits for the transmit register to be empty before writing, preventing data loss by avoiding overwriting the shift register. - `flush` now correctly waits for the Transmission Complete (TC) flag. - The interrupt handler now clears all error flags (ORE, NE, FE, PE) to prevent interrupt storms, rather than just ORE. - Extracted `SetBaudRate` so it can be cleanly overridden by specific MCU families. It also introduces specific fixes for the STM32U5 family: - Enforces a minimum BRR divisor of 16 in `getBaudRateDivisor` to prevent undefined hardware behavior and CPU starvation. - Overrides `SetBaudRate` for STM32U585 to momentarily disable the USART (UE=0) before updating the BRR register, which is read-only when enabled. - Adds a readback after enabling the USART1 clock to ensure the clock is active before register access. Signed-off-by: deadprogram <ron@hybridgroup.com>
177 lines
5.2 KiB
Go
177 lines
5.2 KiB
Go
//go:build stm32u585
|
|
|
|
package machine
|
|
|
|
import (
|
|
"device/stm32"
|
|
"unsafe"
|
|
)
|
|
|
|
func CPUFrequency() uint32 {
|
|
return 4_000_000
|
|
}
|
|
|
|
// 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 = 4e6 // 4MHz (MSI default)
|
|
const APB2_TIM_FREQ = 4e6 // 4MHz (MSI default)
|
|
|
|
//---------- UART related code
|
|
|
|
// Configure the UART.
|
|
func (uart *UART) configurePins(config UARTConfig) {
|
|
if uart.isLPUART1() {
|
|
// LPUART1 is on APB3. Explicitly enable its peripheral clock.
|
|
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_LPUART1EN)
|
|
_ = stm32.RCC.APB3ENR.Get() // delay for clock stabilization
|
|
|
|
// Select PCLK3 as LPUART1 kernel clock source.
|
|
stm32.RCC.CCIPR3.ReplaceBits(
|
|
stm32.RCC_CCIPR3_LPUART1SEL_PCLK3<<stm32.RCC_CCIPR3_LPUART1SEL_Pos,
|
|
stm32.RCC_CCIPR3_LPUART1SEL_Msk, 0)
|
|
}
|
|
|
|
if config.RX.getPort() == stm32.GPIOG || config.TX.getPort() == stm32.GPIOG {
|
|
// Enable VDDIO2 voltage monitoring and wait for ready before
|
|
// declaring VDDIO2 supply valid (matches HAL_PWREx_EnableVddIO2).
|
|
stm32.PWR.SetSVMCR_IO2VMEN(1)
|
|
for stm32.PWR.GetSVMSR_VDDIO2RDY() == 0 {
|
|
}
|
|
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)
|
|
}
|
|
|
|
// isLPUART1 returns true if this UART is backed by the LPUART1 peripheral.
|
|
func (uart *UART) isLPUART1() bool {
|
|
return uintptr(unsafe.Pointer(uart.Bus)) == uintptr(unsafe.Pointer(stm32.LPUART1))
|
|
}
|
|
|
|
// 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 {
|
|
if uart.isLPUART1() {
|
|
// LPUART uses BRR = 256 * fclk / baud
|
|
return (256 * CPUFrequency()) / baudRate
|
|
}
|
|
// USART requires BRR >= 16 for 16x oversampling (OVER8=0).
|
|
// A divisor below 16 is invalid per the STM32 reference manual and causes
|
|
// undefined hardware behaviour — in practice the receiver fires ORE/RXNE
|
|
// interrupts at an impossible rate, completely starving the CPU.
|
|
const minBRR = 16
|
|
divisor := CPUFrequency() / baudRate
|
|
if divisor < minBRR {
|
|
divisor = minBRR
|
|
}
|
|
return divisor
|
|
}
|
|
|
|
// 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
|
|
uart.errClearReg = &uart.Bus.ICR
|
|
}
|
|
|
|
// SetBaudRate overrides the shared implementation for STM32U5. On this
|
|
// family the BRR register is read-only while UE=1 (USART enabled), so the
|
|
// USART must be briefly disabled to change the baud rate. This matters when
|
|
// the servo library (or any code) calls SetBaudRate after Configure has
|
|
// already enabled the USART.
|
|
func (uart *UART) SetBaudRate(br uint32) {
|
|
cr1 := uart.Bus.CR1.Get()
|
|
if cr1&stm32.USART_CR1_UE != 0 {
|
|
// Disable the USART so BRR becomes writable.
|
|
uart.Bus.CR1.Set(cr1 &^ stm32.USART_CR1_UE)
|
|
}
|
|
uart.Bus.BRR.Set(uart.getBaudRateDivisor(br))
|
|
if cr1&stm32.USART_CR1_UE != 0 {
|
|
// Restore CR1 exactly as it was (re-enables USART, TE, RE, etc.).
|
|
uart.Bus.CR1.Set(cr1)
|
|
}
|
|
}
|
|
|
|
//---------- 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
|
|
}
|
|
}
|