Files
tinygo/src/machine/machine_stm32_uart.go
T
deadprogram 4c3680635e machine/stm32: fix UART interrupt storm caused by uncleared overrun error
The UART handleInterrupt handler unconditionally read RDR on every
interrupt without checking which flag triggered it. On newer STM32
USART peripherals (U5, L4, L5, L0, G0, F7, WL), RXNEIE enables
interrupts for both RXFNE (data ready) and ORE (overrun error).
Unlike older families (F1, F4), ORE is not cleared by reading the
data register, it must be explicitly cleared via the ICR register.

When an overrun occurred (e.g. serial data arriving while ADC
busy-waits in Get()), ORE would trigger the interrupt, the handler
would fire without clearing it, and the interrupt would re-trigger
immediately, causing an infinite interrupt storm that locks up the
CPU.

Fix by:
- Checking RXFNE/RXNE (bit 5) before reading data from RDR
- Clearing ORE (bit 3) via ICR on newer peripherals when set
- Adding errClearReg field to UART struct, set to &Bus.ICR in
  setRegisters() for all ICR-capable families
- Preserving the SR+DR clearing sequence for older F1/F4 families

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-15 15:42:06 +01:00

111 lines
3.0 KiB
Go

//go:build stm32
package machine
// Peripheral abstraction layer for UARTs on the stm32 family (except stm32g0).
import (
"device/stm32"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
// UART representation
type UART struct {
Buffer *RingBuffer
Bus *stm32.USART_Type
Interrupt interrupt.Interrupt
TxAltFuncSelector uint8
RxAltFuncSelector uint8
// Registers specific to the chip
rxReg *volatile.Register32
txReg *volatile.Register32
statusReg *volatile.Register32
txEmptyFlag uint32
// errClearReg points to the ICR register on newer STM32 USART peripherals
// (L0, L4, L5, G0, F7, U5, WL, etc.) for clearing error flags. Nil for
// older peripherals (F1, F4) where errors are cleared by reading SR+DR.
errClearReg *volatile.Register32
}
// Configure the UART.
func (uart *UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Set the GPIO pins to defaults if they're not set
if config.TX == 0 && config.RX == 0 {
config.TX = UART_TX_PIN
config.RX = UART_RX_PIN
}
// STM32 families have different, but compatible, registers for
// basic UART functions. For each family populate the registers
// into `uart`.
uart.setRegisters()
// Enable USART clock
enableAltFuncClock(unsafe.Pointer(uart.Bus))
uart.configurePins(config)
// Set baud rate
uart.SetBaudRate(config.BaudRate)
// Enable USART port, tx, rx and rx interrupts
uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ
uart.Interrupt.SetPriority(0xc0)
uart.Interrupt.Enable()
}
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
s := uart.statusReg.Get()
// Only read data when RXNE/RXFNE (bit 5) is set. On all STM32 families,
// RXNEIE enables both the RX-data-ready and overrun-error (ORE) interrupts.
// Without this check, an ORE-only interrupt reads garbage from RDR.
if s&0x20 != 0 { // RXNE / RXFNE
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
}
// Clear overrun error (ORE, bit 3) to prevent an interrupt storm.
if s&0x8 != 0 {
if uart.errClearReg != nil {
// Newer USART peripherals: clear ORE via the ICR register.
uart.errClearReg.Set(0x8) // ORECF
} else if s&0x20 == 0 {
// Older USART (F1/F4): ORE is 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.
uart.rxReg.Get()
}
}
}
// 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))
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
}
return nil
}
func (uart *UART) flush() {}