The most reliable way to trigger an ESP32 wake from deep sleep is by using the Real-Time Clock (RTC) GPIO pins with the EXT0 or EXT1 wake-up sources. Unlike standard digital pins, RTC GPIOs remain powered and monitored by the ultra-low-power (ULP) co-processor domain while the main CPU, radios, and flash memory are completely shut down. To wake the board, you must configure a valid RTC pin, set the correct internal pull resistor, and call esp_deep_sleep_start(). This guide targets the classic ESP32-WROOM-32 DevKit V1, addressing its specific hardware quirks, providing copy-pasteable firmware, and solving the most common brownout and configuration errors that prevent wake-up.

Project Spec Sheet & Parts List

Difficulty Rating: ⭐⭐☆☆☆ (Intermediate Beginner)
Estimated Time: 20 minutes
Target Board Variant: ESP32-WROOM-32 DevKit V1 (30-pin or 38-pin variant, ESP32 Arduino Core v3.x)

Required Materials

  • Microcontroller: 1x ESP32-WROOM-32 DevKit V1 (Espressif or reputable clone like HiLetgo)
  • Switch: 1x Momentary tactile pushbutton (SPST, normally open)
  • Resistor: 1x 10kΩ through-hole resistor (only required if using input-only pins like GPIO 34-39)
  • Capacitor: 1x 100µF electrolytic or low-ESR ceramic capacitor (crucial for brownout mitigation)
  • Wiring: 22 AWG solid core jumper wires, breadboard
  • Software: Arduino IDE 2.x with Espressif Systems ESP32 board manager installed

RTC Pin Mapping & Wiring Constraints

The most frequent reason an ESP32 fails to wake is attempting to use a standard digital GPIO (like GPIO 16 or 17) for an RTC wake source. The WROOM-32 module only routes specific pins to the RTC domain. Furthermore, pins GPIO 34 through 39 are strictly input-only and lack internal pull-up/pull-down resistors.

RTC GPIO Number Standard GPIO Mapping Direction Internal Pull-Up/Down Wake-Up Suitability
RTC_GPIO0 GPIO 36 (VP) Input Only No Requires external 10kΩ pull-up/down
RTC_GPIO3 GPIO 39 (VN) Input Only No Requires external 10kΩ pull-up/down
RTC_GPIO4 GPIO 34 Input Only No Requires external 10kΩ pull-up/down
RTC_GPIO5 GPIO 35 Input Only No Requires external 10kΩ pull-up/down
RTC_GPIO8 GPIO 33 Input/Output Yes Recommended for EXT0
RTC_GPIO9 GPIO 32 Input/Output Yes Recommended for EXT0
RTC_GPIO10-17 GPIO 25, 26, 27, 14, 12, 13, 15, 2 Input/Output Yes Excellent for EXT1 multi-pin masks
⚠️ Callout Tip: For this project, we are wiring our pushbutton to GPIO 33. Because GPIO 33 has an internal pull-up resistor, we can wire the button directly between GPIO 33 and GND without needing an external resistor. When pressed, the pin reads LOW, triggering the wake.

Complete Firmware: EXT0 Pushbutton Wake-Up

The following code targets the ESP32-WROOM-32 using the ESP32 Arduino Core v3.x. It configures EXT0 wake-up, includes explicit pin definitions, handles configuration errors, and prints the wake-up reason to the serial monitor. Copy and paste this directly into your Arduino IDE.

#include <esp_sleep.h>
#include <driver/rtc_io.h>

// --- PIN DEFINITIONS ---
// GPIO 33 is mapped to RTC_GPIO8 and supports internal pull-ups
#define WAKE_PIN GPIO_NUM_33 

void setup() {
  Serial.begin(115200);
  delay(1000); // Brief pause to allow Serial Monitor to attach
  Serial.println("\n--- ESP32 Deep Sleep Wake-Up Example ---");

  // 1. Identify and print what woke the ESP32
  print_wakeup_reason();

  // 2. Configure the wake pin
  // Using INPUT_PULLUP means the pin sits at 3.3V (HIGH) by default.
  // Pressing the button shorts it to GND (LOW).
  pinMode(WAKE_PIN, INPUT_PULLUP);

  // 3. Enable EXT0 Wake-Up Source
  // Parameters: (GPIO number, wake logic level). 
  // 0 = Wake on LOW, 1 = Wake on HIGH
  esp_err_t err = esp_sleep_enable_ext0_wakeup(WAKE_PIN, 0); 
  
  // Error handling for invalid pin assignment
  if (err != ESP_OK) {
    Serial.printf("[FATAL] Error enabling EXT0 wake: %d\n", err);
    Serial.println("Ensure WAKE_PIN is a valid RTC-capable GPIO.");
    while(1) { delay(1000); } // Halt execution to prevent bootloop
  }

  // Optional: Isolate RTC pins to save extra microamps (Advanced)
  // rtc_gpio_isolate(WAKE_PIN);

  Serial.println("Configuration complete. Going to deep sleep now...");
  Serial.flush(); // Ensure all serial data is transmitted before sleep
  
  // 4. Enter Deep Sleep
  esp_deep_sleep_start();
}

void loop() {
  // This block will never execute. Deep sleep halts the CPU.
  // Upon wake, the ESP32 resets and starts from setup() again.
}

void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0: 
      Serial.println("[WAKE] External signal using RTC_IO (EXT0)"); 
      break;
    case ESP_SLEEP_WAKEUP_EXT1: 
      Serial.println("[WAKE] External signal using RTC_CNTL (EXT1)"); 
      break;
    case ESP_SLEEP_WAKEUP_TIMER: 
      Serial.println("[WAKE] Timer expired"); 
      break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: 
      Serial.println("[WAKE] Touchpad threshold crossed"); 
      break;
    case ESP_SLEEP_WAKEUP_ULP: 
      Serial.println("[WAKE] ULP coprocessor"); 
      break;
    default: 
      Serial.printf("[WAKE] Reset (Not from deep sleep): %d\n", wakeup_reason); 
      break;
  }
}

Debugging: When the ESP32 Fails to Wake

If your ESP32 goes to sleep but refuses to wake up, or immediately crashes upon waking, run through these first three things to check:

  1. Is the pin in the RTC domain? Check the table above. Passing GPIO 16 to esp_sleep_enable_ext0_wakeup will fail silently or throw an error.
  2. Does the pull-resistor match the wake logic? If you configure a wake on LOW (0), the pin must be pulled HIGH via an internal or external resistor. If it's floating, electromagnetic noise will trigger false wakes or prevent the button from registering.
  3. Is the 3V3 rail sagging on wake? When the ESP32 wakes, the WiFi/BT radio and flash memory initialize simultaneously, drawing a sudden spike of ~250mA. If your USB port or LDO cannot supply this instantly, the voltage drops and the brownout detector resets the chip.

Ranked Error Causes & Exact Strings

Error 1: The Hardware Brownout
Brownout detector was triggered
Cause: Power rail voltage dropped below ~2.4V during the wake-up initialization spike.
Fix: Solder or breadboard a 100µF low-ESR capacitor directly across the 3V3 and GND pins on the DevKit. This provides the instantaneous current the LDO cannot supply fast enough. See the Espressif Hardware Design Guidelines for power tree specifics.

Error 2: The Software RTC Fault
E (142) sleep: esp_sleep_enable_ext0_wakeup: GPIO number error
Cause: You passed a non-RTC GPIO (e.g., GPIO 17, 21, 22) to the EXT0 function.
Fix: Change your #define WAKE_PIN to a valid RTC GPIO from the mapping table (e.g., GPIO 33, 32, 25, 26, 27, 14, 12, 13, 15, 2).

Error 3: The Immediate Wake Loop
Symptom: The serial monitor shows the ESP32 going to sleep, but it instantly wakes up without you pressing the button.
Cause: The wake pin is floating, or the button wiring is picking up 60Hz/50Hz mains hum.
Fix: Ensure INPUT_PULLUP or INPUT_PULLDOWN is explicitly defined in setup(), and keep the wire between the button and the GPIO under 3 inches.

Extending or Simplifying the Build

Depending on your application, you may need to modify the wake behavior. Here is how to adapt the code above:

  • Simplify (Timer Wake Only): If you don't need a physical button and just want the ESP32 to wake every hour to read a sensor, remove the EXT0 configuration and replace it with esp_sleep_enable_timer_wakeup(3600000000ULL); (value is in microseconds).
  • Extend (EXT1 Multi-Pin Wake): EXT0 only supports one pin. If you need to wake from any of three different door sensors, use EXT1. Define a bitmask of RTC pins: uint64_t wakeup_mask = (1ULL << GPIO_NUM_32) | (1ULL << GPIO_NUM_33); and call esp_sleep_enable_ext1_wakeup(wakeup_mask, ESP_EXT1_WAKEUP_ANY_HIGH);. You can identify which specific pin triggered the wake by reading esp_sleep_get_ext1_wakeup_status() after boot.
  • Extend (Maintain Pin State): If you need an LED to stay ON while the ESP32 is in deep sleep, you must power it from an RTC GPIO and use rtc_gpio_hold_en((gpio_num_t)LED_PIN); right before calling esp_deep_sleep_start(). This latches the pin state in the RTC domain.

FAQ: ESP32 Deep Sleep Wake-Up Questions

Why does my ESP32 consume 5mA in deep sleep instead of 10µA?

The ESP32-WROOM-32 module itself draws about 10µA to 150µA in deep sleep depending on whether the RTC memory is powered. However, the DevKit V1 carrier board has an onboard AMS1117-3.3 LDO voltage regulator and a CP2102/CH340 USB-to-Serial chip. These components have a quiescent current draw of 3mA to 5mA. To achieve true microamp deep sleep, you must design a custom PCB using the raw ESP32-WROOM-32 module, or physically desolder the LDO and USB chip from the DevKit and power the 3V3 pin directly from a clean lithium battery source.

Can I use an interrupt (ISR) to wake the ESP32 from deep sleep?

No. Standard hardware interrupts (attachInterrupt()) rely on the main CPU, which is completely powered off during deep sleep. You must use the dedicated RTC wake sources (EXT0, EXT1, Timer, Touch, or ULP). If you only need light sleep (where the CPU is paused but RAM is retained and wakes in microseconds), then standard GPIO interrupts via esp_sleep_enable_gpio_wakeup() will work.

How do I retain variables in memory after waking from deep sleep?

Standard RAM is wiped clean when entering deep sleep. To retain data (like a boot counter or sensor calibration), you must declare your variables with the RTC_DATA_ATTR attribute. For example: RTC_DATA_ATTR int bootCount = 0;. This forces the compiler to place the variable in the RTC Slow Memory domain, which remains powered and intact across deep sleep cycles.

Is it safe to wake the ESP32 using a 5V signal?

Absolutely not. The ESP32 GPIO pins are strictly 3.3V tolerant. Feeding a 5V signal into an RTC GPIO to trigger a wake will destroy the pin's internal ESD protection diodes and likely fry the RTC domain, permanently bricking the chip's ability to sleep or wake. Always use a voltage divider or an optocoupler if your wake signal originates from a 5V or 12V system.