DMA‑Enabled UART Transmission in Embedded C – STM32 Guide
Offload UART transmit to DMA on STM32 with a practical C example, configuration steps, common pitfalls, and verification methods to free CPU cycles and enable low‑power modes.
30 Dec 2025, 07:37 UTC

Why Use DMA for UART Transmission?
When a microcontroller must keep the CPU free for other tasks or enter deep‑sleep, a DMA‑backed UART TX is a common solution. DMA (Direct Memory Access) transfers data directly from a memory buffer to the UART peripheral’s data register (DR) without CPU intervention. The CPU can start the transfer, then resume normal operation while the DMA engine handles the byte‑by‑byte writes.
Configuring DMA for UART Transmission
On an STM32 family MCU (e.g., STM32F4), the DMA controller is split into streams and channels. For UART1 TX, DMA1 Stream 4 Channel 4 is typically used. The configuration steps are:
- Enable clocks for UART1, DMA1, and the SYSCFG peripheral.
- Set the DMA stream’s peripheral address to
UART1->DRand memory address to the data buffer. - Configure the stream for
MEMORY_TO_PERIPHdirection, enable circular mode if continuous streaming is required, and set the data size (byte, half‑word, word). - Enable transfer‑complete and error interrupts for the DMA stream.
- Clear any pending DMA flags before starting the transfer.
- Start the DMA stream and enable UART TX DMA request.
Concrete Example on STM32F4
Below is a minimal, self‑contained C snippet that demonstrates the above steps. Replace UART1 and buffer names with your own identifiers.
#include <stm32f4xx.h>
/* Buffer that will be sent via UART */
uint8_t txBuffer[] = {
0x55, 0xAA, 0x33, 0xCC, 0xFF, 0x00, 0x11, 0x22
};
void UART1_DMA_TX_Init(void) {
/* 1. Enable clocks */
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DMA1 clock
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // UART1 clock
RCC->AHB1ENR |= RCC_AHB1ENR_SYSCFGEN; // SYSCFG for DMA request mapping
/* 2. Configure DMA stream 4 (UART1_TX) */
DMA1_Stream4->CR = 0; // Reset control register
/* 2a. Peripheral address: UART1 DR */
DMA1_Stream4->PAR = (uint32_t)&(UART1->DR);
/* 2b. Memory address: txBuffer */
DMA1_Stream4->M0AR = (uint32_t)txBuffer;
/* 2c. Number of data items */
DMA1_Stream4->NDTR = sizeof(txBuffer); // 8 bytes
/* 2d. Control register settings */
DMA1_Stream4->CR =
DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | // Channel 4
DMA_SxCR_PL_1 | // Priority: Medium
DMA_SxCR_MSIZE_0 | // Memory size: 8‑bit
DMA_SxCR_PSIZE_0 | // Peripheral size: 8‑bit
DMA_SxCR_MINC | // Memory increment
DMA_SxCR_TCIE | // Transfer complete interrupt
DMA_SxCR_TEIE | // Transfer error interrupt
DMA_SxCR_DIR_0; // Memory to peripheral
/* 3. Enable DMA request from UART1 TX */
UART1->CR3 |= USART_CR3_DMAT;
/* 4. Enable DMA stream */
DMA1_Stream4->CR |= DMA_SxCR_EN;
/* 5. Enable NVIC interrupt for DMA1 Stream 4 */
NVIC_SetPriority(DMA1_Stream4_IRQn, 5);
NVIC_EnableIRQ(DMA1_Stream4_IRQn);
}
/* DMA interrupt handler */
void DMA1_Stream4_IRQHandler(void) {
if (DMA1->HISR & DMA_HISR_TCIF4) {
/* Transfer complete */
DMA1->HIFCR |= DMA_HIFCR_CTCIF4; // Clear flag
/* Optionally disable stream if not circular */
DMA1_Stream4->CR &= ~DMA_SxCR_EN;
}
if (DMA1->HISR & DMA_HISR_TEIF4) {
/* Transfer error */
DMA1->HIFCR |= DMA_HIFCR_CTEIF4;
}
}
Key points in the code:
- The DMA stream’s peripheral address is set to the UART data register; the memory address points to the buffer.
- Direction is
MEMORY_TO_PERIPH(bitDIR_0). - Both memory and peripheral sizes are 8‑bit because UART DR is 8‑bit wide.
- Increment mode (
MINC) allows the DMA to step through the buffer. - Transfer‑complete and error interrupts are enabled so the CPU knows when the burst ends.
- After starting the stream, the UART TX DMA request bit (
DMAT) is set.
Common Pitfalls
- Misaligned buffer: DMA requires 32‑bit alignment for the memory address on some STM32 parts. Use
__attribute__((aligned(4)))if needed. - Clock not enabled: Forgetting to enable the DMA clock or the UART peripheral clock will leave registers inaccessible.
- Clearing the flag too late: If you clear the transfer‑complete flag after the DMA has already re‑enabled itself (e.g., in circular mode), you may lose the interrupt.
- Buffer lifetime: The buffer must remain valid until the DMA completes. Do not free or overwrite it in the meantime.
- Interrupt priority: If the DMA interrupt priority is lower than other UART interrupts, a race can occur. Set a higher priority for DMA.
- DMA request line masked: Some MCUs require enabling the DMA request line in the SYSCFG or peripheral registers. Verify the request mapping for your device.
Limits and When DMA Is Not Enough
DMA can only transfer up to the UART’s FIFO depth in a single burst. On STM32F4, the UART FIFO holds 16 bytes. If you need to send more than that continuously, you must use double‑buffering or circular mode to avoid gaps. For very high‑speed streams, consider a hardware FIFO or a dedicated UART DMA channel that supports burst transfers.
Another limitation is that DMA can’t handle UART framing errors or time‑outs. If you need error detection, you must still monitor UART status registers or use a UART interrupt in parallel.
Verification Strategies
- Loopback Test: Connect TX to RX on the same MCU or a second UART. Send a known pattern via DMA and read back via UART RX interrupt. Verify that the received buffer matches the sent data.
- Logic Analyzer: Capture the TX line while the CPU is idle. The analyzer should show continuous UART frames and no CPU activity during the DMA burst.
- Flag Checks: After the DMA transfer, read
DMAx->HISRforTCIFandTEIF. A cleared error flag indicates a clean transfer. - CPU Load Measurement: Use a profiler or a simple cycle counter to compare CPU usage during a blocking UART send versus a DMA send. A noticeable drop confirms the benefit.
Conclusion
DMA‑based UART transmission is a proven technique to free the CPU on STM32 MCUs. By carefully configuring the stream, ensuring the buffer’s lifetime, and handling interrupts correctly, you can achieve efficient, low‑latency data transfer. Remember to verify with a loopback test and monitor DMA flags to catch any hidden errors early.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.