This page is the exhaustive drill room for DMA . The parent note built the formulas; here we push them into every corner — every transfer direction, the tricky zero and degenerate inputs, the limiting cases, a real-world word problem, and an exam-style twist that hides a trap.
Everything rests on two formulas from the parent. Let us re-state them so no symbol is unearned:
Definition The two address-step symbols
Δ S and Δ D
Every beat, the DMA controller updates its source and destination pointers by a fixed amount. We name those amounts:
Δ S = the ==per-beat change in the source address, in bytes== ("how far the read pointer jumps after each item").
Δ D = the ==per-beat change in the destination address, in bytes== ("how far the write pointer jumps after each item").
Three legal values for each: + w (increment — walk forward one item), 0 (fixed — stay put, used for a hardware register), or − w (decrement — walk backward one item). We use every one of these across the examples below.
Every DMA question you will ever face lands in one of these cells. The examples below are labelled [CELL n] so you can see the whole space is covered. (Recall from above: Δ S , Δ D are the per-beat source/destination address steps in bytes; + w = forward, 0 = fixed, − w = backward.)
Cell
Case class
What makes it special
Covered by
1
Mem-to-mem , both addresses increment
Δ S = Δ D = + w
Ex. 1
2
Periph-to-mem , source fixed
Δ S = 0 , Δ D = + w
Ex. 2
3
Mem-to-periph , dest fixed
Δ S = + w , Δ D = 0
Ex. 3
4
Address decrement mode
Δ = − w (walk backward)
Ex. 4
5
Degenerate: N = 0
nothing moves — does time = 0?
Ex. 5
6
Degenerate: wait states c > 1
slow peripheral inflates every beat
Ex. 6
7
Limiting case: width sweep
throughput as w : 1 → 4
Ex. 7
8
Real-world word problem
audio streaming, must-not-miss deadline
Ex. 8
9
Exam twist
mixed units + the "started ≠ done" trap
Ex. 9
Worked example Ex. 1 — Copy an array with DMA
Copy an array of 512 words , each word w = 4 bytes, mem-to-mem. Bus clock f = 72 MHz, c = 1 cycle/beat.
Find: total bytes B , transfer time t , and the address deltas Δ S , Δ D .
Forecast: guess before computing — will t be nanoseconds, microseconds, or milliseconds? (512 tiny beats at 72 million/s...)
B = N w = 512 × 4 = 2048 bytes.
Why this step? Total bytes is always count × width — needed to sanity-check throughput later.
t = f N c = 72 × 1 0 6 512 × 1 ≈ 7.11 μ s .
Why this step? One beat per word, one cycle per beat, so 512 cycles of a 72 MHz clock.
Δ S = Δ D = + w = + 4 . Both the read pointer and write pointer step forward 4 bytes each beat.
Why this step? Mem-to-mem walks both buffers, because each side is a distinct RAM region filling/emptying sequentially.
The figure below draws this: the blue slots are the source RAM, the green slots the destination RAM, and each orange arrow is one beat carrying src[i] down to dst[i]. Notice both index labels advance in lock-step — that is Δ S = Δ D = + 4 made visible.
Figure 1 — Mem-to-mem: read pointer (blue) and write pointer (green) both march forward, one orange arrow per beat.
Verify: Throughput = w f / c = 4 × 72 × 1 0 6 = 288 MB/s. Then B / throughput = 2048/ ( 288 × 1 0 6 ) ≈ 7.11 μ s — matches step 2. ✓ Units: bytes ÷ (bytes/s) = seconds. ✓
Compare this to memcpy and Block Copy done in software — the CPU would burn ~4 cycles/word here instead of freeing the pipeline.
Worked example Ex. 2 — Logging ADC samples into RAM
An ADC writes 12-bit results into 16-bit (2-byte) RAM slots , periph-to-mem. N = 2000 samples, f = 48 MHz, c = 1 .
Find t , throughput, and the deltas.
Forecast: the ADC's data register is one hardware address . Will Δ S be + 2 or 0 ?
Δ S = 0 , Δ D = + w = + 2 .
Why this step? The peripheral register (ADC->DR) is a single fixed address — new samples appear at the same spot. See Memory-Mapped IO . We read the same place repeatedly but scatter into successive RAM slots.
t = f N c = 48 × 1 0 6 2000 ≈ 41.7 μ s .
Why this step? 2000 beats, 1 cycle each, 48 M cycles/s.
Throughput = w f / c = 2 × 48 × 1 0 6 = 96 MB/s.
Why this step? Each beat carries 2 bytes; the beat rate is f / c = 48 M/s.
In the figure, the single red box on the left is the one peripheral register (ADC->DR) — every orange arrow starts from that same box (Δ S = 0 ), yet each lands in a different green RAM slot moving rightward (Δ D = + 2 ). That fan-out from one fixed source is the whole idea of periph-to-mem.
Figure 2 — Periph-to-mem: all arrows leave the same fixed red register; only the green destination advances.
Verify: B = 2000 × 2 = 4000 bytes. B / throughput = 4000/ ( 96 × 1 0 6 ) ≈ 41.7 μ s ✓. And note N did not appear in throughput — as promised in the parent, rate depends only on w , f , c .
Common mistake The classic periph-to-mem trap
Setting Δ S = + w here would make DMA read ADC->DR, then ADC->DR + 2, then +4... wandering into neighbouring registers → silent corruption. Fix a peripheral source; increment only the RAM destination.
Worked example Ex. 3 — Streaming a waveform to a DAC
Push 1024 samples from a RAM buffer to a DAC (DAC ), w = 2 , f = 48 MHz, c = 1 .
Forecast: which delta is now zero?
Δ S = + w = + 2 (buffer empties forward), Δ D = 0 (DAC register fixed).
Why this step? Mem-to-periph is the mirror of Ex. 2 — the destination is the single hardware register.
t = f N c = 48 × 1 0 6 1024 × 1 ≈ 21.3 μ s .
Why this step? State the full formula with c = 1 shown explicitly: 1024 beats, one cycle per beat, at 48 MHz.
Sample rate delivered: if the DAC requests one item at a time, the DMA is rate-matched by the hardware request line — it delivers exactly when the DAC is ready.
Why this step? Hardware-triggered transfers do one beat per request; the peripheral sets the pace, not the bus.
The figure mirrors Ex. 2: now the green RAM buffer on the left has arrows leaving successive slots (Δ S = + 2 ), all converging into the single red DAC register on the right (Δ D = 0 ). It is the fan-in mirror image of periph-to-mem's fan-out.
Figure 3 — Mem-to-periph: successive RAM slots (green) feed into one fixed DAC register (red); source walks, destination frozen.
Verify: B = 1024 × 2 = 2048 bytes. Throughput = 2 × 48 × 1 0 6 = 96 MB/s; 2048/ ( 96 × 1 0 6 ) ≈ 21.3 μ s ✓, matching step 2.
For continuous audio you would loop this with Circular and Double Buffering so the buffer refills while DMA drains the other half.
Worked example Ex. 4 — Reverse-copy a buffer with
Δ = − w
Some controllers can step an address backward . Reverse the byte order of a 64-byte block, mem-to-mem, w = 1 : read the source forward but write the destination from the end down to the start . f = 48 MHz, c = 1 .
Find N , t , and the two deltas — including the sign.
Forecast: if the write pointer moves backward, does the time change at all? (Think: does the formula care about direction?)
N = 64 (one byte per item, 64 bytes). Δ S = + w = + 1 (read start→end). Δ D = − w = − 1 (write end→start).
Why this step? Reversing means destination slot 63 gets source byte 0 , slot 62 gets byte 1 , etc. A negative step is exactly "walk backward one item per beat."
t = f N c = 48 × 1 0 6 64 × 1 ≈ 1.33 μ s .
Why this step? The timing formula counts beats , not direction. A backward step still costs one beat, so t is unchanged from a forward copy of the same size — direction is free.
Correctness of the reversal: after beat i , destination address = D 0 − i ⋅ w . When i runs 0 … 63 , we touch D 0 down to D 0 − 63 exactly once each — no overlap, no gap.
Why this step? This is the "cover all cases" check: the decrement must visit every slot exactly once, or you corrupt or skip data.
Verify: B = 64 × 1 = 64 bytes. Throughput = 1 × 48 × 1 0 6 = 48 MB/s; 64/ ( 48 × 1 0 6 ) ≈ 1.33 μ s ✓, identical to a forward copy of 64 bytes — confirming direction does not affect timing.
Common mistake Sign confusion with decrement mode
Why it feels right: you enabled "increment" out of habit. The fix: for a reverse copy you must start the destination at the high address and set Δ D = − w . Setting only one of the two (high start with + w , or low start with − w ) walks the pointer out of the buffer → memory corruption.
Worked example Ex. 5 — What if the count is zero?
A programmer sets up a transfer but a bug leaves N = 0 . f = 48 MHz, c = 1 , w = 4 .
What is t ? Does the transfer-complete interrupt still fire?
Forecast: is t = 0 exactly , or is there a hidden setup cost?
t DMA = f N c = 48 × 1 0 6 0 × 1 = 0 s .
Why this step? The formula counts beats; zero beats = zero transfer time.
But setup is not zero. Before starting, the CPU must program four control registers — remember the parent's S.C.A.M. checklist: S ource address, C ount, A ddress-increment mode, and M ode/trigger. There are four because those are the four independent things a transfer needs, and each lives in its own hardware register. Writing them costs O ( 1 ) CPU cycles regardless of N .
Why this step? The parent's t DMA formula explicitly says "ignoring setup". Degenerate N = 0 exposes that the only remaining cost is that fixed four-register overhead.
Throughput = w f / c = 4 × 48 × 1 0 6 = 192 MB/s is still defined — but it's a rate , not an amount. With B = 0 , you move nothing at 192 MB/s, i.e. 0 bytes.
Why this step? Throughput is w , f , c only; it never depended on N , so it stays finite even when nothing moves. This is the clean illustration of why N cancels .
Verify: B = 0 × 4 = 0 bytes; 0/ ( 192 × 1 0 6 ) = 0 s , consistent with step 1. ✓ Many controllers treat N = 0 as "already complete" and fire the interrupt immediately — always guard against it.
Worked example Ex. 6 — A slow peripheral with
c > 1
A slow external SPI flash inserts wait states , so c = 5 cycles/beat. Read N = 1000 items, w = 1 , f = 24 MHz.
Find t and throughput; compare against the ideal c = 1 case.
Forecast: if each beat is 5× slower, does throughput drop by 5×?
t = f N c = 24 × 1 0 6 1000 × 5 ≈ 208.3 μ s .
Why this step? Wait states multiply the cycles per beat , so total cycles = N ⋅ c = 5000 .
Throughput = c w f = 5 1 × 24 × 1 0 6 = 4.8 MB/s.
Why this step? Throughput has c in the denominator — bigger c ⇒ proportionally lower rate. This is exactly the "not free for the memory subsystem" idea from the parent.
Ideal c = 1 would give t = 1000/ ( 24 × 1 0 6 ) ≈ 41.7 μ s and 24 MB/s.
Why this step? Shows the 5 × penalty explicitly: 208.3/41.7 = 5 , 24/4.8 = 5 .
Verify: B = 1000 bytes. B / throughput = 1000/ ( 4.8 × 1 0 6 ) ≈ 208.3 μ s ✓. The 5× slowdown appears identically in time (×5) and throughput (÷5). ✓
Worked example Ex. 7 — Same bytes, different width
You must move exactly 4096 bytes at f = 48 MHz, c = 1 . Try w = 1 , 2 , 4 . Each time N = 4096/ w . Find t for each and the limiting behaviour.
Forecast: doubling w — does t halve?
w = 1 ⇒ N = 4096 : t = ( 48 × 1 0 6 ) 4096 ≈ 85.3 μ s .
w = 2 ⇒ N = 2048 : t = ( 48 × 1 0 6 ) 2048 ≈ 42.7 μ s .
w = 4 ⇒ N = 1024 : t = ( 48 × 1 0 6 ) 1024 ≈ 21.3 μ s .
Why these steps? Fixed B means N = B / w , so t = f N c = w f B c — time is inversely proportional to w . Wider beats, same payload, fewer cycles.
Limiting takeaway: as w grows, t shrinks by the same factor — but w does not grow without bound. Two hard ceilings stop it:
Bus width: the physical data bus carries at most a fixed number of bytes per beat (typically 4 on a 32-bit bus). Asking for w = 8 on a 4-byte bus is impossible — the hardware would need two beats anyway, so there is no gain past the bus width.
Alignment: you can only use w = 4 if both the source and destination start addresses are multiples of 4. A misaligned buffer forces the controller down to a smaller w (often w = 1 ), silently erasing the speedup.
Why this step? The limit is not infinite speedup. The practical rule is: use the widest width that both the bus and the buffer alignment allow — beyond that, extra "width" buys nothing because the hardware simply cannot move more per beat.
The bar chart shows the three times shrinking as w grows: red (w = 1 ) is tallest, green (w = 4 ) is a quarter of its height. The gray curved arrow annotates the trend — "wider beats = fewer cycles." Read the halving between adjacent bars directly off the heights.
Figure 4 — Fixed 4096-byte payload: transfer time falls as 1/ w ; each doubling of width halves the bar, until bus width / alignment cap it.
Verify: t ∝ 1/ w : 85.3/42.7 ≈ 2 , 85.3/21.3 ≈ 4 ✓. All three move the same B = 4096 bytes.
Worked example Ex. 8 — Will DMA keep up with 48 kHz stereo audio?
Stereo audio: 48 000 frames/s , each frame = 2 channels × 2 bytes = 4 bytes. A DMA drains a buffer to a DAC, w = 4 , f = 72 MHz, c = 1 . You refill in chunks of N = 256 frames using double buffering .
Question: does each 256-frame DMA finish before the next chunk of audio is needed?
Forecast: audio needs data slowly (human ears); DMA moves fast. Should be a huge margin — but by how much?
Deadline — time the audio consumes 256 frames: t deadline = 48000 256 ≈ 5.33 ms .
Why this step? The DAC eats 48000 frames/s; 256 frames last 256/48000 seconds. That is our budget.
DMA time to move 256 frames: t DMA = f N c = 72 × 1 0 6 256 × 1 ≈ 3.56 μ s .
Why this step? One beat per frame (w = 4 ), 256 beats at 72 MHz, one cycle per beat.
Margin = t deadline / t DMA = 5.33 × 1 0 − 3 /3.56 × 1 0 − 6 ≈ 1500 × .
Why this step? The ratio tells us DMA finishes ~1500× faster than needed — enormous slack for the CPU to compute the next buffer and for the ISR to fire.
Verify: B = 256 × 4 = 1024 bytes per chunk. Throughput = 4 × 72 × 1 0 6 = 288 MB/s; 1024/ ( 288 × 1 0 6 ) ≈ 3.56 μ s ✓. Deadline ≈ 5.33 ms ≫ 3.56 μ s ⇒ easily meets deadline. ✓
Worked example Ex. 9 — The trick question
"A DMA is set to copy N = 5000 items, w = 2 , on a bus of f = 100 MHz, c = 1 . Immediately after calling dma_start(), the CPU reads buffer[4999]. The transfer time is t . (a) Find t in µs. (b) Is the read valid? (c) What must the CPU wait for?"
Forecast: the numbers are easy — the twist is (b)/(c). Spot the trap before reading on.
(a) t = f N c = 100 × 1 0 6 5000 × 1 = 50 μ s .
Why this step? 5000 beats, 1 cycle each, 100 M cycles/s. Watch the unit: 5000/1 0 8 = 5 × 1 0 − 5 s = 50 μ s .
(b) No — the read is invalid. DMA is asynchronous ; dma_start() only launches it. At the instant of the read, the last item (buffer[4999]) almost certainly hasn't been written yet.
Why this step? This is the parent's "started ≠ done" mistake. The CPU races ahead of the mover and grabs stale bytes.
(c) The CPU must wait for the transfer-complete interrupt / flag (see Interrupts and ISRs ) — and , if a data cache exists, invalidate the buffer region first so it re-reads fresh RAM (Cache Coherency ).
Why this step? Two independent hazards: timing (wait for done) and coherency (cache may hold stale copies). Covering both is what the twist tests.
Verify: B = 5000 × 2 = 10000 bytes; throughput = 2 × 100 × 1 0 6 = 200 MB/s; 10000/ ( 200 × 1 0 6 ) = 5 × 1 0 − 5 s = 50 μ s ✓.
Recall Did we hit every cell?
Mem-to-mem (Ex.1) ::: ✓ both deltas = +w
Periph-to-mem (Ex.2) ::: ✓ source fixed (0), dest +w
Mem-to-periph (Ex.3) ::: ✓ source +w, dest fixed (0)
Decrement mode (Ex.4) ::: ✓ Δ = − w , timing unchanged
N = 0 degenerate (Ex.5) ::: ✓ t=0 but setup (four SCAM regs) ≠ 0
Wait states c > 1 (Ex.6) ::: ✓ 5× penalty in t and throughput
Width sweep limit (Ex.7) ::: ✓ t ∝ 1/ w , capped by bus width / alignment
Real-world deadline (Ex.8) ::: ✓ ~1500× margin, meets audio deadline
Exam twist (Ex.9) ::: ✓ async + cache coherency traps
For every worked example: "B-T-T-D" — compute B ytes, T ime, T hroughput, then check D eltas (which address moves, and in which direction: + w , 0 , or − w ). If the answer disagrees with B / throughput , you dropped a unit.