Exercises — GPIO — input - output, pull-up - pull-down, interrupt on pin change
Two symbols recur, so let us pin them down once in plain words:

Look at the figure above: it is the reference circuit for almost every problem — an internal pull-up resistor from the pin to , a button between the pin and , and the CPU's sense buffer "listening" at the pin. The red element is whatever each problem asks about.
Level 1 — Recognition
L1.1 A pin is configured so that when nothing is connected or pressed, the CPU reads logic 1 (HIGH). Which resistor is enabled — pull-up or pull-down?
Recall Solution
A pull-up. By definition a pull-up ties the pin to through a weak resistor, so the idle (undriven) read is HIGH = logic 1. A pull-down would tie it to and idle LOW = logic 0.
L1.2 A button is wired active-LOW. The CPU reads the pin as 0. Is the button pressed or released?
Recall Solution
Pressed. "Active-LOW" means the active (pressed) state produces LOW = 0. Released → pull-up → HIGH = 1. So 0 ⇒ pressed.
L1.3 Name the three registers you touch on almost every MCU to use one GPIO pin, and say what each controls.
Recall Solution
- Direction register (
DDRx/MODER/TRIS) — sets input vs output. - Output-data register (
PORTx/ODR) — the HIGH/LOW you drive when output. - Input-data register (
PINx/IDR) — the level you read when input.
Level 2 — Application
L2.1 . An internal pull-up of is used. When the button is pressed (pin shorted to GND through the button), how much current flows through the pull-up? Give your answer in .
Recall Solution
When pressed the pin sits at , so the entire is dropped across . WHY the whole ? Because the button short holds one end of at GND and holds the other end, so the voltage across the resistor is .
L2.2 You must keep the pressed-state current at or below with . What is the minimum pull-up resistance?
Recall Solution
Smaller ⇒ bigger current, so a maximum current gives a minimum . Rearrange Ohm's law for : So use ≥ 100 kΩ. Anything smaller wastes more than the allowed while the button is held.
L2.3 A real output transistor drives the pin LOW with an on-resistance , fighting a pull-up to . What voltage does the pin actually settle at? Is that a valid LOW?
Recall Solution
The pull-up and the driver form a voltage divider: pushes down through , the driver pulls to GND through . WHY a divider? Because both resistors are in series from to GND with the pin as the middle tap, and the pin voltage is the fraction of that lands across the bottom resistor : That is essentially — a rock-solid LOW. This is the whole reason "weak" pull-ups work: the strong driver overwhelms them.
Level 3 — Analysis
L3.1 An input pin has no pull resistor enabled and nothing is driving it. A colleague says "it just reads 0, that's fine." Explain in circuit terms why the read is actually unreliable, and what physically stores the stray voltage.
Recall Solution
The input is a high-impedance sense buffer — it draws almost no current, so it cannot pull the pin anywhere. The pin plus nearby traces form a tiny capacitor (a charge store). With nothing to fix its voltage, that capacitor picks up charge from mains hum, nearby switching wires, even your finger's field. So the read is whatever noise last charged the node — sometimes 0, sometimes 1, randomly. A weak pull resistor supplies a defined path to or , so the capacitor always relaxes to a known level.
L3.2 For an active-LOW button you must choose a trigger for the interrupt. Rising, falling, or level? Justify why the other two are wrong.
Recall Solution
Falling edge. Active-LOW idles HIGH and goes LOW on press, i.e. a transition = falling edge. That fires exactly once per press — the event we care about.
- Rising edge would fire on release (), not press — wrong moment.
- Level (LOW) would keep re-firing continuously while the button is held, flooding the CPU. An edge is a one-shot event; a level is a sustained state.
L3.3 A pressed button generates a burst of ~14 edge interrupts in 6 ms even though you pressed once. Name the phenomenon, explain the physical cause, and give a software fix with a concrete time window.
Recall Solution
This is switch bounce. The metal contacts physically slam, spring apart, and re-touch several times over ~1–10 ms before settling, so each micro-contact is a real edge to the hardware. Fix = debounce: on the first edge, record a timestamp and ignore further edges until, say, 20 ms has elapsed (20 ms comfortably exceeds the ~6 ms bounce window). Alternatively filter the line with an RC network so fast wiggles never reach the pin. See Switch Debouncing.
Level 4 — Synthesis
L4.1 Design the pull-up for a keypad line. Constraints: ; pressed-state current must stay below ; the pull-up must be strong enough that leakage of up to into the pin cannot lift the LOW above . Give a valid resistance range.
Recall Solution
Upper bound (current cap). Bigger = less current, so the current limit sets a minimum : Lower... wait, the leakage sets a maximum . When the button pulls LOW, a leakage current flows through the pull-up too and lifts the pin. The pin voltage above GND is (Ohm's law across the pull-up, with as the current). To keep : So any in works. A classic 100 kΩ sits safely in that band. (Related sizing logic: Pull-up Resistor Sizing & Ohm's Law.)
L4.2 Write a correct interrupt-driven skeleton (pseudo-C) for an active-LOW button on pin 3 that (a) uses the internal pull-up, (b) triggers once per press, (c) is debounced by 20 ms, and (d) is safe under -O2 optimization. State why each safety choice is there.
Recall Solution
volatile bool pressed = false; // shared ISR<->main => volatile
volatile uint32_t last_edge_ms = 0;
void ISR_PIN3() { // hardware jumps here on falling edge
uint32_t now = millis();
if (now - last_edge_ms >= 20) // debounce: ignore bounce burst
pressed = true;
last_edge_ms = now;
clear_interrupt_flag(PIN3); // else it re-fires immediately
}
int main() {
pin_mode(3, INPUT_PULLUP); // idle = HIGH (active-LOW)
attach_interrupt(3, FALLING, ISR_PIN3); // once per press
while (1) {
if (pressed) { handle_press(); pressed = false; }
}
}volatileonpressedandlast_edge_ms: the compiler otherwise caches them in a register and the main loop never sees the ISR's writes — the exact bug that "works in debug, breaks at-O2."- Falling trigger: one ISR per press for active-LOW.
clear_interrupt_flag: the hardware latches a flag; if not cleared the ISR re-enters forever.- 20 ms debounce window: swallows the bounce burst. Tiny ISR body: set a flag, do heavy work in
main. See Interrupts & ISR Design and volatile & Memory-Mapped Registers.
Level 5 — Mastery
L5.1 A student sets pin 5 as a push-pull output driving 1, then in the same loop reads pin 5 hoping to sense a button wired to it. The read is always 1 regardless of the button. Diagnose fully: why the read is stuck, what the button current does, and the correct fix.
Recall Solution
A push-pull output actively drives the line: driving 1 turns on the top transistor, hard-connecting the pin to through a few ohms. Reading the input register then just returns the level the output is forcing — 1 — not the button. Worse: if the button is pressed it shorts button through that low-ohm transistor, a near-short that can wastefully sink large current or damage the pin.
Fix: to sense a button you must set the pin direction = INPUT with a pull-up (INPUT_PULLUP), which disconnects the drive transistors and lets the world set the level. Output and input are physically different paths (see Push-Pull vs Open-Drain Outputs and Polling vs Interrupt-Driven I/O).
L5.2 Two devices on a shared line, one open-drain output with an external pull-up to . When a device pulls the line LOW (), compute the bus current. Then explain why open-drain, not push-pull, is mandatory on such a shared bus.
Recall Solution
When one device holds the line LOW, the full drops across the pull-up: Why open-drain is mandatory: every device can only pull LOW or release (go Hi-Z); the shared pull-up provides the HIGH. So if two devices act at once, worst case both pull LOW — harmless. With push-pull, one device driving HIGH and another driving LOW would connect directly to GND through two low-ohm transistors: a destructive short. Open-drain makes the wired-AND bus safe. This is exactly how Open-drain & I²C Bus works.
Recall One-line recap of every formula used
- Current through a pull-up when pressed: .
- Minimum from a current cap: .
- Maximum from a leakage/voltage limit: .
- LOW voltage under a real driver (divider): .
Return to the parent GPIO note.