Updates to X3-specific display and power button handling
Centralize display initialization and power button verification for X3 devices
This commit is contained in:
+16
-1
@@ -7,7 +7,22 @@ HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_R
|
|||||||
|
|
||||||
HalDisplay::~HalDisplay() {}
|
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) {
|
void HalDisplay::setDisplayDimensions(uint16_t width, uint16_t height) {
|
||||||
einkDisplay.setDisplayDimensions(width, height);
|
einkDisplay.setDisplayDimensions(width, height);
|
||||||
|
|||||||
+56
-7
@@ -3,6 +3,9 @@
|
|||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <esp_sleep.h>
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
|
// Global HalGPIO instance
|
||||||
|
HalGPIO gpio;
|
||||||
|
|
||||||
void HalGPIO::begin() {
|
void HalGPIO::begin() {
|
||||||
inputMgr.begin();
|
inputMgr.begin();
|
||||||
SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS);
|
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.
|
// reconfigures the pin for I2C, so it must run last.
|
||||||
if (_deviceType == DeviceType::X3) {
|
if (_deviceType == DeviceType::X3) {
|
||||||
Wire.begin(20, 0, 400000);
|
Wire.begin(20, 0, 400000);
|
||||||
_useI2C = true;
|
_batteryUseI2C = true;
|
||||||
_i2cAddr = 0x55;
|
_batteryI2cAddr = 0x55;
|
||||||
_socRegister = 0x2C;
|
_batterySocRegister = 0x2C;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,14 +56,60 @@ void HalGPIO::startDeepSleep() {
|
|||||||
esp_deep_sleep_start();
|
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 {
|
int HalGPIO::getBatteryPercentage() const {
|
||||||
if (_useI2C) {
|
if (_batteryUseI2C) {
|
||||||
// Read SOC directly from I2C fuel gauge (16-bit LE register).
|
// Read SOC directly from I2C fuel gauge (16-bit LE register).
|
||||||
// Returns 0 on I2C error so the UI shows 0% rather than crashing.
|
// Returns 0 on I2C error so the UI shows 0% rather than crashing.
|
||||||
Wire.beginTransmission(_i2cAddr);
|
Wire.beginTransmission(_batteryI2cAddr);
|
||||||
Wire.write(_socRegister);
|
Wire.write(_batterySocRegister);
|
||||||
if (Wire.endTransmission(false) != 0) return 0;
|
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;
|
if (Wire.available() < 2) return 0;
|
||||||
const uint8_t lo = Wire.read();
|
const uint8_t lo = Wire.read();
|
||||||
const uint8_t hi = Wire.read();
|
const uint8_t hi = Wire.read();
|
||||||
|
|||||||
+13
-3
@@ -30,13 +30,18 @@ class HalGPIO {
|
|||||||
DeviceType _deviceType = DeviceType::X4;
|
DeviceType _deviceType = DeviceType::X4;
|
||||||
int _detectAdcValue = 0;
|
int _detectAdcValue = 0;
|
||||||
int _batteryPin = BAT_GPIO0;
|
int _batteryPin = BAT_GPIO0;
|
||||||
bool _useI2C = false;
|
// I2C fuel gauge configuration for X3 battery monitoring
|
||||||
uint8_t _i2cAddr = 0;
|
bool _batteryUseI2C = false; // Whether to use I2C fuel gauge (X3) vs ADC (X4)
|
||||||
uint8_t _socRegister = 0;
|
uint8_t _batteryI2cAddr = 0; // I2C address of fuel gauge chip
|
||||||
|
uint8_t _batterySocRegister = 0; // Register address for state-of-charge
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HalGPIO() = default;
|
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
|
// Start button GPIO and setup SPI for screen and SD card
|
||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
@@ -52,6 +57,11 @@ class HalGPIO {
|
|||||||
// Setup wake up GPIO and enter deep sleep
|
// Setup wake up GPIO and enter deep sleep
|
||||||
void startDeepSleep();
|
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)
|
// Get battery percentage (range 0-100)
|
||||||
int getBatteryPercentage() const;
|
int getBatteryPercentage() const;
|
||||||
|
|
||||||
|
|||||||
@@ -683,7 +683,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && gpio.getDeviceType() != HalGPIO::DeviceType::X3;
|
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !gpio.deviceIsX3();
|
||||||
if (useGrayscaleAA) {
|
if (useGrayscaleAA) {
|
||||||
// Save BW buffer only when we actually run grayscale passes.
|
// Save BW buffer only when we actually run grayscale passes.
|
||||||
renderer.storeBwBuffer();
|
renderer.storeBwBuffer();
|
||||||
|
|||||||
+3
-87
@@ -30,7 +30,6 @@
|
|||||||
#include "util/ButtonNavigator.h"
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
HalDisplay display;
|
HalDisplay display;
|
||||||
HalGPIO gpio;
|
|
||||||
MappedInputManager mappedInputManager(gpio);
|
MappedInputManager mappedInputManager(gpio);
|
||||||
GfxRenderer renderer(display);
|
GfxRenderer renderer(display);
|
||||||
Activity* currentActivity;
|
Activity* currentActivity;
|
||||||
@@ -124,10 +123,6 @@ EpdFont ui12RegularFont(&ubuntu_12_regular);
|
|||||||
EpdFont ui12BoldFont(&ubuntu_12_bold);
|
EpdFont ui12BoldFont(&ubuntu_12_bold);
|
||||||
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
||||||
|
|
||||||
// measurement of power button press duration calibration value
|
|
||||||
unsigned long t1 = 0;
|
|
||||||
unsigned long t2 = 0;
|
|
||||||
|
|
||||||
void exitActivity() {
|
void exitActivity() {
|
||||||
if (currentActivity) {
|
if (currentActivity) {
|
||||||
currentActivity->onExit();
|
currentActivity->onExit();
|
||||||
@@ -141,50 +136,6 @@ void enterNewActivity(Activity* activity) {
|
|||||||
currentActivity->onEnter();
|
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() {
|
void waitForPowerRelease() {
|
||||||
gpio.update();
|
gpio.update();
|
||||||
while (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
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
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity();
|
APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity();
|
||||||
@@ -221,7 +153,6 @@ void enterDeepSleep() {
|
|||||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
display.deepSleep();
|
display.deepSleep();
|
||||||
LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1);
|
|
||||||
LOG_DBG("MAIN", "Entering deep sleep");
|
LOG_DBG("MAIN", "Entering deep sleep");
|
||||||
|
|
||||||
gpio.startDeepSleep();
|
gpio.startDeepSleep();
|
||||||
@@ -277,9 +208,6 @@ void onGoHome() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setupDisplayAndFonts() {
|
void setupDisplayAndFonts() {
|
||||||
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
|
|
||||||
display.setDisplayDimensions(792, 528);
|
|
||||||
}
|
|
||||||
display.begin();
|
display.begin();
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
LOG_DBG("MAIN", "Display initialized");
|
LOG_DBG("MAIN", "Display initialized");
|
||||||
@@ -305,8 +233,6 @@ void setupDisplayAndFonts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
t1 = millis();
|
|
||||||
|
|
||||||
gpio.begin();
|
gpio.begin();
|
||||||
|
|
||||||
// Only start serial if USB connected
|
// Only start serial if USB connected
|
||||||
@@ -337,15 +263,9 @@ void setup() {
|
|||||||
const auto wakeupReason = gpio.getWakeupReason();
|
const auto wakeupReason = gpio.getWakeupReason();
|
||||||
switch (wakeupReason) {
|
switch (wakeupReason) {
|
||||||
case HalGPIO::WakeupReason::PowerButton:
|
case HalGPIO::WakeupReason::PowerButton:
|
||||||
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
|
LOG_DBG("MAIN", "Verifying power button press duration");
|
||||||
LOG_DBG("MAIN", "Verifying power button press duration (X3)");
|
gpio.verifyPowerButtonWakeup(SETTINGS.getPowerButtonDuration(),
|
||||||
if (!verifyPowerButtonDurationX3()) {
|
SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP);
|
||||||
gpio.startDeepSleep();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_DBG("MAIN", "Verifying power button press duration");
|
|
||||||
verifyPowerButtonDuration();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case HalGPIO::WakeupReason::AfterUSBPower:
|
case HalGPIO::WakeupReason::AfterUSBPower:
|
||||||
// If USB power caused a cold boot, go back to sleep
|
// 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);
|
LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION);
|
||||||
|
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
|
|
||||||
wakeupReason == HalGPIO::WakeupReason::Other) {
|
|
||||||
display.requestResync();
|
|
||||||
}
|
|
||||||
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
||||||
|
|||||||
Reference in New Issue
Block a user