Ground Embedded KO Subscribe

Notes / Computer Architecture

DMA: the worker that moves data without the CPU

Hardware takes over copying between memory and peripherals. How ADC sampling, UART reception and SPI display transfers get faster.

GROUND EMBEDDED · NOTEComputer Architecture 09DMAMCU

Receive 1000 bytes over UART and the interrupt fires 1000 times. With DMA you configure once, "move 1000 bytes from the UART data register to this buffer", and get a single interrupt when it is finished.

PeripheralADC, UARTDMAthe moverMemory bufferNotify the CPU when full
With DMA, data goes straight to memory without passing through the CPU.

Values to configure

  • Source and destination addresses: for example &USART1->DR → rx_buf
  • Transfer count: 1000
  • Increment: the peripheral address is fixed, the memory address increases by 1
  • Data size: byte, half-word, word
  • Trigger: which peripheral event moves one item each time
  • Mode: once (normal), or wrap to the start and repeat (circular)

Common combinations

Combination Effect
Timer → ADC → DMA Samples stacked in a buffer at an exact period
UART receive + circular DMA + IDLE interrupt Receiving packets of unknown length
Memory → SPI Sending a display frame

Cautions

  • If the CPU reads a buffer DMA is writing, it sees half-updated data. Use double buffering, splitting the buffer in two with the half-complete interrupt.
  • On cores with a cache (M7), cache cleaning and invalidation are needed.

It takes over the copying the CPU was doing

When receiving 1000 bytes over UART, the interrupt method calls the handler 1000 times. With DMA, hardware moves each byte straight to the buffer as it arrives, and the CPU is notified just once, when everything is done.

Polling Interrupt DMA
CPU use Waits the whole time Interrupted for every byte Once at the end
Fast data Easily missed Missed if the handler is delayed Copes well
Set-up Easiest Medium Most complex
Suited to Slow, infrequent work Byte-by-byte handling Long buffers, high speeds

Five things the set-up needs

  1. Source: the address of the peripheral's data register (for example &USART1->RDR)
  2. Destination: the address of a buffer in RAM
  3. Count: the number of items to move
  4. Direction and size: peripheral → memory or the reverse, and how many bits at a time
  5. Address increment: increasing on the buffer side, fixed on the peripheral side

Setting the peripheral-side address to increment is a common mistake. The data register is always at the same address, so it must be fixed.

RoadmapSee where this note sits in Computer Architecture→

Receiving UART in circular mode

static uint8_t rx_buf[256];
// DMA configured in circular mode: when full, it starts filling again from the beginning

uint16_t rx_tail;
void uart_poll(void) {                        // from the main loop or a 1 ms tick
    uint16_t head = 256u - dma_remaining();   // where DMA has written up to
    while (rx_tail != head) {
        process_byte(rx_buf[rx_tail]);
        rx_tail = (rx_tail + 1u) & 255u;
    }
}

The position DMA is writing to acts as the head of a ring buffer. No bytes are lost even with no interrupts at all. For packets of varying length, combine it with the UART IDLE interrupt (the line has been quiet for one character time) to know where a packet ends.

Several ADC channels at once

Put the ADC in scan mode and connect DMA, and the values fill an array in channel order.

volatile uint16_t adc_val[4];   // [0]=current A, [1]=current B, [2]=voltage, [3]=temperature

With a timer triggering the ADC and DMA moving the results, the code only has to read the array.

Seamless handling with the half-complete interrupt

For data that flows continuously, such as audio, split the buffer in two. Process the front half on the notification that it is full, and the back half on the notification that the end was reached. The half DMA is writing and the half the code is reading never overlap.

Problems that come up in practice

  • The buffer is a local variable: when the function ends the stack is reused, but DMA keeps writing to that address. Make the buffer global or static.
  • MCUs with a cache (Cortex-M7): the memory DMA changed and the cache contents diverge. Align the buffer to cache lines and invalidate, or place it in a non-cached region.
  • Channel conflicts: the DMA channels each peripheral can use are fixed, and two may want the same one. Check with a table early in the design.
  • Changing settings during a transfer: always disable the channel, change it, then enable it again.
  • Error flags not handled: a transfer error stops the channel. Restart it in the error interrupt.
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →