From 4c3680635ef62d30576facc2cabc1a88b179ba6d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 15 Apr 2026 14:24:37 +0200 Subject: [PATCH] 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 --- src/machine/machine_stm32_uart.go | 27 ++++++++++++++++++++++++++- src/machine/machine_stm32f7x2.go | 1 + src/machine/machine_stm32g0.go | 1 + src/machine/machine_stm32l0.go | 1 + src/machine/machine_stm32l4.go | 1 + src/machine/machine_stm32l5x2.go | 1 + src/machine/machine_stm32u585.go | 1 + src/machine/machine_stm32wlx.go | 1 + 8 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_stm32_uart.go b/src/machine/machine_stm32_uart.go index 6beafe4e1..10cf7d87b 100644 --- a/src/machine/machine_stm32_uart.go +++ b/src/machine/machine_stm32_uart.go @@ -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 diff --git a/src/machine/machine_stm32f7x2.go b/src/machine/machine_stm32f7x2.go index 7da407071..350b5fd7b 100644 --- a/src/machine/machine_stm32f7x2.go +++ b/src/machine/machine_stm32f7x2.go @@ -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 diff --git a/src/machine/machine_stm32g0.go b/src/machine/machine_stm32g0.go index 7390aed6c..fe89edb9e 100644 --- a/src/machine/machine_stm32g0.go +++ b/src/machine/machine_stm32g0.go @@ -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 diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index 1ecd958b8..4903bbea6 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -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 diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index 8453b2829..6e7e5fe9e 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -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 diff --git a/src/machine/machine_stm32l5x2.go b/src/machine/machine_stm32l5x2.go index 82ca1ecf6..f442e9c0c 100644 --- a/src/machine/machine_stm32l5x2.go +++ b/src/machine/machine_stm32l5x2.go @@ -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 diff --git a/src/machine/machine_stm32u585.go b/src/machine/machine_stm32u585.go index 5492e14e6..fcb4c0152 100644 --- a/src/machine/machine_stm32u585.go +++ b/src/machine/machine_stm32u585.go @@ -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 diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index b94e551ff..c229f0322 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -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