Skip to content

Fix wakeup from sleep: touch wakeup on CoreS3, and the wakeup pin being ignored on older ESP-IDF - #291

Merged
lovyan03 merged 2 commits into
m5stack:developfrom
ainyan03:fix-sleep-wakeup-cores3-and-idf-compat
Jul 31, 2026
Merged

lovyan03 merged 2 commits into
m5stack:developfrom
ainyan03:fix-sleep-wakeup-cores3-and-idf-compat

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

Fixes #214

Two changes in the sleep wakeup pin handling. They are independent, so they are kept as separate commits.

1. The wakeup pin was ignored on older ESP-IDF

deepSleep() chose its wakeup API with #if SOC_PM_SUPPORT_EXT0_WAKEUP / #elif SOC_PM_SUPPORT_EXT1_WAKEUP, but ESP-IDF v4 defines neither of them; it only has the generic SOC_PM_SUPPORT_EXT_WAKEUP. Both branches therefore vanished, and the wakeup pin was silently dropped on every board built with Arduino 2.x, leaving the timer as the only way out of deep sleep. This has been the case since the EXT1 branch was added for M5Tab5.

esp_sleep_enable_ext1_wakeup_io() has the same problem in the other direction: it was added in ESP-IDF 5.3, so the EXT1 branch did not compile on older versions at all.

  • add M5UNIFIED_PM_SUPPORT_EXT0 / EXT1, falling back to SOC_PM_SUPPORT_EXT_WAKEUP when the split macros are missing (EXT0 exists on ESP32 / S2 / S3, EXT1 on all of them)
  • call esp_sleep_enable_ext1_wakeup() when the _io variant is not available
  • lightSleep() decided between EXT0 and gpio wakeup by board name (board_M5PaperS3), which is really a question of whether the pin is an RTC IO. It now asks rtc_gpio_is_valid_gpio() and falls back to gpio wakeup, so any board with a non RTC IO wakeup pin — and any target without EXT0 — behaves the same way without being named.

The pull settings in the EXT1 branch look inverted against the ANY_LOW wakeup condition (pulling the pin down biases it toward the wakeup condition, so a pin without an external pull-up would wake immediately). They are left as they are with a TODO, because they were introduced for M5Tab5 and the wiring of its wakeup pin is unknown to me. Only M5Tab5 (P4) and C6 reach that branch now that S3 uses EXT0.

2. Wakeup by touch on CoreS3 (#214)

CoreS3 had no wakeup pin at all, so lightSleep() / deepSleep() could only be left by the timer or by a power cycle.

From the schematic, the touch interrupt reaches the ESP32 as:

TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN -> I2C_INT -> GPIO21

GPIO21 is an RTC IO on ESP32-S3, so it works for both light sleep and deep sleep.

Wakeup by the power key is not possible on this board. The power key is wired to the AXP2101 PWRON pin only, and the AXP2101 IRQ pin is shared with the RTC INT without reaching the ESP32. Touch is the only usable wakeup source here.

Setting the pin alone is not enough:

  • the touch panel holds its INT asserted until the touch data is read
  • the AW9523 latches INTN and reports changes only, so its input port has to be read as well — otherwise its input stays in the touched state, a later touch produces no change, and the wakeup source is dead

If either is left asserted, esp_light_sleep_start() returns ESP_ERR_SLEEP_REJECT_WAKEUP, or the device sleeps with no way to be woken.

Releasing them requires knowledge of how the board is wired, which belongs to M5Unified rather than to Power_Class. It is added as an internal M5Unified::_clearWakeupInterrupt() that Power_Class reaches through a friend declaration. The release is repeated immediately before sleeping, because an interrupt arriving after the wait loop would otherwise stay latched.

Reading the touch data is not specific to CoreS3, so it is done for every board that has a touch panel. M5Paper (GPIO36), M5PaperS3 (GPIO48) and Core2 / Tough (GPIO39) use the touch INT as their wakeup pin too, and their lightSleep() had no touch read at all while waiting for the pin to be released; deepSleep() happened to avoid the problem only because its wait loop calls M5.update(). The extra read costs nothing on other boards, because _clearWakeupInterrupt() is only called while the wakeup pin is asserted.

Finally, requesting touch_wakeup on a board that has no wakeup pin now logs a warning instead of failing silently.

Verification

Verified on CoreS3, driven by touch so that nothing depends on timing:

Arduino 2.0.17 (ESP-IDF 4.4) Arduino 3.3.4 (ESP-IDF 5.5)
light sleep, touch wakeup OK OK
deep sleep, touch wakeup OK (EXT0) OK (EXT0)

Build checked for ESP32-S3 (ESP-IDF 4.4 / 5.5), ESP32 and the PC build.

Not verified on hardware:

  • deep sleep wakeup on ESP32 classic boards (Core2 / M5Paper / CoreInk / StickCPlus2) — I have no such device at hand. These are the boards most affected by change 1, so a second pair of eyes there would be welcome.
  • the lightSleep() improvement for M5Paper / M5PaperS3 / Core2 / Tough. Note that the current code loops forever in that situation, so there is little room for a regression.
  • M5Tab5 / C6, which are the only remaining users of the EXT1 branch.

ainyan03 added 2 commits July 31, 2026 12:48
deepSleep() selected its wakeup API with `#if SOC_PM_SUPPORT_EXT0_WAKEUP` /
`#elif SOC_PM_SUPPORT_EXT1_WAKEUP`, but ESP-IDF v4 defines neither of them; it
only has the generic SOC_PM_SUPPORT_EXT_WAKEUP. Both branches therefore
disappeared and the wakeup pin was silently dropped on every board built with
Arduino 2.x, leaving the timer as the only wakeup source.
esp_sleep_enable_ext1_wakeup_io() has the same problem: it was added in
ESP-IDF 5.3, so the EXT1 branch did not even compile on older versions.

- add M5UNIFIED_PM_SUPPORT_EXT0 / EXT1, which fall back to
  SOC_PM_SUPPORT_EXT_WAKEUP when the split macros are missing
  ( EXT0 exists on ESP32 / S2 / S3 , EXT1 on all of them )
- call esp_sleep_enable_ext1_wakeup() when the _io variant is not available
- lightSleep() decided between EXT0 and gpio wakeup by board name
  ( board_M5PaperS3 ), which is really a question of whether the pin is an
  RTC IO. Ask rtc_gpio_is_valid_gpio() instead and fall back to gpio wakeup,
  so any board with a non RTC IO wakeup pin, and any target without EXT0,
  behaves the same way.

The pull settings of the EXT1 branch look inverted against the ANY_LOW wakeup
condition, but they are left as they are with a TODO, because they were
introduced for M5Tab5 and the wiring of its wakeup pin is unknown here.
CoreS3 had no wakeup pin, so lightSleep() and deepSleep() could only be left by
the timer or by a power cycle.

According to the schematic the touch interrupt reaches the ESP32 as
TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN -> I2C_INT -> GPIO21, which is an
RTC IO and can therefore be used for both light sleep and deep sleep.
The power key is wired to the AXP2101 PWRON only, and the AXP2101 IRQ pin is
shared with the RTC INT without reaching the ESP32, so touch is the only usable
wakeup source on this board.

Simply setting the pin is not enough. The touch panel holds its INT asserted
until the touch data is read, and the AW9523 latches its INTN until its input
port is read; if either of them is left asserted, the sleep request is rejected
or the wakeup source is dead while sleeping. Releasing them needs to know how
the board is wired, which is knowledge that belongs to M5Unified rather than to
Power_Class, so it is added as an internal M5Unified::_clearWakeupInterrupt()
that Power_Class calls through a friend declaration. The release is repeated
immediately before sleeping, because an interrupt that arrives after the wait
loop would otherwise stay latched.

Reading the touch data is not specific to CoreS3, so it is done for every board
that has a touch panel. M5Paper ( GPIO36 ), M5PaperS3 ( GPIO48 ) and
Core2 / Tough ( GPIO39 ) use the touch INT as their wakeup pin as well, and
their lightSleep() had no touch read at all while waiting for the pin to be
released. deepSleep() happened to avoid this only because its wait loop calls
M5.update(). The extra read costs nothing on the other boards, because
_clearWakeupInterrupt() is called only while the wakeup pin is asserted.

Also warn when touch_wakeup is requested on a board that has no wakeup pin at
all; this used to fail silently.

Verified on CoreS3 with Arduino 2.0.17 ( ESP-IDF 4.4 ) and Arduino 3.3.4
( ESP-IDF 5.5 ): touch wakes the device from both light sleep and deep sleep.
The other boards are checked by build only.
@lovyan03
lovyan03 merged commit 55e591b into m5stack:develop Jul 31, 2026
23 checks passed
@lovyan03
lovyan03 deleted the fix-sleep-wakeup-cores3-and-idf-compat branch July 31, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants