machine/stm32: fix UART transmission, baud rate, and interrupt handling

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>
This commit is contained in:
deadprogram
2026-05-06 13:55:54 +02:00
committed by Ron Evans
parent 37c080083b
commit 7d51044892
4 changed files with 59 additions and 16 deletions
+17 -15
View File
@@ -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.
@@ -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
}
}
@@ -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
View File
@@ -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
+28 -1
View File
@@ -58,7 +58,16 @@ func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
// LPUART uses BRR = 256 * fclk / baud
return (256 * CPUFrequency()) / 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
@@ -70,6 +79,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