diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index dce045a21..41cfb718d 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -7,7 +7,22 @@ HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_R HalDisplay::~HalDisplay() {} -void HalDisplay::begin() { einkDisplay.begin(); } +void HalDisplay::begin() { + // Set X3-specific display dimensions before initializing + if (gpio.deviceIsX3()) { + einkDisplay.setDisplayDimensions(792, 528); + } + + einkDisplay.begin(); + + // Request resync after specific wakeup events to ensure clean display state + const auto wakeupReason = gpio.getWakeupReason(); + if (wakeupReason == HalGPIO::WakeupReason::PowerButton || + wakeupReason == HalGPIO::WakeupReason::AfterFlash || + wakeupReason == HalGPIO::WakeupReason::Other) { + einkDisplay.requestResync(); + } +} void HalDisplay::setDisplayDimensions(uint16_t width, uint16_t height) { einkDisplay.setDisplayDimensions(width, height); diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp index 3672643ed..279c6d277 100644 --- a/lib/hal/HalGPIO.cpp +++ b/lib/hal/HalGPIO.cpp @@ -3,6 +3,9 @@ #include #include +// Global HalGPIO instance +HalGPIO gpio; + void HalGPIO::begin() { inputMgr.begin(); SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS); @@ -21,9 +24,9 @@ void HalGPIO::begin() { // reconfigures the pin for I2C, so it must run last. if (_deviceType == DeviceType::X3) { Wire.begin(20, 0, 400000); - _useI2C = true; - _i2cAddr = 0x55; - _socRegister = 0x2C; + _batteryUseI2C = true; + _batteryI2cAddr = 0x55; + _batterySocRegister = 0x2C; } } @@ -53,14 +56,60 @@ void HalGPIO::startDeepSleep() { esp_deep_sleep_start(); } +void HalGPIO::verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed) { + if (shortPressAllowed) { + // Fast path - no duration check needed + return; + } + + // Calibrate: subtract boot time already elapsed, assuming button held since boot + const uint16_t calibration = millis(); + const uint16_t calibratedDuration = (calibration < requiredDurationMs) ? (requiredDurationMs - calibration) : 1; + + if (deviceIsX3()) { + // X3: Direct GPIO read (inputMgr not yet reliable at this point) + const uint8_t powerPin = InputManager::POWER_BUTTON_PIN; + if (digitalRead(powerPin) != LOW) { + startDeepSleep(); + } + const unsigned long holdStart = millis(); + while (millis() - holdStart < calibratedDuration) { + if (digitalRead(powerPin) != LOW) { + startDeepSleep(); + } + delay(5); + } + } else { + // X4: Use inputMgr with wait window for it to stabilize + const auto start = millis(); + inputMgr.update(); + // inputMgr.isPressed() may take up to ~500ms to return correct state + while (!inputMgr.isPressed(BTN_POWER) && millis() - start < 1000) { + delay(10); + inputMgr.update(); + } + if (inputMgr.isPressed(BTN_POWER)) { + do { + delay(10); + inputMgr.update(); + } while (inputMgr.isPressed(BTN_POWER) && inputMgr.getHeldTime() < calibratedDuration); + if (inputMgr.getHeldTime() < calibratedDuration) { + startDeepSleep(); + } + } else { + startDeepSleep(); + } + } +} + int HalGPIO::getBatteryPercentage() const { - if (_useI2C) { + if (_batteryUseI2C) { // Read SOC directly from I2C fuel gauge (16-bit LE register). // Returns 0 on I2C error so the UI shows 0% rather than crashing. - Wire.beginTransmission(_i2cAddr); - Wire.write(_socRegister); + Wire.beginTransmission(_batteryI2cAddr); + Wire.write(_batterySocRegister); if (Wire.endTransmission(false) != 0) return 0; - Wire.requestFrom(_i2cAddr, (uint8_t)2); + Wire.requestFrom(_batteryI2cAddr, (uint8_t)2); if (Wire.available() < 2) return 0; const uint8_t lo = Wire.read(); const uint8_t hi = Wire.read(); diff --git a/lib/hal/HalGPIO.h b/lib/hal/HalGPIO.h index 052ea5893..d00cfd94f 100644 --- a/lib/hal/HalGPIO.h +++ b/lib/hal/HalGPIO.h @@ -30,13 +30,18 @@ class HalGPIO { DeviceType _deviceType = DeviceType::X4; int _detectAdcValue = 0; int _batteryPin = BAT_GPIO0; - bool _useI2C = false; - uint8_t _i2cAddr = 0; - uint8_t _socRegister = 0; + // I2C fuel gauge configuration for X3 battery monitoring + bool _batteryUseI2C = false; // Whether to use I2C fuel gauge (X3) vs ADC (X4) + uint8_t _batteryI2cAddr = 0; // I2C address of fuel gauge chip + uint8_t _batterySocRegister = 0; // Register address for state-of-charge public: HalGPIO() = default; + // Inline device type helpers for cleaner downstream checks + inline bool deviceIsX3() const { return _deviceType == DeviceType::X3; } + inline bool deviceIsX4() const { return _deviceType == DeviceType::X4; } + // Start button GPIO and setup SPI for screen and SD card void begin(); @@ -52,6 +57,11 @@ class HalGPIO { // Setup wake up GPIO and enter deep sleep void startDeepSleep(); + // Verify power button was held long enough after wakeup. + // If verification fails, enters deep sleep and does not return. + // Should only be called when wakeup reason is PowerButton. + void verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed); + // Get battery percentage (range 0-100) int getBatteryPercentage() const; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index b2dd23e29..037e65bd2 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -683,7 +683,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or pagesUntilFullRefresh--; } - const bool useGrayscaleAA = SETTINGS.textAntiAliasing && gpio.getDeviceType() != HalGPIO::DeviceType::X3; + const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !gpio.deviceIsX3(); if (useGrayscaleAA) { // Save BW buffer only when we actually run grayscale passes. renderer.storeBwBuffer(); diff --git a/src/main.cpp b/src/main.cpp index 72175734b..fbfeaee6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,6 @@ #include "util/ButtonNavigator.h" HalDisplay display; -HalGPIO gpio; MappedInputManager mappedInputManager(gpio); GfxRenderer renderer(display); Activity* currentActivity; @@ -124,10 +123,6 @@ EpdFont ui12RegularFont(&ubuntu_12_regular); EpdFont ui12BoldFont(&ubuntu_12_bold); EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); -// measurement of power button press duration calibration value -unsigned long t1 = 0; -unsigned long t2 = 0; - void exitActivity() { if (currentActivity) { currentActivity->onExit(); @@ -141,50 +136,6 @@ void enterNewActivity(Activity* activity) { currentActivity->onEnter(); } -// Verify power button press duration on wake-up from deep sleep -// Pre-condition: isWakeupByPowerButton() == true -void verifyPowerButtonDuration() { - if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) { - // Fast path for short press - // Needed because inputManager.isPressed() may take up to ~500ms to return the correct state - return; - } - - // Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration() - const auto start = millis(); - bool abort = false; - // Subtract the current time, because inputManager only starts counting the HeldTime from the first update() - // This way, we remove the time we already took to reach here from the duration, - // assuming the button was held until now from millis()==0 (i.e. device start time). - const uint16_t calibration = start; - const uint16_t calibratedPressDuration = - (calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1; - - gpio.update(); - // Needed because inputManager.isPressed() may take up to ~500ms to return the correct state - while (!gpio.isPressed(HalGPIO::BTN_POWER) && millis() - start < 1000) { - delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration. - gpio.update(); - } - - t2 = millis(); - if (gpio.isPressed(HalGPIO::BTN_POWER)) { - do { - delay(10); - gpio.update(); - } while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() < calibratedPressDuration); - abort = gpio.getHeldTime() < calibratedPressDuration; - } else { - abort = true; - } - - if (abort) { - // Button released too early. Returning to sleep. - // IMPORTANT: Re-arm the wakeup trigger before sleeping again - gpio.startDeepSleep(); - } -} - void waitForPowerRelease() { gpio.update(); while (gpio.isPressed(HalGPIO::BTN_POWER)) { @@ -193,25 +144,6 @@ void waitForPowerRelease() { } } -bool verifyPowerButtonDurationX3() { - // Match X4 semantics: require only the *remaining* hold time after boot. - // This avoids extra wake-delay windows and sleep/retry loops. - const uint16_t requiredHoldMs = SETTINGS.getPowerButtonDuration(); - const uint16_t calibration = millis(); - const uint16_t calibratedPressDuration = (calibration < requiredHoldMs) ? (requiredHoldMs - calibration) : 1; - const uint8_t powerPin = InputManager::POWER_BUTTON_PIN; - - // Wake is already caused by power button; require it to still be held now. - if (digitalRead(powerPin) != LOW) return false; - - const unsigned long holdStart = millis(); - while (millis() - holdStart < calibratedPressDuration) { - if (digitalRead(powerPin) != LOW) return false; - delay(5); - } - return true; -} - // Enter deep sleep mode void enterDeepSleep() { APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity(); @@ -221,7 +153,6 @@ void enterDeepSleep() { enterNewActivity(new SleepActivity(renderer, mappedInputManager)); display.deepSleep(); - LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1); LOG_DBG("MAIN", "Entering deep sleep"); gpio.startDeepSleep(); @@ -277,9 +208,6 @@ void onGoHome() { } void setupDisplayAndFonts() { - if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) { - display.setDisplayDimensions(792, 528); - } display.begin(); renderer.begin(); LOG_DBG("MAIN", "Display initialized"); @@ -305,8 +233,6 @@ void setupDisplayAndFonts() { } void setup() { - t1 = millis(); - gpio.begin(); // Only start serial if USB connected @@ -337,15 +263,9 @@ void setup() { const auto wakeupReason = gpio.getWakeupReason(); switch (wakeupReason) { case HalGPIO::WakeupReason::PowerButton: - if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) { - LOG_DBG("MAIN", "Verifying power button press duration (X3)"); - if (!verifyPowerButtonDurationX3()) { - gpio.startDeepSleep(); - } - } else { - LOG_DBG("MAIN", "Verifying power button press duration"); - verifyPowerButtonDuration(); - } + LOG_DBG("MAIN", "Verifying power button press duration"); + gpio.verifyPowerButtonWakeup(SETTINGS.getPowerButtonDuration(), + SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP); break; case HalGPIO::WakeupReason::AfterUSBPower: // If USB power caused a cold boot, go back to sleep @@ -363,10 +283,6 @@ void setup() { LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); setupDisplayAndFonts(); - if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash || - wakeupReason == HalGPIO::WakeupReason::Other) { - display.requestResync(); - } exitActivity(); enterNewActivity(new BootActivity(renderer, mappedInputManager));