The puzzle
A button reads correctly on the bench, and on a customer’s desk it sometimes registers presses nobody made. A rotary encoder occasionally counts backwards for one step. Both inputs are wired correctly and both read the pin exactly as the manual says. What else does a reliable input need?
STEP 1
Every input needs a driver or a pull
An input pin reads a defined level only while something drives it: an output, a button connected to a rail, or a pull resistor. A button connects the pin to one rail only while pressed; the pull must hold the opposite rail the rest of the time (unit 1, lesson 3). The two usual wirings:
| wiring | pull | released | pressed |
|---|---|---|---|
| button to GND | pull-up | 1 | 0 (active low) |
| button to VDD | pull-down | 0 | 1 (active high) |
↑ This step uses the figure at the top of the page.
While the button is held, the pull resistor carries VDD/R continuously; with a 50 kΩ internal pull at 3.3 V that is 66 µA, negligible for a mains device and significant for a battery device that holds a button for hours. The RP2040 also offers a bus keeper (both pulls on): a weak pull toward whatever level the pin last had, useful on a shared line, but no help for a button.
STEP 2
Floating and unused pins
An input connected to nothing floats: its level is set by leakage, noise and whatever charge it last held, so it can read anything and change on its own. Worse, a CMOS input buffer at an intermediate voltage has both its transistors partly on and draws current. Unused pins should therefore be put in a defined state:
- enable a pull (the RP2040 pads reset with the pull-down on), or
- configure them as outputs driving a fixed level, if nothing on the board connects to them, or
- disconnect the input buffer (RP2040: clear the pad’s input enable; STM32F4: analog mode).
Pins used as analog inputs need the digital buffer off too: the RP2040’s boot ROM and the SDK both clear the input enable of its ADC pins, GPIO26–29. Calling gpio_init() on those pins turns it back on; the SDK’s ADC pin set-up function turns it off again.
STEP 4
Worked example: a battery-powered button
A handheld device uses an internal pull-up with a button to ground. Assume 50 kΩ (internal pulls are specified as a range; take the value from your datasheet).
If the user can leave the button held (in a pocket, under a case), that is 66 µA for as long as it lasts, which may exceed the whole chip’s sleep current. Options: an external pull-up of 1 MΩ with the internal pulls disabled (gpio_disable_pulls(); against the RP2040’s default pull-down it would read 0 for ever): 3.3 µA, but 1 µA of leakage already drops 1 V across it, leaving little margin above the input threshold. Or firmware that disables the pull while the button is known to be held and polls it rarely.
MYTHS AND FACTS
Common misconceptions
An input with nothing connected reads 0
It floats and can read anything.
The internal pull is always enough
It is weak (tens of kΩ): fine for a button next to the chip, marginal on long wires in a noisy environment, where a stronger external pull and filtering help.
Reading pins one by one is equivalent to reading the port
Separate reads sample at different instants.
Unused pins can be left as they come out of reset
Only if the reset state is defined; on many chips pins reset as floating inputs.
Check yourself
Answer in your head, then open the card.
A button connects a pin to 3.3 V. Which pull do you need, and what does the pin read when released and pressed?
A pull-down: released reads 0, pressed reads 1.
An encoder's A and B are read with two gpio_get() calls. Occasionally the count moves the wrong way for one step. Why, and what fixes it?
The two reads sample A and B at different instants; if both lines change between them (an interrupt between the two calls makes that likely), the program sees a state that never existed. Read GPIO_IN once with gpio_get_all() and take both bits from that value.
Why do the RP2040's boot ROM and SDK clear the input enable of GPIO26–29?
They are the ADC inputs. With the digital input buffer enabled, an analog voltage between the thresholds can make the buffer draw current and couple noise; turning it off leaves the pin to the ADC.
A pull-up of 10 kΩ holds a line that a button shorts to ground. The button is held for 10 minutes. What charge does that take from a 3.3 V battery?
330 µA × 600 s = 0.198 C, about 55 µAh.
Sources (4)
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_gpio/gpio.h and hardware_regs sio.h, pads_bank0.h — gpio_get() reads one bit of sio_hw->gpio_in and gpio_get_all() the whole register (SIO GPIO_IN, “Input value for GPIO0...29”); gpio_set_pulls: “setting both pulls enables a bus keep function, i.e. a weak pull to whatever is current high/low state”; gpio_set_input_enabled, gpio_set_input_hysteresis_enabled; pads reset with IE = 1, PDE = 1, SCHMITT = 1
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_regs/pio.h (PIO_INPUT_SYNC_BYPASS) — “There is a 2-flipflop synchronizer on each GPIO input, which protects PIO logic from metastabilities. This increases input delay”
- Raspberry Pi Ltd, pico-bootrom-rp2040, bootrom/bootrom_rt0.S, and pico-sdk 1.5.1 pico_runtime/runtime.c — both the boot ROM (disable_adc_ie) and runtime_init() clear the input enable of GPIO26–29, the ADC-capable pins, “after resetting BANK0”
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_gpio.h and Src/stm32f4xx_hal_gpio.c — GPIO_MODE_INPUT “Input Floating Mode” (no pull unless PUPDR is set); GPIO_MODE_ANALOG; HAL_GPIO_ReadPin reads IDR