Pull-up and pull-down resistors: why your button needs one

A button wired straight to a pin reads random presses. One 10kΩ resistor fixes it. Here is how pull-up and pull-down resistors work, and when the ESP32-S3's built-in ones are enough.

Published

Pull-up and pull-down resistors: why your button needs one
On this page

You wire a button to your ESP32-S3, upload a sketch that prints "pressed", and the Serial Monitor starts printing it before you've touched anything. Wave your hand near the wires and it prints faster.

Nothing is broken. The pin is floating, and one resistor fixes it.

What is a floating pin?

A GPIO (general-purpose input/output) pin set as an input measures the voltage on its wire. It barely uses any current to do that: the ESP32-S3 datasheet lists 50nA at most, which is tiny.

That's great for battery life, and it means the pin has no opinion of its own. When the button is open, the pin is connected to nothing. Static from your hand or a signal in a nearby wire can nudge it up and down.

A graph of a floating pin's voltage wandering between 0V and 3.3V over time. Above 2.48V is marked reads HIGH, below 0.83V reads LOW, and the band between them could read either.

The ESP32-S3 datasheet only promises a HIGH reading above 0.75 × 3.3V (about 2.48V) and a LOW reading below 0.25 × 3.3V (about 0.83V). A floating pin wanders through all of it, so your sketch sees presses that never happened.

What does a pull-up resistor do?

A pull-up resistor connects the pin to the board's 3V3 pin, its 3.3V supply, usually through 10kΩ. Then the button connects the pin to GND (ground), the 0V side of the circuit.

  • Button released: the resistor gently pulls the pin up to 3.3V. It reads HIGH.
  • Button pressed: the button connects the pin straight to GND, which wins easily over the resistor. It reads LOW.

The resistor also stops a short circuit. If you replaced it with a plain wire, pressing the button would connect 3V3 straight to GND. With 10kΩ in the way, only 0.33mA flows while you hold the button down (3.3V ÷ 10kΩ).

Pressed reads LOW, which can feel backwards at first. Your code checks for LOW to spot a press.

What does a pull-down resistor do?

A pull-down resistor flips the circuit. It connects the pin to GND through the resistor, and the button connects the pin to 3V3.

  • Button released: the pin is pulled down to GND. It reads LOW.
  • Button pressed: the pin connects to 3V3. It reads HIGH.

Three button circuits on GPIO4 side by side. No resistor: the button connects GPIO4 to GND, released reads random, pressed reads LOW. Pull-up: 10kΩ from 3V3 to GPIO4, button from GPIO4 to GND, released HIGH, pressed LOW. Pull-down: button from 3V3 to GPIO4, 10kΩ from GPIO4 to GND, released LOW, pressed HIGH.

Pull-upPull-down
Resistor goes to3V3GND
Button goes toGND3V3
Released readsHIGHLOW
Pressed readsLOWHIGH

Both do the same job. Most example sketches, including the starter kit's, use a pull-up with the button to GND.

Wire one with the kit's button

The Schematik Starter Kit comes with 6 tactile buttons and a pack of 10kΩ resistors, which is everything you need for both circuits.

For a pull-up on GPIO4:

  1. Put the button across the center gap of the breadboard.
  2. Find 2 legs on different sides of the switch: in a multimeter's continuity mode, they beep only while the button is down.
  3. Connect one of those legs to GPIO4.
  4. Connect the other to GND.
  5. Connect a 10kΩ resistor between GPIO4 and 3V3.

The tactile buttons have 4 legs, joined in 2 pairs inside. If the pin reads pressed all the time, your 2 legs are on the same pair.

Then read it:

const int BUTTON_PIN = 4;

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT);  // the 10kΩ resistor does the pulling
}

void loop() {
  if (digitalRead(BUTTON_PIN) == LOW) {
    Serial.println("pressed");
  }
  delay(50);
}

Use the ESP32-S3's built-in pull-ups

Most ESP32-S3 GPIO pins, GPIO4 included, have a pull-up and a pull-down resistor built in, about 45kΩ each. Your code can switch one on, so you can skip the external resistor:

pinMode(BUTTON_PIN, INPUT_PULLUP);  // built-in pull-up to 3.3V

With INPUT_PULLUP, wire the button between GPIO4 and GND, with no resistor at all. That's how the starter kit's example projects wire their buttons: the virtual pet reads 3 buttons on GPIO4, GPIO5 and GPIO6 this way. For a built-in pull-down, use INPUT_PULLDOWN instead and wire the button to 3V3.

Your ESP32-S3 board already uses both kinds of pull-up:

Inside the ESP32-S3 chip, GPIO0 has a built-in 45kΩ pull-up to 3.3V, and the BOOT button connects GPIO0 to GND. Outside the chip, the EN pin has a 10kΩ resistor to 3V3 and a 1µF capacitor to GND, and the RESET button connects EN to GND.

  • RESET connects the chip's EN pin to GND. A 10kΩ pull-up on the board holds EN high so the chip runs. Espressif's datasheet is direct about it: "Do not leave the CHIP_PU pin floating."
  • BOOT connects GPIO0 to GND. GPIO0 has a weak pull-up inside the chip, on from the moment it powers up. Holding BOOT while you press RESET starts the board in download mode.

When you still want an external pull-up

The built-in resistors cover most buttons. Reach for a real resistor when:

  • The pin has no built-in pull-up. On the classic ESP32 (not the S3), GPIO34 to GPIO39 are input-only and have no internal pull-up or pull-down. A button on one of those needs an external resistor.
  • It's an I2C bus. I2C is the 2-wire link most small screens and sensors use: SDA carries the data and SCL carries the clock. Both need stronger pull-ups than 45kΩ. The ESP32-S3 datasheet says its I2C speed is limited by pull-up strength. Most I2C modules already carry their own.
  • The wire is long or the room is noisy. A 10kΩ resistor holds the pin more firmly than 45kΩ, so stray signals have a harder time moving it.
  • The pin must be right before your code runs. INPUT_PULLUP only switches on when your sketch reaches that line. If a pin drives something that must stay off during power-up, like a transistor switching a motor, an external pull-down holds it off until your code takes over. Pick a pin like GPIO4, not GPIO18 to GPIO20: the datasheet lists those three as briefly driving HIGH at power-up, which a pull-down can't stop.

Do not add a pull-down to GPIO0, or a pull-up to GPIO45 or GPIO46. The ESP32-S3 reads these pins at reset to decide how to start, and the wrong resistor can stop your sketch from running.

Build something with a button

The arcade reaction tower is a fast reflex game. Its button uses INPUT_PULLUP and connects to GND when pressed, the same circuit as the virtual pet. It's built on a classic ESP32 board, so its pin numbers differ from the ESP32-S3's. Open it in Schematik to get the wiring, parts list and code, then press Deploy.

The desk presence busy light, also on a classic ESP32, uses a button with the built-in pull-up to switch busy mode on and off.

Once your button works, try it the other way round: take the resistor out, move the button's wire from GND to 3V3, change the sketch to INPUT_PULLDOWN and check for HIGH. Does it still print the right thing when you wave your hand near the wires?

Keep building from here

Jump into a step-by-step build, or open Schematik and turn your own idea into code, wiring, and assembly instructions.