Skip to content
Open
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
32 changes: 17 additions & 15 deletions src/machine/machine_stm32_uart.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
}

// Clear overrun error (ORE, bit 3) to prevent an interrupt storm.
if s&0x8 != 0 {
// Clear error flags (ORE=bit3, NE=bit2, FE=bit1, PE=bit0) to prevent
// an interrupt storm and ensure the USART can continue receiving.
if s&0xF != 0 {
if uart.errClearReg != nil {
// Newer USART peripherals: clear ORE via the ICR register.
uart.errClearReg.Set(0x8) // ORECF
// Newer USART peripherals (L0, L4, L5, G0, F7, U5, WL, etc.):
// clear all error flags via ICR (ORECF|NECF|FECF|PECF = bits 3:0).
uart.errClearReg.Set(s & 0xF)
} else if s&0x20 == 0 {
// Older USART (F1/F4): ORE is cleared by reading SR then DR.
// Older USART (F1/F4): errors are cleared by reading SR then DR.
// SR was already read above. If RXNE was set, DR was read in
// the Receive path. Otherwise do a dummy DR read to complete
// the clearing sequence.
Expand All @@ -91,20 +93,20 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
}
}

// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
// routines for calculation
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}

// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
uart.txReg.Set(uint32(c))

// Wait for the transmit data register to be empty before writing, so we
// don't overwrite a byte that hasn't moved to the shift register yet.
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
}
uart.txReg.Set(uint32(c))
return nil
}

func (uart *UART) flush() {}
// flush waits until the USART shift register has finished transmitting the
// last byte (TC = Transmission Complete, bit 6). Without this, Write() returns
// while the final byte is still clocking out on the wire.
func (uart *UART) flush() {
for !uart.statusReg.HasBits(1 << 6) { // TC bit
}
}
13 changes: 13 additions & 0 deletions src/machine/machine_stm32_uart_setbaudrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build stm32 && !stm32u585

package machine

// SetBaudRate sets the communication speed for the UART. Defers to
// chip-specific getBaudRateDivisor for the divisor calculation.
//
// On STM32U585 this function is overridden in machine_stm32u585.go because
// the U5 family requires UE=0 to write BRR.
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
1 change: 1 addition & 0 deletions src/machine/machine_stm32u5.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ func enableAltFuncClock(bus unsafe.Pointer) {
stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN)
case unsafe.Pointer(stm32.USART1): // USART1 clock enable
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
_ = stm32.RCC.APB2ENR.Get() // readback: ensure clock is active before accessing USART1 registers
case unsafe.Pointer(stm32.USART2): // USART2 clock enable
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN)
case unsafe.Pointer(stm32.USART3): // USART3 clock enable
Expand Down
41 changes: 35 additions & 6 deletions src/machine/machine_stm32u585.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
)

func CPUFrequency() uint32 {
return 4_000_000
return 160_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)
const APB1_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
const APB2_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)

//---------- UART related code

Expand Down Expand Up @@ -55,10 +55,21 @@ func (uart *UART) isLPUART1() bool {
// 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
// LPUART uses BRR = 256 * fclk / baud.
// Use 64-bit arithmetic to avoid overflow: at 160 MHz,
// 256 * 160_000_000 = 40_960_000_000 which exceeds uint32 max.
return uint32(uint64(256) * uint64(CPUFrequency()) / uint64(baudRate))
}
return 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
Expand All @@ -70,6 +81,24 @@ func (uart *UART) setRegisters() {
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
Expand Down
48 changes: 41 additions & 7 deletions src/runtime/runtime_stm32u5.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,52 @@ func buffered() int {
}

func initCLK() {
// Use MSI at 4MHz — the reset default clock configuration.
// The MCU boots with MSI at 4MHz, VOS Range 4, and 0 flash wait states.
// Configure SYSCLK to 160 MHz via PLL1 using MSIS (4 MHz reset default) as the source.
// Formula: Fout = Fin × N / M / R = 4 × 80 / 1 / 2 = 160 MHz
// - M = 1 (Div1), N = 80 (stored as N-1 = 79), R = 2 (Div2)
// - VCO input = 4 MHz (PLL1RGE Range1: 4–8 MHz), VCO output = 320 MHz
// VOS Range 1 (1.2V) is required for SYSCLK > 100 MHz (RM0456 §6.3.6).

// Enable PWR peripheral clock (required on STM32U5 before accessing PWR registers).
stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_PWREN)
_ = stm32.RCC.AHB3ENR.Get() // read-back for clock stabilization

// Switch from VOS Range 4 to Range 3. Range 4 doesn't support ADC (RM0456 §6.3.6).
// Range 3 supports up to 50 MHz HCLK and enables ADC. No flash wait-state change needed at 4 MHz.
// The EPOD booster must be enabled before changing VOS (RM0456 §10.5.4).
// Enable the EPOD booster before raising VOS (RM0456 §10.5.4).
stm32.PWR.VOSR.SetBits(stm32.PWR_VOSR_BOOSTEN)
stm32.PWR.VOSR.ReplaceBits(stm32.PWR_VOSR_VOS_Range3<<stm32.PWR_VOSR_VOS_Pos, stm32.PWR_VOSR_VOS_Msk, 0)
for !stm32.PWR.VOSR.HasBits(stm32.PWR_VOSR_VOSRDY) {

// Raise voltage scaling to Range 1 (1.2V) to support 160 MHz operation.
stm32.PWR.VOSR.ReplaceBits(stm32.PWR_VOSR_VOS_Range1<<stm32.PWR_VOSR_VOS_Pos, stm32.PWR_VOSR_VOS_Msk, 0)

// Wait for both the VOS regulator and the EPOD booster to become ready.
for !stm32.PWR.VOSR.HasBits(stm32.PWR_VOSR_VOSRDY | stm32.PWR_VOSR_BOOSTRDY) {
}

// Set Flash latency to 4 wait states and enable prefetch before raising the clock
// (required for 160 MHz at VOS Range 1, RM0456 §7.3.3).
stm32.FLASH.ACR.ReplaceBits(4, stm32.Flash_ACR_LATENCY_Msk, 0)
stm32.FLASH.ACR.SetBits(stm32.Flash_ACR_PRFTEN)

// Configure PLL1: source = MSIS (4 MHz), M = 1, PLL1RGE = 4–8 MHz, R output enabled.
stm32.RCC.PLL1CFGR.Set(
stm32.RCC_PLL1CFGR_PLL1SRC_MSIS |
(stm32.RCC_PLL1CFGR_PLL1RGE_Range1 << stm32.RCC_PLL1CFGR_PLL1RGE_Pos) |
(stm32.RCC_PLL1CFGR_PLL1M_Div1 << stm32.RCC_PLL1CFGR_PLL1M_Pos) |
stm32.RCC_PLL1CFGR_PLL1REN,
)

// Set PLL1 dividers: N = 80 (stored as N-1 = 79), R = 2 (Div2 = 1).
stm32.RCC.PLL1DIVR.Set(
(79 << stm32.RCC_PLL1DIVR_PLL1N_Pos) |
(stm32.RCC_PLL1DIVR_PLL1R_Div2 << stm32.RCC_PLL1DIVR_PLL1R_Pos),
)

// Enable PLL1 and wait for it to lock.
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLL1ON)
for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLL1RDY) {
}

// Switch SYSCLK to PLL1 and wait for the hardware to confirm the switch.
stm32.RCC.SetCFGR1_SW(stm32.RCC_CFGR1_SW_PLL)
for stm32.RCC.GetCFGR1_SWS() != stm32.RCC_CFGR1_SWS_PLL {
}
}
Loading