From f4fa27a91381865ffe4b64f8dcb3e492bd902bdf Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Fri, 7 Aug 2026 03:17:43 +0000 Subject: [PATCH 1/2] Wake ToughC5 from deep sleep through the PM1 IRQ output On ToughC5 neither the RTC nIRQ nor the touch INT reaches the ESP32 directly: they are collected by the PM1 (GPIO3 / GPIO0), and the PM1 IRQ output (GPIO1) is the only line wired to the ESP32 (GPIO4). Waking from deep sleep therefore has to ride on that single active-low line. - begin(): configure PM1 GPIO1 as the IRQ output and GPIO3 (RX8130 nIRQ) as an input. Without an IRQ pin the PM1 clears the IRQ status registers (0x40-0x42) by itself, so neither the power button nor the RTC alarm could ever be observed. The line has no external pull-up, so GPIO4 is set up as an input with the internal pull-up. - _clearWakeupInterrupt(): clear WAKE_SRC and the IRQ statuses. While WAKE_SRC is left set, the WAKEUP bit of IRQ status 3 keeps reasserting and the IRQ output never releases. - _powerOff(): with a timer, arm EXT1 (ANY_LOW), enable the RTC-domain pull-up that stays effective during deep sleep, and wait for the IRQ output to release before sleeping. - deepSleep(): include C5 in the wakeup pin handling. The pull policy is the opposite of the boards that have an external pull-up: ToughC5 needs the pull-up, not the pull-down. --- src/M5Unified.cpp | 17 +++++++++ src/utility/Power_Class.cpp | 74 +++++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 80ea3b5..e3d50e5 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -2888,6 +2888,23 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + default: + break; + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + switch (getBoard()) + { + case board_t::board_M5ToughC5: + { // TOUCH_INT や RTC_INT は PM1 に集約され、PM1 の IRQ 出力 -> GPIO4 が + // 唯一の wakeup ピンになる。IRQ 出力は IRQ ステータス (0x40-0x42) が + // 全て 0 になるまで Low を保つため、ここでクリアして解放する。 + // WAKE_SRC が残っていると IRQ Status 3 の WAKEUP ビットが再セット + // され続けるので、先に WAKE_SRC を消す。 + Power.M5pm1.clearWakeSource(); + Power.M5pm1.clearIRQStatus(); + } + break; + default: break; } diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index c38b7ea..98795a9 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -232,6 +232,25 @@ namespace m5 M5pm1.setDCDCOutput(true); M5pm1.setLDOOutput(true); M5pm1.setLedEnLevel(true); + /// PM1 GPIO1 は ESP32 の G4 へ配線された IRQ 出力。IRQ ピンを設定して + /// おかないと PM1 が IRQ ステータス (0x40-0x42) を自動クリアしてしまい、 + /// 電源ボタンや RTC アラームの IRQ 検出が機能しない。 + /// IRQ 機能へ切り替える前に push-pull 出力の High として設定しておき、 + /// 解放時に能動的に High が駆動されるようにする。 + M5pm1.setGPIOMode(M5PM1_Class::gpio1, M5PM1_Class::output); + M5pm1.setGPIODrive(M5PM1_Class::gpio1, M5PM1_Class::push_pull); + M5pm1.setGPIOPull(M5PM1_Class::gpio1, M5PM1_Class::pull_up); + M5pm1.setGPIOOutput(M5PM1_Class::gpio1, true); + M5pm1.setGPIOFunction(M5PM1_Class::gpio1, M5PM1_Class::irq); + /// PM1 GPIO3 は RX8130 の nIRQ 入力 (外部プルアップ・アクティブ Low)。 + /// 入力にしておくと IRQ ピン設定時のレベル変化スキャン対象になり、 + /// RTC アラームが IRQ 出力 (= ESP32 G4 の Low) として伝わる。 + M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio3, M5PM1_Class::input); + /// PM1 の IRQ 出力を wakeup ピンとして読めるよう入力にしておく。 + /// この線には外部プルアップが無く、IRQ 解放時に High へ戻す駆動も + /// 期待できないため、内部プルアップを有効にする。 + m5gfx::pinMode(_wakeupPin, m5gfx::pin_mode_t::input_pullup); break; } @@ -1199,6 +1218,27 @@ namespace m5 if (!withTimer) { M5pm1.powerOff(); } +#if SOC_PM_SUPPORT_EXT1_WAKEUP + else if (_wakeupPin < GPIO_NUM_MAX) + { /// RTC の nIRQ は ESP32 に直結されておらず PM1 の IRQ 出力に集約される + /// 構成 (ToughC5 等)。IRQ 出力が解放される (High に戻る) まで待って + /// から眠り、その Low 遷移で deep sleep から復帰できるようにする。 + if (ESP_OK != esp_sleep_enable_ext1_wakeup(1ULL << _wakeupPin, ESP_EXT1_WAKEUP_ANY_LOW)) + { + M5_LOGW("_powerOff: GPIO%d cannot be used as a wakeup source.", (int)_wakeupPin); + } +#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED + if (rtc_gpio_is_valid_gpio((gpio_num_t)_wakeupPin)) + { /// IRQ 線には外部プルアップが無いため、deep sleep 中も有効な + /// RTC ドメインのプルアップで High を維持する + rtc_gpio_pullup_en((gpio_num_t)_wakeupPin); + rtc_gpio_pulldown_dis((gpio_num_t)_wakeupPin); + } +#endif + int retry = 40; + while (!_releaseWakeupPin(_wakeupPin) && --retry) { m5gfx::delay(10); } + } +#endif break; #endif @@ -1345,7 +1385,7 @@ namespace m5 (void)touch_wakeup; #else ESP_LOGD("Power","deepSleep"); -#if defined (CONFIG_IDF_TARGET_ESP32C3) || defined (CONFIG_IDF_TARGET_ESP32C6) || defined (CONFIG_IDF_TARGET_ESP32C5) // || defined (CONFIG_IDF_TARGET_ESP32P4) +#if defined (CONFIG_IDF_TARGET_ESP32C3) || defined (CONFIG_IDF_TARGET_ESP32C6) // || defined (CONFIG_IDF_TARGET_ESP32P4) #else @@ -1381,16 +1421,28 @@ namespace m5 #endif #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED if (pin_wakeup_enabled) - { /// TODO: reconsider these pull settings. - // They came in with this EXT1 branch, which was written for M5Tab5 (ESP32-P4), - // and every board that reaches here now shares them. - // Pulling the pin down biases it toward the ANY_LOW wakeup condition, so a pin - // without an external pull-up would wake up immediately. On CoreS3 this works - // only because I2C_INT has an external 10k pull-up, and it costs about 60uA - // through that divider for as long as the device sleeps. - // Check how the wakeup pin of M5Tab5 is wired before changing this. - rtc_gpio_pullup_dis((gpio_num_t)wpin); - rtc_gpio_pulldown_en((gpio_num_t)wpin); + { +#if defined (CONFIG_IDF_TARGET_ESP32C5) + if (M5.getBoard() == board_t::board_M5ToughC5) + { // PM1 の IRQ 出力線には外部プルアップが無く、プルダウンすると + // Low に固定されて wakeup ピンが解放されなくなる。内部プルアップで + // High を維持し、IRQ アサート (Low) だけを wakeup 条件にする。 + rtc_gpio_pullup_en((gpio_num_t)wpin); + rtc_gpio_pulldown_dis((gpio_num_t)wpin); + } + else +#endif + { /// TODO: reconsider these pull settings. + // They came in with this EXT1 branch, which was written for M5Tab5 (ESP32-P4), + // and every board that reaches here now shares them. + // Pulling the pin down biases it toward the ANY_LOW wakeup condition, so a pin + // without an external pull-up would wake up immediately. On CoreS3 this works + // only because I2C_INT has an external 10k pull-up, and it costs about 60uA + // through that divider for as long as the device sleeps. + // Check how the wakeup pin of M5Tab5 is wired before changing this. + rtc_gpio_pullup_dis((gpio_num_t)wpin); + rtc_gpio_pulldown_en((gpio_num_t)wpin); + } } #endif } From fb96ff315269f228931bccbcae8f903bf26ad1c4 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Fri, 7 Aug 2026 03:17:43 +0000 Subject: [PATCH 2/2] Expose the ToughC5 M5IOE1 through getIOExpander Beside the LCD power / reset / backlight, the TF card power (PYG6) and card detect (PYG14) hang off this IO expander, so applications need a way to reach it. --- src/M5Unified.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index e3d50e5..f3b1308 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1855,6 +1855,15 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { _io_expander[0].reset(ioexp); } break; +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + case board_t::board_M5ToughC5: + { /// LCD 電源/リセット/バックライトのほか TF 電源 (PYG6) と + /// TF カード検出 (PYG14) がこの IOE にぶら下がる + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; #endif default: break;