diff --git a/.gitignore b/.gitignore index a19a9b74a..1eec5bb6b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ build **/__pycache__/ /compile_commands.json /.cache -notes.md +/notes.md diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp index 2a39dee98..3672643ed 100644 --- a/lib/hal/HalGPIO.cpp +++ b/lib/hal/HalGPIO.cpp @@ -1,5 +1,6 @@ #include #include +#include #include void HalGPIO::begin() { @@ -14,6 +15,16 @@ void HalGPIO::begin() { pinMode(_batteryPin, INPUT); pinMode(UART0_RXD, INPUT); + + // I2C init must come AFTER pinMode(UART0_RXD) because GPIO20 is shared + // between USB detection (digital read) and I2C SDA. Wire.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; + } } void HalGPIO::update() { inputMgr.update(); } @@ -43,8 +54,18 @@ void HalGPIO::startDeepSleep() { } int HalGPIO::getBatteryPercentage() const { - if (_deviceType == DeviceType::X3) { - return 0; + if (_useI2C) { + // 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); + if (Wire.endTransmission(false) != 0) return 0; + Wire.requestFrom(_i2cAddr, (uint8_t)2); + if (Wire.available() < 2) return 0; + const uint8_t lo = Wire.read(); + const uint8_t hi = Wire.read(); + const uint16_t soc = (hi << 8) | lo; + return soc > 100 ? 100 : soc; } static const BatteryMonitor bat(BAT_GPIO0); return bat.readPercentage(); diff --git a/lib/hal/HalGPIO.h b/lib/hal/HalGPIO.h index 75914d268..052ea5893 100644 --- a/lib/hal/HalGPIO.h +++ b/lib/hal/HalGPIO.h @@ -30,6 +30,9 @@ class HalGPIO { DeviceType _deviceType = DeviceType::X4; int _detectAdcValue = 0; int _batteryPin = BAT_GPIO0; + bool _useI2C = false; + uint8_t _i2cAddr = 0; + uint8_t _socRegister = 0; public: HalGPIO() = default; @@ -73,3 +76,5 @@ class HalGPIO { static constexpr uint8_t BTN_DOWN = 5; static constexpr uint8_t BTN_POWER = 6; }; + +extern HalGPIO gpio; diff --git a/platformio.ini b/platformio.ini index 281eab178..8229f074b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,10 +41,12 @@ extra_scripts = ; Libraries lib_deps = - BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor - InputManager=symlink://open-x4-sdk/libs/hardware/InputManager - EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay - SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager + ; Use standalone community-sdk checkout (sibling folder) instead of submodule. + ; This makes local SDK edits in ../community-sdk immediately used by crosspoint-reader. + BatteryMonitor=symlink://../community-sdk/libs/hardware/BatteryMonitor + InputManager=symlink://../community-sdk/libs/hardware/InputManager + EInkDisplay=symlink://../community-sdk/libs/display/EInkDisplay + SDCardManager=symlink://../community-sdk/libs/hardware/SDCardManager bblanchon/ArduinoJson @ 7.4.2 ricmoo/QRCode @ 0.0.1 links2004/WebSockets @ 2.7.3 diff --git a/src/Battery.cpp b/src/Battery.cpp deleted file mode 100644 index e68a95d29..000000000 --- a/src/Battery.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Battery.h" -#include - -void BatteryProvider::setI2CFuelGauge(uint8_t i2cAddr, uint8_t socRegister) { - _useI2C = true; - _i2cAddr = i2cAddr; - _socRegister = socRegister; -} - -uint16_t BatteryProvider::readPercentage() const { - if (_useI2C) { - // 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); - if (Wire.endTransmission(false) != 0) return 0; - Wire.requestFrom(_i2cAddr, (uint8_t)2); - if (Wire.available() < 2) return 0; - const uint8_t lo = Wire.read(); - const uint8_t hi = Wire.read(); - const uint16_t soc = (hi << 8) | lo; - return soc > 100 ? 100 : soc; - } - // ADC path: read raw voltage, apply divider, convert via LiPo polynomial - return _adcMonitor.readPercentage(); -} - -// Meyer's singleton — single shared instance across all translation units. -// Defaults to X4 ADC mode. For X3, main.cpp calls setI2CFuelGauge() to switch. -BatteryProvider& battery() { - static BatteryProvider instance; - return instance; -} diff --git a/src/Battery.h b/src/Battery.h deleted file mode 100644 index ac23c3831..000000000 --- a/src/Battery.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include -#include - -#define BAT_GPIO0 0 // Battery voltage (X4 ADC pin) - -// Unified battery reader supporting two backends: -// - X4: ADC voltage divider on GPIO0 (default, no setup needed) -// - X3: BQ27220 fuel gauge via I2C at 0x55, SOC register 0x2C -// (call setI2CFuelGauge() after Wire.begin()) -class BatteryProvider { - public: - // Read battery percentage (0-100). Delegates to ADC or I2C depending on mode. - uint16_t readPercentage() const; - - // Switch to I2C fuel gauge mode. Wire.begin() must be called first. - // i2cAddr: fuel gauge I2C address (e.g. 0x55 for BQ27220) - // socRegister: register holding state-of-charge 0-100% (e.g. 0x2C) - void setI2CFuelGauge(uint8_t i2cAddr, uint8_t socRegister); - - private: - BatteryMonitor _adcMonitor{BAT_GPIO0}; - bool _useI2C = false; - uint8_t _i2cAddr = 0; - uint8_t _socRegister = 0; -}; - -// Shared singleton used by themes and activities. -BatteryProvider& battery(); diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 5ae4ea5d6..a634b684b 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -10,7 +10,6 @@ #include #include -#include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" #include "MappedInputManager.h" diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index e0243f3c1..b2dd23e29 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -34,12 +35,6 @@ int clampPercent(int percent) { return percent; } -bool isX3DisplayGeometry(const GfxRenderer& renderer) { - const int w = renderer.getScreenWidth(); - const int h = renderer.getScreenHeight(); - return (w == 792 && h == 528) || (w == 528 && h == 792); -} - // Apply the logical reader orientation to the renderer. // This centralizes orientation mapping so we don't duplicate switch logic elsewhere. void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) { @@ -688,7 +683,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or pagesUntilFullRefresh--; } - const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !isX3DisplayGeometry(renderer); + const bool useGrayscaleAA = SETTINGS.textAntiAliasing && gpio.getDeviceType() != HalGPIO::DeviceType::X3; if (useGrayscaleAA) { // Save BW buffer only when we actually run grayscale passes. renderer.storeBwBuffer(); diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 802ed8b5d..9686f8818 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -8,7 +8,7 @@ #include #include -#include "Battery.h" +#include #include "RecentBooksStore.h" #include "components/UITheme.h" #include "fontIds.h" @@ -23,7 +23,7 @@ constexpr int homeMarginTop = 30; void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { // Left aligned battery icon and percentage // TODO refactor this so the percentage doesnt change after we position it - const uint16_t percentage = battery().readPercentage(); + const uint16_t percentage = gpio.getBatteryPercentage(); if (showPercentage) { const auto percentageText = std::to_string(percentage) + "%"; renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y, @@ -233,7 +233,7 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth; if (showBatteryPercentage) { - const uint16_t percentage = battery().readPercentage(); + const uint16_t percentage = gpio.getBatteryPercentage(); const auto percentageText = std::to_string(percentage) + "%"; batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); } diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 48482e1a0..c7f55307c 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -6,7 +6,7 @@ #include #include -#include "Battery.h" +#include #include "RecentBooksStore.h" #include "components/UITheme.h" #include "fontIds.h" @@ -22,7 +22,7 @@ constexpr int topHintButtonY = 345; void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { // Left aligned battery icon and percentage - const uint16_t percentage = battery().readPercentage(); + const uint16_t percentage = gpio.getBatteryPercentage(); if (showPercentage) { const auto percentageText = std::to_string(percentage) + "%"; renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + LyraMetrics::values.batteryWidth, rect.y, @@ -64,7 +64,7 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth; if (showBatteryPercentage) { - const uint16_t percentage = battery().readPercentage(); + const uint16_t percentage = gpio.getBatteryPercentage(); const auto percentageText = std::to_string(percentage) + "%"; batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); } diff --git a/src/main.cpp b/src/main.cpp index d71e051fa..7335b27dc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,12 +6,9 @@ #include #include #include -#include #include #include - -#include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" #include "KOReaderCredentialStore.h" @@ -130,12 +127,6 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); unsigned long t1 = 0; unsigned long t2 = 0; -inline void requestResyncIfX3(uint8_t settlePasses = 0) { - if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) { - display.requestResync(settlePasses); - } -} - void exitActivity() { if (currentActivity) { currentActivity->onExit(); @@ -243,7 +234,7 @@ void enterDeepSleep() { APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity(); APP_STATE.saveToFile(); exitActivity(); - requestResyncIfX3(0); + display.requestResync(); enterNewActivity(new SleepActivity(renderer, mappedInputManager)); display.deepSleep(); @@ -294,9 +285,7 @@ void onGoToBrowser() { void onGoHome() { const bool returningFromReader = currentActivity && currentActivity->isReaderActivity(); - if (returningFromReader && (gpio.getDeviceType() == HalGPIO::DeviceType::X3)) { - // Force Home's first frame to run a full resync on X3. - // Avoid doing a blocking scrub refresh before activity transition. + if (returningFromReader) { display.requestResync(1); } exitActivity(); @@ -307,11 +296,6 @@ void onGoHome() { void setupDisplayAndFonts() { if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) { display.setDisplayDimensions(792, 528); - // X3 has a BQ27220 fuel gauge on I2C (addr 0x55) instead of an ADC voltage - // divider. SOC (0-100%) is read directly from register 0x2C. - // I2C bus: SDA=GPIO20, SCL=GPIO0, 400kHz (matches stock X3 firmware). - Wire.begin(20, 0, 400000); - battery().setI2CFuelGauge(0x55, 0x2C); } display.begin(); renderer.begin(); @@ -401,7 +385,7 @@ void setup() { setupDisplayAndFonts(); if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash || wakeupReason == HalGPIO::WakeupReason::Other) { - requestResyncIfX3(0); + display.requestResync(); } exitActivity();