Why is DMA preferred over interrupt-driven UART reception, and how do you set it up?
Interrupt-driven UART reception fires an ISR for every single byte received. At 115200 baud, that is up to 11520 interrupts per second — each one requiring context save/restore, flag checking, and a byte copy. At higher baud rates (921600+), the interrupt overhead can consume a significant fraction of CPU time, especially on lower-end Cortex-M0 cores. Worse, if interrupts are briefly disabled (during a flash erase, for example), bytes are lost to overrun errors.
DMA (Direct Memory Access) solves both problems. The DMA controller transfers received bytes directly from the UART data register into a RAM buffer without CPU intervention. The CPU is only interrupted when the buffer is half-full, completely full, or when the UART detects an idle line — reducing interrupt frequency from thousands per second to a handful.
A typical STM32 setup uses DMA in circular mode with a buffer of 64-256 bytes. The DMA controller writes to the buffer continuously, wrapping around to the beginning when it reaches the end. Firmware tracks a read index (tail pointer) and compares it against the DMA's current write position (obtained from the DMA NDTR register) to determine how many new bytes are available. The idle-line interrupt is critical for protocols where messages are separated by pauses — it tells firmware "the transmitter stopped sending, process what you have" even if the buffer is not full.
// Pseudo-code: checking for new data in DMA circular bufferuint16_t dma_head = BUFFER_SIZE - DMA1_Channel5->CNDTR;while (uart_tail != dma_head) {process_byte(rx_buffer[uart_tail]);uart_tail = (uart_tail + 1) % BUFFER_SIZE;}
Source: UART Q&A
