Skip to content

iot_button + Auto Light Sleep: Baseline Current Never Returns to Idle After Button Activity (ESP32-S3) (AEGHB-1318) #614

@michaelboeding

Description

@michaelboeding

Answers checklist.

  • I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
  • I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
  • I have searched the issue tracker for a similar issue and not found a similar issue.

General issue report

iot_button + Auto Light Sleep: Baseline Current Never Returns to Idle After Button Activity (ESP32-S3)

Overview

When using Dynamic Frequency Scaling (DFS) with automatic light sleep enabled, pressing a button managed by the iot_button component causes the baseline current draw to increase and never return to the original low-power level.

This happens after one or more button interactions (especially multiple/rapid clicks). Once it occurs, the system appears to stop entering automatic light sleep until reboot.

I've attached a current trace screenshot showing:

  • Initial low baseline (auto light sleep active)
  • Several button presses (single/double)
  • A step change to a higher baseline current that never recovers

Environment

  • Chip: ESP32-S3
  • Framework: ESP-IDF (ESP32-S3 target)
  • iot_button version: v3.5.0
  • Power Management:
    • esp_pm_configure() with:
      • DFS enabled
      • light_sleep_enable = true
  • Button Library: iot_button component
    • enable_power_save = true in configuration
  • Transports tested:
    • Wi-Fi enabled
    • BLE enabled
      Issue reproduces in both cases, so it doesn't appear Wi-Fi–specific.

(If useful: I can provide exact ESP-IDF version and commit hash I'm currently using.)


Power Management Configuration

esp_pm_config_t pm_config = {
    .max_freq_mhz = 240,
    .min_freq_mhz = 40,
    .light_sleep_enable = true,
};

ESP_ERROR_CHECK(esp_pm_configure(&pm_config));

Button initialization (simplified):

button_config_t cfg = {
    .type = BUTTON_TYPE_GPIO,
    .gpio_button_config = {
        .gpio_num = BUTTON_GPIO,
        .active_level = 0,
    },
    .enable_power_save = true, // Also tested with false
};

button_handle_t btn = iot_button_create(&cfg);

// single click / double click handlers registered as normal...

Steps to Reproduce

  1. Configure DFS + auto light sleep via esp_pm_configure() as above.
  2. Initialize iot_button with enable_power_save = true (also happens with false).
  3. Let the device run idle until:
    • Current settles at low baseline (auto light sleep clearly active).
  4. Perform:
    • A few single clicks ➜ LED updates, current returns to baseline.
    • Then double-click or several rapid clicks in a row.
  5. Observe:
    • Current climbs to a higher "active" baseline and
    • Never returns to the original low-power level, even after the button is idle for a long period, no tasks running, etc.

This reproduces consistently on my hardware.


Expected Behavior

  • After button activity finishes and the button enters the idle state, the iot_button timer should stop, GPIO wakeup should be re-enabled, and:
    • ESP-IDF auto light sleep should resume as before.
    • Baseline current should drop back to the original light-sleep level.

Actual Behavior

  • After some sequences of button presses (often double/multiple rapid clicks):
    • Baseline current jumps to a higher, non-sleep state.
    • The device never returns to the original light-sleep baseline.
    • This persists indefinitely (until reset), even though:
      • No button presses are happening
      • Button is logically idle
      • There is no obvious high-activity task in my app code.

It looks like the system is stuck in a non-light-sleep state as far as the PM subsystem is concerned.


What I've Tried / Ruled Out

Change / Test Result
Disabled all LED/status logic inside singleClick() ❌ Baseline still gets stuck high
Disabled multi-click (e.g., 15-click handler) ❌ No change
Added explicit double-click handler (to consume event) ❌ No change
Added a race-condition flag (s_entering_power_save) ❌ No change
Added GPIO level checks in ISR to filter spurious edges ❌ No change
Disabled enable_power_save in iot_button ❌ Problem still occurs

So far this does not appear to be caused by my application callbacks (LEDs, app logic, etc.). Even with minimal handlers, the behavior is the same.


Relevant Code Path (Button Timer Lifecycle)

From the iot_button side (simplified view of the logic):

// On GPIO interrupt (button edge):
static void button_isr_handler(void *arg) {
    // ...
    // Start periodic timer to poll button state
    if (!g_is_timer_running) {
        esp_timer_start_periodic(g_button_timer_handle, 5 * 1000); // 5 ms
        g_is_timer_running = true;
    }
}

// In the timer callback:
static void button_timer_callback(void *arg) {
    // Poll button state, detect click, etc.

    if (button_state == BUTTON_NONE_PRESS) {
        // Stop timer when idle
        esp_timer_stop(g_button_timer_handle);
        g_is_timer_running = false;

        // Re-enable GPIO interrupt and wakeup
        button_gpio_intr_control(button_handle, true);
        button_gpio_enable_gpio_wakeup(button_handle, true);
    }
}

In my testing, logs confirm that:

  • esp_timer_stop() is called when the button becomes idle.
  • g_is_timer_running is set to false.
  • GPIO wakeup is re-enabled.

Despite this, auto light sleep appears not to re-engage after certain press sequences.


Hypothesis

My working theory is that there is a subtle interaction between esp_timer (used by iot_button) and ESP-IDF's automatic light sleep / DFS logic:

  1. iot_button uses a periodic esp_timer (5 ms) to poll button state.
  2. Automatic light sleep requires:
    • No active timers / pending work that would prevent tickless idle and light sleep.
  3. In some sequence of presses (especially double/multiple), it seems that:
    • Either a timer, wake-up source, or PM lock is left in a state that prevents idle detection, or
    • Some internal subsystem (GPIO, timer, etc.) keeps the chip from returning to light sleep, even though esp_timer_stop() has been called.

Since the issue persists even when enable_power_save is disabled in iot_button, I suspect this may relate to:

  • How iot_button manages esp_timer + GPIO wakeup together, or
  • How those interact with tickless idle / PM locks on ESP32-S3 in auto light sleep mode.

Image

Tickless Idle + Auto Light Sleep Settings

For reference, I’m using:

  • CONFIG_PM_ENABLE=y
  • CONFIG_PM_DFS_INIT_AUTO=y
  • CONFIG_PM_RTOS_IDLE_OPT=y
  • CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y
  • CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
  • CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
  • CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=y
  • CONFIG_BUTTON_PERIOD_TIME_MS=5
  • CONFIG_BUTTON_DEBOUNCE_TICKS=2

Additional Observation:
Once the system enters the bad state (baseline current stuck high), further button presses do not restore light sleep. The button still wakes the CPU and my click callbacks run, but after each subsequent press the device never returns to the original light-sleep baseline — it stays at the elevated “active” baseline indefinitely, even with long idle periods.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions