Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

GPIO Pull-up/Pull-down Resistor Configuration

Recommended resources

Hello! Welcome back to our C++ for ESP32 course.

In our last lesson, we learned how to read digital inputs and write digital outputs using gpio_get_level() and gpio_set_level(). We briefly touched on enabling a pull-up resistor during configuration, but we didn't explore why this is so crucial for reliable input reading.

This lesson focuses squarely on that "why". We will explore the concept of floating inputs and learn how to use the ESP32's built-in pull-up and pull-down resistors to create stable and predictable digital input circuits. Mastering this is fundamental for correctly interfacing with buttons, switches, and many types of sensors.

1. The Problem: Floating Input Pins

When a GPIO pin is configured as an input but isn't connected to a definite voltage level—either VCC (3.3V) or Ground (0V)—it is said to be "floating." A floating pin is like a tiny antenna, highly susceptible to electromagnetic interference (EMI) from the environment. This can cause its logical state to fluctuate randomly between HIGH and LOW, leading to completely unreliable readings.

Given your mechanical engineering background, you can think of this as a lever or switch with no spring to return it to a default position. If it's not actively being pushed one way or the other, it can just wobble unpredictably in the middle. We need a "return spring" to ensure it has a known default state.

To see what this looks like in practice, let's watch a video that demonstrates the problem. While it uses an Arduino, the electrical principle is identical for any microcontroller, including the ESP32.

How to use Pushbuttons with Arduino. Pull up vs Pull down resistors

This video from Mario's Ideas clearly demonstrates the unpredictable behavior of a floating input pin.

Watch the segment from 01:12 to 03:55. Observe how the LED connected to the output flickers randomly when the button is not pressed. This is because the input pin is 'floating' and picking up environmental noise, causing the microcontroller to read random HIGH and LOW values.

As you saw, a floating pin is unusable. We need a way to "pull" the pin to a default voltage level.


2. The Solution: Pull-up and Pull-down Resistors

The solution is to add a resistor that weakly connects the input pin to a known voltage. This ensures the pin has a defined state when it's not being actively driven by an external signal (like a button press). There are two ways to do this:

Pull-down Resistor

A pull-down resistor connects the GPIO pin to Ground (GND).

  • Default State: When the button is not pressed, the resistor "pulls" the pin's voltage down to GND, so it reads as LOW (0).
  • Active State: When the button is pressed, it connects the pin directly to VCC (3.3V). Since the connection to VCC is a much lower resistance path than through the pull-down resistor, the pin's voltage is pulled up to 3.3V, and it reads as HIGH (1).
    This is known as an active-high configuration: the active state (button pressed) is a HIGH signal.
ESP32 Pull-up / Pull-down Input Circuit Diagram
A circuit showing a pushbutton connected with an external pull-down resistor (left button). The resistor pulls the pin to ground by default. The right button is shown without an external resistor, implying the use of an internal one.

Pull-up Resistor

A pull-up resistor connects the GPIO pin to VCC (3.3V).

  • Default State: When the button is not pressed, the resistor "pulls" the pin's voltage up to VCC, so it reads as HIGH (1).
  • Active State: When the button is pressed, it connects the pin directly to GND. This pulls the pin's voltage down to 0V, and it reads as LOW (0).
    This is known as an active-low configuration: the active state (button pressed) is a LOW signal. Active-low is a very common convention in embedded systems.

The following video segments explain and demonstrate both of these configurations.

How to use Pushbuttons with Arduino. Pull up vs Pull down resistors

Let's return to the video from Mario's Ideas to see how pull-down and pull-up resistors solve the floating pin problem.

First, watch the explanation of the pull-down resistor from 03:55 to 06:16. Notice how it provides a default LOW state. The video's reference to a voltage divider is a precise way to understand why the pin reads HIGH when the button is pressed. Next, watch the pull-up resistor explanation from 06:16 to 09:07. Pay close attention to how the logic is inverted: the pin is HIGH by default and goes LOW when pressed. Finally, watch the short segment from 09:07 to 09:42, which introduces the concept of built-in pull-up resistors, which is exactly what we will use on the ESP32.

Test your understanding!

You have a motion sensor with a digital output that goes HIGH when motion is detected and LOW otherwise. You connect this sensor to an ESP32 GPIO pin. To ensure a reliable reading when no motion is detected (and the sensor might be in a high-impedance state), should you enable the internal pull-up or pull-down resistor?

Show answer

You should use a pull-down resistor. This will ensure that when no motion is detected and the sensor's output is not actively driving the line, the pin is pulled to a stable LOW state, correctly reflecting the "no motion" condition. Using a pull-up would cause the pin to read HIGH by default, incorrectly signaling that motion is always present.


3. Configuring Internal Resistors in ESP-IDF

Fortunately, the ESP32 has internal (on-chip) pull-up and pull-down resistors that can be enabled and disabled in software. This is extremely convenient as it saves us from adding external components to our circuits for simple inputs.

There are two primary ways to configure these resistors using the ESP-IDF.

Method 1: The gpio_config_t Structure

This is the all-in-one configuration method we used in previous lessons. When initializing a GPIO pin, you can specify whether to enable the pull-up or pull-down resistor directly within the gpio_config_t struct. This is the most common and recommended way to set the initial state of a pin.

The following tutorial provides a clear, practical example of this method.

ESP-IDF: ESP32 GPIO - Read Digital Inputs

The guide 'ESP32 GPIO - Read Digital Inputs' from Random Nerd Tutorials gives an excellent C-code example of configuring a pin with an internal pull-up resistor.

First, read the introductory section 'ESP32 GPIO Digital Inputs' and look at the example gpio_config_t structure. Note these two critical lines: .pull_up_en = GPIO_PULLUP_ENABLE, and .pull_down_en = GPIO_PULLDOWN_DISABLE,. Next, glance at the gpio_config_t structure table to see the available enum values. Finally, read the 'How the Code Works' section, which reinforces how this configuration enables the internal pull-up, making an external resistor unnecessary.

Here is the key snippet from that resource, configured for a pull-up:

// Configure GPIO for an active-low button
gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << BUTTON_PIN),   // Select our button pin
    .mode = GPIO_MODE_INPUT,               // Set as input
    .pull_up_en = GPIO_PULLUP_ENABLE,      // Enable internal pull-up
    .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
    .intr_type = GPIO_INTR_DISABLE         // Disable interrupts for now
};
gpio_config(&io_conf);

To use a pull-down instead, you would simply swap the enable/disable flags:

// Configure for a pull-down (active-high button)
...
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_ENABLE,
...

Method 2: Dedicated Functions

ESP-IDF also provides dedicated functions to change the pull-up/pull-down configuration on the fly, after a pin has already been initialized. This can be useful in more advanced scenarios where a pin's behavior might need to change during runtime.

Let's look at a video that uses one of these functions, gpio_set_pull_mode().

ESP32 - How to create your First ESP IDF project (From Scratch)

This clip from a project by Yuri R shows the use of gpio_set_pull_mode() to enable a pull-up resistor.

Watch the short segment from 10:20 to 10:53. The presenter identifies the need for a stable signal and uses the gpio_set_pull_mode() function with the GPIO_PULLUP_ONLY parameter to enable the internal resistor.

For your reference, the official ESP-IDF documentation lists all the relevant functions. You don't need to study this in depth now, but it's good to know where to find it.

GPIO & RTC GPIO - ESP32 - — ESP-IDF Programming Guide ...

The official ESP-IDF documentation is the ultimate source of truth. Let's briefly consult it to see the list of available functions.

In the 'Functions' section, scroll down and locate the following functions: gpio_set_pull_mode(), gpio_pullup_en(), gpio_pullup_dis(), gpio_pulldown_en(), and gpio_pulldown_dis(). This shows the different ways you can manipulate the pull resistors programmatically.

Important Note: Not all GPIO pins on the ESP32 have internal pull-up and pull-down resistors. Specifically, GPIOs 34, 35, 36 (VP), and 39 (VN) are input-only pins and do not have internal pull resistors. If you need to use these pins for inputs, you must use an external pull-up or pull-down resistor.


Conclusion

In this lesson, we took a deep dive into a crucial concept for reliable digital inputs. You now have a solid understanding of why pull-up and pull-down resistors are essential and how to implement them using the ESP32's convenient internal hardware.

Here are the key takeaways:

  • An input pin not connected to a defined voltage level is floating and will give unreliable readings.
  • Pull-down resistors provide a default LOW state (active-high configuration).
  • Pull-up resistors provide a default HIGH state (active-low configuration).
  • The ESP32's internal pull-up/down resistors can be configured easily using the gpio_config_t struct during initialization, eliminating the need for external components in many cases.

Now that we can reliably read the state of a button, our next step is to make our code react to it efficiently. Continuously checking the pin in a while loop, known as polling, is simple but wastes CPU cycles. In the next lesson, we will learn about GPIO interrupts, a powerful and efficient mechanism that allows the ESP32 to automatically run a piece of code the moment a pin's state changes.