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>
This commit is contained in:
deadprogram
2026-04-15 14:24:37 +02:00
committed by Ron Evans
parent 0755145824
commit 4c3680635e
8 changed files with 33 additions and 1 deletions
+26 -1
View File
@@ -24,6 +24,10 @@ type UART struct {
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.
@@ -63,7 +67,28 @@ func (uart *UART) Configure(config UARTConfig) {
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
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
+1
View File
@@ -46,6 +46,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- I2C related code
+1
View File
@@ -232,6 +232,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- SPI related types and code
+1
View File
@@ -224,6 +224,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- SPI related types and code
+1
View File
@@ -299,6 +299,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- SPI related types and code
+1
View File
@@ -44,6 +44,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- I2C related code
+1
View File
@@ -67,6 +67,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
uart.errClearReg = &uart.Bus.ICR
}
//---------- SPI related types and code
+1
View File
@@ -331,6 +331,7 @@ func (uart *UART) setRegisters() {
uart.txReg = &uart.Bus.TDR
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXFNF //(TXFNF == TXE == bit 7, but depends alternate RM0461/1094)
uart.errClearReg = &uart.Bus.ICR
}
//---------- Timer related code