mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat: rework GxEPDDisplay for rotation support and integrate e-ink build
- Remove fixed 128×128 virtual space and scaling; use 1:1 pixel mapping - Display dimensions derived from panel native size + DISPLAY_ROTATION - setCursor compensates for GFX font baseline offset (ascender per font size) - Add getLineHeight() / getCharWidth() overrides for FreeSans fonts - New envs: landscape (250×122) and portrait (122×250), BLE and dual variants - Integrate all wio-tracker-l1-improvements features into e-ink build Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "GxEPDDisplay.h"
|
||||
|
||||
#ifdef EXP_PIN_BACKLIGHT
|
||||
@@ -7,13 +6,22 @@
|
||||
#endif
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 3
|
||||
#define DISPLAY_ROTATION 0
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
SPIClass SPI1 = SPIClass(FSPI);
|
||||
#endif
|
||||
|
||||
// GFX fonts use the baseline as the cursor origin. UI code assumes top-of-cell
|
||||
// coordinates (same convention as the OLED driver). Add the font ascender so
|
||||
// the two conventions match.
|
||||
static int fontAscender(int sz) {
|
||||
if (sz == 3) return 26; // FreeSans18pt7b
|
||||
if (sz == 2) return 17; // FreeSansBold12pt7b
|
||||
return 13; // FreeSans9pt7b (sz == 1)
|
||||
}
|
||||
|
||||
bool GxEPDDisplay::begin() {
|
||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
#ifdef ESP32
|
||||
@@ -23,15 +31,14 @@ bool GxEPDDisplay::begin() {
|
||||
#endif
|
||||
display.init(115200, true, 2, false);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
setTextSize(1); // Default to size 1
|
||||
setTextSize(1);
|
||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.display(true);
|
||||
#if DISP_BACKLIGHT
|
||||
#if DISP_BACKLIGHT
|
||||
digitalWrite(DISP_BACKLIGHT, LOW);
|
||||
pinMode(DISP_BACKLIGHT, OUTPUT);
|
||||
#endif
|
||||
#endif
|
||||
_init = true;
|
||||
return true;
|
||||
}
|
||||
@@ -64,30 +71,24 @@ void GxEPDDisplay::clear() {
|
||||
void GxEPDDisplay::startFrame(Color bkg) {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.setTextColor(_curr_color = GxEPD_BLACK);
|
||||
_text_sz = 1;
|
||||
display.setFont(&FreeSans9pt7b);
|
||||
display_crc.reset();
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setTextSize(int sz) {
|
||||
_text_sz = sz;
|
||||
display_crc.update<int>(sz);
|
||||
switch(sz) {
|
||||
case 1: // Small
|
||||
display.setFont(&FreeSans9pt7b);
|
||||
break;
|
||||
case 2: // Medium Bold
|
||||
display.setFont(&FreeSansBold12pt7b);
|
||||
break;
|
||||
case 3: // Large
|
||||
display.setFont(&FreeSans18pt7b);
|
||||
break;
|
||||
default:
|
||||
display.setFont(&FreeSans9pt7b);
|
||||
break;
|
||||
switch (sz) {
|
||||
case 2: display.setFont(&FreeSansBold12pt7b); break;
|
||||
case 3: display.setFont(&FreeSans18pt7b); break;
|
||||
default: display.setFont(&FreeSans9pt7b); break;
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setColor(Color c) {
|
||||
display_crc.update<Color> (c);
|
||||
// colours need to be inverted for epaper displays
|
||||
display_crc.update<Color>(c);
|
||||
// e-ink: DARK background = white paper, LIGHT foreground = black ink
|
||||
if (c == DARK) {
|
||||
display.setTextColor(_curr_color = GxEPD_WHITE);
|
||||
} else {
|
||||
@@ -98,7 +99,9 @@ void GxEPDDisplay::setColor(Color c) {
|
||||
void GxEPDDisplay::setCursor(int x, int y) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display.setCursor((x+offset_x)*scale_x, (y+offset_y)*scale_y);
|
||||
// Offset y by the font ascender: callers pass top-of-cell y, GFX fonts
|
||||
// expect baseline y. Without this, text would be clipped at the top.
|
||||
display.setCursor(x, y + fontAscender(_text_sz));
|
||||
}
|
||||
|
||||
void GxEPDDisplay::print(const char* str) {
|
||||
@@ -111,7 +114,7 @@ void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display.fillRect(x*scale_x, y*scale_y, w*scale_x, h*scale_y, _curr_color);
|
||||
display.fillRect(x, y, w, h, _curr_color);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
@@ -119,7 +122,7 @@ void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display.drawRect(x*scale_x, y*scale_y, w*scale_x, h*scale_y, _curr_color);
|
||||
display.drawRect(x, y, w, h, _curr_color);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
@@ -128,36 +131,13 @@ void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
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
|
||||
uint16_t startX = x * scale_x;
|
||||
uint16_t startY = 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);
|
||||
if (pgm_read_byte(bits + byteOffset) & bitMask) {
|
||||
display.drawPixel(x + bx, y + by, _curr_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +147,7 @@ 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);
|
||||
return w;
|
||||
}
|
||||
|
||||
void GxEPDDisplay::endFrame() {
|
||||
|
||||
@@ -16,37 +16,62 @@
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
class GxEPDDisplay : public DisplayDriver {
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 0
|
||||
#endif
|
||||
|
||||
// Panel native dimensions before rotation. Derived from the model class when
|
||||
// EINK_DISPLAY_MODEL is set; override with EINK_PANEL_W/H for other panels.
|
||||
#if defined(EINK_DISPLAY_MODEL)
|
||||
#ifndef EINK_PANEL_W
|
||||
#define EINK_PANEL_W EINK_DISPLAY_MODEL::WIDTH
|
||||
#endif
|
||||
#ifndef EINK_PANEL_H
|
||||
#define EINK_PANEL_H EINK_DISPLAY_MODEL::HEIGHT
|
||||
#endif
|
||||
#else
|
||||
#ifndef EINK_PANEL_W
|
||||
#define EINK_PANEL_W 200
|
||||
#define EINK_PANEL_H 200
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Odd rotations (1, 3) swap width and height.
|
||||
#define EINK_DISP_W ((DISPLAY_ROTATION & 1) ? EINK_PANEL_H : EINK_PANEL_W)
|
||||
#define EINK_DISP_H ((DISPLAY_ROTATION & 1) ? EINK_PANEL_W : EINK_PANEL_H)
|
||||
|
||||
class GxEPDDisplay : public DisplayDriver {
|
||||
#if defined(EINK_DISPLAY_MODEL)
|
||||
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> display;
|
||||
const float scale_x = EINK_SCALE_X;
|
||||
const float scale_y = EINK_SCALE_Y;
|
||||
const float offset_x = EINK_X_OFFSET;
|
||||
const float offset_y = EINK_Y_OFFSET;
|
||||
#else
|
||||
GxEPD2_BW<GxEPD2_150_BN, 200> display;
|
||||
const float scale_x = 1.5625f;
|
||||
const float scale_y = 1.5625f;
|
||||
const float offset_x = 0;
|
||||
const float offset_y = 10;
|
||||
#endif
|
||||
bool _init = false;
|
||||
bool _isOn = false;
|
||||
uint16_t _curr_color;
|
||||
CRC32 display_crc;
|
||||
int last_display_crc_value = 0;
|
||||
int _text_sz = 1;
|
||||
|
||||
public:
|
||||
#if defined(EINK_DISPLAY_MODEL)
|
||||
GxEPDDisplay() : DisplayDriver(128, 128), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {}
|
||||
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
|
||||
display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {}
|
||||
#else
|
||||
GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {}
|
||||
GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H),
|
||||
display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {}
|
||||
#endif
|
||||
|
||||
// Line height and approx. char width for each font size:
|
||||
// 1 = FreeSans9pt (lineH=16, charW≈9)
|
||||
// 2 = FreeSansBold12pt (lineH=20, charW≈12)
|
||||
// 3 = FreeSans18pt (lineH=28, charW≈17)
|
||||
int getCharWidth() const override { return _text_sz == 3 ? 17 : _text_sz == 2 ? 12 : 9; }
|
||||
int getLineHeight() const override { return _text_sz == 3 ? 28 : _text_sz == 2 ? 20 : 16; }
|
||||
|
||||
bool begin();
|
||||
|
||||
bool isOn() override {return _isOn;};
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
[WioTrackerL1Eink]
|
||||
extends = nrf52_base
|
||||
board = seeed-wio-tracker-l1
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
-I variants/wio-tracker-l1
|
||||
-I examples/companion_radio/ui-new
|
||||
-D WIO_TRACKER_L1
|
||||
-D WIO_TRACKER_L1_EINK
|
||||
-D USE_SX1262
|
||||
@@ -15,57 +17,68 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_OLED_RESET=-1
|
||||
-D EINK_DISPLAY_MODEL=GxEPD2_213_B74
|
||||
-D EINK_SCALE_X=1.953125f
|
||||
-D EINK_SCALE_Y=1.28f
|
||||
-D EINK_X_OFFSET=0
|
||||
-D EINK_Y_OFFSET=10
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT
|
||||
-D AUTO_OFF_MILLIS=0
|
||||
-D GPS_BAUD_RATE=9600
|
||||
-D ENV_PIN_SDA=PIN_WIRE1_SDA
|
||||
-D ENV_PIN_SCL=PIN_WIRE1_SCL
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT
|
||||
-D AUTO_OFF_MILLIS=0
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=GxEPDDisplay
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D UI_HAS_JOYSTICK_UPDOWN=1
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
-D FIRMWARE_PLUS_BUILD=1
|
||||
-D UI_SENSORS_PAGE=1
|
||||
-D BLE_PIN_CODE=123456
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<WioTrackerL1Board.cpp>
|
||||
+<../variants/wio-tracker-l1>
|
||||
+<helpers/ui/GxEPDDisplay.cpp>
|
||||
+<helpers/sensors>
|
||||
lib_deps= ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
zinggjm/GxEPD2 @ 1.6.2
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:WioTrackerL1Eink_companion_radio_ble]
|
||||
extends = WioTrackerL1Eink
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags = ${WioTrackerL1Eink.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=GxEPDDisplay
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D UI_RECENT_LIST_SIZE=6
|
||||
-D UI_SENSORS_PAGE=1
|
||||
-D DUAL_SERIAL
|
||||
build_src_filter = ${WioTrackerL1Eink.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<helpers/sensors>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps = ${WioTrackerL1Eink.lib_deps}
|
||||
lib_deps = ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
zinggjm/GxEPD2 @ 1.6.2
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
debug_tool=stlink
|
||||
|
||||
; Landscape: 250×122 px (physical panel rotated 90°)
|
||||
[env:WioTrackerL1Eink_companion_radio_ble_landscape]
|
||||
extends = WioTrackerL1Eink
|
||||
build_flags = ${WioTrackerL1Eink.build_flags}
|
||||
-D DISPLAY_ROTATION=1
|
||||
extra_scripts = post:create-uf2.py
|
||||
|
||||
; Portrait: 122×250 px (physical panel in native orientation)
|
||||
[env:WioTrackerL1Eink_companion_radio_ble_portrait]
|
||||
extends = WioTrackerL1Eink
|
||||
build_flags = ${WioTrackerL1Eink.build_flags}
|
||||
-D DISPLAY_ROTATION=0
|
||||
extra_scripts = post:create-uf2.py
|
||||
|
||||
; Dual BLE+USB — landscape
|
||||
[env:WioTrackerL1Eink_companion_radio_dual_landscape]
|
||||
extends = WioTrackerL1Eink
|
||||
build_flags = ${WioTrackerL1Eink.build_flags}
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DUAL_SERIAL=1
|
||||
extra_scripts = post:create-uf2.py
|
||||
|
||||
; Dual BLE+USB — portrait
|
||||
[env:WioTrackerL1Eink_companion_radio_dual_portrait]
|
||||
extends = WioTrackerL1Eink
|
||||
build_flags = ${WioTrackerL1Eink.build_flags}
|
||||
-D DISPLAY_ROTATION=0
|
||||
-D DUAL_SERIAL=1
|
||||
extra_scripts = post:create-uf2.py
|
||||
|
||||
Reference in New Issue
Block a user