Visual walkthrough — DMA — memory-to-memory, peripheral-to-memory without CPU
We will assume only this: data lives at numbered locations ("addresses"), and a shared road (the bus) carries one item at a time. If those two ideas are new, glance at Memory-Mapped IO and Bus Arbitration first — but you can follow along without them.
Step 1 — One item, one trip: the "beat"
WHY start here? Because a big transfer is nothing but many identical beats. If we understand the cost of one beat exactly, we get the cost of a million by multiplying. This is the whole trick — find the atom, then count atoms.
PICTURE. Below, the violet box travels along the orange road from address (source) to address (destination). That single journey is one beat.

Step 2 — What one beat costs: cycles and the clock
Let us earn two symbols, slowly:
- — the bus clock frequency. This is how many ticks happen per second. Its unit is hertz (Hz) = ticks per second. A bus ticks times every second.
- — cycles per beat. This is how many ticks one box-carry uses up. For fast on-chip RAM, : one tick, one box. A slow peripheral may say "wait, I'm not ready" and stretch the beat to ticks — those extra ticks are called wait states.
WHY these two and not, say, a speed in metres per second? Because hardware has no notion of distance — it only knows ticks. Time on a chip is always "how many ticks × how long each tick lasts". So the natural cost of a beat is:
Reading the equation term by term: is "how long is one tick?" — if there are ticks in a second, each tick lasts seconds. Multiply by ticks and you have the seconds for one beat. The downstairs and the "per second" of frequency are the same fact written two ways.
PICTURE. The metronome ticks; a beat fills exactly of the little tick-boxes.

Step 3 — Count the beats: the transfer-time formula
WHY multiply and not add something clever? Because the beats are identical and sequential — one road, one box at a time (we handle the CPU sharing the road in Step 7). equal costs in a row is exactly multiplication. No hidden overhead is being ignored except a tiny one-time setup, which does not grow with .
Reading it: more items () → longer. Slower beats (, wait states) → longer. Faster clock (, downstairs) → shorter. Every arrow points the way your gut expects — a good sign the formula is honest.
PICTURE. A row of boxes; the total time is the row of tick-groups laid end to end.

Step 4 — Each item has a width: bytes, not just boxes
WHY does width matter if a beat still costs regardless? Here is the beautiful part: a wide beat costs the same seconds as a narrow one — the road is the same width, one shove carries the whole item. So making bigger delivers more bytes for the same time. That is free speed, if your data is aligned to allow it.
Total bytes moved:
PICTURE. Same number of beats, but each box is now stamped " bytes". A box carries four little byte-tiles in one trip.

Step 5 — Throughput: why vanishes
WHY compute a rate at all? Because "how fast is this channel?" should not depend on how much you happen to send. A firehose has a rate whether you run it for one second or ten. We are about to prove the DMA channel behaves the same way.
Reading the cancellation: the top counts bytes (), the bottom counts time (). Both grow at the same rate with , so their ratio forgets entirely. What's left — — is a property of the channel: wider items, faster clock, fewer wait-cycles → faster. Send 10 items or 10 million; the rate is identical.
PICTURE. Two transfers of very different length ( small vs large) drawn as pipes of the same diameter — same rate, different pipe length.

Step 6 — Address generation: where the boxes are picked up and dropped
WHY are the steps sometimes zero? Because of what sits at the address. A RAM buffer is a row of slots — to fill it you must step forward by each time. But a peripheral's data register (say ADC->DR) is one fixed hardware address; new samples appear at the same spot whenever the peripheral is ready. Stepping it would make DMA wander off into unrelated registers — silent corruption. So:
| Direction | Why | ||
|---|---|---|---|
| Mem→Mem | both are buffers, walk both forward | ||
| Periph→Mem | read one fixed register, scatter into slots | ||
| Mem→Periph | gather from slots, feed one fixed register |
PICTURE. Left: mem→mem, both pointers march. Right: periph→mem, the source pin stays pinned on one register while the destination pointer walks the buffer.

Step 7 — The degenerate & edge cases (nothing left unshown)
Case (empty transfer). . Zero beats, zero time — the controller simply fires "complete" immediately. Your code must still wait for that flag (don't read early!).
Case (single item). : exactly one beat. This is the smallest real transfer; here the one-time setup cost (which we ignored) actually dominates — for one word, a plain CPU load/store is often cheaper than programming DMA at all.
Case (wait states). A slow peripheral stretches each beat. grows linearly in ; throughput shrinks. The peripheral, not the bus, is now the bottleneck — this is why a slow ADC and DAC can't be sped up by a faster clock alone.
Case: sharing the road (bus arbitration). So far DMA owned the bus. Reality: the CPU wants it too, and only one may drive it — an arbiter decides (Bus Arbitration). In cycle-stealing mode DMA takes one beat then hands the road back, so the CPU is never starved but DMA runs a touch slower. In burst mode DMA holds the road for the whole block — fastest DMA, but the CPU may stall on any memory access meanwhile. Our is the ideal, uninterrupted time; real time is that.
PICTURE. Two timelines. Top (burst): one long violet block, CPU stalled. Bottom (cycle-steal): violet beats interleaved with orange CPU cycles — both make progress.

The one-picture summary

Everything on one canvas: one beat costs seconds → beats cost → each beat carries bytes so → dividing bytes by time, cancels and leaves the channel rate . Address steps () decide the pattern; arbitration decides the real (non-ideal) timing.
Recall Feynman: retell the whole walkthrough
Imagine a robot carrying boxes across a room, one shove per box. One shove takes a fixed number of clock ticks (), and the clock ticks times a second — so one box takes seconds. Line up boxes and it's just that, times: . Now, each box can be small or big ( bytes) — a big box costs the same shove but delivers more, so total stuff is bytes. Ask "how fast does stuff arrive?" and you divide stuff by time: the 's on top and bottom cancel, because doubling the boxes doubles both the stuff and the time. What's left, , is just how good the robot-and-room combo is — box size, tick speed, ticks per shove. Where the robot picks up and drops off is set by two step-sizes; if it's grabbing from a peripheral's single mailbox, that step is zero so it keeps reaching into the same slot. And the room has one door: sometimes the brain (CPU) needs it too, so they take turns — which is why real transfers are never quite as fast as the clean says.
Recall
One beat's time ::: (cycles per beat ÷ ticks per second). Why does vanish from throughput? ::: Bytes () and time () both scale with , so their ratio drops it. Why is for periph→mem? ::: A peripheral data register is one fixed address; new items appear at the same spot. Real transfer time vs ? ::: , because the CPU shares the bus (arbitration), stalling DMA in cycle-steal mode.