Compare commits
5
Commits
1.1.1
...
d97a436a48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97a436a48 | ||
|
|
6114b80e5d | ||
|
|
d4eb089e49 | ||
|
|
9a1548189c | ||
|
|
cbea838f91 |
@@ -9,3 +9,4 @@ build
|
|||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
/compile_commands.json
|
/compile_commands.json
|
||||||
/.cache
|
/.cache
|
||||||
|
notes.md
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ void GfxRenderer::begin() {
|
|||||||
Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis());
|
Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis());
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
panelWidth = display.getDisplayWidth();
|
||||||
|
panelHeight = display.getDisplayHeight();
|
||||||
|
panelWidthBytes = display.getDisplayWidthBytes();
|
||||||
|
frameBufferSize = display.getBufferSize();
|
||||||
|
bwBufferChunks.assign((frameBufferSize + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||||
@@ -15,25 +20,25 @@ void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.ins
|
|||||||
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
||||||
// This should always be inlined for better performance
|
// This should always be inlined for better performance
|
||||||
static inline void rotateCoordinates(const GfxRenderer::Orientation orientation, const int x, const int y, int* phyX,
|
static inline void rotateCoordinates(const GfxRenderer::Orientation orientation, const int x, const int y, int* phyX,
|
||||||
int* phyY) {
|
int* phyY, const uint16_t panelWidth, const uint16_t panelHeight) {
|
||||||
switch (orientation) {
|
switch (orientation) {
|
||||||
case GfxRenderer::Portrait: {
|
case GfxRenderer::Portrait: {
|
||||||
// Logical portrait (480x800) → panel (800x480)
|
// Logical portrait (480x800) → panel (800x480)
|
||||||
// Rotation: 90 degrees clockwise
|
// Rotation: 90 degrees clockwise
|
||||||
*phyX = y;
|
*phyX = y;
|
||||||
*phyY = HalDisplay::DISPLAY_HEIGHT - 1 - x;
|
*phyY = panelHeight - 1 - x;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GfxRenderer::LandscapeClockwise: {
|
case GfxRenderer::LandscapeClockwise: {
|
||||||
// Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right)
|
// Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right)
|
||||||
*phyX = HalDisplay::DISPLAY_WIDTH - 1 - x;
|
*phyX = panelWidth - 1 - x;
|
||||||
*phyY = HalDisplay::DISPLAY_HEIGHT - 1 - y;
|
*phyY = panelHeight - 1 - y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GfxRenderer::PortraitInverted: {
|
case GfxRenderer::PortraitInverted: {
|
||||||
// Logical portrait (480x800) → panel (800x480)
|
// Logical portrait (480x800) → panel (800x480)
|
||||||
// Rotation: 90 degrees counter-clockwise
|
// Rotation: 90 degrees counter-clockwise
|
||||||
*phyX = HalDisplay::DISPLAY_WIDTH - 1 - y;
|
*phyX = panelWidth - 1 - y;
|
||||||
*phyY = x;
|
*phyY = x;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -53,16 +58,16 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|||||||
int phyY = 0;
|
int phyY = 0;
|
||||||
|
|
||||||
// Note: this call should be inlined for better performance
|
// Note: this call should be inlined for better performance
|
||||||
rotateCoordinates(orientation, x, y, &phyX, &phyY);
|
rotateCoordinates(orientation, x, y, &phyX, &phyY, panelWidth, panelHeight);
|
||||||
|
|
||||||
// Bounds checking against physical panel dimensions
|
// Bounds checking against physical panel dimensions
|
||||||
if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) {
|
if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) {
|
||||||
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, phyX, phyY);
|
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, phyX, phyY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate byte position and bit position
|
// Calculate byte position and bit position
|
||||||
const uint16_t byteIndex = phyY * HalDisplay::DISPLAY_WIDTH_BYTES + (phyX / 8);
|
const uint32_t byteIndex = static_cast<uint32_t>(phyY) * panelWidthBytes + (phyX / 8);
|
||||||
const uint8_t bitPosition = 7 - (phyX % 8); // MSB first
|
const uint8_t bitPosition = 7 - (phyX % 8); // MSB first
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
@@ -383,7 +388,7 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
|||||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||||
int rotatedX = 0;
|
int rotatedX = 0;
|
||||||
int rotatedY = 0;
|
int rotatedY = 0;
|
||||||
rotateCoordinates(orientation, x, y, &rotatedX, &rotatedY);
|
rotateCoordinates(orientation, x, y, &rotatedX, &rotatedY, panelWidth, panelHeight);
|
||||||
// Rotate origin corner
|
// Rotate origin corner
|
||||||
switch (orientation) {
|
switch (orientation) {
|
||||||
case Portrait:
|
case Portrait:
|
||||||
@@ -648,7 +653,7 @@ void GfxRenderer::clearScreen(const uint8_t color) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::invertScreen() const {
|
void GfxRenderer::invertScreen() const {
|
||||||
for (int i = 0; i < HalDisplay::BUFFER_SIZE; i++) {
|
for (uint32_t i = 0; i < frameBufferSize; i++) {
|
||||||
frameBuffer[i] = ~frameBuffer[i];
|
frameBuffer[i] = ~frameBuffer[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,13 +689,13 @@ int GfxRenderer::getScreenWidth() const {
|
|||||||
case Portrait:
|
case Portrait:
|
||||||
case PortraitInverted:
|
case PortraitInverted:
|
||||||
// 480px wide in portrait logical coordinates
|
// 480px wide in portrait logical coordinates
|
||||||
return HalDisplay::DISPLAY_HEIGHT;
|
return panelHeight;
|
||||||
case LandscapeClockwise:
|
case LandscapeClockwise:
|
||||||
case LandscapeCounterClockwise:
|
case LandscapeCounterClockwise:
|
||||||
// 800px wide in landscape logical coordinates
|
// 800px wide in landscape logical coordinates
|
||||||
return HalDisplay::DISPLAY_WIDTH;
|
return panelWidth;
|
||||||
}
|
}
|
||||||
return HalDisplay::DISPLAY_HEIGHT;
|
return panelHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getScreenHeight() const {
|
int GfxRenderer::getScreenHeight() const {
|
||||||
@@ -698,13 +703,13 @@ int GfxRenderer::getScreenHeight() const {
|
|||||||
case Portrait:
|
case Portrait:
|
||||||
case PortraitInverted:
|
case PortraitInverted:
|
||||||
// 800px tall in portrait logical coordinates
|
// 800px tall in portrait logical coordinates
|
||||||
return HalDisplay::DISPLAY_WIDTH;
|
return panelWidth;
|
||||||
case LandscapeClockwise:
|
case LandscapeClockwise:
|
||||||
case LandscapeCounterClockwise:
|
case LandscapeCounterClockwise:
|
||||||
// 480px tall in landscape logical coordinates
|
// 480px tall in landscape logical coordinates
|
||||||
return HalDisplay::DISPLAY_HEIGHT;
|
return panelHeight;
|
||||||
}
|
}
|
||||||
return HalDisplay::DISPLAY_WIDTH;
|
return panelWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
@@ -841,7 +846,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
|
|
||||||
uint8_t* GfxRenderer::getFrameBuffer() const { return frameBuffer; }
|
uint8_t* GfxRenderer::getFrameBuffer() const { return frameBuffer; }
|
||||||
|
|
||||||
size_t GfxRenderer::getBufferSize() { return HalDisplay::BUFFER_SIZE; }
|
size_t GfxRenderer::getBufferSize() { return EInkDisplay::MAX_BUFFER_SIZE; }
|
||||||
|
|
||||||
// unused
|
// unused
|
||||||
// void GfxRenderer::grayscaleRevert() const { display.grayscaleRevert(); }
|
// void GfxRenderer::grayscaleRevert() const { display.grayscaleRevert(); }
|
||||||
@@ -869,7 +874,7 @@ void GfxRenderer::freeBwBufferChunks() {
|
|||||||
*/
|
*/
|
||||||
bool GfxRenderer::storeBwBuffer() {
|
bool GfxRenderer::storeBwBuffer() {
|
||||||
// Allocate and copy each chunk
|
// Allocate and copy each chunk
|
||||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
for (size_t i = 0; i < bwBufferChunks.size(); i++) {
|
||||||
// Check if any chunks are already allocated
|
// Check if any chunks are already allocated
|
||||||
if (bwBufferChunks[i]) {
|
if (bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n",
|
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n",
|
||||||
@@ -879,20 +884,20 @@ bool GfxRenderer::storeBwBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||||
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
const size_t chunkSize = std::min(BW_BUFFER_CHUNK_SIZE, static_cast<size_t>(frameBufferSize - offset));
|
||||||
|
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
|
|
||||||
if (!bwBufferChunks[i]) {
|
if (!bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i,
|
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i, chunkSize);
|
||||||
BW_BUFFER_CHUNK_SIZE);
|
|
||||||
// Free previously allocated chunks
|
// Free previously allocated chunks
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
memcpy(bwBufferChunks[i], frameBuffer + offset, chunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
|
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), bwBufferChunks.size(),
|
||||||
BW_BUFFER_CHUNK_SIZE);
|
BW_BUFFER_CHUNK_SIZE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -917,7 +922,7 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
for (size_t i = 0; i < bwBufferChunks.size(); i++) {
|
||||||
// Check if chunk is missing
|
// Check if chunk is missing
|
||||||
if (!bwBufferChunks[i]) {
|
if (!bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
|
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
|
||||||
@@ -926,7 +931,8 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||||
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
const size_t chunkSize = std::min(BW_BUFFER_CHUNK_SIZE, static_cast<size_t>(frameBufferSize - offset));
|
||||||
|
memcpy(frameBuffer + offset, bwBufferChunks[i], chunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
display.cleanupGrayscaleBuffers(frameBuffer);
|
display.cleanupGrayscaleBuffers(frameBuffer);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <HalDisplay.h>
|
#include <HalDisplay.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Bitmap.h"
|
#include "Bitmap.h"
|
||||||
|
|
||||||
@@ -25,16 +26,17 @@ class GfxRenderer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = HalDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
|
||||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == HalDisplay::BUFFER_SIZE,
|
|
||||||
"BW buffer chunking does not line up with display buffer size");
|
|
||||||
|
|
||||||
HalDisplay& display;
|
HalDisplay& display;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
Orientation orientation;
|
Orientation orientation;
|
||||||
bool fadingFix;
|
bool fadingFix;
|
||||||
uint8_t* frameBuffer = nullptr;
|
uint8_t* frameBuffer = nullptr;
|
||||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH;
|
||||||
|
uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT;
|
||||||
|
uint16_t panelWidthBytes = HalDisplay::DISPLAY_WIDTH_BYTES;
|
||||||
|
uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE;
|
||||||
|
std::vector<uint8_t*> bwBufferChunks;
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||||
EpdFontFamily::Style style) const;
|
EpdFontFamily::Style style) const;
|
||||||
@@ -68,6 +70,7 @@ class GfxRenderer {
|
|||||||
// Screen ops
|
// Screen ops
|
||||||
int getScreenWidth() const;
|
int getScreenWidth() const;
|
||||||
int getScreenHeight() const;
|
int getScreenHeight() const;
|
||||||
|
void requestResync(uint8_t settlePasses = 0) const { display.requestResync(settlePasses); }
|
||||||
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
// void displayWindow(int x, int y, int width, int height) const;
|
// void displayWindow(int x, int y, int width, int height) const;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ HalDisplay::~HalDisplay() {}
|
|||||||
|
|
||||||
void HalDisplay::begin() { einkDisplay.begin(); }
|
void HalDisplay::begin() { einkDisplay.begin(); }
|
||||||
|
|
||||||
|
void HalDisplay::setDisplayDimensions(uint16_t width, uint16_t height) { einkDisplay.setDisplayDimensions(width, height); }
|
||||||
|
|
||||||
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
|
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||||
|
|
||||||
void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||||
@@ -36,6 +38,8 @@ void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen
|
|||||||
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
|
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HalDisplay::requestResync(uint8_t settlePasses) { einkDisplay.requestResync(settlePasses); }
|
||||||
|
|
||||||
void HalDisplay::deepSleep() { einkDisplay.deepSleep(); }
|
void HalDisplay::deepSleep() { einkDisplay.deepSleep(); }
|
||||||
|
|
||||||
uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
||||||
@@ -51,3 +55,11 @@ void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay
|
|||||||
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
|
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
|
||||||
|
|
||||||
void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }
|
void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }
|
||||||
|
|
||||||
|
uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); }
|
||||||
|
|
||||||
|
uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); }
|
||||||
|
|
||||||
|
uint16_t HalDisplay::getDisplayWidthBytes() const { return einkDisplay.getDisplayWidthBytes(); }
|
||||||
|
|
||||||
|
uint32_t HalDisplay::getBufferSize() const { return einkDisplay.getBufferSize(); }
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ class HalDisplay {
|
|||||||
// Initialize the display hardware and driver
|
// Initialize the display hardware and driver
|
||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
|
// Pre-begin display config passthroughs (used by X3 setup path)
|
||||||
|
void setDisplayDimensions(uint16_t width, uint16_t height);
|
||||||
|
|
||||||
// Display dimensions
|
// Display dimensions
|
||||||
static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH;
|
static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH;
|
||||||
static constexpr uint16_t DISPLAY_HEIGHT = EInkDisplay::DISPLAY_HEIGHT;
|
static constexpr uint16_t DISPLAY_HEIGHT = EInkDisplay::DISPLAY_HEIGHT;
|
||||||
@@ -33,6 +36,9 @@ class HalDisplay {
|
|||||||
|
|
||||||
void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
||||||
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
||||||
|
// Hint the display driver to perform a one-shot full resync on next update.
|
||||||
|
// Optional settle passes are used by X3 only.
|
||||||
|
void requestResync(uint8_t settlePasses = 0);
|
||||||
|
|
||||||
// Power management
|
// Power management
|
||||||
void deepSleep();
|
void deepSleep();
|
||||||
@@ -47,6 +53,12 @@ class HalDisplay {
|
|||||||
|
|
||||||
void displayGrayBuffer(bool turnOffScreen = false);
|
void displayGrayBuffer(bool turnOffScreen = false);
|
||||||
|
|
||||||
|
// Runtime geometry passthrough
|
||||||
|
uint16_t getDisplayWidth() const;
|
||||||
|
uint16_t getDisplayHeight() const;
|
||||||
|
uint16_t getDisplayWidthBytes() const;
|
||||||
|
uint32_t getBufferSize() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EInkDisplay einkDisplay;
|
EInkDisplay einkDisplay;
|
||||||
};
|
};
|
||||||
|
|||||||
+13
-3
@@ -5,7 +5,14 @@
|
|||||||
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);
|
||||||
pinMode(BAT_GPIO0, INPUT);
|
|
||||||
|
// X3 boards bias GPIO4 (EPD DC) around ~700 ADC counts at boot in our setup.
|
||||||
|
// X4 boards do not, and use GPIO0 for battery ADC.
|
||||||
|
_detectAdcValue = analogRead(4);
|
||||||
|
_deviceType = (_detectAdcValue > 500 && _detectAdcValue < 1200) ? DeviceType::X3 : DeviceType::X4;
|
||||||
|
_batteryPin = (_deviceType == DeviceType::X3) ? 4 : BAT_GPIO0;
|
||||||
|
|
||||||
|
pinMode(_batteryPin, INPUT);
|
||||||
pinMode(UART0_RXD, INPUT);
|
pinMode(UART0_RXD, INPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +43,11 @@ void HalGPIO::startDeepSleep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int HalGPIO::getBatteryPercentage() const {
|
int HalGPIO::getBatteryPercentage() const {
|
||||||
static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0);
|
if (_deviceType == DeviceType::X3) {
|
||||||
return battery.readPercentage();
|
return 0;
|
||||||
|
}
|
||||||
|
static const BatteryMonitor bat(BAT_GPIO0);
|
||||||
|
return bat.readPercentage();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HalGPIO::isUsbConnected() const {
|
bool HalGPIO::isUsbConnected() const {
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ class HalGPIO {
|
|||||||
InputManager inputMgr;
|
InputManager inputMgr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class DeviceType : uint8_t { X4, X3 };
|
||||||
|
|
||||||
|
private:
|
||||||
|
DeviceType _deviceType = DeviceType::X4;
|
||||||
|
int _detectAdcValue = 0;
|
||||||
|
int _batteryPin = BAT_GPIO0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HalGPIO() = default;
|
HalGPIO() = default;
|
||||||
|
|
||||||
@@ -47,6 +55,11 @@ class HalGPIO {
|
|||||||
// Check if USB is connected
|
// Check if USB is connected
|
||||||
bool isUsbConnected() const;
|
bool isUsbConnected() const;
|
||||||
|
|
||||||
|
// Device detection helpers
|
||||||
|
DeviceType getDeviceType() const { return _deviceType; }
|
||||||
|
int getDetectAdcValue() const { return _detectAdcValue; }
|
||||||
|
int getBatteryPin() const { return _batteryPin; }
|
||||||
|
|
||||||
enum class WakeupReason { PowerButton, AfterFlash, AfterUSBPower, Other };
|
enum class WakeupReason { PowerButton, AfterFlash, AfterUSBPower, Other };
|
||||||
|
|
||||||
WakeupReason getWakeupReason() const;
|
WakeupReason getWakeupReason() const;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#include "Battery.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+25
-2
@@ -1,6 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <BatteryMonitor.h>
|
#include <BatteryMonitor.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#define BAT_GPIO0 0 // Battery voltage
|
#define BAT_GPIO0 0 // Battery voltage (X4 ADC pin)
|
||||||
|
|
||||||
static BatteryMonitor battery(BAT_GPIO0);
|
// 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();
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ int clampPercent(int percent) {
|
|||||||
return 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.
|
// Apply the logical reader orientation to the renderer.
|
||||||
// This centralizes orientation mapping so we don't duplicate switch logic elsewhere.
|
// This centralizes orientation mapping so we don't duplicate switch logic elsewhere.
|
||||||
void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
|
void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
|
||||||
@@ -682,12 +688,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save bw buffer to reset buffer state after grayscale data sync
|
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !isX3DisplayGeometry(renderer);
|
||||||
renderer.storeBwBuffer();
|
if (useGrayscaleAA) {
|
||||||
|
// Save BW buffer only when we actually run grayscale passes.
|
||||||
|
renderer.storeBwBuffer();
|
||||||
|
|
||||||
// grayscale rendering
|
// grayscale rendering
|
||||||
// TODO: Only do this if font supports it
|
// TODO: Only do this if font supports it
|
||||||
if (SETTINGS.textAntiAliasing) {
|
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||||
@@ -702,10 +709,10 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
// display grayscale part
|
// display grayscale part
|
||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
renderer.setRenderMode(GfxRenderer::BW);
|
renderer.setRenderMode(GfxRenderer::BW);
|
||||||
}
|
|
||||||
|
|
||||||
// restore the bw data
|
// restore the bw data
|
||||||
renderer.restoreBwBuffer();
|
renderer.restoreBwBuffer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom,
|
void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "ReaderActivity.h"
|
#include "ReaderActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
|
||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
@@ -83,6 +84,7 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
|||||||
const auto epubPath = epub->getPath();
|
const auto epubPath = epub->getPath();
|
||||||
currentBookPath = epubPath;
|
currentBookPath = epubPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
renderer.requestResync(1);
|
||||||
enterNewActivity(new EpubReaderActivity(
|
enterNewActivity(new EpubReaderActivity(
|
||||||
renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); }));
|
renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
@@ -91,6 +93,7 @@ void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) {
|
|||||||
const auto xtcPath = xtc->getPath();
|
const auto xtcPath = xtc->getPath();
|
||||||
currentBookPath = xtcPath;
|
currentBookPath = xtcPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
renderer.requestResync(1);
|
||||||
enterNewActivity(new XtcReaderActivity(
|
enterNewActivity(new XtcReaderActivity(
|
||||||
renderer, mappedInput, std::move(xtc), [this, xtcPath] { goToLibrary(xtcPath); }, [this] { onGoBack(); }));
|
renderer, mappedInput, std::move(xtc), [this, xtcPath] { goToLibrary(xtcPath); }, [this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
@@ -99,6 +102,7 @@ void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
|||||||
const auto txtPath = txt->getPath();
|
const auto txtPath = txt->getPath();
|
||||||
currentBookPath = txtPath;
|
currentBookPath = txtPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
renderer.requestResync(1);
|
||||||
enterNewActivity(new TxtReaderActivity(
|
enterNewActivity(new TxtReaderActivity(
|
||||||
renderer, mappedInput, std::move(txt), [this, txtPath] { goToLibrary(txtPath); }, [this] { onGoBack(); }));
|
renderer, mappedInput, std::move(txt), [this, txtPath] { goToLibrary(txtPath); }, [this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ constexpr int homeMarginTop = 30;
|
|||||||
void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
||||||
// Left aligned battery icon and percentage
|
// Left aligned battery icon and percentage
|
||||||
// TODO refactor this so the percentage doesnt change after we position it
|
// TODO refactor this so the percentage doesnt change after we position it
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery().readPercentage();
|
||||||
if (showPercentage) {
|
if (showPercentage) {
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y,
|
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y,
|
||||||
@@ -232,7 +232,7 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
|||||||
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
||||||
int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth;
|
int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth;
|
||||||
if (showBatteryPercentage) {
|
if (showBatteryPercentage) {
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery().readPercentage();
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ constexpr int topHintButtonY = 345;
|
|||||||
|
|
||||||
void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
||||||
// Left aligned battery icon and percentage
|
// Left aligned battery icon and percentage
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery().readPercentage();
|
||||||
if (showPercentage) {
|
if (showPercentage) {
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + LyraMetrics::values.batteryWidth, rect.y,
|
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;
|
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
||||||
int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth;
|
int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth;
|
||||||
if (showBatteryPercentage) {
|
if (showBatteryPercentage) {
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery().readPercentage();
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-4
@@ -5,6 +5,7 @@
|
|||||||
#include <HalGPIO.h>
|
#include <HalGPIO.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
#include <Wire.h>
|
||||||
#include <builtinFonts/all.h>
|
#include <builtinFonts/all.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -128,6 +129,12 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
|||||||
unsigned long t1 = 0;
|
unsigned long t1 = 0;
|
||||||
unsigned long t2 = 0;
|
unsigned long t2 = 0;
|
||||||
|
|
||||||
|
inline void requestResyncIfX3(uint8_t settlePasses = 0) {
|
||||||
|
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
|
||||||
|
display.requestResync(settlePasses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void exitActivity() {
|
void exitActivity() {
|
||||||
if (currentActivity) {
|
if (currentActivity) {
|
||||||
currentActivity->onExit();
|
currentActivity->onExit();
|
||||||
@@ -193,11 +200,49 @@ void waitForPowerRelease() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X3 wake gate: avoid "timing lottery" from strict calibration logic,
|
||||||
|
// but still require an intentional press (not a tap).
|
||||||
|
bool verifyPowerButtonDurationX3() {
|
||||||
|
constexpr uint16_t detectWindowMs = 1200; // time to detect an intentional wake press
|
||||||
|
constexpr uint16_t minHoldMs = 180; // short, deliberate hold (blocks accidental taps)
|
||||||
|
|
||||||
|
const unsigned long start = millis();
|
||||||
|
bool sawPress = false;
|
||||||
|
unsigned long pressStart = 0;
|
||||||
|
|
||||||
|
// Stage 1: wait for the button state to settle and detect a press.
|
||||||
|
while (millis() - start < detectWindowMs) {
|
||||||
|
gpio.update();
|
||||||
|
if (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||||
|
sawPress = true;
|
||||||
|
pressStart = millis();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sawPress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage 2: require a short continuous hold.
|
||||||
|
while (millis() - pressStart < minHoldMs) {
|
||||||
|
gpio.update();
|
||||||
|
if (!gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
requestResyncIfX3(0);
|
||||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
display.deepSleep();
|
display.deepSleep();
|
||||||
@@ -247,12 +292,26 @@ void onGoToBrowser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
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.
|
||||||
|
display.requestResync(1);
|
||||||
|
}
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onGoToReader, onGoToMyLibrary, onGoToRecentBooks,
|
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onGoToReader, onGoToMyLibrary, onGoToRecentBooks,
|
||||||
onGoToSettings, onGoToFileTransfer, onGoToBrowser));
|
onGoToSettings, onGoToFileTransfer, onGoToBrowser));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupDisplayAndFonts() {
|
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();
|
display.begin();
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
||||||
@@ -307,11 +366,21 @@ void setup() {
|
|||||||
UITheme::getInstance().reload();
|
UITheme::getInstance().reload();
|
||||||
ButtonNavigator::setMappedInputManager(mappedInputManager);
|
ButtonNavigator::setMappedInputManager(mappedInputManager);
|
||||||
|
|
||||||
switch (gpio.getWakeupReason()) {
|
const auto wakeupReason = gpio.getWakeupReason();
|
||||||
|
switch (wakeupReason) {
|
||||||
case HalGPIO::WakeupReason::PowerButton:
|
case HalGPIO::WakeupReason::PowerButton:
|
||||||
// For normal wakeups, verify power button press duration
|
// X3 uses a relaxed fixed hold check to avoid strict timing behavior
|
||||||
Serial.printf("[%lu] [ ] Verifying power button press duration\n", millis());
|
// while still filtering accidental single-click wakes.
|
||||||
verifyPowerButtonDuration();
|
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
|
||||||
|
Serial.printf("[%lu] [ ] Verifying relaxed power-button wake on X3\n", millis());
|
||||||
|
if (!verifyPowerButtonDurationX3()) {
|
||||||
|
gpio.startDeepSleep();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For non-X3 wakeups, keep existing verification behavior.
|
||||||
|
Serial.printf("[%lu] [ ] Verifying power button press duration\n", millis());
|
||||||
|
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
|
||||||
@@ -329,6 +398,10 @@ void setup() {
|
|||||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||||
|
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
|
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
|
||||||
|
wakeupReason == HalGPIO::WakeupReason::Other) {
|
||||||
|
requestResyncIfX3(0);
|
||||||
|
}
|
||||||
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
||||||
|
|||||||
Reference in New Issue
Block a user