5.5.2 · D2Embedded Systems & Real-Time Software

Visual walkthrough — GPIO — input - output, pull-up - pull-down, interrupt on pin change

2,863 words13 min readBack to topic

Every symbol below is earned before it is used. If you have never seen a resistor, a transistor, or the word "voltage", start at Step 1 and you will still be fine.


Step 1 — A wire has a voltage, and the pin reads it

WHAT. Look at the figure. A GPIO pin is the end of a metal wire. Electricity in a wire is described by one number: its voltage, written and measured in volts (V). Think of voltage as how much electrical "push" or "height" the wire is sitting at, like the height of water in a tank.

WHY this idea first. Everything else — HIGH, LOW, pull-up, edge — is just a voltage compared to a threshold. If you don't have the picture of "a wire sitting at some height", nothing later makes sense.

PICTURE. The wire sits at a height between the floor (, meaning ground, defined as — our zero reference) and the ceiling (, the chip's supply voltage, e.g. ). The little triangle is the sense buffer — the chip's "eye" that looks at the wire and decides:

  • ::: the wire's actual voltage (its "height") right now.
  • ::: the threshold, a fixed height (≈ half of ) the chip compares against.
  • The buffer outputs a single bit: if the wire is above the line, if below.
Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change
Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 2 — Nothing connected = the wire floats = random reads

WHAT. Disconnect everything. Now no one is holding the wire at a height. The sense buffer is high-impedance — a fancy way of saying it barely draws any current, so it can't set the wire's height either. The wire is left as a tiny capacitor: a bucket that holds a tiny bit of charge and whose voltage drifts.

WHY this step exists. This is the beginner disaster the parent note warns about. To justify the pull resistor in Step 3, you must first feel the problem: an unheld wire's height wanders up and down as nearby noise couples in.

PICTURE. The red trace wobbles across the threshold line unpredictably. Every time it crosses the read flips The pressed/not-pressed answer is pure noise. (Hysteresis from Step 1b helps once the wire is near a level, but here it swings across the whole range, so even a Schmitt trigger reads garbage — the real fix is a pull resistor.)

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 3 — A pull-up resistor gives the wire a resting height

WHAT. Add a resistor — a component that lets current through but resists it, measured in ohms () — from the wire up to the ceiling . This is a pull-up. When nothing else touches the wire, the resistor gently ties it to , so it rests at HIGH.

WHY a resistor and not a plain wire to ? A plain wire would jam the pin HIGH forever — a button could never pull it down without a dead short. A resistor is a soft connection: strong enough to set the idle height, weak enough that a button can overrule it. We prove "weak enough" in Step 5.

PICTURE. With the button open, the resistor's top is at and its bottom is the wire; since the buffer draws ≈ 0 current, no current flows, so there's no voltage drop across the resistor and the whole wire sits at → reads HIGH, rock-steady.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 4 — Press the button: the wire is yanked to LOW (active-LOW)

WHAT. Wire the button between the pin and (the floor). Press it → the wire is now connected to through the button's near-zero resistance. So it drops to LOW and reads .

WHY the button reads 0 when pressed. With a built-in pull-up, "resting = HIGH, pressed = LOW". Pressed = 0 is called active-LOW. It looks backwards but it's the clean, standard wiring — grounding a wire is electrically tidy and pull-ups are built into most chips.

PICTURE. Now a complete loop exists: . Current flows. The whole is dropped across (the button drops almost nothing), so the pin sits at ≈ → LOW.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

The current that now flows is set by Ohm's Law with the full supply across :

  • ::: the full supply voltage now dropped across the resistor (button pressed).
  • ::: the pull-up resistance.
  • ::: the wasted current that flows the whole time you hold the button.

Step 5 — WHY the resistor value matters: pick to cap wasted current

WHAT. The pressed-state current is wasted as heat while held. We choose big enough to keep small, but not so big it becomes floppy.

WHY. Two opposing forces — this is the whole sizing argument:

  • Too small → large , lots of wasted power (and heat).
  • Too big → so weak that noise can shove the wire around → back to the Step 2 disaster.

PICTURE. The curve falls as grows. We draw the current budget line and read off the smallest allowed . Axes are labelled: horizontal is in kΩ, vertical is held current in μA.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 6 — Reading forever wastes the CPU: polling vs. interrupt

WHAT. How does the CPU learn the wire changed? Option A: poll — loop forever re-reading the pin. Option B: interrupt — let hardware watch the pin and yank the CPU into a special function only when it changes.

WHY interrupts. Polling burns 100% of the CPU asking "changed yet? changed yet?" and can miss a pulse that arrives between two reads. An interrupt is a tap on the shoulder: the CPU does other work and is called only on the event. See Polling vs Interrupt-Driven I/O.

PICTURE. Top track: polling repeatedly stabs at the wire (wasted stabs shown grey, the one that catches the change in orange). Bottom track: the CPU sleeps until the falling edge fires one arrow into the ISR.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 7 — Trigger on the falling edge: one press = one call

WHAT. A press is a transition (HIGH to LOW), called a falling edge. We tell the hardware: fire the ISR (Interrupt Service Routine — the function hardware jumps to) only on the falling edge.

WHY the edge and not the level. A level trigger fires continuously while the wire is LOW — hold the button and the ISR re-fires forever. An edge fires once at the instant of the transition → exactly one call per press, which is what a button-press event deserves.

PICTURE. The wire drops from HIGH to LOW; the down-arrow marks the single falling-edge instant where the ISR launches. The flat LOW that follows produces no further calls.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

Step 8 — The degenerate case: contacts bounce → many false edges (on BOTH press and release)

WHAT. A mechanical switch does not close cleanly. For ~1–10 ms its metal contacts physically bounce, producing a burst of extra transitions. This happens twice per button use:

  • On press (the closing bounce): a burst of edges → many spurious falling edges.
  • On release (the opening bounce): as the contacts spring apart they also chatter → a burst of edges → many spurious rising edges.

So even if you only trigger on the falling edge, the press alone fires 5–20 times; and if you also watch rising edges (e.g. CHANGE trigger, or to detect release), the release bounce fires again.

WHY this needs its own step. It's the degenerate input the ideal edge model ignores. If you don't handle it, your one-press logic silently multiplies — and forgetting the release bounce is the classic second bug once people fix only the press side.

PICTURE. Two zoomed windows side by side: the press window (contacts settling to LOW, spurious falling edges marked ✗) and the release window (contacts settling back to HIGH, spurious rising edges marked ✗).

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change

The fix — debounce. After the first edge, ignore further edges for ~20 ms — this covers both the press and the release bounce because each burst is shorter than the guard window. In pseudocode: record now in the ISR and bail out if now - last < 20 ms. Or filter the wire with an RC network feeding a Schmitt-trigger input (Step 1b), which cleans the small wobbles in hardware. Full treatment in Switch Debouncing.


The one-picture summary

Everything above, compressed into a single circuit-plus-timeline: the pull-up sets the resting HIGH, the button yanks it LOW via a well-sized , the Schmitt-trigger input cleans near-threshold wobble, and the one clean falling edge (after debouncing away both bounces) fires the ISR exactly once.

Figure — GPIO — input - output, pull-up - pull-down, interrupt on pin change
Recall Feynman retelling — the whole walkthrough in plain words

A pin is the end of a wire, and all the chip cares about is how high the wire is sitting: above the middle line = HIGH (1), below = LOW (0). Real chips don't use one line but two close lines (a Schmitt trigger), so a wire hovering in between keeps its last answer instead of chattering. A wire with nothing attached is like a swing in the wind — it drifts and the answer is random noise. So we tie a soft rubber band from the wire up to the ceiling: that's the pull-up resistor. It holds the wire high but gently, so when you press a button that connects the wire to the floor, the wire drops to LOW — that's why "pressed = 0". We can't use a plain wire for the band, because a big enough resistor keeps the wasted electricity trickle tiny when the button is held; too small wastes power, too big and noise sneaks back in — so about 33 kΩ. Now, instead of asking the wire "did you change?" a million times a second (polling, which burns the CPU and can miss a blink), we let the hardware tap us on the shoulder only at the exact instant the wire falls from high to low — that's the falling-edge interrupt, and it runs our tiny ISR once per press. One catch: real switches chatter for a few milliseconds — both when you press AND when you let go — so we ignore any extra taps for ~20 ms. That's the entire journey: bare wire → floating chaos → pull-up rest → button pulls low → sized resistor → two clean thresholds → shoulder-tap on the edge → ignore the chatter on both sides.

Recall Rebuild the chain from memory

What does the buffer read exactly at in the ideal model, and how do real chips avoid it? ::: Ideally undefined (tips either way, can chatter); real chips use a Schmitt trigger with two thresholds so the output sticks. Why does a floating input read randomly? ::: No component sets its voltage, so it drifts across the threshold picking up noise. What does a pull-up do when the button is open? ::: Ties the wire to ; no current flows so no drop → clean HIGH. Why must the pull-up be "weak" (large)? ::: So a real driver overrides it and so held-current stays small. Why trigger on an edge, not a level? ::: An edge fires once per transition; a level re-fires forever while held. Why does one button use cause many interrupts, and when? ::: Contacts bounce on BOTH press (falling burst) and release (rising burst); ignore further edges for ~20 ms (debounce).