Skip to content

Fix external RTC detection on Port A and the sleep wakeup handling - #290

Merged
lovyan03 merged 3 commits into
m5stack:developfrom
ainyan03:fix-external-rtc-and-sleep-wakeup
Jul 30, 2026
Merged

Fix external RTC detection on Port A and the sleep wakeup handling#290
lovyan03 merged 3 commits into
m5stack:developfrom
ainyan03:fix-external-rtc-and-sleep-wakeup

Conversation

@ainyan03

@ainyan03 ainyan03 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Three independent fixes found while going through the open issues. Each one is a separate commit.

1. External RTC on Port A is never detected (#266)

RTC_Class::begin() passes the given I2C_Class* to the RX8130 instances, but not to the PCF8563 fallback, whose third parameter defaults to &In_I2C. With cfg.external_rtc = true the call M5.Rtc.begin(&M5.Ex_I2C) therefore probes the internal bus, and UNIT-RTC on Port A is never found.

The argument was present in v0.2.10 and was dropped in e35a905 (first released in v0.2.11), which matches the reporter's version bisection - including why M5Grey is the one board that still works: its internal bus is G21/G22, the same pins as Port A.

2. deepSleep() aborts on M5PaperS3 when touch_wakeup is requested (#205)

On targets without EXT0 support, deepSleep() enabled EXT1 wakeup unconditionally inside ESP_ERROR_CHECK. The PaperS3 touch INT pin is GPIO48, which is not an RTC IO, so esp_sleep_enable_ext1_wakeup_io() returns ESP_ERR_INVALID_ARG and the call aborts instead of sleeping. lightSleep() already handled this case with gpio_wakeup_enable().

The pin is now validated with rtc_gpio_is_valid_gpio(), and a warning is logged when it cannot be used as a deep sleep wakeup source.

The same block also built the EXT1 pin mask from _wakeupPin while the wait loop used wpin, so the M5PaperMono override (GPIO4) never reached the mask; both use wpin now.

3. A sleep duration of zero silently means "sleep forever" (#285)

micro_seconds = 0 meant "no timer wakeup", and 0 was also the default argument - so deepSleep() with no arguments means "sleep until the wakeup pin triggers". A caller that computes a duration and accidentally ends up with 0 sleeps until an external reset, which is hard to tell apart from a freeze.

That intent now has its own value:

    static constexpr std::uint64_t sleep_no_timer = ~0ull;

    void deepSleep (std::uint64_t micro_seconds = sleep_no_timer, bool touch_wakeup = true);
    void lightSleep(std::uint64_t micro_seconds = sleep_no_timer, bool touch_wakeup = true);
  • calls without arguments behave exactly as before;
  • an explicit 0 no longer sleeps and logs a warning instead. For deepSleep() the check runs before M5.Display.sleep(), so the caller resumes with the display still on;
  • the sentinel is never handed to esp_sleep_enable_timer_wakeup() (about 580,000 years, which would be rejected) - it keeps taking the "disable the timer wakeup source" path;
  • lightSleep() now also tracks whether a wakeup pin was actually enabled, and warns when neither a timer nor a pin wakeup source is in effect.

This is a breaking change for code that passes a literal 0 in order to sleep indefinitely: such code needs M5.Power.sleep_no_timer instead. In public GitHub code the no-argument form outnumbers explicit 0 by roughly 97 to 11, and the direction of the change is the safe one - the device stays awake and logs why, rather than never waking up.

Verification

Builds cleanly with PlatformIO for esp32 (EXT0 path), esp32s3 (EXT1 path) and the PC build (M5UNIFIED_PC_BUILD).

Not verified on hardware yet - deepSleep() on PaperS3 and UNIT-RTC on Port A would be the two worth checking.

ainyan03 added 3 commits July 30, 2026 14:39
RTC_Class::begin() passed the given I2C bus to the RX8130 instances but not
to the PCF8563 fallback, whose third parameter defaults to In_I2C. As a
result cfg.external_rtc had no effect for UNIT-RTC on Port A, except on
boards where the internal bus and Port A are the same pins (M5Grey).
…set (m5stack#205 m5stack#285)

deepSleep() enabled EXT1 wakeup unconditionally on targets without EXT0
support, wrapped in ESP_ERROR_CHECK. On M5PaperS3 the touch INT pin is
GPIO48, which is not an RTC IO, so deepSleep(us, true) aborted instead of
sleeping. The pin is now checked with rtc_gpio_is_valid_gpio() and a
warning is logged when it cannot be used; lightSleep() already handled
this case via gpio wakeup.

The EXT1 path also used _wakeupPin for the pin mask while the wait loop
used wpin, so the M5PaperMono override (GPIO4) was not applied to the mask.

In addition, a warning is logged when neither a timer nor a wakeup pin is
requested, which is the deepSleep(0, false) case that silently sleeps
forever.
deepSleep() and lightSleep() took micro_seconds = 0 as "sleep without a
timer wakeup", so the default arguments (0, true) meant "sleep until the
wakeup pin is triggered". As a result a caller that computed a duration
and accidentally got 0 slept until an external reset, which is hard to
tell apart from a freeze.

The intent "no timer wakeup" now has its own value, Power.sleep_no_timer,
which is also the new default argument, so calls without arguments behave
as before. An explicit 0 no longer sleeps and logs a warning instead; for
deepSleep the check runs before the display is put to sleep, so the caller
resumes with the display still on.

lightSleep now also tracks whether a wakeup pin was actually enabled, and
warns when neither a timer nor a pin wakeup source is in effect.

BREAKING: code that passed a literal 0 to sleep indefinitely has to pass
Power.sleep_no_timer instead.
@lovyan03
lovyan03 merged commit 54dc56e into m5stack:develop Jul 30, 2026
23 checks passed
@lovyan03
lovyan03 deleted the fix-external-rtc-and-sleep-wakeup branch July 30, 2026 06:55
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