From 552599fb34d425e88bd7d6fef215f7367b875081 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 17 Oct 2026 23:08:46 +0700 Subject: [PATCH] Improved sleep() and added enterDeepSleep() --- src/MeshCore.h | 11 +++++++++++ src/helpers/ESP32Board.cpp | 14 +++++++++----- src/helpers/ESP32Board.h | 22 ++++++++++++++++++---- src/helpers/NRF52Board.cpp | 23 +++++++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/MeshCore.h b/src/MeshCore.h index b9cfb4182..4983c7c63 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -37,6 +37,15 @@ #define BRIDGE_DEBUG_PRINTLN(...) {} #endif +#if POWERSAVING_DEBUG && ARDUINO + #include + #define POWERSAVING_DEBUG_PRINT(F, ...) Serial.printf("POWERSAVING: " F, ##__VA_ARGS__) + #define POWERSAVING_DEBUG_PRINTLN(F, ...) Serial.printf("POWERSAVING: " F "\n", ##__VA_ARGS__) +#else + #define POWERSAVING_DEBUG_PRINT(...) {} + #define POWERSAVING_DEBUG_PRINTLN(...) {} +#endif + namespace mesh { #define BD_STARTUP_NORMAL 0 // getStartupReason() codes @@ -53,6 +62,7 @@ public: virtual void onAfterTransmit() { } virtual void reboot() = 0; virtual void powerOff() { /* no op */ } + virtual void enterDeepSleep(uint32_t secs) { /* no op */ } // Called by example setup() functions to signal that boot is complete. // Boards may override to stop a boot-indicator LED sequence or similar. // Default no-op: boards that don't care need not implement anything. @@ -90,6 +100,7 @@ public: // Called from the example main loops. Lets a board feed its watchdog and // run periodic housekeeping. Default no-op. virtual void loop() { /* no op */ } + inline static uint32_t n_cad_busy = 0; }; /** diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index a55abb264..57bff06e1 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -45,11 +45,7 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { } #endif -void ESP32Board::powerOff() { - enterDeepSleep(0); // Do not wakeup -} - -void ESP32Board::enterDeepSleep(uint32_t secs) { +void ESP32Board::shutdownPeripherals() { // Power off the display if any #ifdef DISPLAY_CLASS display.turnOff(); @@ -74,6 +70,14 @@ void ESP32Board::enterDeepSleep(uint32_t secs) { // Flush serial buffers Serial.flush(); delay(100); +} + +void ESP32Board::powerOff() { + enterDeepSleep(0); // Do not wakeup +} + +void ESP32Board::enterDeepSleep(uint32_t secs) { + shutdownPeripherals(); // Clear stale wakeup sources to avoid ghost wakeup // This is required when Power Management and automatic lightsleep are enabled diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 3f95e6923..a98121c2b 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -21,6 +21,7 @@ class ESP32Board : public mesh::MainBoard { protected: uint8_t startup_reason; bool inhibit_sleep = false; + uint32_t inhibit_sleep_until = 0; static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED; public: @@ -66,8 +67,9 @@ public: return raw / 4; } + virtual void shutdownPeripherals(); virtual void powerOff() override; - void enterDeepSleep(uint32_t secs); + virtual void enterDeepSleep(uint32_t secs) override; uint32_t getIRQGpio() override { return P_LORA_DIO_1; // default for SX1262 @@ -75,13 +77,13 @@ public: void sleep(uint32_t secs) override { // Skip if not allow to sleep - if (inhibit_sleep) { + if (inhibit_sleep || (int32_t)(inhibit_sleep_until - millis()) > 0) { delay(1); // Give MCU to OTA to run return; } // Set GPIO wakeup - gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio(); + gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio(); // Configure timer wakeup if (secs > 0) { @@ -100,7 +102,10 @@ public: // Configure GPIO wakeup esp_sleep_enable_gpio_wakeup(); - gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet + gpio_wakeup_enable(wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet +#if defined(PIN_USER_BTN) && (PIN_USER_BTN >= 0) + gpio_wakeup_enable((gpio_num_t)PIN_USER_BTN, GPIO_INTR_LOW_LEVEL); // Wakeup when pressing button +#endif // MCU enters light sleep esp_light_sleep_start(); @@ -108,6 +113,15 @@ public: // Avoid ISR flood during wakeup due to HIGH LEVEL interrupt gpio_wakeup_disable(wakeupPin); gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE); +#if defined(PIN_USER_BTN) && (PIN_USER_BTN >= 0) + gpio_wakeup_disable((gpio_num_t)PIN_USER_BTN); + gpio_set_intr_type((gpio_num_t)PIN_USER_BTN, GPIO_INTR_POSEDGE); + + // Check if waken up by PIN_USER_BTN + if (gpio_get_level((gpio_num_t)PIN_USER_BTN) == LOW) { + inhibit_sleep_until = millis() + 30000; // Do not sleep in the next 30s. Should be longer than OLED timeout + } +#endif // Enable CPU interrupt servicing portEXIT_CRITICAL(&sleepMux); diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index f2fa7743a..47563b0ba 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -390,6 +390,29 @@ void NRF52Board::powerOff() { } } +void NRF52Board::enterDeepSleep(uint32_t secs) { + if (secs == 0) { + powerOff(); // Not wake up + } else { + shutdownPeripherals(); + +// Clear FPU interrupt flags to avoid insomnia +// see errata 87 for details +// https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html +#if (__FPU_USED == 1) + __set_FPSCR(__get_FPSCR() & ~(0x0000009F)); + (void)__get_FPSCR(); + NVIC_ClearPendingIRQ(FPU_IRQn); +#endif + + // Attemp to sleep + vTaskDelay(pdMS_TO_TICKS(secs * 1000)); + + // Reboot for a fresh recovery + reboot(); + } +} + bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) { static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 43944b5f7..c361347e1 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -54,6 +54,7 @@ public: virtual void reboot() override { NVIC_SystemReset(); } virtual void shutdownPeripherals(); virtual void powerOff() override; + virtual void enterDeepSleep(uint32_t secs) override; virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual void sleep(uint32_t secs) override;