-
Notifications
You must be signed in to change notification settings - Fork 925
Description
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_buttoncomponentenable_power_save = truein 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
- Configure DFS + auto light sleep via
esp_pm_configure()as above. - Initialize
iot_buttonwithenable_power_save = true(also happens withfalse). - Let the device run idle until:
- Current settles at low baseline (auto light sleep clearly active).
- Perform:
- A few single clicks ➜ LED updates, current returns to baseline.
- Then double-click or several rapid clicks in a row.
- 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_buttontimer 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_runningis set tofalse.- 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:
iot_buttonuses a periodicesp_timer(5 ms) to poll button state.- Automatic light sleep requires:
- No active timers / pending work that would prevent tickless idle and light sleep.
- 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_buttonmanagesesp_timer+ GPIO wakeup together, or - How those interact with tickless idle / PM locks on ESP32-S3 in auto light sleep mode.
Tickless Idle + Auto Light Sleep Settings
For reference, I’m using:
CONFIG_PM_ENABLE=yCONFIG_PM_DFS_INIT_AUTO=yCONFIG_PM_RTOS_IDLE_OPT=yCONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=yCONFIG_FREERTOS_USE_TICKLESS_IDLE=yCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=yCONFIG_BUTTON_PERIOD_TIME_MS=5CONFIG_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.