Files
Meck/src/helpers/ui/GxEPDDisplay.cpp

225 lines
6.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "GxEPDDisplay.h"
#ifdef EXP_PIN_BACKLIGHT
#include <PCA9557.h>
extern PCA9557 expander;
#endif
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 0
#endif
#ifdef ESP32
// E-ink and LoRa SHARE the same SPI bus (SCK=36, MOSI=33)
// They MUST use the same SPI peripheral (HSPI) to avoid GPIO conflicts
// Different chip selects allow both to coexist: E-ink CS=34, LoRa CS=3
SPIClass displaySpi(HSPI);
#endif
bool GxEPDDisplay::begin() {
#ifdef ESP32
// Initialize HSPI with shared pins
// SCK=36, MISO=47 (for LoRa receive), MOSI=33, SS=34 (e-ink CS)
displaySpi.begin(PIN_DISPLAY_SCLK, 47, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS);
// Tell GxEPD2 to use our SPI instance
// Using slower speed (4MHz) for reliable e-ink communication
display.epd2.selectSPI(displaySpi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
#endif
// Initialize with:
// - 115200 baud for debug serial
// - initial=true (do initial update)
// - reset_duration=2 (ms)
// - pulldown_rst_mode=false
display.init(115200, true, 2, false);
display.setRotation(DISPLAY_ROTATION);
setTextSize(1); // Default to size 1
display.setPartialWindow(0, 0, display.width(), display.height());
display.fillScreen(GxEPD_WHITE);
display.display(true);
#if DISP_BACKLIGHT
digitalWrite(DISP_BACKLIGHT, LOW);
pinMode(DISP_BACKLIGHT, OUTPUT);
#endif
_init = true;
_isOn = true; // Set display as "on" after initialization
return true;
}
void GxEPDDisplay::turnOn() {
if (!_init) begin();
#if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN)
digitalWrite(DISP_BACKLIGHT, HIGH);
#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN)
expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH);
#endif
_isOn = true;
}
void GxEPDDisplay::turnOff() {
#if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN)
digitalWrite(DISP_BACKLIGHT, LOW);
#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN)
expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW);
#endif
_isOn = false;
}
void GxEPDDisplay::clear() {
if (_darkMode) {
display.fillScreen(GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
} else {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
}
display_crc.reset();
}
void GxEPDDisplay::startFrame(Color bkg) {
if (_darkMode) {
display.fillScreen(GxEPD_BLACK);
display.setTextColor(_curr_color = GxEPD_WHITE);
} else {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(_curr_color = GxEPD_BLACK);
}
display_crc.reset();
}
void GxEPDDisplay::setTextSize(int sz) {
display_crc.update<int>(sz);
switch(sz) {
case 0: // Tiny - built-in 6x8 pixel font
display.setFont(NULL);
display.setTextSize(1);
break;
case 1: // Small - use 9pt (was 9pt)
display.setFont(&FreeSans9pt7b);
display.setTextSize(1);
break;
case 2: // Medium Bold - use 9pt bold instead of 12pt
display.setFont(&FreeSans9pt7b);
display.setTextSize(1);
break;
case 3: // Large - use 12pt instead of 18pt
display.setFont(&FreeSansBold12pt7b);
display.setTextSize(1);
break;
case 5: // Extra Large - lock screen clock face
display.setFont(&FreeSansBold12pt7b);
display.setTextSize(2); // GxEPD2 native 2× scaling on 12pt bold
break;
default:
display.setFont(&FreeSans9pt7b);
display.setTextSize(1);
break;
}
}
void GxEPDDisplay::setColor(Color c) {
display_crc.update<Color> (c);
if (_darkMode) {
// Dark mode: DARK = black (background), LIGHT/GREEN/YELLOW = white (foreground)
if (c == DARK) {
display.setTextColor(_curr_color = GxEPD_BLACK);
} else {
display.setTextColor(_curr_color = GxEPD_WHITE);
}
} else {
// Normal e-paper: DARK = white (background), LIGHT/GREEN/YELLOW = black (foreground)
if (c == DARK) {
display.setTextColor(_curr_color = GxEPD_WHITE);
} else {
display.setTextColor(_curr_color = GxEPD_BLACK);
}
}
}
void GxEPDDisplay::setCursor(int x, int y) {
display_crc.update<int>(x);
display_crc.update<int>(y);
// Add extra offset (+5) to push text baseline down, preventing ascenders from overlapping elements above
display.setCursor((x+offset_x)*scale_x, (y+offset_y+5)*scale_y);
}
void GxEPDDisplay::print(const char* str) {
display_crc.update<char>(str, strlen(str));
display.print(str);
}
void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
display_crc.update<int>(x);
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display.fillRect((x+offset_x)*scale_x, (y+offset_y)*scale_y, w*scale_x, h*scale_y, _curr_color);
}
void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
display_crc.update<int>(x);
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display.drawRect((x+offset_x)*scale_x, (y+offset_y)*scale_y, w*scale_x, h*scale_y, _curr_color);
}
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display_crc.update<int>(x);
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display_crc.update<uint8_t>(bits, w * h / 8);
// Calculate the base position in display coordinates (with offset applied)
uint16_t startX = (x + offset_x) * scale_x;
uint16_t startY = (y + offset_y) * scale_y;
// Width in bytes for bitmap processing
uint16_t widthInBytes = (w + 7) / 8;
// Process the bitmap row by row
for (uint16_t by = 0; by < h; by++) {
// Calculate the target y-coordinates for this logical row
int y1 = startY + (int)(by * scale_y);
int y2 = startY + (int)((by + 1) * scale_y);
int block_h = y2 - y1;
// Scan across the row bit by bit
for (uint16_t bx = 0; bx < w; bx++) {
// Calculate the target x-coordinates for this logical column
int x1 = startX + (int)(bx * scale_x);
int x2 = startX + (int)((bx + 1) * scale_x);
int block_w = x2 - x1;
// Get the current bit
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
uint8_t bitMask = 0x80 >> (bx & 7);
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
// If the bit is set, draw a block of pixels
if (bitSet) {
// Draw the block as a filled rectangle
display.fillRect(x1, y1, block_w, block_h, _curr_color);
}
}
}
}
uint16_t GxEPDDisplay::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return ceil((w + 1) / scale_x);
}
void GxEPDDisplay::endFrame() {
uint32_t crc = display_crc.finalize();
if (crc != last_display_crc_value) {
display.display(true); // Partial refresh
last_display_crc_value = crc;
}
}