The puzzle
You wire a push button between a microcontroller pin and ground, write if (pin_is_low) …, and it works, sometimes. Left alone, the pin reports presses that never happened, or nothing at all, or something that changes when you bring your hand near the board. The missing part costs a fraction of a cent: a resistor. This lesson is about why it is there and how big it should be.
STEP 1
What a resistor does
A resistor is a component that obeys Ohm’s law on purpose: put a voltage across it and exactly flows, no more. That makes it the tool for limiting current. Put 3.3 V across a bare wire and the current is limited only by the supply’s ability to deliver it; put 3.3 V across 10 kΩ and the current is 330 µA, full stop.
The other thing a resistor does is set a default. Connect one end to a known voltage and the other end to a node with nothing else pulling on it, and that node drifts to the known voltage. Almost no current flows, because nothing is drawing any, but the connection is enough to hold the node in place. That second job is what pull-ups and pull-downs do.
STEP 2
The floating input
An input pin with nothing connected is a tiny capacitor, a few picofarads, joined to the rest of the world only by leakage paths of many megohms (drawn dashed). Whatever charge it happens to hold sets its voltage, and a neighbouring switching trace, a finger or humidity nudges it. It may sit steady for minutes or drift through the undefined band; either way your circuit does not define the reading.
A digital input is designed to draw as little current as possible: the RP2040 and ATmega328P datasheets both specify at most 1 µA of leakage. That is a virtue when the pin is driven, and a problem when it is not. With nothing connected, the pin is a tiny capacitor, a few picofarads, joined to the world only by leakage paths of megohms. Whatever charge it happens to hold sets its voltage. A neighbouring trace that toggles couples a little charge across; a finger nearby changes the capacitance; humidity changes the leakage.
The pin might sit at a clean 0 for an hour and then start reading 1. It might hover in the undefined band. It might follow your hand. None of that is “random toggling” in any predictable sense; it is simply not defined by anything you built. The fix is to define it.
STEP 3
Pull-up and pull-down
↑ This step uses the figure at the top of the page.
A pull-up is a resistor from the input node to the supply, with the button from the node to ground. Button open: nothing draws current through the resistor, so the node sits at the supply voltage and the input reads HIGH. Button pressed: the switch ties the node to ground; now the full supply voltage sits across the resistor, flows through it into the switch, and the input reads LOW. The pressed state is the low one, so the signal is active-low.
A pull-down is the mirror image: resistor to ground, button to the supply. Open reads LOW, pressed reads HIGH: active-high.
Both are correct. Pull-ups are more common for buttons for practical reasons: many chips have internal pull-ups available on every pin, an open-drain output can share the same net, and a switch to ground is easy to route. What matters is that firmware and hardware agree on which level means “pressed”. An active-low button read as if it were active-high inverts every press and release.
STEP 4
Worked example: current, power and the level
With a 3.3 V supply and a 10 kΩ pull-up, the button is held for one second. How much current flows, and how much power does the resistor dissipate?
Negligible heat, and 330 µA is trivial next to a running microcontroller. But suppose the product sleeps at 5 µA and a lid switch is held closed for its entire life in the closed position: 330 µA through the pull-up would dominate the battery budget by a factor of sixty. That is when a 100 kΩ or 1 MΩ pull-up, or a pull-down arranged so that the usual state draws nothing, earns its place.
Now the level with leakage. If the input leaks 1 µA toward ground while the button is open, the pull-up drops mV, leaving 3.29 V: still a solid HIGH. With a 1 MΩ pull-up the same leakage drops 1 V, leaving 2.3 V, which is above the 2.0 V of a typical 3.3 V input but with far less margin, and a damp or contaminated board with 10 µA of leakage would drag it into the undefined band.
STEP 5
The trade-off
A smaller resistor wastes more current whenever the button is held, but recharges the pin faster and shrugs off leakage. A larger resistor saves power but lets small leakage currents drag the level, and with the pin and wiring capacitance it makes a slower edge. Rise time here is the 10–90 % time of an RC edge, 2.2·R·C. The leakage model is a constant current sink while the node remains positive; values clamped at 0 V indicate model saturation, not a predicted real voltage.
Three things move together when you change the resistor:
- Current while pressed falls as rises. Smaller resistors waste more power whenever the button is held.
- Sensitivity to leakage grows with . The level droops by , so a large pull-up on a leaky node loses its margin.
- Rise time grows with . When the button releases, the resistor has to recharge the pin capacitance and whatever wiring hangs on the node. Treated as an RC edge, the 10–90 % rise time is about . With 50 pF and 10 kΩ that is 1.1 µs, irrelevant for a button. With 1 MΩ and a long cable at 1 nF it is 2.2 ms, long enough to matter, and a slow edge is exactly what makes a plain input flicker.
Values from 4.7 kΩ to 100 kΩ cover most button and signal uses, and the choice is rarely critical for a button. It becomes critical on a shared bus. The I²C specification sizes its pull-ups by exactly this trade-off: the smallest value comes from the output’s ability to sink current, with V at 3 mA, and the largest comes from the rise-time limit against the bus capacitance, . At 3.3 V, 400 pF and a 300 ns Fast-mode rise time, that range is roughly 970 Ω to 885 Ω, which does not even overlap, so these assumed sink-current and capacitance limits leave no valid resistor range; stronger guaranteed sink capability or lower bus capacitance changes the result. The same formula tells you why a short, low-capacitance bus tolerates a lazy 10 kΩ pull-up and a long one does not.
STEP 6
Internal pull resistors
Most microcontrollers can enable a weak pull-up or pull-down inside the pin. The datasheets quote a range, not a value: 50–80 kΩ for the RP2040, 20–50 kΩ for the ATmega328P. That is fine for a button on the same board and often saves a part. It is a poor choice when the node is long or noisy, when the level must be defined before firmware has run (reset defaults and boot-time configuration depend on the particular pin and device), or when you need a known resistance for a bus. In those cases, fit an external resistor.
STEP 7
Drawing it correctly
A dot means the wires join. No dot at a crossing means they pass over each other without touching. To avoid any doubt, good schematics never put four wires through one dot; they stagger the two junctions.
Pull-ups create junctions: the resistor, the switch and the pin all meet at one node. On a schematic, a dot marks that the wires join; two wires that cross without a dot do not touch. Careful drawings avoid four wires meeting in one dot, because a missing dot at such a crossing would be invisible; they stagger the two junctions instead. When you read a schematic, the dots are where the current can split, and the pull-up node is the first place to check that the button, the resistor and the pin are really on the same net.
MYTHS AND FACTS
Common misconceptions
A floating input toggles randomly
It is undefined: it may be steady, may drift, may follow a hand. The lesson is not that it is noisy but that nothing you built determines it.
Pull-ups are for buttons only
Any input that can be left undriven needs one: reset lines, chip-select lines, open-drain buses, unused inputs.
Bigger is always better because it saves power
Bigger also means more leakage sensitivity and slower edges. Pick the largest value that still gives you margin and speed.
The internal pull-up is a 47 kΩ resistor
It is a range that may span more than 2:1. Its reset state is device- and pin-specific; some pins have a pull enabled by default.
Active-low means the signal is broken or inverted
It is a convention: pressed = LOW. Read the schematic, then write the comparison to match.
Check yourself
Answer in your head, then open the card.
A 5 V system uses a 4.7 kΩ pull-up on a button. What current flows while the button is held, and what does the pin read?
mA through the resistor and the switch. The pin is tied to ground by the switch, so it reads LOW (active-low).
The same button is rewired as a pull-down. What changes in the firmware?
The idle level is LOW and the pressed level is HIGH. The comparison inverts: pressed means the input reads 1. The current while held is the same 1.06 mA, now flowing from the supply through the switch and the resistor to ground.
A 1 MΩ pull-up holds an input that leaks 2 µA. Is the idle level still a guaranteed HIGH for a 3.3 V input with VIH = 2.0 V?
The droop is V, leaving 1.3 V: inside the undefined band. Not guaranteed. Use a smaller pull-up or reduce the leakage.
Why can an internal pull-up not be relied on to hold a chip’s enable pin in a safe state at power-up?
Check the reset-state table and power-up behavior first: some internal pulls are enabled at reset, others are not. RP2040 ordinary GPIO pads, for example, reset with a pull-down enabled (PADS_BANK0 register description, §2.19.6). An external resistor is useful when the required default must be independent of firmware and the internal reset configuration.
Sources (4)
- Raspberry Pi Ltd, RP2040 Datasheet (build 2025-02-20), Table 625, pp. 615–616 — internal pull-up and pull-down resistance 50–80 kΩ; input leakage up to 1 µA
- Microchip, ATmega328P Data Sheet DS40002061B (Rev. B, 08/2020), Table 30-1, pp. 322–323 — I/O pin pull-up resistor RPU 20–50 kΩ; input leakage IIL and IIH up to 1 µA at VCC = 5.5 V
- NXP, UM10204 “I²C-bus specification and user manual”, Rev. 7.0 (1 October 2021), §7.1 “Pull-up resistor sizing”, p. 50; Tables 9 and 10 — Rp(min) = (VDD − VOL(max)) / IOL with VOL ≤ 0.4 V at 3 mA; Rp(max) = t_r / (0.8473 · Cb); rise time ≤ 1000 ns (Standard-mode) and ≤ 300 ns (Fast-mode); bus capacitance ≤ 400 pF
- Tony R. Kuphaldt, Lessons in Electric Circuits, Vol. I (DC), ch. 2 “Ohm’s Law” — current limiting with a series resistor follows directly from I = E/R