◈ computers mapVol 1 · Ch 19/19
How Computers Work from the transistor up · chapter 19

19Talking to the World

Last chapter we built the surgeon's kit: a shift to build a mask, an OR to set a bit, an AND to clear or test one. That's the whole toolkit for touching one bit and leaving the other 31 alone. But I ended on a question, and this whole chapter is the answer. Every one of those operations happened inside the CPU, rearranging a register's own bits. So here's what should nag at you: the processor has no "turn on the light" instruction. There is no opcode for internet, none for touch, none for sound. It can add, it can move a number, it can jump. That's the whole vocabulary. So how does a machine that only ever shuffles numbers reach out and make an LED physically glow? The answer is a beautiful lie the memory system tells. A few special addresses aren't wired to RAM cells at all. They're wired straight to hardware. Store a number there and nothing gets saved. Instead a real switch flips, a pin drives, a light comes on. That trick is called memory-mapped I/O. It means the load and store you built chapters ago are already, secretly, the way a computer talks to the world. We'll follow one wire the whole way. We wake the hardware out of reset, aim a single pin, and drive it high with the exact 1 << n mask from last chapter. Then we blink an LED under a program you wrote. Then we stand at the bottom of the ladder and look all the way up.

01Some addresses aren't memory

Let's stand exactly where Chapter 18 left us. You can now reach into a register and set, clear, or test any single bit cleanly, with no carry and no collateral damage. But every one of those moves just rearranged bits inside the chip. To light an LED, that carefully-set bit has to escape the processor and reach a wire in the room. And the CPU's only tool for putting a number somewhere is the one you built back in Chapter 9: store, which writes a value to an address. Every store you've ever run landed in a RAM cell. So here is the whole trick of this chapter in one move: what if a few addresses aren't RAM? Pick an address below and store a number to it. Watch where it actually goes.

ADDRESS BUS — drag to pick an address (they all look like memory) → 0xF3 ADDRESS DECODER routes by address alone RAM (memory cells) empty LED MOTOR This address is NOT memory. One decoder line enables a device wire.
Predict, then hit STORE
Two of these addresses are wired to devices, not RAM. Can you find them?
Fig. 1. The MMIO lie. Every slot on the address bus looks like plain memory — just a number. Drag to pick one, guess whether a store lands in RAM or trips a device, then STORE. Most addresses save your byte in a memory cell. But a few are a trick: the address decoder raises a single output line that’s wired straight to a device, so the same store instruction turns on an LED or spins a motor instead of saving anything. That’s memory-mapped I/O — the memory system telling a lie.

That's memory-mapped I/O, and it's the keystone of everything that follows. A store to one of those addresses doesn't save anything. It does something. And look at what the CPU needed to pull that off: no new instruction. The same store, pointed at a different address, is how it drives the entire outside world. Now, the tempting way to file this is as an exception: a thin band of special addresses wired to devices, and ordinary RAM everywhere else. RAM behaves exactly as you expect — write a number and it's saved, read it back and there it is. Hold that frame loosely, because I think it's backwards. So here's the question I want you to answer before I do. How does the machine know which addresses are memory and which are hardware? Or, sharper: what would the CPU have to do differently to store to a device instead of to a RAM cell?

You can't, and neither can it. Watch a store from the CPU's own side and count what leaves. The address lines go out, carrying the number you named. The data lines go out, carrying the byte. One write-enable line goes high to say now. Three things out — and then nothing comes back. No confirmation, no receipt, no identity. A store is fire-and-forget. So go hunting inside the processor for the wire that would carry the difference between "that landed in a RAM cell" and "that just spun a motor". There isn't one. There's nothing to build it out of, because the CPU never finds out what answered. It isn't that the CPU tolerates devices. It's that the CPU is blind, and always was. Whatever sits on the far end of those wires is somebody else's decision, made in copper, outside the processor — by a router you already built.

Recall the address decoder from Chapter 9. It takes an address and raises exactly one output line, the one-hot line that selected a single RAM word. Then it stops. The decoder has no opinion about what sits on the far end of the line it raised. So ask what's out there. In Chapter 9 the answer was a RAM row every time — but that was a fact about what we had soldered on, not about what an address means. Wire a device's enable pin to one of those lines instead, and the decoder behaves identically, because as far as it can tell nothing changed. Same decoder, same one-hot rule, another customer on the far end of the wire. RAM was never the default. RAM is just the customer we wired up first.

ADDRESS BITS WHERE DOES THE HOT LINE GO? DECODER recall Ch.9 · one-hot RAM ARRAY idle — no word-line high PERIPHERAL EN EN = 0 — asleep
Click a bit to set the address — same decoder as Ch.9, still exactly one hot line. Now toggle where that line actually runs.
addr 101₂ = 5  →  line 5 is hot
line 5 hot → wired to RAM row 5
Fig. 2. The exact decoder from Chapter 9, unchanged: click a bit to set the address, and one gold line goes hot — still one-hot, still just AND gates. But now follow that single wire past the decoder's edge. Flick the switch and the same hot line lands on a RAM row's word-line, or on a peripheral's EN pin — nothing rewired inside the decoder, just a different customer soldered to the far end. That's why MMIO needed no new instruction: same store, same decoder, one wire pointed somewhere new.

So nothing was invented. A wire was rerouted. The decoder still lights one line for one address; that line just happens to wake a peripheral instead of a memory cell. This leads to a strange and useful tell. If a device address has no memory cell behind it, then writing a number there and reading it back should not hand your number back. Try it. Write to a plain RAM address, then to a device address, and read each one.

value = 137 RAM · 0x2000 a real memory cell not written yet STORAGE DEVICE · 0xF000 printer status register not written yet SIDE EFFECT
pick a value, then WRITE it
predict: will 0xF000 hand your number back too?
Fig. 3. Same move, two addresses: pick a number, WRITE it to RAM at 0x2000 and to a DEVICE register at 0xF000, then READ BACK and compare. RAM hands back the exact number every time — there's a real cell sitting behind that address, holding it. The device address hands back a fixed status flag, no matter what you wrote — because there's no memory cell behind 0xF000; the write triggered a side effect (telling hardware to do something) instead of saving anything. That's the tell: an address that never echoes what you stored isn't storage — it's a command.

There's the tell. A write to RAM is storage: it faithfully remembers. A write to a device is a side effect: it makes something happen. Read that address back and you get the device's status rather than your number, because behind that one there's no cell to remember it — only wires. Now, careful: "device addresses never echo" is not the rule. Plenty of device addresses have a real storage cell behind them, hand your number straight back, and act on the world anyway. You'll meet one shortly. The rule that survives both kinds is plainer. A write to a device does something beyond remembering — whether it also remembers is incidental. Now let's open one of these devices up and see what's actually inside.

02A peripheral is a bank of registers

A peripheral — the pins, a timer, a radio, a USB controller — sounds exotic. But from the CPU's side it's almost boring, and that's the point. Every peripheral is just a small block of registers, each pinned to its own address. They're laid out at a base address plus a fixed offset. To use the device you read and write those numbered registers, nothing more. Here's the register map of a GPIO block: a base, and a handful of offsets, each with one job.

So what is one of those registers, physically? Nothing you haven't already built. You have a register from earlier chapters: D inputs to feed it, a load enable to say take it now, Q outputs to read it back. Wire it up. The decoder's hot line for this address goes to the load enable. The data bus goes to the D inputs. That much is exactly how a RAM word works, and a store lands in the cells the same way it always did. Now here's the only change in the whole chapter. The Q outputs don't loop back to the bus. They run out to the pins. That's it. Nothing new is on the page — one wire has a different destination.

And right there a word quietly split in two. Through Chapter 18, a register meant a CPU register: a thing with a name, sitting in the datapath, that an instruction reaches directly. GPIO_OUT is not that animal. It's a device register: a thing with an address, sitting past the decoder, that only load and store can reach. Same word, opposite access. So if you've been wondering why we store to GPIO_OUT instead of just moving a value into it, that's the answer — there is no name to move it to. It lives out on the bus. That's also why driver code looks the way it does: a stream of loads and stores, hardly any arithmetic. Every touch of a peripheral is a trip down the address lines and back.

GPIOA · PERIPH BASE 0x4000_0000 base + offset +0x00 +0x00 DIR pin: in or out +0x04 OUT output level +0x08 SET force HIGH +0x0C CLR force LOW +0x10 IN sense pins +0x14 RESET_DONE ready flag one base address, six numbered slots — that's the whole peripheral
DIR 0x4000_0000
sets each pin: input or output
base + offset = address. That's why every peripheral looks like a patch of memory addresses: it's just a bank of registers, and driving hardware is nothing more than reading and writing them.
Fig. 4. A peripheral is just a base address plus a handful of offsets — click any row in the GPIOA register map and watch the offset add onto the base to give one exact address, with its job named beside it. SET and CLR aren't decoration: writing a 1 bit there forces that one pin HIGH or LOW without ever touching the others, so an interrupt can't race you the way it could if every pin lived in one shared OUT word. That's the whole trick behind "talking to hardware" — it's reading and writing numbered slots, nothing more.

That's the shape of every driver ever written: a base + offset table of registers, and a sequence of reads and writes to them. The one that reaches the world is the output register, and its wiring is the whole reason any of this works. Each bit of that register is soldered to one physical pin. Bit k of GPIO_OUT is pin k. Flip the bit, drive the wire. One promise before you play with it: what you're about to see is this block doing its job once it is awake. Getting it awake turns out to be a job of its own, and it's the next section's. Toggle bits below and watch the pins follow.

inside the chip — GPIO_OUT[7:0], the low 8 bits of the 32-bit register outside the chip — the physical header MSB · bit 7 LSB · bit 0 0 L P7 0 L P6 1 H P5 0 L P4 1 H P3 0 L P2 0 L P1 1 H P0 GPIO_OUT[7:0] = 00101001
pins HIGH: 0,3,5
click any cell OR pin — HIGH drives ~3.3V, LOW sits at 0V (GND)
aha: the wire isn't copying the bit — it is the bit. One cell, two names: bit k, pin k.
Fig. 5. The low eight bits of the 32-bit GPIO_OUT register — GPIO_OUT[7:0] — and the eight numbered pins they drive, written the way a number is written: bit 7 on the left, bit 0 on the right. Click a register cell, a pin pad, or hit "walk" — bit k and pin k switch in the same instant, because they aren't two things kept in sync; they're one storage cell with two names. Flip the bit, drive the wire.

So the register is the row of pins, mirrored into the address space. Set a bit and its pin goes high; clear it and the pin goes low. This is where last chapter's mask work stops being an exercise and starts driving copper. To light exactly one LED, you need to set exactly one bit of this register and leave the rest untouched. That is precisely what 1 << n and OR were for. But we can't drive the pin yet. Fresh out of power-on, the hardware is asleep.

03Waking the hardware: reset and the poll

Here's a detail almost every beginner trips on. When the chip first powers up, its peripherals aren't merely idle. They are held in reset: clamped in a known-dead state, unclocked, deaf to every register write you send. Write to GPIO_OUT right now and nothing happens, because the GPIO block isn't even awake to hear it. Your first job is to release its reset. Watch a peripheral come out of reset.

GPIO PERIPHERAL ASLEEP · held in reset CLK WR reset gate blocked ✕ GPIO_OUT[7:0] 0 0 0 0 0 0 0 0 GPIO_OUT[7:0] = 0x00 pin outside fresh silicon boots held in reset — unclocked, deaf to writes. the gate only opens once you release reset — then it latches. that's why brand-new hardware ignores you at power-on.
held in reset — writes ignored
a peripheral's registers are deaf until its reset line is released.
Fig. 6. Every peripheral wakes up held in reset: unclocked, deaf, its write gate shut. The eight cells here are the low 8 bits of GPIO_OUT, GPIO_OUT[7:0] — bit 7 on the left down to bit 0 on the right. Click write 0xFF now and the gate blocks it — GPIO_OUT[7:0] stays 0x00, nothing latches. Release reset and the gate swings open, the clock starts ticking; write again and it takes: 0xFF lands, the pin lights. Hold reset again and the register itself clears — that's why fresh hardware ignores every write until you let it out of reset first.

Releasing reset isn't instant, though. The hardware needs a few cycles to power up its internals and steady itself, and if you poke it too soon it's still not listening. So how does a program wait for a physical thing to become ready? It does the most literal thing imaginable: it polls. There's a RESET_DONE status bit that the hardware flips to 1 when it's truly awake. The CPU sits in a tight loop AND-testing that one bit, the exact test recipe from Chapter 18, spinning until it reads 1. Step through the spin.

poll STATUS until RESET_DONE = 1 busy-wait loop: read STATUS → AND with mask → loop while 0 b3 b2 b1 b0 STATUS & MASK (constant) 0 0 0 1 = OUT RESET_DONE (ignored) test: (STATUS & (1≪0)) ≠ 0 beat 00 spins 00 OUT = ? loop while OUT = 0 spin — reread & test again OUT≠0 → break CPU continues →
press Step ▸ to poll STATUS
each beat: the CPU rereads STATUS, ANDs it with the RESET_DONE mask, and loops back while the result is 0.
Fig. 7. The exact test from Ch18 — STATUS & (1<<0) — run over and over. Press Step ▸: each beat rereads the STATUS register (its other bits jitter with unrelated flags — the mask forces them all to 0, so only b0 ever survives the AND), tests whether the result is 0, and loops if it is. Nothing else happens on the CPU while this runs — no other instruction executes — which is exactly why it's called busy-wait polling. The moment RESET_DONE flips to 1, the loop breaks and the CPU moves on.

That's busy-wait polling: the processor does nothing else. Every cycle, it re-reads the status bit and asks "ready yet?" until the answer is yes. It works, and for a one-time boot check it's perfectly fine. But notice the waste. While it spins, all that compute is burned staring at a wire. When the thing you're waiting for is rare or slow — a key press, a packet, a byte from a sensor — spinning is a terrible deal. So the hardware offers a smarter arrangement, and it's worth meeting even briefly: the interrupt. Instead of the CPU asking, the device tells.

POLL — CPU stuck re-reading a bit device ready CPU cycles, left → right polling (wasted) real work the tap (interrupt) wasted 8 · useful 8 of 16
poll burns 8 cycles asking
That's why interrupts exist — the hardware taps the CPU's shoulder only when there's real work, so it never has to keep asking.
Fig. 8. Sixteen CPU cycles, one strip. In poll mode the CPU spends every cycle before the device is ready just re-reading the same bit — orange ? cells, cycles it will never get back. Switch to interrupt mode and those same early cycles turn green: the CPU is off doing real work, and only gets a one-cycle gold tap the instant the device is actually ready. Drag the slider to move when the device becomes ready — the later it is, the more cycles polling throws away, while interrupt's cost stays pinned at one quick tap.

That's the whole idea of an interrupt. The hardware taps the CPU on the shoulder. The processor saves its place, jumps to a little handler you registered, deals with the event, and resumes. That way it can do real work instead of spinning. Polling asks a thousand times; an interrupt answers once, exactly when it matters. We'll blink with a simple poll-free loop, but now you know the door the real world knocks on. With the block awake, let's aim our pin.

04Aiming the pin: function and direction

A physical pin is expensive real estate, so chip designers make each one do many jobs. The same pin might be a UART transmit line, a PWM output, or plain I/O. Only one function can own the wire at a time, and something has to choose. You've met that something already: it's the multiplexer from Chapter 3. There's a function-select mux behind every pin, and its select bits decide which internal block gets the copper. Pick a function and watch the mux route it out.

chip edge UART TX PWM GPIO OUT MUX 0 0 select bits S1 S0 PIN PA9 UART voltage on that one wire 1 0
S1S0=00 → UART drives the pin
Same pin, same wire, same solder pad. The MUX (Ch3's multiplexer) just points a different internal block at it.
Fig. 9. Flip the select bits and the mux points a different internal block at the same physical pin — that's why one pin can be UART, PWM, or plain GPIO: the Ch3 multiplexer sits behind it, and its select bits pick who drives the wire.

The most-reused block in this whole book shows up one last time, doing exactly what it always did: steer one of several inputs to a single output. To blink an LED we select plain software I/O, the function that lets our registers drive the pin directly. But there's one more switch to throw. A pin can either listen to the outside world (an input) or drive it (an output). A direction register holds one bit per pin to choose, and, of course, you set that bit with a shifted mask. Set the direction and see the pin change job.

MCU pin logic DIR bit (direction register) 0 internal drive request LOW HIGH-Z (disconnected) still listening ✕ blocked LED the world
DIR — the direction-register bit for this pin
try pushing this pin HIGH from inside the chip
input — pin only listens, LED dark
flip DIR to output, then drive HIGH, to light the LED
Fig. 10. Every pin has a DIR bit guarding a tri-state buffer between the chip's internal drive signal and the physical pin. Set DIR = 0 (input) and the buffer disconnects — High-Z — so the pin just listens; try drive: HIGH and it's blocked, the LED stays dark. Flip to DIR = 1 (output) and that same buffer closes the connection, so drive: HIGH now reaches straight through and the LED lights. That's why firmware sets a pin's direction register before it ever tries to drive it.

Leave a pin as an input and no matter how hard you write GPIO_OUT, the LED stays dark. You're shouting down a wire that's set only to listen. Nothing you write reaches the pad, because the buffer between them is switched off. That disconnected state has a name: high-impedance, or High-Z. It doesn't mean "input". It means the chip isn't pushing that wire either way, high or low. The copper is left free to be moved by whatever is outside. Flip its direction bit to output and the pin now drives: the buffer connects, and the chip is on the wire. The block is awake, the function is plain I/O, the pin points outward. Everything is finally aimed. Time to pull the trigger.

05Drive it, clear it, light it

This is the moment the whole ladder has been climbing toward. To turn the LED on, you drive its pin high, and you do it with the exact recipe from Chapter 18. Build the mask 1 << n with a shift, then OR it into the output register: GPIO_OUT |= (1 << n). One bit goes high, its pin drives, the light comes on, and the other 31 pins don't so much as twitch. Store the mask and watch it light.

But hold on. "The pin drives high" is not yet an explanation of light. High relative to what? Follow the current and the last inch stops being magic. Driving the pin high closes a switch inside the chip that ties the pad to Vdd. The LED's other leg goes through a resistor to ground. So the instant that switch closes, there is a complete loop — Vdd, through the switch, out the pin, through the LED, through the resistor, into ground — and current runs around it. An LED is a diode built so that electrons crossing its junction shed their energy as photons rather than heat. Current through it is the light. Drive the pin low instead and the other switch closes: the pad is tied to ground, both legs of the LED sit at the same potential, no current flows, and the light goes out. None of that is new either. It is the same voltage-controlled switch this book opened with, still the only thing doing anything.

GPIO_OUT |= (1 << 0) lit 0 / 32 1<<n GPIO_OUT pins |= OR writes a 1 only where the mask has a 1. Every other bit keeps exactly what it held — 31 pins untouched.
pick a pin, then store the OR-mask
The mask is a lone 1 shifted to bit n. OR-ing it in drives that one pin high and leaves the rest alone — store again on another pin and this one stays lit.
Fig. 11. The Chapter 18 set-recipe, GPIO_OUT |= (1<<n), wired straight to copper. Slide n and a single 1 — the mask — slides to bit n. Press store and that lone 1 is OR-ed into the output register: bit n snaps to 1 and its LED lights, while every other bit keeps exactly the value it already held. Store on a second pin and the first stays lit — that's the whole point of OR. One single-1 mask sets one pin and touches no other; the other 31 are left completely alone.

There it is: a program setting a bit, and a real light answering. That single OR to a magic address is the entire bridge from arithmetic to the physical world. Now, turning it back off seems just as easy: GPIO_OUT &= ~(1 << n), the clear recipe. One statement, one line of C. It is not one thing. That clear is a read-modify-write: three separate trips to the bus. Read the whole register into a CPU register. Flip one bit there. Write the whole thing back. Say GPIO_OUT holds 0b0001 — pin 0 is on. Your code runs the clear recipe on pin 3. In the gap, after your read and before your write-back, an interrupt fires and its handler turns on pin 1. What does the register hold when the dust settles? Guess before you read on. Most people say 0b0011: your pin 0 still on, the handler's pin 1 now on too. It holds 0b0001. The handler's bit is gone. Your CPU register still held the stale copy — 0b0001, read before the handler ran — and the write-back clobbers the live register with it. Your logic was fine. The window was the bug. So real chips give you a separate GPIO_OUT_CLR register with a cleaner rule: write a 1 to clear that pin, atomically, touching nothing else. Compare the two ways to switch a pin off.

GPIO_OUT (live pin states) 0 0 0 0 1 0 0 0 PIN5 (neighbour) PIN3 (our LED) IRQ: set PIN5 temp (CPU copy) 0 0 0 0 1 0 0 0 ① read reg into temp ③ write temp back — whole reg! GPIO_OUT_CLR (write-only) 1 bit3 = 1 one atomic store, bit3 only click “step ▶” to switch PIN3 off  
PIN3 = 1, PIN5 = 0 — press step
Fig. 12. Same job, two mechanisms: reg &= ~mask reads the whole register, edits it in the CPU, then writes the whole thing back — if an interrupt changes a neighbouring pin in between, that write-back stomps it flat. GPIO_OUT_CLR is a special write-only register: writing a 1 tells the hardware "clear exactly this bit" in one atomic store, so there's no read-modify-write window for an interrupt to land in.

That's why real GPIO blocks split set and clear into separate addresses: OUT_SET and OUT_CLR, each write-1-to-act. No read, no modify, no race. One atomic store changes exactly the bits you name. We can now turn our LED cleanly on and cleanly off. That's everything we need to make it blink, except for one small problem with time.

06Make it blink

First, a puzzle in a program's shape. Our "light it" program has run its last useful instruction, so the pin is high. What now? A CPU never just stops and sits politely; it can't not fetch. Left alone, the program counter would march off the end of your code into whatever bytes lie beyond and run garbage. So a "finished" program ends by trapping itself: a JMP to its own address. It's an infinite self-branch that fetches the same instruction forever, holding the machine (and the LED) exactly where it is. Watch the PC lock onto itself.

PC LD ADD OUT ?? ?? ?? 0 1 2 3 4 5 LED "JMP 3" means "go to address 3." We're already there — so it loops, forever.
click step to fetch the first byte
PC = program counter, the address the CPU fetches next. ?? = uninitialised memory — whatever bytes happen to be sitting there.
Fig. 13. A finished program can't just stop — the CPU always fetches whatever address the PC points to next. HALT is really JMP to itself: park the PC on one address and it re-fetches that same address forever, holding the LED (and the whole machine) dead still. Skip the trap, and the PC marches straight into uninitialised memory, "executing" garbage.

A self-JMP is how you say "done" to a machine that can't stop. The LED holds steady because the program is pinned in place. But we don't want steady, we want blink: on, off, on, off. The obstacle is speed. The CPU flips the pin millions of times a second, far too fast for your eye, which would just see a dim, permanent glow. So we deliberately waste time. And if that stings, good. I spent a whole section telling you that spinning is a terrible deal, and now I'm about to spin on purpose. Both loops have the identical shape. Only the goal is opposite. Polling burns time you wanted to use; a delay burns time you wanted to lose. Waste is only waste when the time had somewhere better to be, and here the burnt cycles are the product. We load a big number into a register and count it down to zero — DEC then a jump-back-while-nonzero — Chapter 14's countdown, folded into one branch — doing nothing useful, just burning cycles until enough real time has passed to see. Run the delay and watch it stall.

the loop body: DEC R then JNZ loop 1 · the countdown register register R holds 0 load a value below 2 · DEC + JNZ, repeating DEC R JNZ loop R ≠ 0 → loop R = 0 → exit 3 · the pulse it produces output pin (LED) cycles burned so far 0 real time this burns 0 ns 1 GHz clock → 1 cycle ≈ 1 ns · JNZ = jump if not zero · DEC+JNZ = 2 cycles per pass
will load 933 · ≈1.87 µs
scrub R, then press Load to begin
A 1 GHz CPU ticks a billion times a second; your eye needs about 1/20 s to notice anything at all. That gap is why the loop wastes cycles — on purpose.
Fig. 14. The loop body is just two instructions: DEC R subtracts one from the register, JNZ loop jumps back to the top while R isn't zero yet. Press Load to set R and switch the output pin on, then Step through single passes or hit Run to burn the whole countdown at once. At 1 GHz each pass costs about 2 nanoseconds — scrub R small and Run finishes before you could ever notice; scrub it up toward a billion and the same two instructions, repeated, stretch into a real, visible pause before the LED finally switches off. Nothing is happening in that loop — and that's the point: doing nothing, on purpose, for long enough, is how software makes a blink a human can actually see.

A delay loop is honest waste: thousands of do-nothing cycles that stretch a blink into something an eye can catch. Now assemble the whole thing from parts you already own. Set the pin (OR a mask), delay, clear the pin (write the CLR register), delay again, then JMP back to the top, forever. That's a blinking LED, in five steps, every one of them something you built in an earlier chapter. Run the program.

program 0 SET   P0 ; LED on 1 DELAY #4 ; wait 2 CLR   P0 ; LED off 3 DELAY #4 ; wait 4 JMP   0 ; loop output pin P0 pc (line) 0 count
ready — click Step or Run
DELAY reuses the counting register from Ch.14; JMP just rewrites the PC. Five familiar steps, wired in a circle.
Fig. 15. Five lines, one loop: SET drives the pin HIGH, DELAY counts a register down to 0 (the same countdown loop you built in Ch.14), CLR drives it LOW, DELAY counts again, and JMP sends the PC back to line 0 — the blink's first instruction. The one-time setup — release the reset, select the mux function, set the direction — sits above this loop and never re-runs. Press Step to walk it one tick at a time or Run to watch the LED actually blink. Every instruction here was built in an earlier chapter; blinking is just five familiar steps wired into a circle.

Look at what that is: set, wait, clear, wait, loop. A mask from Chapter 18, a store to a magic address from this chapter, a countdown from Chapter 14, a jump from Chapter 13. There is no magic anywhere in it, just the machine doing precisely what we told it. One last question remains, and it's the one that turns this from a simulation into a chip on your desk: how did the program get into the machine in the first place?

07Flash, and the whole climb

You wrote the blink program on your laptop. The chip runs it on power-up with no laptop attached. Something has to carry those bytes across and make them stick. That's the bootloader: a tiny program baked into the chip. When you plug in USB, it accepts your compiled bytes and writes them into on-chip flash, a non-volatile memory. Then, every time the chip powers on, the CPU starts fetching from flash and your program simply runs. Follow the bytes from laptop to running chip.

code.bin your laptop USB microcontroller chip CPU run bootloader FLASH · non-volatile 0 / 8 bytes AHA flash remembers without power
click Upload to send bytes over USB
upload writes bytes into flash, then flip power off and on — watch what it still remembers.
Fig. 16. Click Upload over USB and watch a byte ride the cable into the chip's bootloader, which writes eight bytes straight into flash. Now flip power OFF: the CPU, bootloader, and run LED all go dark — but the flash cells at the bottom stay lit, untouched. Flip power ON again and the CPU's fetch arrow lights up, pulling those same bytes straight out of flash and running them. That's the trick: flash is non-volatile — it needs no power to remember — so the program you sent over USB is still sitting there waiting, no matter how many times the chip loses power.

The load-bearing word there is non-volatile. Your program survives being unplugged because flash is a member of the ROM family from Chapter 11: it keeps its bits with the power off. That's the opposite of RAM, whose cells are two inverters chasing each other in a loop that only exists while power races through it. Cut the power and RAM forgets everything instantly. Cut the power to both and watch which one remembers.

POWER SUPPLY ON RAM needs power to hold a 1 FLASH holds a 1 with no power bits 0000 bits 0000
power ON — click cells, then cut power
that's why RAM forgets and flash remembers — RAM's loop needs power; flash's trapped charge doesn't.
Fig. 17. Click cells to set bits in the RAM block and the flash block, then pull the power lever. RAM's bit lives in a loop that only holds together while current keeps racing through it — cut the power and the loop stops, the charge drains, the bit is gone. Flash's bit is an electron parked behind an insulating gate, trapped with nowhere to leak to — the power can vanish and the charge just sits there, which is why flash remembers and RAM doesn't.

Volatile versus non-volatile: RAM is fast scratch that vanishes; flash is slower storage that persists. That's why your code lives in flash and your variables live in RAM. We've now told the entire story. But I've been showing it to you in pictures, and you shouldn't take a picture on faith. So let's do what we did every chapter: trace the whole MMIO machine one store at a time. Memory becomes a lookup table. A few addresses are wired to a device. The blink logic writes to them, and we watch the LED trace come out.

mem · store → router → device. run, then diff vs hardware. store = idle press Step or Run to execute the blink loop memory mem mem[0x08] · RAM 0 mem[0x40] · → LED 0 pin() = 0 the routed store reproduced the LED trace — bit for bit. sim ▷ hw  ▷
idle — press Step or Run
The router sends a store to 0x40 to the LED, everything else to RAM. Route it right and the trace follows the wire, store for store. Mis-route it and the LED never hears the store.
Fig. 18. Model the machine in code. Memory is just a lookup table: an address in, a value out. One address, 0x40, is wired to the LED; the router sends any store there to the device, everything else to plain RAM. Run the blink loop and the pin follows the store exactly as the wire does — routing by address is the whole mechanism. Mis-route the store to 0x08 and the LED never hears it: the trace diverges, and you can see exactly where the model would have lied.

The LED follows the same rule the hardware does, because the model is the hardware's logic: store to a magic address, trigger a side effect, and everything else is ordinary memory. No hand-waving; the same bits, both ways. And with that, the machine is complete. So take a breath and look back down. You started with one voltage-controlled switch that couldn't decide anything on its own, and here you are, blinking a real light with a program you wrote. Every layer between was the same idea composed again. Here is the whole ladder; you are at the top.

read all the way down 9 a blinking LED — the machine speaks ◄ YOU ARE HERE 8 a program — orders as numbers 7 a CPU — the fetch-do-repeat loop 6 a bus — shared wires + addresses 5 RAM — a grid of many numbers 4 a register — it remembers one number 3 an adder — wires that do arithmetic 2 a gate — a switch answering yes/no 1 a transistor — one voltage-run switch ↓ all the way down: doped silicon — the sand
a blinking LED
Store one bit, push it out a pin — and the machine finally does something you can see.
built from: a register + one output pin
rung 9 of 9 — the world
Every rung is the rung below it, wired to itself. One idea, restacked nine times — so you can stand up here and read all the way down to the sand.
Fig. 19. The whole ladder, on one screen. The blinking LED at the top is where this course ends — and every rung under it is just the rung below it wired to itself: a transistor is a switch, a few switches make a gate, gates make an adder, a looped gate remembers, registers become RAM, a bus lets them talk, and the fetch-do-repeat CPU runs orders that are themselves just numbers. Click any rung to recall its keystone. That's why you can see all the way down: it was one idea, composed again, nine times — from a light you can see, down to the sand.

And that's the finale. We opened on a lie the memory system tells — that a handful of addresses aren't memory — and rode it all the way to a blinking LED. Memory-mapped I/O means the store you built long ago is how a computer touches the world: aim it at a device register and a physical switch flips. We woke a peripheral out of reset, polled a status bit until it was ready, routed a pin through the Chapter-3 mux, set its direction, and drove it high with the Chapter-18 mask. Then we wrapped set and clear around a Chapter-14 delay loop to make it blink, and stored the whole thing in non-volatile flash so it runs on power-up. Not one piece of that was new machinery. It was the ladder you already built, pointed outward. So here's the thing worth carrying away: a computer is not magic and never was. Gates steer electricity; memory is feedback holding still; a CPU is a ROM driving a datapath; and "software" is just numbers a machine has agreed to obey. You can now stand at any rung of this ladder and see all the way down to the sand. That is the whole point of having climbed it.

iolinked.com
Written by Ajai Raj