Imagine you're a manager (the CPU ) and a truckload of boxes (data) needs to move from the loading dock (a peripheral) to the warehouse shelves (memory). You could carry every box yourself, one at a time — but then you can't do anything else. Instead you hire a dedicated mover (the DMA controller ) who copies boxes directly while you go do real work. That mover is DMA: Direct Memory Access — a hardware engine that shuffles bytes between addresses without the CPU touching each one.
DMA (Direct Memory Access) is a hardware controller that performs data transfers between two memory regions, or between a peripheral and memory, independently of the CPU . The CPU only programs the transfer (source, destination, count, mode) and then the DMA engine moves each word, signalling an interrupt when done.
The three canonical transfer directions:
Direction
Source
Destination
Typical use
Mem-to-Mem
RAM
RAM
memcpy acceleration, framebuffer copy
Periph-to-Mem
UART/ADC/SPI data register
RAM buffer
logging sensor samples
Mem-to-Periph
RAM buffer
DAC/SPI/UART data register
streaming audio out
Intuition Why not just let the CPU copy?
A CPU for loop copying N N N words executes load → store per word, plus loop overhead, and it cannot do anything else meanwhile. For a 1 MB framebuffer that's millions of cycles wasted on plumbing. DMA does the same load/store in dedicated silicon, freeing the CPU to compute. It also enables deterministic, low-latency capture of fast peripheral data the CPU might otherwise miss (e.g., an ADC firing every microsecond).
The deep win is parallelism : while DMA owns the bus moving data, the CPU runs from cache/instruction memory.
A transfer is fully described by 4 things you must set:
Source address S S S
Destination address D D D
Count N N N (number of items)
Item width w w w (bytes per item: 1, 2, or 4)
Total bytes moved: B = N ⋅ w B = N \cdot w B = N ⋅ w .
Each DMA "beat" moves one item and consumes some bus cycles. Let:
f f f = bus clock frequency (Hz)
c c c = bus cycles per beat (typically 1 for fast SRAM, more if a peripheral inserts wait states)
Time per beat:
t beat = c f t_{\text{beat}} = \frac{c}{f} t beat = f c
Total transfer time (ignoring setup):
t DMA = N ⋅ t beat = N c f \boxed{t_{\text{DMA}} = N \cdot t_{\text{beat}} = \frac{N\,c}{f}} t DMA = N ⋅ t beat = f N c
Per beat, the controller updates addresses:
S i + 1 = S i + Δ S , D i + 1 = D i + Δ D S_{i+1} = S_i + \Delta_S, \qquad D_{i+1} = D_i + \Delta_D S i + 1 = S i + Δ S , D i + 1 = D i + Δ D
Mem-to-mem : Δ S = Δ D = w \Delta_S = \Delta_D = w Δ S = Δ D = w (both increment).
Periph-to-mem : Δ S = 0 \Delta_S = 0 Δ S = 0 (fixed peripheral register), Δ D = w \Delta_D = w Δ D = w (buffer fills).
Mem-to-periph : Δ S = w \Delta_S = w Δ S = w , Δ D = 0 \Delta_D = 0 Δ D = 0 .
Intuition Why fix the peripheral address?
A peripheral's data register is a single hardware address (e.g., UART->DR). New bytes appear at the same address each time the peripheral is ready. So you read repeatedly from one spot, but scatter into successive RAM slots.
Mem-to-mem is usually software-triggered : program it, set the enable bit, it runs full speed.
Periph transfers are hardware-triggered : the peripheral asserts a DMA request line each time it has/needs one item (e.g., "ADC conversion ready"). DMA performs one beat per request → naturally rate-matched.
Intuition Bus arbitration ("cycle stealing")
The DMA and CPU share one memory bus — only one can drive it at a time. An arbiter grants the bus. In cycle-stealing mode DMA grabs the bus for one beat, then releases it so the CPU isn't starved. In burst mode DMA holds the bus for the whole block (faster, but can stall the CPU). It's a latency vs. throughput trade-off.
Worked example 1 — Throughput of an ADC capture
A 12-bit ADC stores into 16-bit (2-byte) RAM slots, periph-to-mem, N = 1000 N = 1000 N = 1000 samples, bus f = 48 f = 48 f = 48 MHz, c = 1 c = 1 c = 1 cycle/beat.
t DMA = N c / f = 1000 / 48,000,000 ≈ 20.8 μ s t_{\text{DMA}} = N c / f = 1000 / 48{,}000{,}000 \approx 20.8\ \mu s t DMA = N c / f = 1000/48 , 000 , 000 ≈ 20.8 μ s . Why this step? Each beat = 1 cycle, 1000 beats.
Throughput = w f / c = 2 × 48 e6 = 96 = w f / c = 2 \times 48\text{e6} = 96 = w f / c = 2 × 48 e6 = 96 MB/s. Why? 2 bytes per beat × 48M beats/s.
CPU saved (at k = 4 k=4 k = 4 cyc/word): 1000 × 4 = 4000 1000 \times 4 = 4000 1000 × 4 = 4000 cycles ≈ 83 μ s 83\ \mu s 83 μ s of CPU time freed.
Worked example 2 — Mem-to-mem copy of an array
Copy 256 words (w = 4 w=4 w = 4 ), f = 72 f=72 f = 72 MHz, c = 1 c=1 c = 1 .
B = 256 × 4 = 1024 B = 256 \times 4 = 1024 B = 256 × 4 = 1024 bytes. Why? count × width.
t = 256 / 72 e 6 ≈ 3.56 μ s t = 256/72\text{e}6 \approx 3.56\ \mu s t = 256/72 e 6 ≈ 3.56 μ s .
Δ S = Δ D = 4 \Delta_S = \Delta_D = 4 Δ S = Δ D = 4 each beat (both increment). Why? mem-to-mem walks both buffers forward.
Worked example 3 — Choosing item width
You must move 4096 bytes. Compare w = 1 w=1 w = 1 (N = 4096 N=4096 N = 4096 ) vs w = 4 w=4 w = 4 (N = 1024 N=1024 N = 1024 ), f = 48 f=48 f = 48 MHz, c = 1 c=1 c = 1 .
w = 1 w=1 w = 1 : t = 4096 / 48 e 6 = 85.3 μ s t = 4096/48\text{e}6 = 85.3\ \mu s t = 4096/48 e 6 = 85.3 μ s .
w = 4 w=4 w = 4 : t = 1024 / 48 e 6 = 21.3 μ s t = 1024/48\text{e}6 = 21.3\ \mu s t = 1024/48 e 6 = 21.3 μ s → 4× faster . Why? Fewer, wider beats move the same bytes in fewer cycles. Use the widest aligned width the buffers allow.
Common mistake "DMA is instant / free — zero cost."
Why it feels right: the CPU isn't busy, so it seems like magic. The fix: DMA still consumes bus cycles and bus bandwidth . While it holds the bus, the CPU may stall on a memory access (especially in burst mode). It's free for the CPU's compute pipeline , not free for the memory subsystem .
Common mistake "I can read the DMA buffer immediately after starting the transfer."
Why it feels right: you called the start function, so surely it's done. The fix: DMA is asynchronous . You must wait for the transfer-complete interrupt/flag before using the data. Reading early gives stale/partial bytes.
Common mistake "Cache + DMA just works."
Why it feels right: on simple MCUs there's no cache, so it does. The fix: with a data cache, DMA writes to RAM but the CPU may read a cached stale value (and vice versa). You must invalidate cache before reading DMA input, and clean/flush cache before DMA reads CPU output. This is the classic cache coherency bug.
Common mistake "Forgetting to increment the right address."
Why it feels right: mem-to-mem increments both, so you assume periph does too. The fix: a peripheral register is fixed (Δ = 0 \Delta=0 Δ = 0 ). Incrementing it makes DMA wander into random registers — silent corruption.
Recall Feynman: explain to a 12-year-old
Your computer's brain is busy thinking. Moving a giant pile of papers from one desk to another is boring busy-work. So the brain hires a robot helper (DMA): "Take papers from desk A, put them on desk B, 1000 of them, then tap me on the shoulder when you're done." The brain keeps thinking while the robot carries papers. The robot is fast and never gets bored — but both share one hallway (the bus), so sometimes the brain has to wait a moment for the robot to pass.
"S.C.A.M." to set up any DMA: S ource, C ount, A ddress-increment mode, M ode/trigger. Then enable and wait for the interrupt . (And remember: DMA is a "SCAM" because it does the work and the CPU takes the credit. 😄)
What does DMA stand for and what does it do? Direct Memory Access — a hardware controller that transfers data between memory/peripherals without the CPU handling each word.
Why use DMA instead of a CPU copy loop? Frees the CPU for compute (parallelism), gives deterministic low-latency capture, and uses dedicated hardware to move data in fewer cycles.
Formula for DMA transfer time? t = N c / f t = Nc/f t = N c / f where N=item count, c=bus cycles per beat, f=bus clock.
Formula for DMA throughput, and why doesn't N appear? Throughput = w f / c \text{Throughput}=wf/c Throughput = w f / c ; N cancels because both bytes and time scale with N, so rate depends only on width, clock, cycles/beat.
In periph-to-mem mode, what are the source and destination address increments? Source (peripheral register) Δ=0 (fixed); destination (RAM buffer) Δ=w (increments).
Why is the peripheral address fixed during transfer? A peripheral's data register is a single hardware address; new bytes appear at the same location each time it's ready.
What is cycle stealing? DMA grabs the bus for one beat then releases it, so the CPU isn't starved — trading throughput for lower CPU latency.
What must you do before READING a DMA-filled buffer on a cached CPU? Invalidate the data cache so the CPU sees fresh RAM contents, not stale cached values.
Why is DMA NOT truly "free"? It consumes bus bandwidth/cycles; while DMA holds the bus the CPU may stall on memory accesses.
When can you safely use DMA result data? Only after the transfer-complete interrupt/flag fires (DMA is asynchronous).
How does wider item width affect transfer time for fixed total bytes? Wider items → fewer beats → proportionally faster (4-byte ≈ 4× faster than 1-byte).
What triggers a peripheral-to-memory DMA beat? A hardware DMA request line asserted by the peripheral when it has data ready (rate-matched).
Interrupts and ISRs — DMA signals completion via an interrupt.
Memory-Mapped IO — peripheral data registers are fixed addresses DMA reads/writes.
Cache Coherency — the classic DMA stale-data trap.
Bus Arbitration — how CPU and DMA share one memory bus.
Circular and Double Buffering — DMA modes for continuous streaming.
ADC and DAC — common DMA-driven peripherals.
memcpy and Block Copy — mem-to-mem DMA accelerates this.
Intuition Hinglish mein samjho
Socho CPU ek manager hai jo important sochne ka kaam kar raha hai. Ab agar 1000 boxes (data) ek desk se doosre desk pe le jaane hain, to manager khud ek-ek box uthayega to uska saara time waste ho jayega. Isiliye hum ek DMA controller rakhte hain — ek dedicated hardware robot jo data ko directly source se destination tak copy karta hai, bina CPU ko har word touch kiye. CPU sirf 4 cheezein set karta hai (Source, Count, Address-increment, Mode), enable dabata hai, aur phir apna asli kaam karta rehta hai. Jab transfer complete hota hai, DMA ek interrupt maar ke bolta hai "ho gaya boss".
Teen main modes hain: mem-to-mem (RAM se RAM, jaise fast memcpy — dono addresses badhte hain), periph-to-mem (jaise ADC ke samples RAM buffer me — peripheral ka address fixed rehta hai kyunki naya data hamesha same register pe aata hai), aur mem-to-periph (buffer se UART/DAC ko data bhejna). Time ka formula simple hai: t = N c / f t = Nc/f t = N c / f , aur throughput = w f / c = wf/c = w f / c — yahan dhyaan do ki throughput me N N N cancel ho jaata hai, matlab wider items (4-byte vs 1-byte) use karo to 4x faster transfer milta hai.
Do bade traps yaad rakhna. Pehla: DMA free nahi hai — wo bus use karta hai, aur jab DMA bus pakadta hai to CPU memory access pe ruk sakta hai (cycle stealing isi liye hota hai). Doosra aur zyada dangerous: cache coherency . Agar CPU me data cache hai, to DMA RAM me likhta hai par CPU purana cached value padh sakta hai — isiliye DMA buffer padhne se pehle cache invalidate karo, aur DMA ko data dene se pehle cache clean/flush karo. Aur sabse important: transfer complete hone ka interrupt aane se pehle buffer mat padho, warna adha-adhura data milega.