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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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<<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.
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.
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.
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.
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.
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.
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.
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.
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.