Phase 3: GPIO & Hardware 11 min read

Pull-Up vs Pull-Down Resistors on ESP32: Full Explanation

Understand pull-up and pull-down resistors for ESP32 GPIO — when to use external vs internal resistors, resistor values, wiring diagrams, and power implications.

Updated June 19, 2026

Why Pull Resistors Exist

GPIO pins in INPUT mode have very high impedance — almost no current flows through them. This means they are extremely sensitive to tiny voltages induced by nearby electrical fields. Without a defined reference voltage, the pin reads randomly: sometimes HIGH, sometimes LOW, with no button pressed at all. Pull resistors solve this by providing a weak, continuous connection to either 3.3 V (pull-up) or GND (pull-down), establishing a clear resting state that noise cannot overcome.

How a Pull-Up Resistor Works

A pull-up resistor connects the GPIO pin to the supply voltage (3.3 V on ESP32) through a resistor:

Pull-Up Circuit
  3.3V ──── 10kΩ ──── GPIO pin ──── Button ──── GND

Resting state (button open):  pin = HIGH (3.3V via resistor)
Active state (button closed):  pin = LOW  (shorted to GND)
→ Active-LOW logic

When the button is open, the resistor holds the pin at 3.3 V. When the button is pressed, it connects the pin directly to GND, overriding the resistor (0.33 mA flows through the 10 kΩ — harmless). The GPIO now reads LOW.

How a Pull-Down Resistor Works

Pull-Down Circuit
  3.3V ──── Button ──── GPIO pin ──── 10kΩ ──── GND

Resting state (button open):  pin = LOW  (GND via resistor)
Active state (button closed):  pin = HIGH (3.3V via button)
→ Active-HIGH logic

Pull-downs are less common in ESP32 projects because the INPUT_PULLUP mode is the default convenience option. However, some sensors actively drive their output HIGH when triggered (PIR sensors, active-HIGH reed switches), where a pull-down keeps the idle state clean.

Comparing Pull-Up vs Pull-Down

Feature Pull-Up Pull-Down
Resting pin state HIGH (3.3V) LOW (GND)
Button wired to GND 3.3V
Button pressed = LOW (active-LOW) HIGH (active-HIGH)
ESP32 mode INPUT_PULLUP INPUT_PULLDOWN
More common? Yes ✓ Less common
Used by default I2C Yes (SDA, SCL) No

Internal vs External Resistors

ESP32 has built-in pull-up and pull-down resistors accessible through software. Here is when to use each:

Scenario Use Internal? Use External?
Button on short wire (<15 cm) Yes ✓ Optional
Button on long wire (>30 cm) No — too weak Yes (10 kΩ)
Industrial/noisy environment No Yes (4.7 kΩ)
GPIO34–GPIO39 (no internal) N/A Required
I2C bus (SDA/SCL) Not recommended Yes (4.7 kΩ)
Battery-powered, low power Yes ✓ Higher value (100 kΩ)

Using Internal Pull Resistors in Code

Arduino (C++)
void setup() {
  Serial.begin(115200);

  // Internal pull-up — pin rests HIGH, reads LOW when button pressed to GND
  pinMode(16, INPUT_PULLUP);

  // Internal pull-down — pin rests LOW, reads HIGH when button pressed to 3.3V
  pinMode(17, INPUT_PULLDOWN);
}

void loop() {
  int p16 = digitalRead(16);  // LOW = button pressed (active-LOW)
  int p17 = digitalRead(17);  // HIGH = button pressed (active-HIGH)

  Serial.printf("P16=%d (active-LOW)  P17=%d (active-HIGH)n", p16, p17);
  delay(200);
}

External Pull Resistor Wiring

For a 10 kΩ external pull-up:

Connection Point Connects To
Resistor leg 1 ESP32 3.3V
Resistor leg 2 ESP32 GPIO pin AND button terminal A
Button terminal B ESP32 GND

In code use plain INPUT mode (not INPUT_PULLUP) because the resistor is external:

Arduino (C++)
// External 10kΩ pull-up wired on breadboard
void setup() {
  Serial.begin(115200);
  pinMode(34, INPUT);   // GPIO34: input-only, needs external resistor
}

void loop() {
  Serial.printf("GPIO34 = %dn", digitalRead(34));
  delay(200);
}

Power Consumption Considerations

Pull resistors consume a small continuous current whenever the button is pressed (or whenever the logic is in the opposite state to the rail):

Current Calculation
External 10kΩ pull-up, button pressed (pin shorted to GND):
I = 3.3V / 10,000Ω = 0.33 mA

External 10kΩ pull-up, button released (no current from GPIO):
I ≈ 0 mA (tiny leakage only)

Internal ~47kΩ pull-up, button pressed:
I = 3.3V / 47,000Ω = 0.07 mA

For battery-powered designs: use higher value external resistors (47kΩ–100kΩ)
or enable internal pull-ups only when actively reading the button.

Pull Resistors and I2C

I2C buses (SDA and SCL lines) always require pull-up resistors on the bus lines. Internal pull-ups are too weak (~47 kΩ) for reliable I2C communication. Use 4.7 kΩ external pull-ups to 3.3 V on both SDA and SCL. Many I2C breakout boards (BME280, SSD1306, etc.) include these resistors on-board, so check the datasheet before adding more.

Troubleshooting Pull Resistor Problems

  • Random readings with button not pressed: The pull resistor is missing or too weak. Add or lower the resistor value.
  • Button press not detected: The pull-down resistor is fighting the signal. Check wiring orientation.
  • Inverted logic: You used INPUT_PULLUP but code checks for HIGH. Change to check for LOW (active-LOW).
  • GPIO34 always floats: No internal pull available — add a physical 10 kΩ resistor to 3.3 V.

Summary

Pull-up and pull-down resistors define the resting state of a GPIO input and prevent the floating pin problem. Use INPUT_PULLUP (active-LOW, button to GND) for most buttons. Use INPUT_PULLDOWN (active-HIGH, button to 3.3V) when the signal naturally drives HIGH. Add external 10 kΩ resistors for long wires, noisy environments, and input-only GPIO34–GPIO39 pins.

Frequently Asked Questions

A pull-up resistor connects the GPIO pin to the supply voltage (3.3V on ESP32) through a resistor, ensuring the pin reads HIGH by default when nothing is actively driving it. When a button or switch connects the pin to GND, it overrides the pull-up and the pin reads LOW.
A pull-down resistor connects the GPIO pin to GND through a resistor, ensuring the pin reads LOW by default. When a button connects the pin to 3.3V, it overrides the pull-down and the pin reads HIGH. Pull-downs are used for active-HIGH button circuits.
The standard value is 10 kΩ. This is low enough to overcome electrical noise but high enough to limit current when the button is pressed (3.3V / 10kΩ = 0.33 mA — negligible power consumption). Values from 4.7 kΩ to 47 kΩ are all acceptable.
ESP32 internal pull-up and pull-down resistors are approximately 45–47 kΩ. This is weaker than a typical 10 kΩ external resistor, making them more susceptible to noise on long wires or in electrically noisy environments.
Use external resistors when: the wire from button to ESP32 is longer than 30 cm, the environment is electrically noisy (near motors or RF transmitters), you need faster response times, or the pin is input-only (GPIO34–39, which have no internal resistors).
Yes, indirectly. A floating pin may oscillate between HIGH and LOW, causing spurious interrupts or extra processing. This increases average power consumption in battery-powered designs. Always define a resting state with a pull resistor to minimise this.
Technically yes, but it is wasteful and unusual. The two resistors would form a voltage divider, leaving the pin at half the supply voltage (neither a clean HIGH nor a clean LOW), which creates the indeterminate zone. Use one or the other, not both.
With INPUT_PULLUP, the pin rests at HIGH (3.3V). Connect one button terminal to the GPIO pin and the other to GND. When pressed, the pin is pulled to GND (LOW). This is active-LOW logic: button pressed = LOW state.
With INPUT_PULLDOWN, the pin rests at LOW (GND). Connect one button terminal to the GPIO pin and the other to 3.3V. When pressed, the pin is pulled HIGH. This is active-HIGH logic: button pressed = HIGH state.
Yes. If you use a GPIO pin with an internal pull-up for ADC reading (analogRead), the pull-up resistor adds current to the measurement and skews the voltage. Always use INPUT mode (no pull resistors) on ADC pins and place any conditioning externally.

Projects to Build

Put this knowledge to work — try one of these hands-on projects.