Pull-ups, debouncing and dividers
A GPIO in input mode answers one question: is this wire high or low? If nothing holds the wire at a defined level it floats — the voltage wanders and the pin reads noise. Pull-up and pull-down resistors give an input a default level, buttons need debouncing, and voltage dividers bring 5 V signals down to the Pi's 3.3 V. Together they cover most input wiring.
Floating inputs and pull resistors
A pull-up resistor connects the input weakly to 3V3, so it reads high until something stronger pulls it low. A pull-down connects it weakly to GND, so it reads low until something pulls it high.
The Pi has built-in pulls of about 50 kΩ, selectable per pin in software (the simulator models 50 kΩ; the BCM2711's real ones vary, roughly 33–73 kΩ). You rarely wire your own:
- gpiozero enables the pull for you:
Button(17)uses a pull-up by default. RPi.GPIOwants it explicitly:GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP).
The standard button circuit puts the button between the pin and ground, with the internal pull-up holding the pin high while it is released:
from wiring import *
pi = Pi()
pi.GPIO17 >> Button() >> pi.GND
from gpiozero import Button
button = Button(17)
button.wait_for_press()
print("Pressed!")
A button costs $0.50, and its part page has the mechanical details.
Bouncing
A button's contacts are metal springs, and they do not close cleanly: for a few milliseconds they chatter open and closed before settling. The simulator's buttons reproduce this — by default four bounces spread over 5 ms on each press and release. Software that reacts to every edge sees one press as many.
Debounce by ignoring changes for the bounce time. In gpiozero, pass one:
from gpiozero import Button
button = Button(17, bounce_time=0.05)
button.when_pressed = lambda: print("One press, one message")
bounce_time=None (the default) does no compensation at all, so set it for any mechanical button. The simulator's edge detection implements a trailing debounce: once a bounce time is given, an edge is reported only after the level has stayed put for that long.
The Pi's fixed pull-ups
Two pins have pull-ups you did not ask for:
- GPIO2 and GPIO3 have fixed 1.8 kΩ pull-ups to 3.3 V, because they are the I²C bus pins. They read high when unconnected, and I²C devices — which can only pull a line low — rely on them. This is why an I²C breakout with no pull-ups of its own still works.
- At power-on, the BCM2711 resets with GPIO0–8 pulled up and GPIO9–27 pulled down; your program overrides this when it sets a pin's pull.
Sensors with an open-drain data line need their own pull-ups: 4.7 kΩ for the DS18B20 and 10 kΩ for a bare DHT22. The simulator's sensor kits include them.
Dividers: 5 V down to 3.3 V
Two resistors in series split a voltage. The midpoint sits at:
Vout = Vin × R2 ÷ (R1 + R2)
The classic case is the HC-SR04: it must run from 5 V, and its ECHO output swings to 5 V, but a GPIO tolerates 3.3 V (3.6 V absolute maximum in the simulator). Feeding ECHO straight in raises "GPIO24 sees 5.0 V — GPIO is 3.3 V only"; on real hardware it can damage the pin. Put 1 kΩ between ECHO and the pin, and 2 kΩ between the pin and ground:
Vout = 5 × 2000 ÷ (1000 + 2000) = 3.33 V — safely below the limit.
The distance sensor module kit has exactly this divider fitted and exposes the midpoint as its ECHO pin:
| Part | Qty | Price each |
|---|---|---|
| HC-SR04 distance sensor | 1 | $3 |
| Resistor | 2 | $0.25 |
| Total | 3 | $3.50 |
Going deeper
- The simulator's input model. A pin reads high at or above 2.0 V and low at or below 0.8 V, with hysteresis between. A selected pull is modelled as a 3.3 V (or 0 V) source through 50 kΩ; GPIO2 and GPIO3 carry a 1.8 kΩ resistor to 3V3. A floating net settles at 0 V, is flagged floating, and the pin randomises its level — so an unconnected input visibly misbehaves instead of quietly reading low.
- Pull-up or pull-down? A plain switch has no polarity, so either works; pull-up to ground is the convention, because open-drain devices (I²C, 1-Wire, the DHT data line, most sensors) can only pull low and therefore need a high default. gpiozero's
pull_up=Falseandactive_stateflip a button's sense if you wire it to 3V3 instead. - The value trade-off. A strong pull (1.8 kΩ on I²C) holds the line firmly but wastes current while it is pulled low — 3.3 V ÷ 1.8 kΩ ≈ 1.8 mA. A weak pull (50 kΩ) costs almost nothing but is easier for noise and long wires to disturb. 4.7–10 kΩ is the usual middle ground for external pull-ups.
- Debounce numbers. Mechanical buttons bounce for 1–10 ms; 50 ms is a safe software bounce time and far below human perception. The simulator's Bounce duration and Bounces per change knobs let you make a button as clean or as nasty as you like.
- Dividers are not level shifters. They only step voltage down, and their output is slow into capacitance, so they suit slow signals such as ECHO. For fast signals, or to go the other way (3.3 V → 5 V), use a logic-level shifter or a 3.3 V-tolerant part such as the HC-SR04P.
- gpiozero's documentation covers Button, DistanceSensor and the rest, including the divider it recommends for the HC-SR04.