📚 CMPE2250: Lesson — Introducing DMA with UART (Based on RM0444, Chapter 10)
UART by itself is simple: it sends and receives bytes through a shift register. But without DMA, the CPU must service every byte through interrupts or polling. That’s fine for low throughput, but it collapses when:
- Bursts of data arrive unpredictably
- The CPU is busy with timing-sensitive tasks
To understand real embedded workflows, it is required:
- Deterministic latency
DMA solves this by autonomously moving data between UART and memory.
🎯 Learning Objectives
By the end of this lesson students should be able to:
-
Describe how the DMA controller autonomously transfers UART data to memory.
-
Configure a DMA channel for UART reception, explain the role of key DMA parameters (direction, increment modes, data width, request mapping).
-
Verify correct operation by observing buffer behavior and DMA status registers.
-
Use the DMA controller in conjunction with the
RTOUSART feature.
1️⃣ What DMA actually does (RM0444 Ch. 10 distilled)
DMA is a hardware engine that performs memory transfers without CPU intervention.
For UART, the relevant transfer types are:
-
Peripheral → Memory- Used for UART reception.- DMA reads from the UART RDR register and writes into a buffer.
-
Memory → Peripheral- Used for UART transmission.- DMA reads from a memory buffer and writes into UART TDR.
-
Peripheral → Peripheral- Used for UART passthrough, for instance.
2️⃣ Key DMA concepts from Chapter 10
These are the ones students must internalize:
| Concept | Meaning |
|---|---|
| Channel | A DMA “pipe” that moves data for one peripheral request. |
| Request mapping | UART RX/TX are mapped to specific DMA channels. |
| Direction | P→M for RX, M→P for TX. |
| Increment mode | Memory increments, peripheral does not. |
| Data width | UART is 8‑bit, so byte transfers. |
| Circular mode | Ideal for continuous UART reception. |
| Transfer complete / half‑transfer flags | Used for buffer management and verification. |
3️⃣ Minimal mental model
I like to frame DMA as a robot assistant:
- UART says: “I have a byte ready.”
- DMA robot hears the request.
- DMA robot picks up the byte from RDR.
- DMA robot places it into the next memory slot.
- CPU only checks in occasionally (“robot, how full is the buffer?”).
4️⃣ The simplest useful scenario: UART RX with DMA
Workflow
-
Configure UART normally (baud, pins, enable).
-
Enable DMA clock. (e.g.
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Enable DMA controller 1) -
Select the DMA channel mapped to UART RX. (e.g.
DMA1_Channel1) -
Set:
-
Direction ->
peripheral → memory -
Memory increment = enabled (
DMAChannel->CCR |= DMA_CCR_MINC;) -
Peripheral increment = disabled (
DMAChannel->CCR &= ~DMA_CCR_PINC;) -
Data width = byte (
MSIZEandPSIZEinDMAChannel->CCR) -
Circular mode = optional (For later)
-
Set memory address (
DMAChannel->CMAR = (uint32_t)memAddr;) -
Set peripheral address (
DMAChannel->CPAR = (uint32_t)perAddr;) -
Configure the
DMAMUX(New feature in new STM32 micros)-
(e.g.
DMAMUX1_Channel0->CCR = 52; // Table 55, RM 11.3.2)
-
-
Start transfer
-
Disable channel (
DMAChannel->CCR &= ~DMA_CCR_EN) -
Set transfer size (
DMAChannel->CNDTR = size;) -
Enable channel (
DMAChannel->CCR |= DMA_CCR_EN)
-
-
Enable UART DMA reception (Set
DMARinUSART->CR3).
Now UART will continuously fill the buffer.
-
4.1 DMAMUX table
| DMA Request MUX Input | Resource | DMA Request MUX Input | Resource | DMA Request MUX Input | Resource |
|---|---|---|---|---|---|
| 1 | dmamux_req_gen0 | 27 | TIM2_CH2 | 53 | USART2_TX |
| 2 | dmamux_req_gen1 | 28 | TIM2_CH3 | 54 | USART3_RX |
| 3 | dmamux_req_gen2 | 29 | TIM2_CH4 | 55 | USART3_TX |
| 4 | dmamux_req_gen3 | 30 | TIM2_TRIG | 56 | USART4_RX |
| 5 | ADC | 31 | TIM2_UP | 57 | USART4_TX |
| 6 | AES_IN | 32 | TIM3_CH1 | 58 | UCPD1_RX |
| 7 | AES_OUT | 33 | TIM3_CH2 | 59 | UCPD1_TX |
| 8 | DAC_Channel1 | 34 | TIM3_CH3 | 60 | UCPD2_RX |
| 9 | DAC_Channel2 | 35 | TIM3_CH4 | 61 | UCPD2_TX |
| 10 | I2C1_RX | 36 | TIM3_TRIG | 62 | I2C3_RX |
| 11 | I2C1_TX | 37 | TIM3_UP | 63 | I2C3_TX |
| 12 | I2C2_RX | 38 | TIM6_UP | 64 | LPUART2_RX |
| 13 | I2C2_TX | 39 | TIM7_UP | 65 | LPUART2_TX |
| 14 | LPUART_RX | 40 | TIM15_CH1 | 66 | SPI3_RX |
| 15 | LPUART_TX | 41 | TIM15_CH2 | 67 | SPI3_TX |
| 16 | SPI1_RX | 42 | TIM15_TRIG_COM | 68 | TIM4_CH1 |
| 17 | SPI1_TX | 43 | TIM15_UP | 69 | TIM4_CH2 |
| 18 | SPI2_RX | 44 | TIM16_CH1 | 70 | TIM4_CH3 |
| 19 | SPI2_TX | 45 | TIM16_COM | 71 | TIM4_CH4 |
| 20 | TIM1_CH1 | 46 | TIM16_UP | 72 | TIM4_TRIG |
| 21 | TIM1_CH2 | 47 | TIM17_CH1 | 73 | TIM4_UP |
| 22 | TIM1_CH3 | 48 | TIM17_COM | 74 | USART5_RX |
| 23 | TIM1_CH4 | 49 | TIM17_UP | 75 | USART5_TX |
| 24 | TIM1_TRIG_COM | 50 | USART1_RX | 76 | USART6_RX |
| 25 | TIM1_UP | 51 | USART1_TX | 77 | USART6_TX |
| 26 | TIM2_CH1 | 52 | USART2_RX | — | — |
How to verify it’s working
- Check the DMA NDTR register
- Watch the remaining transfer count decrease and wrap around.
-
Print the buffer over UART TX -A loopback demonstration is extremely satisfying.
- Toggle a GPIO on half-transfer / transfer-complete interrupts
- Great for oscilloscope validation.
- Inject known patterns
- e.g., send “AAAAABBBBBCCCCCDDDD” and watch the buffer fill.
- Conceptual diagram
flowchart TD
UART[UART RX / Receives bytes] --> RDR[UART RDR Register]
RDR -- DMA request --> DMA[DMA Channel\nP→M Transfer Engine]
DMA --> BUFFER[Buffer in RAM]
5️⃣ Where we can go next
-
What happens if the RX buffer overflows? Can we catch this?
-
How to add UART TX with DMA
-
Explore DMA interrupts
-
Compare DMA vs. interrupt-driven UART