Electronics Design AU
Embedded Systems

How Does DMA (Direct Memory Access) Work in Embedded Systems?

Last updated 8 July 2026 · 6 min read

Direct Answer

Direct Memory Access (DMA) is a hardware controller built into most microcontrollers that moves blocks of data between memory and peripherals (or between two memory locations) without the CPU executing an instruction for each byte or word transferred. Firmware configures a DMA channel once — source address, destination address, transfer count, and trigger source — then the DMA controller runs the transfer autonomously in the background, freeing the CPU core to execute other code and typically raising just one interrupt (or none, if polled) when the transfer completes, instead of one interrupt or one CPU cycle per data item.

Detailed Explanation

Most peripherals on a microcontroller — UART, SPI, I2C, ADC, DAC, I2S — ultimately move data one register-width at a time through a data register. Without DMA, firmware either polls that register in a loop or services an interrupt for every single byte or sample, both of which consume CPU cycles proportional to the data volume. A UART receiving at 115200 baud generates roughly 11,500 bytes per second; at higher data rates — a fast SPI link, or an ADC sampling continuously — byte-at-a-time interrupt servicing can consume a meaningful fraction of the CPU's total cycle budget just moving data, leaving less for the rest of the application.

DMA solves this by giving a dedicated hardware block direct access to the system's memory bus. Firmware configures a DMA channel with:

  1. Source address — where data comes from (a peripheral's data register, or a memory buffer).
  2. Destination address — where data goes (a memory buffer, or a peripheral's data register).
  3. Transfer count — how many items to move.
  4. Trigger source — what starts each transfer step: a peripheral's "data ready" signal, a timer event, or an immediate memory-to-memory start.

Once started, the DMA controller moves data autonomously on its own bus cycles, in parallel with the CPU core continuing to execute other instructions. The CPU is typically only interrupted once — when the whole transfer (or, in circular mode, each half or full pass) completes — rather than once per item.

Transfer Directions

DMA channels are commonly used in three directions:

  • Peripheral to memory — the most common case, such as capturing ADC samples or UART receive bytes directly into a RAM buffer as they arrive.
  • Memory to peripheral — streaming a buffer out to a UART transmitter, an SPI peripheral, or a DAC without the CPU touching each outgoing byte.
  • Memory to memory — copying or rearranging data entirely within RAM (or between RAM and flash, where the architecture permits), useful for tasks like fast buffer copies without looping CPU instructions.

Normal vs Circular (Continuous) Transfer Modes

A normal (one-shot) transfer moves a fixed number of items and then stops, raising a completion interrupt or flag. This suits bounded transactions — sending one message, reading one block of samples.

A circular transfer treats the destination (or source) buffer as a ring: once the DMA controller reaches the end of the configured buffer, it wraps back to the start and continues indefinitely, without firmware re-arming the channel for each pass. This is the standard pattern for continuous data capture — a UART receive ring buffer, or continuous ADC sampling — where firmware processes data out of the buffer while the DMA controller keeps filling it. Most DMA controllers that support circular mode also raise a "half transfer complete" interrupt partway through the buffer, in addition to the "transfer complete" interrupt at the wrap point, so firmware can process the buffer in two halves without ever reading data the DMA controller is actively writing to.

Double Buffering

An extension of circular mode, double buffering (also called ping-pong buffering) uses two separate buffers: while the DMA controller fills one, firmware processes the other, then the roles swap on each completion interrupt. This avoids the read/write race that can occur with a single circular buffer under heavy processing load, at the cost of needing twice the buffer memory.

Priority and Arbitration

When multiple DMA channels (or a DMA channel and the CPU core) contend for the same bus at the same instant, a priority/arbitration scheme built into the DMA controller decides which transfer proceeds first. Most controllers expose a configurable priority level per channel (commonly a small number of priority tiers) so that latency-sensitive transfers — audio sample streaming, for instance — can be given precedence over lower-priority bulk transfers, such as a background flash-to-flash copy.

Practical Examples

A UART receiving variable-length, asynchronous messages (a common pattern for command interfaces or GPS module output) is typically configured with circular DMA into a ring buffer sized generously larger than the largest expected message; firmware then scans the buffer for message delimiters on an idle-line interrupt or timer tick, without ever missing a byte to a busy CPU.

Continuous ADC sampling for audio or vibration analysis is another canonical case: a timer triggers each ADC conversion, and circular DMA writes each result directly into a buffer, with the half-transfer and full-transfer interrupts used to process each half of the buffer (often applying a filter or FFT) while the other half continues filling — the ping-pong pattern described above, without needing two literally separate buffers.

Design Considerations

  • Cache coherency matters on cache-enabled cores. On MCUs with a data cache between the CPU and RAM (higher-end Cortex-M7 parts, for example), the DMA controller reads and writes RAM directly, bypassing the cache — a CPU-written buffer may sit in cache without having reached RAM yet when DMA reads it, or DMA-written data may not be visible to the CPU until the corresponding cache line is invalidated. See STM32 HAL DMA configuration for the specific cache-maintenance calls this requires on affected STM32 families.
  • Buffer alignment and placement requirements are architecture-specific. Some DMA controllers require buffers to be aligned to a specific byte boundary, or restrict DMA-accessible buffers to particular RAM banks — check the reference manual before placing a DMA buffer via a generic linker section.
  • A completion interrupt handler should do the minimum necessary work. As with any ISR (see interrupts in embedded systems), a DMA completion handler should set a flag, post to a queue, or copy a small amount of data, then return — not process the full buffer synchronously inside the interrupt context.
  • DMA is not a substitute for understanding peripheral timing. A DMA-fed UART transmit still runs at the configured baud rate; a DMA-fed ADC still samples no faster than its configured trigger. DMA changes who manages the transfer, not the underlying peripheral throughput.

For embedded firmware moving high-throughput sensor, audio, or communication data reliably, Zeus Design's firmware team designs peripheral and memory architectures — including DMA channel allocation and buffering strategy — as part of production embedded firmware development.

Common Mistakes

  • Reading from a circular DMA buffer at an arbitrary point instead of using the half/full-transfer interrupts to bound safe read regions — this risks reading data the DMA controller is actively overwriting, producing intermittent, hard-to-reproduce data corruption.
  • Forgetting cache maintenance on cache-enabled MCUs, producing a bug where DMA appears to transfer the wrong (stale) data intermittently — a classic silent-corruption failure mode that doesn't show up in every build or every run.
  • Assigning the same DMA priority to a latency-sensitive stream and a large background bulk transfer, causing the time-critical stream to stall while the bulk transfer holds the bus.
  • Placing a DMA buffer in a RAM region the assigned DMA channel can't actually reach, which on some parts fails silently or produces a bus fault rather than an obvious configuration error — always confirm which SRAM banks a given DMA controller and channel can address for the specific part in use.

Frequently Asked Questions

Does using DMA make firmware run faster overall?
It depends on what was previously bottlenecking the CPU. DMA doesn't move data any faster than the underlying peripheral and bus allow — a UART still transmits at its configured baud rate whether the CPU or DMA feeds the transmit register. What DMA improves is CPU availability: cycles that would otherwise be spent in a tight polling loop or servicing one interrupt per byte are freed for other work, which often translates into lower overall latency and power consumption for the system as a whole, even though the transfer itself isn't faster.
Can DMA access any memory address?
Not always. On some MCU architectures, DMA controllers can only reach certain memory regions or specific SRAM banks — for example, a DMA channel tied to a peripheral on one AHB bus segment may be unable to reach RAM located on a different bus segment without a bus matrix connection, and some low-power SRAM banks are excluded from DMA access entirely on certain parts. Always check the specific MCU's reference manual for which memories each DMA controller and channel can address before placing a DMA buffer in an arbitrary linker section.
What is the difference between DMA and a PIO-style peripheral like the RP2040 PIO or ESP32 RMT?
DMA moves already-formatted data between memory and a peripheral's data register — it doesn't generate or interpret protocol timing itself. A peripheral like the [RP2040/RP2350 PIO](/questions/rp2040-pio-programmable-io) or the [ESP32 RMT](/questions/esp32-rmt-ws2812-ir-peripheral) is a small programmable hardware sequencer that generates or captures precisely-timed waveforms; these are often paired with DMA so the sequencer's FIFO is fed or drained without CPU involvement, but the sequencer and the DMA controller solve different problems.

References

Related Questions

Related Forum Discussions