From eb79b98f2b319df35e74c0543c15b2ec1e6e72a2 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Mon, 9 Feb 2026 12:45:16 +0100 Subject: [PATCH 1/8] power saving on idle --- src/CrossPointSettings.cpp | 1 + src/main.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index ffeef2b9..4ea59ba0 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -274,6 +274,7 @@ float CrossPointSettings::getReaderLineCompression() const { } unsigned long CrossPointSettings::getSleepTimeoutMs() const { + return 6UL * 60 * 60 * 1000; // TEST switch (sleepTimeout) { case SLEEP_1_MIN: return 1UL * 60 * 1000; diff --git a/src/main.cpp b/src/main.cpp index 33515bce..24bb6466 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -408,6 +408,13 @@ void loop() { if (currentActivity && currentActivity->skipLoopDelay()) { yield(); // Give FreeRTOS a chance to run tasks, but return immediately } else { - delay(10); // Normal delay when no activity requires fast response + static constexpr unsigned long IDLE_POWER_SAVING_MS = 10000; + if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { + // If we've been inactive for a while, increase the delay to save power + delay(50); + } else { + // Short delay to prevent tight loop while still being responsive + delay(10); + } } } From 4e7bb8979c47d1a9630d379ec8858bd8342cac4b Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Mon, 9 Feb 2026 19:20:36 +0100 Subject: [PATCH 2/8] revert test --- src/CrossPointSettings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 4ea59ba0..ffeef2b9 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -274,7 +274,6 @@ float CrossPointSettings::getReaderLineCompression() const { } unsigned long CrossPointSettings::getSleepTimeoutMs() const { - return 6UL * 60 * 60 * 1000; // TEST switch (sleepTimeout) { case SLEEP_1_MIN: return 1UL * 60 * 1000; From d4f25c44bfbf706735045f3b0559ee7a7cc81fd4 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Tue, 10 Feb 2026 11:31:28 +0100 Subject: [PATCH 3/8] lower to 3 seconds --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 24bb6466..7882e3c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -408,7 +408,7 @@ void loop() { if (currentActivity && currentActivity->skipLoopDelay()) { yield(); // Give FreeRTOS a chance to run tasks, but return immediately } else { - static constexpr unsigned long IDLE_POWER_SAVING_MS = 10000; + static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { // If we've been inactive for a while, increase the delay to save power delay(50); From 8cf226613b92fb7d0cd9fc53bdad44d6d0b01288 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Tue, 10 Feb 2026 14:19:16 +0100 Subject: [PATCH 4/8] clang format --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7882e3c4..d47e0788 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -408,7 +408,7 @@ void loop() { if (currentActivity && currentActivity->skipLoopDelay()) { yield(); // Give FreeRTOS a chance to run tasks, but return immediately } else { - static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds + static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { // If we've been inactive for a while, increase the delay to save power delay(50); From b72283d3040e8a5a1efc59495efd83e3260fcb34 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Tue, 10 Feb 2026 23:27:45 +0100 Subject: [PATCH 5/8] change cpu freq on idle --- src/CrossPointSettings.cpp | 1 + src/main.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index ffeef2b9..77d63681 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -274,6 +274,7 @@ float CrossPointSettings::getReaderLineCompression() const { } unsigned long CrossPointSettings::getSleepTimeoutMs() const { + return 12UL * 60 * 60 * 1000; // TEST: 12 hours switch (sleepTimeout) { case SLEEP_1_MIN: return 1UL * 60 * 1000; diff --git a/src/main.cpp b/src/main.cpp index d47e0788..24319e20 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -276,6 +276,37 @@ void setupDisplayAndFonts() { Serial.printf("[%lu] [ ] Fonts setup\n", millis()); } +// FOR TESTING ONLY +static bool isLowerFreq = false; +static int normalFreq = 160; // MHz +class HalPowerManager { + public: + static void setCpuFrequency(bool lower) { + bool changed = false; + if (lower && !isLowerFreq) { + bool success = setCpuFrequencyMhz(10); + if (!success) { + Serial.printf("[%lu] [PWR] Failed to set CPU frequency to 10 MHz\n", millis()); + return; + } + isLowerFreq = true; + changed = true; + } else if (!lower && isLowerFreq) { + bool success = setCpuFrequencyMhz(normalFreq); + if (!success) { + Serial.printf("[%lu] [PWR] Failed to set CPU frequency to %d MHz\n", millis(), normalFreq); + return; + } + isLowerFreq = false; + changed = true; + } + + if (changed) { + Serial.printf("[%lu] [PWR] CPU frequency set to %u MHz\n", millis(), getCpuFrequencyMhz()); + } + } +}; + void setup() { t1 = millis(); @@ -301,6 +332,8 @@ void setup() { return; } + normalFreq = getCpuFrequencyMhz(); + SETTINGS.loadFromFile(); KOREADER_STORE.loadFromFile(); UITheme::getInstance().reload(); @@ -371,6 +404,7 @@ void loop() { static unsigned long lastActivityTime = millis(); if (gpio.wasAnyPressed() || gpio.wasAnyReleased() || (currentActivity && currentActivity->preventAutoSleep())) { lastActivityTime = millis(); // Reset inactivity timer + HalPowerManager::setCpuFrequency(false); // Set normal CPU frequency on user activity } const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs(); @@ -411,6 +445,7 @@ void loop() { static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { // If we've been inactive for a while, increase the delay to save power + HalPowerManager::setCpuFrequency(true); // Lower CPU frequency after extended inactivity delay(50); } else { // Short delay to prevent tight loop while still being responsive From 228a1cb511edbb18328c91f91d4188a49ea06221 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Thu, 12 Feb 2026 11:37:12 +0100 Subject: [PATCH 6/8] rm test --- src/CrossPointSettings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 77d63681..ffeef2b9 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -274,7 +274,6 @@ float CrossPointSettings::getReaderLineCompression() const { } unsigned long CrossPointSettings::getSleepTimeoutMs() const { - return 12UL * 60 * 60 * 1000; // TEST: 12 hours switch (sleepTimeout) { case SLEEP_1_MIN: return 1UL * 60 * 1000; From ea32ba0f8dd7d321f154eba30e4585e28778c167 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Thu, 12 Feb 2026 13:12:13 +0100 Subject: [PATCH 7/8] add HalPowerManager --- lib/hal/HalGPIO.cpp | 19 --------------- lib/hal/HalGPIO.h | 6 ----- lib/hal/HalPowerManager.cpp | 47 ++++++++++++++++++++++++++++++++++++ lib/hal/HalPowerManager.h | 26 ++++++++++++++++++++ src/main.cpp | 48 +++++++------------------------------ 5 files changed, 82 insertions(+), 64 deletions(-) create mode 100644 lib/hal/HalPowerManager.cpp create mode 100644 lib/hal/HalPowerManager.h diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp index 89ce13ba..64a251de 100644 --- a/lib/hal/HalGPIO.cpp +++ b/lib/hal/HalGPIO.cpp @@ -1,11 +1,9 @@ #include #include -#include void HalGPIO::begin() { inputMgr.begin(); SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS); - pinMode(BAT_GPIO0, INPUT); pinMode(UART0_RXD, INPUT); } @@ -23,23 +21,6 @@ bool HalGPIO::wasAnyReleased() const { return inputMgr.wasAnyReleased(); } unsigned long HalGPIO::getHeldTime() const { return inputMgr.getHeldTime(); } -void HalGPIO::startDeepSleep() { - // Ensure that the power button has been released to avoid immediately turning back on if you're holding it - while (inputMgr.isPressed(BTN_POWER)) { - delay(50); - inputMgr.update(); - } - // Arm the wakeup trigger *after* the button is released - esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); - // Enter Deep Sleep - esp_deep_sleep_start(); -} - -int HalGPIO::getBatteryPercentage() const { - static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0); - return battery.readPercentage(); -} - bool HalGPIO::isUsbConnected() const { // U0RXD/GPIO20 reads HIGH when USB is connected return digitalRead(UART0_RXD) == HIGH; diff --git a/lib/hal/HalGPIO.h b/lib/hal/HalGPIO.h index 615a8d63..45ca50a5 100644 --- a/lib/hal/HalGPIO.h +++ b/lib/hal/HalGPIO.h @@ -38,12 +38,6 @@ class HalGPIO { bool wasAnyReleased() const; unsigned long getHeldTime() const; - // Setup wake up GPIO and enter deep sleep - void startDeepSleep(); - - // Get battery percentage (range 0-100) - int getBatteryPercentage() const; - // Check if USB is connected bool isUsbConnected() const; diff --git a/lib/hal/HalPowerManager.cpp b/lib/hal/HalPowerManager.cpp new file mode 100644 index 00000000..5c945f59 --- /dev/null +++ b/lib/hal/HalPowerManager.cpp @@ -0,0 +1,47 @@ +#include + +#include "HalPowerManager.h" +#include "HalGPIO.h" + +void HalPowerManager::begin() { + pinMode(BAT_GPIO0, INPUT); + normalFreq = getCpuFrequencyMhz(); +} + +void HalPowerManager::setPowerSaving(bool enabled) { + if (normalFreq <= 0) { + return; // invalid state + } + if (enabled && !isLowPower) { + Serial.printf("[%lu] [PWR] Going to low-power mode\n", millis()); + if (!setCpuFrequencyMhz(LOW_POWER_FREQ)) { + Serial.printf("[%lu] [PWR] Failed to set low-power CPU frequency\n", millis()); + return; + } + } + if (!enabled && isLowPower) { + Serial.printf("[%lu] [PWR] Restoring normal CPU frequency\n", millis()); + if (!setCpuFrequencyMhz(normalFreq)) { + Serial.printf("[%lu] [PWR] Failed to restore normal CPU frequency\n", millis()); + return; + } + } + isLowPower = enabled; +} + +void HalPowerManager::startDeepSleep(HalGPIO &gpio) const { + // Ensure that the power button has been released to avoid immediately turning back on if you're holding it + while (gpio.isPressed(HalGPIO::BTN_POWER)) { + delay(50); + gpio.update(); + } + // Arm the wakeup trigger *after* the button is released + esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); + // Enter Deep Sleep + esp_deep_sleep_start(); +} + +int HalPowerManager::getBatteryPercentage() const { + static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0); + return battery.readPercentage(); +} diff --git a/lib/hal/HalPowerManager.h b/lib/hal/HalPowerManager.h new file mode 100644 index 00000000..792bb449 --- /dev/null +++ b/lib/hal/HalPowerManager.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +#include "HalGPIO.h" + +class HalPowerManager { + static constexpr int LOW_POWER_FREQ = 10; // MHz + + int normalFreq = 0; // MHz + bool isLowPower = false; + + public: + void begin(); + + // Control CPU frequency for power saving + void setPowerSaving(bool enabled); + + // Setup wake up GPIO and enter deep sleep + void startDeepSleep(HalGPIO &gpio) const; + + // Get battery percentage (range 0-100) + int getBatteryPercentage() const; +}; diff --git a/src/main.cpp b/src/main.cpp index 8098c925..85e0d1b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ HalDisplay display; HalGPIO gpio; +HalPowerManager powerManager; MappedInputManager mappedInputManager(gpio); GfxRenderer renderer(display); Activity* currentActivity; @@ -181,7 +183,7 @@ void verifyPowerButtonDuration() { if (abort) { // Button released too early. Returning to sleep. // IMPORTANT: Re-arm the wakeup trigger before sleeping again - gpio.startDeepSleep(); + powerManager.startDeepSleep(gpio); } } @@ -204,7 +206,7 @@ void enterDeepSleep() { Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1); Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis()); - gpio.startDeepSleep(); + powerManager.startDeepSleep(gpio); } void onGoHome(); @@ -277,41 +279,11 @@ void setupDisplayAndFonts() { Serial.printf("[%lu] [ ] Fonts setup\n", millis()); } -// FOR TESTING ONLY -static bool isLowerFreq = false; -static int normalFreq = 160; // MHz -class HalPowerManager { - public: - static void setCpuFrequency(bool lower) { - bool changed = false; - if (lower && !isLowerFreq) { - bool success = setCpuFrequencyMhz(10); - if (!success) { - Serial.printf("[%lu] [PWR] Failed to set CPU frequency to 10 MHz\n", millis()); - return; - } - isLowerFreq = true; - changed = true; - } else if (!lower && isLowerFreq) { - bool success = setCpuFrequencyMhz(normalFreq); - if (!success) { - Serial.printf("[%lu] [PWR] Failed to set CPU frequency to %d MHz\n", millis(), normalFreq); - return; - } - isLowerFreq = false; - changed = true; - } - - if (changed) { - Serial.printf("[%lu] [PWR] CPU frequency set to %u MHz\n", millis(), getCpuFrequencyMhz()); - } - } -}; - void setup() { t1 = millis(); gpio.begin(); + powerManager.begin(); // Only start serial if USB connected if (gpio.isUsbConnected()) { @@ -333,8 +305,6 @@ void setup() { return; } - normalFreq = getCpuFrequencyMhz(); - SETTINGS.loadFromFile(); KOREADER_STORE.loadFromFile(); UITheme::getInstance().reload(); @@ -349,7 +319,7 @@ void setup() { case HalGPIO::WakeupReason::AfterUSBPower: // If USB power caused a cold boot, go back to sleep Serial.printf("[%lu] [ ] Wakeup reason: After USB Power\n", millis()); - gpio.startDeepSleep(); + powerManager.startDeepSleep(gpio); break; case HalGPIO::WakeupReason::AfterFlash: // After flashing, just proceed to boot @@ -405,8 +375,8 @@ void loop() { // Check for any user activity (button press or release) or active background work static unsigned long lastActivityTime = millis(); if (gpio.wasAnyPressed() || gpio.wasAnyReleased() || (currentActivity && currentActivity->preventAutoSleep())) { - lastActivityTime = millis(); // Reset inactivity timer - HalPowerManager::setCpuFrequency(false); // Set normal CPU frequency on user activity + lastActivityTime = millis(); // Reset inactivity timer + powerManager.setPowerSaving(false); // Restore normal CPU frequency on user activity } const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs(); @@ -447,7 +417,7 @@ void loop() { static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { // If we've been inactive for a while, increase the delay to save power - HalPowerManager::setCpuFrequency(true); // Lower CPU frequency after extended inactivity + powerManager.setPowerSaving(true); // Lower CPU frequency after extended inactivity delay(50); } else { // Short delay to prevent tight loop while still being responsive From 73cd05827ab62287d9f655fb9a6f19e7d062e98f Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Thu, 12 Feb 2026 13:19:37 +0100 Subject: [PATCH 8/8] move IDLE_POWER_SAVING_MS --- lib/hal/HalPowerManager.cpp | 7 ++++--- lib/hal/HalPowerManager.h | 9 +++++---- src/main.cpp | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/hal/HalPowerManager.cpp b/lib/hal/HalPowerManager.cpp index 5c945f59..745579a3 100644 --- a/lib/hal/HalPowerManager.cpp +++ b/lib/hal/HalPowerManager.cpp @@ -1,6 +1,7 @@ +#include "HalPowerManager.h" + #include -#include "HalPowerManager.h" #include "HalGPIO.h" void HalPowerManager::begin() { @@ -10,7 +11,7 @@ void HalPowerManager::begin() { void HalPowerManager::setPowerSaving(bool enabled) { if (normalFreq <= 0) { - return; // invalid state + return; // invalid state } if (enabled && !isLowPower) { Serial.printf("[%lu] [PWR] Going to low-power mode\n", millis()); @@ -29,7 +30,7 @@ void HalPowerManager::setPowerSaving(bool enabled) { isLowPower = enabled; } -void HalPowerManager::startDeepSleep(HalGPIO &gpio) const { +void HalPowerManager::startDeepSleep(HalGPIO& gpio) const { // Ensure that the power button has been released to avoid immediately turning back on if you're holding it while (gpio.isPressed(HalGPIO::BTN_POWER)) { delay(50); diff --git a/lib/hal/HalPowerManager.h b/lib/hal/HalPowerManager.h index 792bb449..636b2c97 100644 --- a/lib/hal/HalPowerManager.h +++ b/lib/hal/HalPowerManager.h @@ -7,19 +7,20 @@ #include "HalGPIO.h" class HalPowerManager { - static constexpr int LOW_POWER_FREQ = 10; // MHz - - int normalFreq = 0; // MHz + int normalFreq = 0; // MHz bool isLowPower = false; public: + static constexpr int LOW_POWER_FREQ = 10; // MHz + static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // ms + void begin(); // Control CPU frequency for power saving void setPowerSaving(bool enabled); // Setup wake up GPIO and enter deep sleep - void startDeepSleep(HalGPIO &gpio) const; + void startDeepSleep(HalGPIO& gpio) const; // Get battery percentage (range 0-100) int getBatteryPercentage() const; diff --git a/src/main.cpp b/src/main.cpp index 85e0d1b2..d5746acb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -414,8 +414,7 @@ void loop() { if (currentActivity && currentActivity->skipLoopDelay()) { yield(); // Give FreeRTOS a chance to run tasks, but return immediately } else { - static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // 3 seconds - if (millis() - lastActivityTime >= IDLE_POWER_SAVING_MS) { + if (millis() - lastActivityTime >= HalPowerManager::IDLE_POWER_SAVING_MS) { // If we've been inactive for a while, increase the delay to save power powerManager.setPowerSaving(true); // Lower CPU frequency after extended inactivity delay(50);