The Light Sleep Conundrum: Why Standard Interrupts Fail

As battery-powered IoT devices continue to dominate the maker landscape in 2026, power management has shifted from a secondary concern to a primary design constraint. For makers utilizing the ESP32 family, achieving ultra-low power consumption often hinges on mastering sleep modes. However, one of the most persistent hurdles discussed across forums and GitHub repositories is configuring an Arduino interrupt in light sleep.

If you have ever tried using the standard Arduino attachInterrupt() function to wake your microcontroller from light sleep, you likely encountered a frustrating reality: it simply does not work. When the ESP32 enters light sleep, the APB clock is gated, and the digital peripheral block is powered down. The CPU halts, meaning standard RAM-based Interrupt Service Routines (ISRs) cannot execute. To successfully trigger an Arduino interrupt in light sleep, you must bypass the standard Arduino core and interface directly with the Real-Time Clock (RTC) controller via the ESP-IDF API.

This community resource roundup curates the most critical insights, hardware gotchas, and code architectures shared by the open-source embedded community to help you master light sleep wakeups.

Top Community Resources for ESP32 Light Sleep Interrupts

Navigating the intersection of the Arduino abstraction layer and the underlying ESP-IDF (Espressif IoT Development Framework) requires reliable documentation. Here are the foundational resources the community relies on.

1. The Espressif System API Documentation (The Source of Truth)

The absolute starting point for any low-power ESP32 project is the official Espressif Sleep Modes API Reference. The community consistently points to this documentation to clarify the distinction between esp_sleep_enable_ext0_wakeup() and esp_sleep_enable_ext1_wakeup().

  • EXT0: Allows waking up via a single RTC-capable GPIO pin. It supports both high and low level triggering, making it ideal for simple push-button interrupts.
  • EXT1: Allows waking up via a bitmask of multiple RTC GPIOs. However, it is strictly limited to edge-triggered logic in older silicon, though newer 2026 ESP-IDF releases have improved level-trigger support for EXT1 on the ESP32-S3 and C6.

2. The Arduino-ESP32 GitHub Issues Archive

The Arduino-ESP32 GitHub repository is a goldmine for edge-case troubleshooting. A recurring theme in the issue tracker involves developers attempting to retain RAM state during light sleep. Unlike deep sleep, light sleep retains the contents of the main SRAM. However, community veterans note that if you do not properly configure the esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO) flag, the RTC peripheral domain may power down, severing your interrupt connection before the sleep cycle even begins.

3. Low-Power Hardware Design Guides & Forum Deep-Dives

Software configuration is only half the battle. Hardware leakage is the silent killer of battery life. Community hardware guides emphasize that floating GPIO pins can cause current spikes exceeding 200 µA, entirely negating the benefits of light sleep. The consensus solution is to utilize internal pull-up/pull-down resistors via rtc_gpio_pullup_en() or external 10kΩ resistors on your interrupt trigger pins.

Sleep Mode Comparison Matrix

Understanding where light sleep fits into the broader power topology is crucial for selecting the right wakeup strategy. Below is a comparison matrix based on 2026 silicon benchmarks (specifically targeting the ESP32-S3 and ESP32-C6).

Sleep Mode Avg. Current (ESP32-S3) Avg. Current (ESP32-C6) Wake Sources RAM State Wake Time
Active ~30 mA ~25 mA N/A Retained N/A
Modem Sleep ~12 mA ~8 mA Wi-Fi/BT MAC Retained ~0.2 ms
Light Sleep ~0.8 mA ~15 µA RTC GPIO, Timer, ULP Retained ~0.3 ms
Deep Sleep ~10 µA ~8 µA RTC GPIO, Timer, ULP Lost (Except RTC FAST) ~10+ ms (Full Boot)
Community Insight: The ESP32-C6 has revolutionized light sleep in 2026. With light sleep currents dropping to ~15 µA, the traditional boundary between light and deep sleep has blurred. For many sensor-logging applications, light sleep on the C6 is now preferred over deep sleep because it retains SRAM state while drawing nearly identical current.

Step-by-Step: Configuring the RTC Wakeup Interrupt

To successfully implement an Arduino interrupt in light sleep, you must configure the RTC wakeup source before calling the sleep function. Below is a robust, community-verified implementation for waking an ESP32 from light sleep using a push-button on GPIO 4.

#include "Arduino.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"

// Define the interrupt pin (Must be an RTC-capable GPIO on original ESP32)
#define WAKE_PIN GPIO_NUM_4

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow serial monitor to connect
  
  Serial.println("System Awake. Configuring Light Sleep...");

  // 1. Configure the pin as input with internal pull-up
  pinMode(WAKE_PIN, INPUT_PULLUP);
  
  // 2. Isolate the pin to prevent leakage during sleep
  rtc_gpio_isolate(WAKE_PIN);
  
  // 3. Enable EXT0 wakeup source (Wake when pin goes LOW)
  esp_sleep_enable_ext0_wakeup(WAKE_PIN, 0);
  
  // 4. Force RTC Periph domain to stay ON to monitor the pin
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

  Serial.println("Entering Light Sleep now.");
  Serial.flush(); // Ensure all serial data is transmitted before sleeping
  
  // 5. Enter Light Sleep
  esp_light_sleep_start();
  
  // Execution resumes here immediately upon wakeup (No reboot!)
  Serial.println("Woke up from Light Sleep via GPIO Interrupt!");
}

void loop() {
  // Perform quick tasks, then return to sleep
  delay(2000); 
}

Crucial Code Nuances

  • rtc_gpio_isolate(): This function is frequently omitted by beginners, leading to phantom current leaks. It disconnects the pin from the digital pad, ensuring only the RTC pad draws current.
  • Serial.flush(): Because light sleep halts the CPU instantly, any bytes left in the UART TX buffer will be truncated or cause bus errors upon waking. Always flush serial streams.
  • Execution Resumption: Unlike deep sleep, which triggers a full hardware reset and starts execution from setup(), light sleep resumes exactly where it left off. Your variables in the main SRAM remain intact.

Hardware Pitfalls: Pin Leakage and Silicon Differences

The community has documented severe hardware edge cases that can destroy your power budget, even with perfect software configuration.

The Floating Pin Disaster

If you configure an interrupt on an input pin but leave it electrically floating (e.g., a disconnected jumper wire), the CMOS input buffer will oscillate rapidly between logic high and low due to ambient electromagnetic noise. In light sleep, this oscillation can spike the current draw from 0.8 mA to over 5 mA. Always use INPUT_PULLUP or INPUT_PULLDOWN, or wire a physical 10kΩ resistor to VCC/GND.

Original ESP32 vs. ESP32-S3/C6 Pin Mapping

A major source of confusion in community forums is pin compatibility. On the original ESP32, ext0 and ext1 wakeups are strictly limited to RTC GPIOs (e.g., GPIO 2, 4, 12-15, 25-27, 32-39). Attempting to use GPIO 21 (an I2C pin) for a light sleep interrupt will result in a silent failure; the chip will simply sleep indefinitely. Conversely, on the ESP32-S3 and ESP32-C6, the RTC controller is multiplexed with almost all digital GPIOs. This architectural shift in newer silicon means you are no longer constrained to specific pins for light sleep interrupts, vastly simplifying PCB routing for custom IoT boards in 2026.

Frequently Asked Questions (FAQ)

Can I use standard attachInterrupt() alongside light sleep?

No. The standard Arduino attachInterrupt() relies on the digital GPIO matrix and the main CPU clock, both of which are disabled during light sleep. You must use the ESP-IDF esp_sleep_enable_ext0_wakeup() or ext1 functions to route the interrupt through the always-on RTC domain.

Does the ULP coprocessor work in light sleep?

Yes, the Ultra-Low-Power (ULP) coprocessor can run during light sleep, but it is generally overkill for simple GPIO interrupts. The ULP is better suited for polling analog sensors or performing bitwise math while the main cores are asleep. For a simple button press, EXT0/EXT1 hardware wakeups are more power-efficient and require less complex code.

Why does my ESP32 wake up immediately after entering light sleep?

Immediate wakeups are almost always caused by the interrupt pin being in the trigger state at the exact moment esp_light_sleep_start() is called. If you configure EXT0 to wake on a LOW signal, and the button is already pressed (or the pin is pulled LOW by an external circuit) when sleep initiates, the RTC controller will instantly flag the wakeup condition. Ensure the pin is in the inactive state before calling the sleep function.