Advanced ESP32 Sleep Modes & Wake-Up Sources
Introduction to Advanced ESP32 Sleep Modes
This guide expands on our ESP32 Sleep Modes documentation to cover advanced wake-up sources, ESP32 model differences, and specialized implementation techniques for optimizing your IoT projects.
For battery-powered applications, understanding these advanced sleep mode features is crucial to achieving maximum power efficiency while maintaining responsive device behavior.
ESP32 Model Differences
Different ESP32 model variants have varying capabilities regarding sleep modes and available wake-up sources. These differences are important to consider when designing your application.
RTC GPIO Availability
| ESP32 Model | Available RTC GPIOs |
|---|---|
| ESP32 | 0, 2, 4, 12-15, 25-27, 32-39 |
| ESP32-S2 | 0-21 |
| ESP32-S3 | 0-21 |
Touch Sensitivity and Behavior
| ESP32 Model | Touch Sensitivity | Multiple Touch Wake-up |
|---|---|---|
| ESP32 | Higher value = more sensitive | Yes |
| ESP32-S2 | Lower value = more sensitive | No (single pin only) |
| ESP32-S3 | Lower value = more sensitive | No (single pin only) |
External Wake-up Methods
| ESP32 Model | ext1 Wake-up Modes |
|---|---|
| ESP32 | ESP_EXT1_WAKEUP_ALL_LOW, ESP_EXT1_WAKEUP_ANY_HIGH |
| ESP32-S2/S3 | ESP_EXT1_WAKEUP_ANY_LOW, ESP_EXT1_WAKEUP_ANY_HIGH |
Note: When porting code between different ESP32 models, be sure to adjust your wake-up configurations to match the capabilities and behavior of the specific model you're using.
Disabling Wake-up Sources
To disable previously enabled wake-up sources, use the esp_sleep_disable_wakeup_source() function. This can be useful when you need to dynamically change which events can wake up your ESP32.
#includevoid setup() { Serial.begin(115200); delay(1000); Serial.println("ESP32 Wake-up Sources Demo"); // First, enable timer wake-up esp_sleep_enable_timer_wakeup(10000000); // 10 seconds Serial.println("Timer wake-up enabled"); // Then enable touch wake-up touchSleepWakeUpEnable(T3, 40); Serial.println("Touch wake-up enabled"); // Now disable just the timer wake-up esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER); Serial.println("Timer wake-up disabled"); // Or disable all wake-up sources // esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); // Serial.println("All wake-up sources disabled"); // Enable a different wake-up source esp_sleep_enable_ext0_wakeup(GPIO_NUM_27, HIGH); Serial.println("External wake-up enabled"); Serial.println("Going to sleep..."); esp_deep_sleep_start(); } void loop() { // Not used }
Available wake-up source arguments:
ESP_SLEEP_WAKEUP_TIMER- Timer wake-upESP_SLEEP_WAKEUP_EXT0- External wake-up (RTC_IO)ESP_SLEEP_WAKEUP_EXT1- External wake-up (RTC_CNTL)ESP_SLEEP_WAKEUP_TOUCHPAD- Touch sensor wake-upESP_SLEEP_WAKEUP_ULP- ULP program wake-upESP_SLEEP_WAKEUP_GPIO- GPIO wake-up (light sleep only)ESP_SLEEP_WAKEUP_UART- UART wake-up (light sleep only)ESP_SLEEP_WAKEUP_ALL- All wake-up sources
External Wake-up Methods
The ESP32 provides three different methods for waking up using external GPIOs, each with different capabilities and use cases.
| Method | Function | GPIO Support | Sleep Modes | Features |
|---|---|---|---|---|
| ext0 | esp_sleep_enable_ext0_wakeup() |
RTC GPIOs only | All sleep modes | Simple single-pin wake-up |
| ext1 | esp_sleep_enable_ext1_wakeup() |
RTC GPIOs only | All sleep modes | Multiple pins with logic operations (ANY, ALL) |
| GPIO | gpio_wakeup_enable() |
Any GPIO | Light sleep only | Flexible GPIO wake-up, supports all pins |
ext1 Wake-up Example (Multiple Buttons)
This example demonstrates how to use ext1 wake-up to wake the ESP32 when any of multiple buttons are pressed.
#include#include "esp_sleep.h" #include "driver/rtc_io.h" int counter = 0; const int ledPin = 2; // GPIO pin for onboard LED const gpio_num_t buttonPin1 = GPIO_NUM_26; // RTC IO for pushbutton 1 const gpio_num_t buttonPin2 = GPIO_NUM_27; // RTC IO for pushbutton 2 #define BUTTON_PIN_BITMASK(GPIO) (1ULL << GPIO) // Macro for individual GPIO bitmask // Create a bitmask for GPIO 26 and GPIO 27 uint64_t bitmask = BUTTON_PIN_BITMASK(buttonPin1) | BUTTON_PIN_BITMASK(buttonPin2); // Method to print the GPIO that triggered the wakeup void print_GPIO_wake_up(){ uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status(); Serial.print("GPIO that triggered the wake up: GPIO "); Serial.println((log(GPIO_reason))/log(2), 0); } void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); pinMode(buttonPin1, INPUT_PULLDOWN); // pull-down resistor pinMode(buttonPin2, INPUT_PULLDOWN); // pull-down resistor // Configure GPIO 26 and GPIO 27 as RTC IOs for EXT1 wake-up rtc_gpio_deinit(buttonPin1); rtc_gpio_deinit(buttonPin2); // Enable EXT1 wake-up source esp_err_t result = esp_sleep_enable_ext1_wakeup(bitmask, ESP_EXT1_WAKEUP_ANY_HIGH); if (result == ESP_OK) { Serial.println("EXT1 Wake-Up set successfully."); } else { Serial.println("Failed to set EXT1 Wake-Up as wake-up source."); } } void loop() { Serial.printf("Counter: %d\n", counter); counter++; digitalWrite(ledPin, HIGH); // LED on to indicate wake-up delay(2000); digitalWrite(ledPin, LOW); // Turn off LED before going to sleep Serial.println("Going into light sleep mode"); delay(500); esp_light_sleep_start(); // Enter light sleep // After wake-up, disable the hold function on the RTC GPIOs rtc_gpio_hold_dis(buttonPin1); rtc_gpio_hold_dis(buttonPin2); Serial.println("----------------------"); Serial.println("Returning from light sleep"); // Print the GPIO (button) that caused the wake-up print_GPIO_wake_up(); }
Circuit Diagram:
Connect pushbuttons to GPIO 26 and 27 as follows:
- Connect one terminal of each button to GND
- Connect the other terminal of the first button to GPIO 26
- Connect the other terminal of the second button to GPIO 27
- The code uses internal pull-down resistors, so no external resistors are needed
GPIO Wake-up Example (Any GPIO)
This method allows you to use any GPIO pin for wake-up, not just RTC GPIOs, but it only works with light sleep mode.
#include#include "esp_sleep.h" #include "driver/gpio.h" int counter = 0; const int ledPin = 2; // GPIO pin for onboard LED const gpio_num_t buttonPin1 = GPIO_NUM_26; // GPIO for pushbutton 1 const gpio_num_t buttonPin2 = GPIO_NUM_27; // GPIO for pushbutton 2 int wakeup_gpio; // Variable to store the GPIO that caused wake-up // ISR for buttonPin1 void IRAM_ATTR handleInterrupt1() { wakeup_gpio = buttonPin1; } // ISR for buttonPin2 void IRAM_ATTR handleInterrupt2() { wakeup_gpio = buttonPin2; } void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); pinMode(buttonPin1, INPUT_PULLDOWN); // pull-down resistor pinMode(buttonPin2, INPUT_PULLDOWN); // pull-down resistor // Configure GPIOs as wake-up source gpio_wakeup_enable(buttonPin1, GPIO_INTR_HIGH_LEVEL); // Trigger wake-up on high level gpio_wakeup_enable(buttonPin2, GPIO_INTR_HIGH_LEVEL); // Trigger wake-up on high level // Enable GPIO wake-up source esp_err_t result = esp_sleep_enable_gpio_wakeup(); if (result == ESP_OK) { Serial.println("GPIO Wake-Up set successfully."); } else { Serial.println("Failed to set GPIO Wake-Up as wake-up source."); } // Attach interrupts to GPIO pins attachInterrupt(digitalPinToInterrupt(buttonPin1), handleInterrupt1, RISING); attachInterrupt(digitalPinToInterrupt(buttonPin2), handleInterrupt2, RISING); } void loop() { Serial.printf("Counter: %d\n", counter); counter++; digitalWrite(ledPin, HIGH); // LED on to indicate wake-up delay(2000); digitalWrite(ledPin, LOW); // Turn off LED before going to sleep Serial.println("Going into light sleep mode"); delay(500); esp_light_sleep_start(); // Enter light sleep Serial.println("----------------------"); Serial.println("Returning from light sleep"); // Print the GPIO that caused the wake-up Serial.printf("Wake-up caused by GPIO %d\n", wakeup_gpio); }
Important Notes:
- The GPIO wake-up method only works with light sleep, not deep sleep or hibernation
- When using interrupts to determine which pin triggered the wake-up, make sure to use the IRAM_ATTR attribute for the interrupt handlers
- You can use any GPIO with this method, not just RTC GPIOs
Touch Wake-up
The ESP32 can wake up from sleep when touch is detected on its touch-sensitive pins. This allows for creating touch-activated devices that consume minimal power when idle.
Touch Pin Mapping
ESP32 Touch Pins
- T0 (GPIO 4)
- T1 (GPIO 0)
- T2 (GPIO 2)
- T3 (GPIO 15)
- T4 (GPIO 13)
- T5 (GPIO 12)
- T6 (GPIO 14)
- T7 (GPIO 27)
- T8 (GPIO 33)
- T9 (GPIO 32)
ESP32-S2/S3 Differences
ESP32-S2 and ESP32-S3 have different touch pin mappings compared to the original ESP32. Please refer to the technical documentation for your specific model when mapping touch pins.
Additionally, these models can only wake up on a single touch pin (unlike the original ESP32 which supports multiple touch pins for wake-up).
Touch Wake-up Example
#include#if CONFIG_IDF_TARGET_ESP32 #define THRESHOLD 40 // Greater the value, more the sensitivity #else // ESP32-S2 and ESP32-S3 + default for other chips #define THRESHOLD 5000 // Lower the value, more the sensitivity #endif int counter = 0; const int ledPin = 2; // GPIO pin for onboard LED touch_pad_t touchPin; //Method to print the reason by which ESP32 has been awaken from sleep void print_wakeup_reason() { esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch (wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break; case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break; default: Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break; } } //Method to print the touchpad by which ESP32 has been awaken from sleep void print_wakeup_touchpad() { touchPin = esp_sleep_get_touchpad_wakeup_status(); #if CONFIG_IDF_TARGET_ESP32 switch (touchPin) { case 0: Serial.println("Touch detected on GPIO 4"); break; case 1: Serial.println("Touch detected on GPIO 0"); break; case 2: Serial.println("Touch detected on GPIO 2"); break; case 3: Serial.println("Touch detected on GPIO 15"); break; case 4: Serial.println("Touch detected on GPIO 13"); break; case 5: Serial.println("Touch detected on GPIO 12"); break; case 6: Serial.println("Touch detected on GPIO 14"); break; case 7: Serial.println("Touch detected on GPIO 27"); break; case 8: Serial.println("Touch detected on GPIO 33"); break; case 9: Serial.println("Touch detected on GPIO 32"); break; default: Serial.println("Wakeup not by touchpad"); break; } #else if (touchPin < TOUCH_PAD_MAX) { Serial.printf("Touch detected on GPIO %d\n", touchPin); } else { Serial.println("Wakeup not by touchpad"); } #endif } void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); delay(1000); //Take some time to open up the Serial Monitor #if CONFIG_IDF_TARGET_ESP32 //Setup sleep wakeup on Touch Pad 3 + 7 (GPIO15 + GPIO 27) touchSleepWakeUpEnable(T3, THRESHOLD); touchSleepWakeUpEnable(T7, THRESHOLD); #else //ESP32-S2 + ESP32-S3 //Setup sleep wakeup on Touch Pad 3 (GPIO3) touchSleepWakeUpEnable(T3, THRESHOLD); #endif } void loop() { Serial.printf("Counter: %d\n", counter); counter++; digitalWrite(ledPin, HIGH); // LED on to indicate wake-up delay(5000); digitalWrite(ledPin, LOW); // LED off before going to sleep Serial.println("Going into deep sleep..."); delay(500); esp_deep_sleep_start(); // If using light sleep instead, uncomment this block and comment out esp_deep_sleep_start() above /* Serial.println("Going into light sleep..."); delay(500); esp_light_sleep_start(); Serial.println("----------------------"); Serial.println("Returning from light sleep"); delay(2000); print_wakeup_reason(); print_wakeup_touchpad(); */ }
Circuit Diagram:
For simple testing, connect a jumper wire to GPIO 15 (T3) or GPIO 27 (T7). Touching the wire will trigger the wake-up.
For a more reliable touch sensor:
- Connect a jumper wire to the touch pin
- Connect the other end to a small piece of aluminum foil or a metal surface
- Optionally add a 1MΩ resistor between the touch pin and GND to reduce sensitivity to false triggers
Important Notes:
- Touch sensitivity settings differ between ESP32 models
- For ESP32: Higher threshold values increase sensitivity
- For ESP32-S2/S3: Lower threshold values increase sensitivity
- The reliability of touch wake-up depends heavily on your hardware design
- A small capacitor (10-100nF) between the touch pin and ground can help filter noise
UART Wake-up
The ESP32 can wake up from light sleep when data is received on a UART port. This is useful for applications where the ESP32 needs to respond to serial commands from sensors or other microcontrollers while minimizing power consumption.
UART Wake-up Limitations:
- UART wake-up is only available in light sleep mode, not deep sleep or hibernation
- The ESP32 can be configured to wake up on any of its UART interfaces (UART0, UART1, UART2)
- The wake-up is triggered by a specific number of signal transitions (edges) on the RX pin
UART Wake-up Example
#include#include int counter = 0; const int ledPin = 2; // GPIO pin for onboard LED String receivedMessage = ""; // Variable to store the complete message // Method to print the reason by which ESP32 has been awaken from sleep void print_wakeup_reason() { esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch (wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break; case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break; case ESP_SLEEP_WAKEUP_UART: Serial.println("Wakeup caused by UART"); break; default: Serial.printf("Wakeup was not caused by light sleep: %d\n", wakeup_reason); break; } } void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); delay(1000); //Take some time to open up the Serial Monitor // Enable UART wake-up from light sleep uart_set_wakeup_threshold(UART_NUM_0, 3); // 3 edges on U0RXD to wakeup esp_sleep_enable_uart_wakeup(0); // UART0 (default Serial (includes Serial Monitor)) } void loop() { Serial.printf("Counter: %d\n", counter); counter++; digitalWrite(ledPin, HIGH); // LED on to indicate wake-up delay(5000); digitalWrite(ledPin, LOW); // LED off before going to sleep Serial.println("Going into light sleep..."); delay(500); esp_light_sleep_start(); Serial.println("----------------------"); Serial.println("Returning from light sleep"); delay(2000); print_wakeup_reason(); // Clear the internal wake-up indication by sending some extra data Serial.write(' '); // Send a single space character while (Serial.available()) { char incomingChar = Serial.read(); // Read each character from the buffer if (incomingChar == '\n') { // Check if the user pressed Enter (new line character) // Print the message Serial.print("You sent: "); Serial.println(receivedMessage); // Clear the message buffer for the next input receivedMessage = ""; } else { // Append the character to the message string receivedMessage += incomingChar; } } }
Using UART Wake-up for Sensor Integration
UART wake-up is particularly useful when interfacing with external sensors or modules that communicate via serial. Here's an example application:
Example Use Case: Motion Detector
- Connect a PIR motion sensor module with serial output to ESP32's UART1
- Configure the ESP32 to wake up when the PIR sensor sends motion detection data
- When motion is detected, the sensor sends data that wakes up the ESP32
- ESP32 reads the motion data, performs actions (notification, logging, etc.)
- ESP32 returns to light sleep to conserve power until the next motion event
This approach is more power-efficient than using GPIO interrupts because the ESP32 only wakes up when actual data is available to process.
UART1/UART2 Wake-up Example
To wake up on UART1 or UART2 instead of the default UART0, use the following configuration:
// For UART1 uart_set_wakeup_threshold(UART_NUM_1, 3); // 3 edges to wake up esp_sleep_enable_uart_wakeup(1); // Enable wake-up on UART1 // For UART2 uart_set_wakeup_threshold(UART_NUM_2, 3); // 3 edges to wake up esp_sleep_enable_uart_wakeup(2); // Enable wake-up on UART2
Optimizing Wake-up Behavior
For battery-powered applications, it's important to optimize not just the sleep power consumption but also the wake-up behavior to minimize active time.
Best Practices for Wake-up Optimization
-
Minimize active time:
- Complete tasks quickly after wake-up
- Avoid unnecessary delays or serial prints during debugging
- Return to sleep as soon as possible
-
Choose appropriate wake-up sources:
- Use deep sleep with timer for periodic sensors
- Use light sleep with UART for sensor modules that send data
- Use touch wake-up for user-triggered events
-
Debounce external wake-up sources:
- For mechanical buttons, add hardware debounce (RC filter)
- For touch sensors, add appropriate threshold settings
- For UART, set appropriate wake-up thresholds
-
Check wake-up reason and handle accordingly:
- Use
esp_sleep_get_wakeup_cause()to determine what triggered wake-up - Only execute the code needed for that specific wake-up source
- Skip unnecessary initialization for certain wake-up types
- Use
Optimized Wake-up Code Example
#includeRTC_DATA_ATTR int bootCount = 0; const int sensorPin = 36; // Analog sensor pin const int buttonPin = 27; // External wake-up button pin const int ledPin = 2; // Indicator LED // Time configurations #define SLEEP_DURATION_NORMAL 1800 // 30 minutes in seconds #define SLEEP_DURATION_ALERT 60 // 1 minute in seconds for alert mode uint64_t sleepDuration = SLEEP_DURATION_NORMAL; // Thresholds #define SENSOR_THRESHOLD 2000 // Threshold for sensor readings #define uS_TO_S_FACTOR 1000000ULL // Conversion factor for seconds to microseconds void setup() { // Only initialize serial for the first few boots (for debugging) if (bootCount < 5) { Serial.begin(115200); delay(100); // Brief delay for serial to initialize Serial.println("Boot count: " + String(bootCount)); esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); Serial.println("Wake-up reason: " + String(wakeup_reason)); } // Quick GPIO setup pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT_PULLDOWN); // Increment boot count bootCount++; // Determine why we woke up and handle accordingly esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); switch(wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: // Button was pressed handleButtonWakeup(); break; case ESP_SLEEP_WAKEUP_TIMER: // Timer triggered wake-up handleTimerWakeup(); break; default: // First boot or reset performInitialSetup(); break; } // Configure wake-up sources for next sleep esp_sleep_enable_timer_wakeup(sleepDuration * uS_TO_S_FACTOR); esp_sleep_enable_ext0_wakeup(GPIO_NUM_27, HIGH); // Go to sleep if (bootCount < 5) { Serial.println("Going to sleep for " + String(sleepDuration) + " seconds"); Serial.flush(); } // Quick flash to indicate going to sleep digitalWrite(ledPin, HIGH); delay(50); digitalWrite(ledPin, LOW); esp_deep_sleep_start(); } void loop() { // Never reached with deep sleep } void handleButtonWakeup() { // Button was pressed - user interaction needed digitalWrite(ledPin, HIGH); // Turn on LED // Read sensor and perform action int sensorValue = analogRead(sensorPin); // Process data and decide on next sleep duration if (sensorValue > SENSOR_THRESHOLD) { // Alert condition detected - sleep for shorter time sleepDuration = SLEEP_DURATION_ALERT; // Blink LED to indicate alert for (int i = 0; i < 5; i++) { digitalWrite(ledPin, LOW); delay(100); digitalWrite(ledPin, HIGH); delay(100); } } else { // Normal condition - standard sleep time sleepDuration = SLEEP_DURATION_NORMAL; delay(1000); // Keep LED on for a second } digitalWrite(ledPin, LOW); // Turn off LED } void handleTimerWakeup() { // Periodic wake-up for sensor reading // Quick sensor read without turning on LED to save power int sensorValue = analogRead(sensorPin); // Adjust sleep duration based on reading if (sensorValue > SENSOR_THRESHOLD) { sleepDuration = SLEEP_DURATION_ALERT; // Brief LED flash to indicate alert condition digitalWrite(ledPin, HIGH); delay(50); digitalWrite(ledPin, LOW); } else { sleepDuration = SLEEP_DURATION_NORMAL; } } void performInitialSetup() { // First boot initialization digitalWrite(ledPin, HIGH); // Perform any one-time setup sleepDuration = SLEEP_DURATION_NORMAL; delay(1000); digitalWrite(ledPin, LOW); }
Power-Saving Techniques in this Example:
- Serial output limited to first few boots to save power during normal operation
- Different handling based on wake-up source (timer vs button)
- Adaptive sleep duration based on sensor readings
- Minimal LED usage to reduce power consumption
- Quick processing and return to sleep
- Use of RTC memory to track boot count and state across sleep cycles