diff --git a/lib/EpdFontRenderer/EpdFontRenderer.hpp b/lib/EpdFontRenderer/EpdFontRenderer.hpp index fb11e22..60d4a64 100644 --- a/lib/EpdFontRenderer/EpdFontRenderer.hpp +++ b/lib/EpdFontRenderer/EpdFontRenderer.hpp @@ -9,18 +9,19 @@ inline int max(const int a, const int b) { return a > b ? a : b; } template class EpdFontRenderer { Renderable& renderer; - void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR); + void renderChar(uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style = REGULAR); public: const EpdFontFamily* fontFamily; explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer) : fontFamily(fontFamily), renderer(renderer) {} ~EpdFontRenderer() = default; - void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR); + void renderString(const char* string, int* x, int* y, bool pixelState = true, EpdFontStyle style = REGULAR); + void drawPixel(int x, int y, bool pixelState); }; template -void EpdFontRenderer::renderString(const char* string, int* x, int* y, const uint16_t color, +void EpdFontRenderer::renderString(const char* string, int* x, int* y, const bool pixelState, const EpdFontStyle style) { // cannot draw a NULL / empty string if (string == nullptr || *string == '\0') { @@ -34,14 +35,48 @@ void EpdFontRenderer::renderString(const char* string, int* x, int* uint32_t cp; while ((cp = utf8NextCodepoint(reinterpret_cast(&string)))) { - renderChar(cp, x, y, color, style); + renderChar(cp, x, y, pixelState, style); } *y += fontFamily->getData(style)->advanceY; } +// TODO: Consolidate this with EpdRenderer implementation template -void EpdFontRenderer::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color, +void EpdFontRenderer::drawPixel(const int x, const int y, const bool pixelState) { + uint8_t* frameBuffer = renderer.getFrameBuffer(); + + // Early return if no framebuffer is set + if (!frameBuffer) { + Serial.printf("!!No framebuffer\n"); + return; + } + + // Bounds checking (portrait: 480x800) + if (x < 0 || x >= EInkDisplay::DISPLAY_HEIGHT || y < 0 || y >= EInkDisplay::DISPLAY_WIDTH) { + Serial.printf("!!Outside range (%d, %d)\n", x, y); + return; + } + + // Rotate coordinates: portrait (480x800) -> landscape (800x480) + // Rotation: 90 degrees clockwise + const int16_t rotatedX = y; + const int16_t rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; + + // Calculate byte position and bit position + const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); + const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first + + // Set or clear the bit + if (pixelState) { + frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit + } else { + frameBuffer[byteIndex] |= (1 << bitPosition); // Set bit + } +} + +template +void EpdFontRenderer::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState, const EpdFontStyle style) { const EpdGlyph* glyph = fontFamily->getGlyph(cp, style); if (!glyph) { @@ -74,7 +109,7 @@ void EpdFontRenderer::renderChar(const uint32_t cp, int* x, const in const uint8_t bit_index = 7 - (pixelPosition % 8); if ((byte >> bit_index) & 1) { - renderer.drawPixel(screenX, screenY, color); + drawPixel(screenX, screenY, pixelState); } } } diff --git a/lib/EpdRenderer/EpdRenderer.cpp b/lib/EpdRenderer/EpdRenderer.cpp index a65d616..a274378 100644 --- a/lib/EpdRenderer/EpdRenderer.cpp +++ b/lib/EpdRenderer/EpdRenderer.cpp @@ -21,11 +21,16 @@ EpdFont ubuntu10Font(&ubuntu_10); EpdFont ununtuBold10Font(&ubuntu_bold_10); EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font); -EpdRenderer::EpdRenderer(XteinkDisplay& display) - : display(display), marginTop(11), marginBottom(30), marginLeft(10), marginRight(10), lineCompression(0.95f) { - this->regularFontRenderer = new EpdFontRenderer(&bookerlyFontFamily, display); - this->smallFontRenderer = new EpdFontRenderer(&smallFontFamily, display); - this->uiFontRenderer = new EpdFontRenderer(&ubuntuFontFamily, display); +EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay) + : einkDisplay(einkDisplay), + marginTop(11), + marginBottom(30), + marginLeft(10), + marginRight(10), + lineCompression(0.95f) { + this->regularFontRenderer = new EpdFontRenderer(&bookerlyFontFamily, einkDisplay); + this->smallFontRenderer = new EpdFontRenderer(&smallFontFamily, einkDisplay); + this->uiFontRenderer = new EpdFontRenderer(&ubuntuFontFamily, einkDisplay); } EpdRenderer::~EpdRenderer() { @@ -34,6 +39,40 @@ EpdRenderer::~EpdRenderer() { delete uiFontRenderer; } +void EpdRenderer::drawPixel(const int x, const int y, const bool state) const { + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + + // Early return if no framebuffer is set + if (!frameBuffer) { + Serial.printf("!!No framebuffer\n"); + return; + } + + const int adjX = x + marginLeft; + const int adjY = y + marginTop; + + // Bounds checking (portrait: 480x800) + if (adjX < 0 || adjX >= EInkDisplay::DISPLAY_HEIGHT || adjY < 0 || adjY >= EInkDisplay::DISPLAY_WIDTH) { + Serial.printf("!!Outside range (%d, %d)\n", adjX, adjY); + return; + } + + // Rotate coordinates: portrait (480x800) -> landscape (800x480) + // Rotation: 90 degrees clockwise + const int rotatedX = adjY; + const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - adjX; + + // Calculate byte position and bit position + const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); + const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first + + if (state) { + frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit + } else { + frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit + } +} + int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const { int w = 0, h = 0; @@ -58,25 +97,25 @@ int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) c return w; } -void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color, +void EpdRenderer::drawText(const int x, const int y, const char* text, const bool state, const EpdFontStyle style) const { int ypos = y + getLineHeight() + marginTop; int xpos = x + marginLeft; - regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); + regularFontRenderer->renderString(text, &xpos, &ypos, state, style); } -void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color, +void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state, const EpdFontStyle style) const { int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop; int xpos = x + marginLeft; - uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); + uiFontRenderer->renderString(text, &xpos, &ypos, state, style); } -void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color, +void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state, const EpdFontStyle style) const { int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop; int xpos = x + marginLeft; - smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); + smallFontRenderer->renderString(text, &xpos, &ypos, state, style); } void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height, @@ -115,51 +154,67 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, } } -void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, uint16_t color) const { - display.drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop, - color > 0 ? GxEPD_BLACK : GxEPD_WHITE); +void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const { + if (x1 == x2) { + if (y2 < y1) { + std::swap(y1, y2); + } + for (int y = y1; y <= y2; y++) { + drawPixel(x1, y, state); + } + } else if (y1 == y2) { + if (x2 < x1) { + std::swap(x1, x2); + } + for (int x = x1; x <= x2; x++) { + drawPixel(x, y1, state); + } + } else { + // TODO: Implement + Serial.println("Line drawing not supported"); + } } -void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const uint16_t color) const { - display.drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); +void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const { + drawLine(x, y, x + width - 1, y, state); + drawLine(x + width - 1, y, x + width - 1, y + height - 1, state); + drawLine(x + width - 1, y + height - 1, x, y + height - 1, state); + drawLine(x, y, x, y + height - 1, state); } -void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const uint16_t color) const { - display.fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); +void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const { + for (int fillY = y; fillY < y + height; fillY++) { + drawLine(x, fillY, x + width - 1, fillY, state); + } } -void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const { - display.drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); +void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { + drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height); } -void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const { - display.fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); +// TODO: Support y-mirror? +void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, + const int height) const { + einkDisplay.drawImage(bitmap, x, y, width, height); } -void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height, - const bool invert, const bool mirrorY) const { - drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height, invert, mirrorY); -} - -void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, const int height, - const bool invert, const bool mirrorY) const { - display.drawImage(bitmap, x, y, width, height, invert, mirrorY); -} - -void EpdRenderer::clearScreen(const bool black) const { +void EpdRenderer::clearScreen(const uint8_t color) const { Serial.println("Clearing screen"); - display.fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE); + einkDisplay.clearScreen(color); } -void EpdRenderer::flushDisplay(const bool partialUpdate) const { display.display(partialUpdate); } +void EpdRenderer::flushDisplay(const bool partialUpdate) const { + einkDisplay.displayBuffer(partialUpdate ? EInkDisplay::FAST_REFRESH : EInkDisplay::FULL_REFRESH); +} +// TODO: Support partial window update void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const { - display.displayWindow(x + marginLeft, y + marginTop, width, height); + einkDisplay.displayBuffer(); } -int EpdRenderer::getPageWidth() const { return display.width() - marginLeft - marginRight; } +int EpdRenderer::getPageWidth() const { return EInkDisplay::DISPLAY_HEIGHT - marginLeft - marginRight; } -int EpdRenderer::getPageHeight() const { return display.height() - marginTop - marginBottom; } +int EpdRenderer::getPageHeight() const { return EInkDisplay::DISPLAY_WIDTH - marginTop - marginBottom; } int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; } diff --git a/lib/EpdRenderer/EpdRenderer.h b/lib/EpdRenderer/EpdRenderer.h index 83822c6..96035f4 100644 --- a/lib/EpdRenderer/EpdRenderer.h +++ b/lib/EpdRenderer/EpdRenderer.h @@ -1,16 +1,14 @@ #pragma once -#include +#include #include -#define XteinkDisplay GxEPD2_BW - class EpdRenderer { - XteinkDisplay& display; - EpdFontRenderer* regularFontRenderer; - EpdFontRenderer* smallFontRenderer; - EpdFontRenderer* uiFontRenderer; + EInkDisplay& einkDisplay; + EpdFontRenderer* regularFontRenderer; + EpdFontRenderer* smallFontRenderer; + EpdFontRenderer* uiFontRenderer; int marginTop; int marginBottom; int marginLeft; @@ -18,25 +16,23 @@ class EpdRenderer { float lineCompression; public: - explicit EpdRenderer(XteinkDisplay& display); + explicit EpdRenderer(EInkDisplay& einkDisplay); ~EpdRenderer(); + void drawPixel(int x, int y, bool state = true) const; int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const; int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const; int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const; - void drawText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const; - void drawUiText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const; - void drawSmallText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const; + void drawText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; + void drawUiText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; + void drawSmallText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const; - void drawLine(int x1, int y1, int x2, int y2, uint16_t color = 1) const; - void drawRect(int x, int y, int width, int height, uint16_t color = 1) const; - void fillRect(int x, int y, int width, int height, uint16_t color = 1) const; - void drawCircle(int x, int y, int radius, uint16_t color = 1) const; - void fillCircle(int x, int y, int radius, uint16_t color = 1) const; - void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false, - bool mirrorY = false) const; - void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false, - bool mirrorY = false) const; - void clearScreen(bool black = false) const; + void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; + void drawRect(int x, int y, int width, int height, bool state = true) const; + void fillRect(int x, int y, int width, int height, bool state = true) const; + void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; + void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height) const; + + void clearScreen(uint8_t color = 0xFF) const; void flushDisplay(bool partialUpdate = true) const; void flushArea(int x, int y, int width, int height) const; @@ -44,6 +40,7 @@ class EpdRenderer { int getPageHeight() const; int getSpaceWidth() const; int getLineHeight() const; + // set margins void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; } void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; } diff --git a/open-x4-sdk b/open-x4-sdk index 8224d27..fe963b3 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2 +Subproject commit fe963b32821dec7fb22abbfa22daa8fc67f286d1 diff --git a/platformio.ini b/platformio.ini index 58f20b5..ec1aa3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,7 +34,7 @@ build_flags = ; Libraries lib_deps = - zinggjm/GxEPD2@^1.6.5 https://github.com/leethomason/tinyxml2.git#11.0.0 BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor InputManager=symlink://open-x4-sdk/libs/hardware/InputManager + EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay diff --git a/src/main.cpp b/src/main.cpp index 40b478f..4b170dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include +#include #include #include -#include #include #include #include @@ -28,10 +28,9 @@ #define SD_SPI_CS 12 #define SD_SPI_MISO 7 -GxEPD2_BW display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC, - EPD_RST, EPD_BUSY)); +EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); InputManager inputManager; -EpdRenderer renderer(display); +EpdRenderer renderer(einkDisplay); Screen* currentScreen; CrossPointState appState; @@ -123,7 +122,7 @@ void enterDeepSleep() { // Enable Wakeup on LOW (button press) esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); - display.hibernate(); + einkDisplay.deepSleep(); // Enter Deep Sleep esp_deep_sleep_start(); @@ -170,10 +169,7 @@ void setup() { SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS); // Initialize display - const SPISettings spi_settings(SPI_FQ, MSBFIRST, SPI_MODE0); - display.init(115200, true, 2, false, SPI, spi_settings); - display.setRotation(3); // 270 degrees - display.setTextColor(GxEPD_BLACK); + einkDisplay.begin(); Serial.println("Display initialized"); exitScreen(); diff --git a/src/screens/BootLogoScreen.cpp b/src/screens/BootLogoScreen.cpp index 8206e81..310de1e 100644 --- a/src/screens/BootLogoScreen.cpp +++ b/src/screens/BootLogoScreen.cpp @@ -11,4 +11,5 @@ void BootLogoScreen::onEnter() { renderer.clearScreen(); // Location for images is from top right in landscape orientation renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128); + renderer.flushDisplay(); } diff --git a/src/screens/SleepScreen.cpp b/src/screens/SleepScreen.cpp index 489fcf5..75696ff 100644 --- a/src/screens/SleepScreen.cpp +++ b/src/screens/SleepScreen.cpp @@ -4,4 +4,9 @@ #include "images/SleepScreenImg.h" -void SleepScreen::onEnter() { renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); } +void SleepScreen::onEnter() { + renderer.clearScreen(); + renderer.flushDisplay(); + renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480); + renderer.flushDisplay(); +}