5.5.2 · D5Embedded Systems & Real-Time Software

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

2,627 words12 min readBack to topic

True or false — justify

A floating input pin will always read LOW because nothing pulls it up.
False. "Floating" means undefined, not LOW — the pin acts like a tiny antenna picking up noise, so it reads a random, jittery mix of HIGH and LOW.
A push-pull output can also be read to sense an external button on the same pin.
False. A push-pull output is actively driving the line through a low-resistance transistor, so reading the input register just returns the value you wrote, not the button's state. You must switch direction to INPUT first.
An internal pull-up is "weak," which means it is too feeble to ever pull the pin fully HIGH.
False. "Weak" refers to its large resistance (≈10–50 kΩ, the range MCU makers build in), not to failure — with an open input it easily brings the pin to because the sense buffer draws almost no current. Weak only means a real driver can override it, which is the whole point.
Active-LOW means the button is wired incorrectly and should be fixed.
False. Active-LOW ("pressed = 0") is the preferred robust wiring: pull-up idle-HIGH, button grounds the pin. It's intentional because MCUs (microcontroller units) have built-in pull-ups and grounding is electrically clean.
A level-triggered interrupt is the right choice for a momentary push-button.
False. A level trigger re-fires continuously while the pin is in the active state, so holding the button floods you with ISR (interrupt-service-routine) calls. A button is an event, so you want edge triggering (one call per transition). The timing figure below contrasts the two. See Interrupts & ISR Design.
If code works in an unoptimized debug build, the ISR-shared flag doesn't really need volatile.
False. Debug builds happen to re-read RAM, hiding the bug. With optimization the compiler may cache the flag in a CPU register forever, so the main loop never sees the ISR's change; volatile forces a fresh memory read so the update is always seen. It is mandatory regardless of build.
Open-drain outputs can drive a line both HIGH and LOW like push-pull.
False. Open-drain has only the pull-to-GND transistor: it can pull LOW or go Hi-Z (high-impedance — connected to neither rail), never actively drive HIGH. An external pull-up supplies the HIGH — this is exactly how Open-drain & I²C Bus shared buses work.
Polling can safely replace interrupts for catching very short input pulses.
False. A poll only sees the pin at the instants the loop reads it; a pulse shorter than the loop period slips through between reads. Hardware interrupts latch the event in a flag so it can't be missed. See Polling vs Interrupt-Driven I/O.
Writing to the input data register lets you set the pin's level.
False. The input register is a sense path; writing to it does nothing useful. You set output levels via the output data register (PORTx/ODR), and the read and write paths are physically separate.
A larger pull-up resistor is always better because it wastes less current.
False. There's a tradeoff: too large (e.g. 1 MΩ) makes the pull so weak that noise and leakage can override it, giving flaky reads. You want small enough to stay firmly at the idle level, large enough to limit current — see Pull-up Resistor Sizing & Ohm's Law.

Spot the error

pin_mode(2, OUTPUT); if (read_pin(2)) handle_button(); — what's wrong?
The pin is configured as OUTPUT while you try to sense a button. Reading it returns whatever you last drove, not the external signal. Set it to INPUT (with a pull) before sensing.
An ISR does for(i=0;i<100000;i++) crunch(); then returns — why is this a bug?
The ISR is doing heavy work while other interrupts may be blocked, causing missed events and timing jitter. Keep the ISR tiny: set a flag and do the crunching in the main loop.
The ISR sets a flag but never calls clear_interrupt_flag(PIN2) — what happens?
The hardware event flag stays set, so the moment the ISR returns it fires again immediately, trapping the CPU in an endless ISR loop. You must clear the flag inside the ISR.
bool pressed = false; is shared between an ISR and main() but not marked volatile — the error?
Without volatile the compiler may cache pressed in a CPU register and never re-read RAM, so main()'s loop never sees the ISR's update. Mark it volatile to force a fresh memory read each time.
A designer uses a 220 Ω pull-up on a 3.3 V active-LOW button — why is this poor?
When the button is held, current is mA wasted continuously as heat. That's far more than needed just to define the idle level; a 33–47 kΩ resistor does the same job at microamps.
A shared I²C-style line uses push-pull outputs on every device — what breaks?
If one device drives HIGH and another drives LOW simultaneously, they fight and can damage the drivers ("bus contention"). Shared buses require open-drain so devices can only pull LOW, letting a common pull-up define HIGH. See Push-Pull vs Open-Drain Outputs.
One button press triggers the ISR 12 times — what was forgotten?
Switch bounce: the mechanical contacts chatter for ~1–10 ms producing a burst of edges. The bounce figure below shows the spray of edges. Add debouncing — ignore further edges for ~20 ms or filter with an RC network. See Switch Debouncing.

Why questions

Why do most MCUs give input and output their own separate data registers?
Because the read path (a sense buffer) and the write path (driver transistors) are physically different circuits inside the pin. Separating them keeps "what I want to drive" and "what the world put here" from colliding.
Why does a strong output driver reliably override a weak pull-up on the same node?
Because the two resistances form a voltage divider from down to GND, and the smaller resistor keeps almost all the voltage. See the divider figure and derivation just below — with a few-ohm driver against a 33 kΩ pull-up the pin lands at essentially 0 V.
Show the divider that proves the driver wins.
When the driver pulls LOW, the pin sits between (up to ) and (down to GND) carrying one current ; the pin voltage is what's left across , so . Plugging , , V gives mV — indistinguishable from GND, so the driver wins overwhelmingly.
Why trigger a button ISR on the falling edge rather than the rising edge (for active-LOW)?
In active-LOW wiring the pin sits HIGH when idle and drops to LOW on press, i.e. the press is a transition. The falling edge is the moment of the press, so you get exactly one ISR call per press.
Why is an interrupt described as "the pin tapping the CPU on the shoulder"?
Instead of the CPU asking "did it change?" thousands of times a second (polling, wasteful and prone to missing pulses), the hardware watches the pin and yanks the CPU into the ISR the instant the event occurs — so the CPU can do other work meanwhile. See Polling vs Interrupt-Driven I/O.
Why must an ISR be kept as short as possible?
While inside an ISR other interrupts may be masked, so a long ISR delays or drops other events and adds timing jitter. Do the minimum (set a flag / enqueue) and hand the real work to the main loop.
Why does an input pin need a pull resistor even though it "only listens"?
Listening to a floating wire returns noise, because the high-impedance sense buffer has nothing holding its voltage steady. A pull-up or pull-down defines the idle level so an un-driven pin reads a known, stable value.

The three pictures behind this bank


Edge cases

What state are most MCU pins in the instant after power-up, and why does it matter?
On reset most pins default to input with pulls disabled — i.e. floating, high-impedance. Until your setup code enables a pull or drives them, any input reads noise and any device wired to them sees an undefined level, so you must configure pins early before trusting or acting on them.
If the pull-up and an external device both try to define the pin, who wins?
The strong device (a few-ohm driver) wins by the divider argument; the kΩ pull-up only matters when nothing else drives the pin. That "only when floating" role is exactly its job.
What does an open-drain output read as when it's in Hi-Z with no external pull-up?
It floats — undefined, noisy — because Hi-Z means it drives neither rail, just like any undriven input. Open-drain requires an external pull-up to give the released line a defined HIGH.
What happens if a pin-change interrupt is set to "change" (both edges) for a bouncing switch?
Every bounce edge — both directions — fires the ISR, so a single press can generate dozens of calls. Both-edge triggering makes the bounce problem worse, not better; debounce first.
If a fast pulse arrives while the CPU is busy but interrupts are enabled, is it lost?
No — the hardware latches the event in the interrupt flag, so the ISR runs as soon as the CPU is free. This latching is the key advantage over polling, which only samples at loop instants.
What does a pull-down input read when the button (wired to ) is not pressed?
LOW (logic 0), because the pull-down gently ties the pin to GND while nothing drives it. Pressing then connects it to for a HIGH — this is the active-HIGH mirror of the usual active-LOW wiring.
If you enable an internal pull-up and wire an external pull-down, what level does the pin idle at?
A divider between (through the internal pull-up) and GND (through the external pull-down) sets the idle voltage; if both are similar in size the pin can sit near mid-supply — an undefined logic region. Don't fight a pull-up against a pull-down.

Recall One-line self-test

Cover every answer above and re-run the bank. If you can justify the verdict (not just guess it) on each, you've mapped the trap.