mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
Merge pull request #46 from pmow/feature/eink-ui-scale
GDEQ0426T82 4.26" panel (#42), bulk SPI writes (#43), Text size and Icon size for big panels # Conflicts: # examples/companion_radio/DataStore.cpp # examples/companion_radio/NodePrefs.h
This commit is contained in:
@@ -335,6 +335,11 @@ static void sanitize(NodePrefs& p) {
|
|||||||
flag(p.ble_off, 0);
|
flag(p.ble_off, 0);
|
||||||
if (p.msg_alert > NodePrefs::MSG_ALERT_OFF) p.msg_alert = NodePrefs::MSG_ALERT_NORMAL;
|
if (p.msg_alert > NodePrefs::MSG_ALERT_OFF) p.msg_alert = NodePrefs::MSG_ALERT_NORMAL;
|
||||||
if (p.advert_loc_prec >= NodePrefs::ADV_LOC_PREC_COUNT) p.advert_loc_prec = 0;
|
if (p.advert_loc_prec >= NodePrefs::ADV_LOC_PREC_COUNT) p.advert_loc_prec = 0;
|
||||||
|
if (p.ui_text_scale > NodePrefs::UI_TEXT_SCALE_MAX) p.ui_text_scale = 0;
|
||||||
|
#ifdef EINK_FONT_SCALE // the Text size row shows the scale in use: start at the build's
|
||||||
|
if (p.ui_text_scale == 0) p.ui_text_scale = EINK_FONT_SCALE;
|
||||||
|
#endif
|
||||||
|
if (p.ui_icon_scale > NodePrefs::UI_ICON_SCALE_MAX) p.ui_icon_scale = 0;
|
||||||
if (!p.ch_scope_follow) {
|
if (!p.ch_scope_follow) {
|
||||||
for (uint8_t i = 0; i < NodePrefs::MAX_SCOPED_CHANNELS; i++)
|
for (uint8_t i = 0; i < NodePrefs::MAX_SCOPED_CHANNELS; i++)
|
||||||
if (p.ch_scope_idx[i] == 0) p.ch_scope_idx[i] = NodePrefs::CH_SCOPE_DEFAULT;
|
if (p.ch_scope_idx[i] == 0) p.ch_scope_idx[i] = NodePrefs::CH_SCOPE_DEFAULT;
|
||||||
|
|||||||
@@ -544,6 +544,15 @@ struct NodePrefs { // persisted to file
|
|||||||
static const uint8_t ADV_LOC_PREC_COUNT = 4;
|
static const uint8_t ADV_LOC_PREC_COUNT = 4;
|
||||||
uint8_t advert_loc_prec;
|
uint8_t advert_loc_prec;
|
||||||
|
|
||||||
|
// ── Text and icon size (ui-new, big e-ink panels) ──────────────────────
|
||||||
|
// Settings > Display, only on builds that opt in (EINK_FONT_SCALE /
|
||||||
|
// STATUS_ICON_SCALE). ui_text_scale: the font's pixel multiplier, 1-5; 0 is
|
||||||
|
// the build's own (DataStore sanitize sets EINK_FONT_SCALE there).
|
||||||
|
// ui_icon_scale: Home's status icons, 0 = Auto (1.5x the text), else 1-8.
|
||||||
|
static const uint8_t UI_TEXT_SCALE_MAX = 5, UI_ICON_SCALE_MAX = 8;
|
||||||
|
uint8_t ui_text_scale;
|
||||||
|
uint8_t ui_icon_scale;
|
||||||
|
|
||||||
// Single source of truth for the live-share option tables (shared by the Map
|
// Single source of truth for the live-share option tables (shared by the Map
|
||||||
// UI labels and the auto-send engine in UITask).
|
// UI labels and the auto-send engine in UITask).
|
||||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ static const Key KEYS[] = {
|
|||||||
K("contacts.expiry", contact_expiry_idx), K("contacts.fav.first", fav_sort), K("dm.resend", dm_resend_count),
|
K("contacts.expiry", contact_expiry_idx), K("contacts.fav.first", fav_sort), K("dm.resend", dm_resend_count),
|
||||||
K("display.brightness", display_brightness), K("display.off", auto_off_secs), K("display.wake", msg_wake),
|
K("display.brightness", display_brightness), K("display.off", auto_off_secs), K("display.wake", msg_wake),
|
||||||
K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact),
|
K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact),
|
||||||
|
K("display.text.size", ui_text_scale), K("display.icon.size", ui_icon_scale),
|
||||||
K("batt.display", batt_display_mode), K("batt.shutdown", low_batt_mv), K("gps.interval", gps_interval),
|
K("batt.display", batt_display_mode), K("batt.shutdown", low_batt_mv), K("gps.interval", gps_interval),
|
||||||
K("clock.12h", clock_12h), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
|
K("clock.12h", clock_12h), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
|
||||||
K("sound.volume", buzzer_volume), K("quiet", quiet_hours), K("quiet.from", quiet_from), K("quiet.until", quiet_to),
|
K("sound.volume", buzzer_volume), K("quiet", quiet_hours), K("quiet.from", quiet_from), K("quiet.until", quiet_to),
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ static void optMsgAlert(uint8_t v, char* b, int n, const NodePrefs&) {
|
|||||||
static const char* const L[] = { "Normal", "Compact", "Off" };
|
static const char* const L[] = { "Normal", "Compact", "Off" };
|
||||||
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
||||||
}
|
}
|
||||||
|
#ifdef EINK_FONT_SCALE
|
||||||
|
static const int32_t TEXT_SCALE[] = { 1, 2, 3, 4, 5 };
|
||||||
|
static void optTextScale(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%ldx", (long)TEXT_SCALE[v]); }
|
||||||
|
#endif
|
||||||
|
#ifdef STATUS_ICON_SCALE
|
||||||
|
static void optIconScale(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||||
|
if (v == 0) snprintf(b, n, "Auto"); else snprintf(b, n, "%ux", (unsigned)v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%s", v ? "Compact" : "Big"); }
|
static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%s", v ? "Compact" : "Big"); }
|
||||||
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
|
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||||
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
||||||
@@ -247,6 +256,15 @@ static const Setting ALL[] = {
|
|||||||
SW("Lock screen", "Slide to unlock after sleep", SEC_DISPLAY, auto_lock, nullptr),
|
SW("Lock screen", "Slide to unlock after sleep", SEC_DISPLAY, auto_lock, nullptr),
|
||||||
IDX("Lock clock", "Big, or compact with the clock fields", SEC_DISPLAY, lock_compact, 2, optLockLook, nullptr),
|
IDX("Lock clock", "Big, or compact with the clock fields", SEC_DISPLAY, lock_compact, 2, optLockLook, nullptr),
|
||||||
IDX("Battery display", "In the status bar", SEC_DISPLAY, batt_display_mode, 3, optBattDisplay, nullptr),
|
IDX("Battery display", "In the status bar", SEC_DISPLAY, batt_display_mode, 3, optBattDisplay, nullptr),
|
||||||
|
// Only the builds that ask for them (big e-ink panels): each screen elsewhere
|
||||||
|
// is laid out for one cell size.
|
||||||
|
#ifdef EINK_FONT_SCALE
|
||||||
|
MAP("Text size", "Bigger letters on a big panel", SEC_DISPLAY, ui_text_scale, TEXT_SCALE, optTextScale, applyDisplay),
|
||||||
|
#endif
|
||||||
|
#ifdef STATUS_ICON_SCALE
|
||||||
|
IDX("Icon size", "Home's status icons", SEC_DISPLAY, ui_icon_scale, NodePrefs::UI_ICON_SCALE_MAX + 1,
|
||||||
|
optIconScale, applyDisplay),
|
||||||
|
#endif
|
||||||
|
|
||||||
MAP("Battery shutdown", "Power off below this voltage", SEC_POWER, low_batt_mv, LOW_BATT, optLowBatt, nullptr),
|
MAP("Battery shutdown", "Power off below this voltage", SEC_POWER, low_batt_mv, LOW_BATT, optLowBatt, nullptr),
|
||||||
MAP("GPS power saving", "Sleep between fixes", SEC_POWER, gps_interval, GPS_DUTY, optGpsDuty, applyGpsDuty),
|
MAP("GPS power saving", "Sleep between fixes", SEC_POWER, gps_interval, GPS_DUTY, optGpsDuty, applyGpsDuty),
|
||||||
|
|||||||
@@ -352,8 +352,8 @@ class HomeScreen : public UIScreen {
|
|||||||
|
|
||||||
// First row under the header and the page-icon row (see render()).
|
// First row under the header and the page-icon row (see render()).
|
||||||
static int contentTop(DisplayDriver& d) {
|
static int contentTop(DisplayDriver& d) {
|
||||||
const int pg_half = (PAGE_ICON_PX * miniIconScale(d) + 1) / 2;
|
const int pg_half = (PAGE_ICON_PX * statusIconScale(d) + 1) / 2;
|
||||||
return d.getLineHeight() + 2 * pg_half + 4;
|
return statusBarH(d) + 2 * pg_half + 4;
|
||||||
}
|
}
|
||||||
// 0..1 eased progress of the running slide, or -1 when none.
|
// 0..1 eased progress of the running slide, or -1 when none.
|
||||||
float slideProgress() {
|
float slideProgress() {
|
||||||
@@ -739,8 +739,11 @@ class HomeScreen : public UIScreen {
|
|||||||
|
|
||||||
const int lh = display.getLineHeight();
|
const int lh = display.getLineHeight();
|
||||||
const int cw = display.getCharWidth();
|
const int cw = display.getCharWidth();
|
||||||
const int ind = cw + 2; // single-char indicator width
|
const int sis = statusIconScale(display); // STATUS_ICON_SCALE, or the font's
|
||||||
const int ind_h = display.isSingleFont() ? lh - 2 : lh;
|
const int bar_h = statusBarH(display);
|
||||||
|
const int ind = STATUS_ICONS_SCALED ? 7 * sis + 2 : cw + 2; // indicator slot width
|
||||||
|
const int ind_h = (bar_h > lh) ? bar_h - 2 : (display.isSingleFont() ? lh - 2 : lh);
|
||||||
|
const int text_y = (bar_h > lh) ? (bar_h - lh) / 2 : 0; // % / V text centred in a tall bar
|
||||||
const int ind_gap = display.pixelScale() > 1 ? 3 : 1; // gap between indicator boxes
|
const int ind_gap = display.pixelScale() > 1 ? 3 : 1; // gap between indicator boxes
|
||||||
|
|
||||||
int battLeftX;
|
int battLeftX;
|
||||||
@@ -748,17 +751,17 @@ class HomeScreen : public UIScreen {
|
|||||||
char buf[6];
|
char buf[6];
|
||||||
snprintf(buf, sizeof(buf),"%d%%", pct);
|
snprintf(buf, sizeof(buf),"%d%%", pct);
|
||||||
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
||||||
display.setCursor(battLeftX, 0);
|
display.setCursor(battLeftX, text_y);
|
||||||
display.print(buf);
|
display.print(buf);
|
||||||
} else if (mode == battery::VOLTAGE) {
|
} else if (mode == battery::VOLTAGE) {
|
||||||
char buf[8];
|
char buf[8];
|
||||||
snprintf(buf, sizeof(buf),"%u.%02uV", batteryMilliVolts / 1000, (batteryMilliVolts % 1000) / 10);
|
snprintf(buf, sizeof(buf),"%u.%02uV", batteryMilliVolts / 1000, (batteryMilliVolts % 1000) / 10);
|
||||||
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
||||||
display.setCursor(battLeftX, 0);
|
display.setCursor(battLeftX, text_y);
|
||||||
display.print(buf);
|
display.print(buf);
|
||||||
} else { // icon — scales with lh, same box height as the status icons beside it (ind_h)
|
} else { // icon — scales with lh, same box height as the status icons beside it (ind_h)
|
||||||
const int iconH = ind_h;
|
const int iconH = ind_h;
|
||||||
const int iconW = lh * 2;
|
const int iconW = (bar_h > lh) ? ind_h * 2 : lh * 2;
|
||||||
const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px
|
const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px
|
||||||
battLeftX = display.width() - iconW - 3;
|
battLeftX = display.width() - iconW - 3;
|
||||||
// The outline with its corner pixels left out, softly rounded.
|
// The outline with its corner pixels left out, softly rounded.
|
||||||
@@ -824,7 +827,8 @@ class HomeScreen : public UIScreen {
|
|||||||
if (!s.active) continue;
|
if (!s.active) continue;
|
||||||
int ix = x - ind - ind_gap;
|
int ix = x - ind - ind_gap;
|
||||||
if (ix < name_min) break; // out of room — drop this + all lower priority
|
if (ix < name_min) break; // out of room — drop this + all lower priority
|
||||||
drawSlotIcon(display, ix, ind, ind_h, *s.icon);
|
if (STATUS_ICONS_SCALED) drawSlotIconScaled(display, ix, ind, ind_h, *s.icon, sis);
|
||||||
|
else drawSlotIcon(display, ix, ind, ind_h, *s.icon);
|
||||||
x = ix;
|
x = ix;
|
||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
@@ -1255,7 +1259,7 @@ public:
|
|||||||
const int W = d.width(), H = d.height();
|
const int W = d.width(), H = d.height();
|
||||||
d.setColor(DisplayDriver::LIGHT);
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
d.setTextSize(1);
|
d.setTextSize(1);
|
||||||
const int lh = d.getLineHeight(), top = lh + 3;
|
const int lh = d.getLineHeight(), top = statusBarH(d) + 3;
|
||||||
const bool hint = Features::IS_EINK || _task->lockHintShown();
|
const bool hint = Features::IS_EINK || _task->lockHintShown();
|
||||||
const int pill_y = H - lh - 3; // the bottom pill, clear of what's above
|
const int pill_y = H - lh - 3; // the bottom pill, clear of what's above
|
||||||
const int unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount();
|
const int unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount();
|
||||||
@@ -1491,8 +1495,8 @@ public:
|
|||||||
// Page-indicator row: small page icons (PAGE_ICON_PX) replace the old dots. Centre and
|
// Page-indicator row: small page icons (PAGE_ICON_PX) replace the old dots. Centre and
|
||||||
// gap scale with the font so the band clears the header above and content
|
// gap scale with the font so the band clears the header above and content
|
||||||
// below (identical to the old lh+4 / +6 dots layout at 1x).
|
// below (identical to the old lh+4 / +6 dots layout at 1x).
|
||||||
const int pg_half = (PAGE_ICON_PX * miniIconScale(display) + 1) / 2;
|
const int pg_half = (PAGE_ICON_PX * statusIconScale(display) + 1) / 2; // page icons follow STATUS_ICON_SCALE
|
||||||
const int dots_y = lh + pg_half + 1; // icon-row centre, below the header
|
const int dots_y = statusBarH(display) + pg_half + 1; // icon-row centre, below the header
|
||||||
const int content_y = contentTop(display); // first content row, below the icons
|
const int content_y = contentTop(display); // first content row, below the icons
|
||||||
const float slide = slideProgress(); // -1, or how far a page turn has got
|
const float slide = slideProgress(); // -1, or how far a page turn has got
|
||||||
|
|
||||||
@@ -1507,6 +1511,7 @@ public:
|
|||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
|
|
||||||
if (_page != LOCK) {
|
if (_page != LOCK) {
|
||||||
|
const int bar_ty = (statusBarH(display) - lh) / 2; // text centred in a tall bar (STATUS_ICON_SCALE)
|
||||||
// The time, once the clock is set (the node name before that): the
|
// The time, once the clock is set (the node name before that): the
|
||||||
// name is what others see, the time is what you glance at here.
|
// name is what others see, the time is what you glance at here.
|
||||||
char filtered_name[sizeof(_node_prefs->node_name)];
|
char filtered_name[sizeof(_node_prefs->node_name)];
|
||||||
@@ -1532,10 +1537,10 @@ public:
|
|||||||
char pwr_buf[8];
|
char pwr_buf[8];
|
||||||
snprintf(pwr_buf, sizeof(pwr_buf), "%ddB", (int)radio_driver.getTxPower());
|
snprintf(pwr_buf, sizeof(pwr_buf), "%ddB", (int)radio_driver.getTxPower());
|
||||||
int pwr_w = display.getTextWidth(pwr_buf);
|
int pwr_w = display.getTextWidth(pwr_buf);
|
||||||
display.drawTextEllipsized(0, 0, rightEdge - 2 - pwr_w - 2, filtered_name);
|
display.drawTextEllipsized(0, bar_ty, rightEdge - 2 - pwr_w - 2, filtered_name);
|
||||||
display.drawTextRightAlign(rightEdge - 2, 0, pwr_buf);
|
display.drawTextRightAlign(rightEdge - 2, bar_ty, pwr_buf);
|
||||||
} else {
|
} else {
|
||||||
display.drawTextEllipsized(0, 0, rightEdge - 2, filtered_name);
|
display.drawTextEllipsized(0, bar_ty, rightEdge - 2, filtered_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1550,7 +1555,7 @@ public:
|
|||||||
int order[(int)Count]; int n = buildVisibleOrder(order);
|
int order[(int)Count]; int n = buildVisibleOrder(order);
|
||||||
int curr_vis = 0;
|
int curr_vis = 0;
|
||||||
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
|
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
|
||||||
const int s = miniIconScale(display);
|
const int s = statusIconScale(display); // STATUS_ICON_SCALE, or the font's
|
||||||
const int icon_w = PAGE_ICON_PX * s;
|
const int icon_w = PAGE_ICON_PX * s;
|
||||||
int pitch = icon_w + 5 * s; // comfortable spacing
|
int pitch = icon_w + 5 * s; // comfortable spacing
|
||||||
if (n > 1) { // shrink to fit if many pages
|
if (n > 1) { // shrink to fit if many pages
|
||||||
@@ -1575,7 +1580,7 @@ public:
|
|||||||
if (d2 <= pw - icon_w) display.setColor(DisplayDriver::DARK); // inside the pill
|
if (d2 <= pw - icon_w) display.setColor(DisplayDriver::DARK); // inside the pill
|
||||||
else if (d2 < pw + icon_w) { x += pitch; continue; } // half under the gliding pill
|
else if (d2 < pw + icon_w) { x += pitch; continue; } // half under the gliding pill
|
||||||
else display.setColor(DisplayDriver::LIGHT);
|
else display.setColor(DisplayDriver::LIGHT);
|
||||||
if (ic) miniIconDrawCentered(display, x, dots_y, *ic);
|
if (ic) miniIconDrawCenteredScaled(display, x, dots_y, *ic, s);
|
||||||
x += pitch;
|
x += pitch;
|
||||||
}
|
}
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
@@ -2058,6 +2063,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
applyBrightness();
|
applyBrightness();
|
||||||
applyRotation();
|
applyRotation();
|
||||||
applyFullRefreshInterval();
|
applyFullRefreshInterval();
|
||||||
|
applyUiScale(); // Settings > Display > Text size / Icon size
|
||||||
applyAllGpioModes(); // restore persisted pin modes to hardware before any UI/bot use
|
applyAllGpioModes(); // restore persisted pin modes to hardware before any UI/bot use
|
||||||
// Locked above (a PIN, or the cover closed) before Home existed to be told.
|
// Locked above (a PIN, or the cover closed) before Home existed to be told.
|
||||||
if (_locked) syncLockToHome();
|
if (_locked) syncLockToHome();
|
||||||
@@ -4157,6 +4163,19 @@ void UITask::applyFullRefreshInterval() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text size and Icon size (Settings > Display, NodePrefs ui_text_scale /
|
||||||
|
// ui_icon_scale), on the builds that offer them. Elsewhere the fields stay 0:
|
||||||
|
// the build's font scale and Auto icons, as stock.
|
||||||
|
void UITask::applyUiScale() {
|
||||||
|
if (!_node_prefs) return;
|
||||||
|
#ifdef EINK_FONT_SCALE
|
||||||
|
if (_display != NULL) _display->setUiScale(_node_prefs->ui_text_scale);
|
||||||
|
#endif
|
||||||
|
#if STATUS_ICONS_SCALED
|
||||||
|
statusIconScaleSetting() = _node_prefs->ui_icon_scale;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void UITask::applySoundPrefs() {
|
void UITask::applySoundPrefs() {
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
if (_node_prefs) setBuzzerVolumeLevel(_node_prefs->buzzer_volume); // with a sample at the new level
|
if (_node_prefs) setBuzzerVolumeLevel(_node_prefs->buzzer_volume); // with a sample at the new level
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ public:
|
|||||||
void applyAllGpioModes(); // boot-time restore from NodePrefs, called from begin()
|
void applyAllGpioModes(); // boot-time restore from NodePrefs, called from begin()
|
||||||
void applyBrightness();
|
void applyBrightness();
|
||||||
// Settings changed through the schema (ui-core/SettingsSchema.h).
|
// Settings changed through the schema (ui-core/SettingsSchema.h).
|
||||||
void applyDisplayPrefs() override { applyBrightness(); _next_refresh = 0; }
|
void applyDisplayPrefs() override { applyBrightness(); applyUiScale(); _next_refresh = 0; }
|
||||||
void applySoundPrefs() override;
|
void applySoundPrefs() override;
|
||||||
bool cliCommand(const char* command, char* reply, int n) override;
|
bool cliCommand(const char* command, char* reply, int n) override;
|
||||||
void setBuzzerVolumeLevel(uint8_t level);
|
void setBuzzerVolumeLevel(uint8_t level);
|
||||||
@@ -543,6 +543,7 @@ public:
|
|||||||
bool savePrefsIfDirty(bool& dirty);
|
bool savePrefsIfDirty(bool& dirty);
|
||||||
void applyRotation();
|
void applyRotation();
|
||||||
void applyFullRefreshInterval();
|
void applyFullRefreshInterval();
|
||||||
|
void applyUiScale(); // Settings > Display > Text size / Icon size, from the prefs
|
||||||
uint32_t autoOffMillis() const {
|
uint32_t autoOffMillis() const {
|
||||||
if (!_node_prefs || _node_prefs->auto_off_secs == 0) return 0;
|
if (!_node_prefs || _node_prefs->auto_off_secs == 0) return 0;
|
||||||
return (uint32_t)_node_prefs->auto_off_secs * 1000UL;
|
return (uint32_t)_node_prefs->auto_off_secs * 1000UL;
|
||||||
|
|||||||
@@ -91,6 +91,54 @@ inline void miniIconDrawCentered(DisplayDriver& d, int cx, int cy, const MiniIco
|
|||||||
miniIconDrawTop(d, cx - (ic.w * s) / 2, cy - (ic.h * s) / 2, ic);
|
miniIconDrawTop(d, cx - (ic.w * s) / 2, cy - (ic.h * s) / 2, ic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Home status bar icon scale. Defining STATUS_ICON_SCALE (build flag) turns on
|
||||||
|
// scalable status icons: the bar's icons, battery and the page-icon row below it
|
||||||
|
// are drawn at n x their pixel grid, and the bar grows tall enough to hold them --
|
||||||
|
// for big panels whose 1-line bar leaves them tiny -- and adds Settings > Display >
|
||||||
|
// Icon size (NodePrefs::ui_icon_scale): 0 = Auto (1.5x the text's pixel scale),
|
||||||
|
// else n. Unset: icons follow the font, as stock.
|
||||||
|
#ifdef STATUS_ICON_SCALE
|
||||||
|
#define STATUS_ICONS_SCALED 1
|
||||||
|
#else
|
||||||
|
#define STATUS_ICONS_SCALED 0
|
||||||
|
#endif
|
||||||
|
static constexpr int STATUS_ICON_ROWS = MINI_ICONS_LARGE == 2 ? 9 : 7; // tallest status icon
|
||||||
|
// The Icon size setting (0 = Auto), copied from the prefs by UITask::applyUiScale().
|
||||||
|
// One instance across translation units.
|
||||||
|
inline uint8_t& statusIconScaleSetting() {
|
||||||
|
static uint8_t v = 0;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
inline int statusIconScale(DisplayDriver& d) {
|
||||||
|
if (!STATUS_ICONS_SCALED) return miniIconScale(d);
|
||||||
|
int s = statusIconScaleSetting();
|
||||||
|
if (s == 0) s = (3 * d.pixelScale() + 1) / 2; // Auto: 1.5x the text
|
||||||
|
return s < 1 ? 1 : s;
|
||||||
|
}
|
||||||
|
// Height of the home status bar: one text line, or the scaled icons if taller.
|
||||||
|
inline int statusBarH(DisplayDriver& d) {
|
||||||
|
const int lh = d.getLineHeight();
|
||||||
|
const int ih = STATUS_ICON_ROWS * statusIconScale(d) + 4;
|
||||||
|
return (STATUS_ICONS_SCALED && ih > lh) ? ih : lh;
|
||||||
|
}
|
||||||
|
// miniIconDrawCentered at an explicit scale (the home page-icon row's).
|
||||||
|
inline void miniIconDrawCenteredScaled(DisplayDriver& d, int cx, int cy, const MiniIcon& ic, int s) {
|
||||||
|
const int x = cx - (ic.w * s) / 2, y = cy - (ic.h * s) / 2;
|
||||||
|
for (int r = 0; r < ic.h; r++)
|
||||||
|
for (int c = 0; c < ic.w; c++)
|
||||||
|
if (ic.rows[r] & (1 << c)) d.fillRect(x + c * s, y + r * s, s, s);
|
||||||
|
}
|
||||||
|
// drawSlotIcon at an explicit scale (the status bar's).
|
||||||
|
inline void drawSlotIconScaled(DisplayDriver& d, int x, int box_w, int box_h, const MiniIcon& ic, int s) {
|
||||||
|
int ix = x + (box_w - ic.w * s) / 2;
|
||||||
|
int iy = (box_h - ic.h * s) / 2;
|
||||||
|
if (ix < x) ix = x;
|
||||||
|
if (iy < 0) iy = 0;
|
||||||
|
for (int r = 0; r < ic.h; r++)
|
||||||
|
for (int c = 0; c < ic.w; c++)
|
||||||
|
if (ic.rows[r] & (1 << c)) d.fillRect(ix + c * s, iy + r * s, s, s);
|
||||||
|
}
|
||||||
|
|
||||||
// Centre a mini-icon inside the slot [x, 0, box_w, box_h] using the current ink
|
// Centre a mini-icon inside the slot [x, 0, box_w, box_h] using the current ink
|
||||||
// colour (no background). Centres on the box itself, not the text line, so the
|
// colour (no background). Centres on the box itself, not the text line, so the
|
||||||
// glyph sits dead-centre regardless of font line height.
|
// glyph sits dead-centre regardless of font line height.
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
; Solo build for Seeed Wio Tracker L1 (Lite) + Good Display GDEQ0426T82
|
||||||
|
; 4.26" 800x480 e-ink, plugged into the board's 24-pin e-ink FPC connector.
|
||||||
|
; Build config only -- no source changes. Extends WioTrackerL1Eink
|
||||||
|
; (variants/wio-tracker-l1-eink/platformio.ini) for its source files and
|
||||||
|
; libraries, but carries its own full build_flags list, so the stock panel's
|
||||||
|
; -D EINK_DISPLAY_MODEL / DISPLAY_ROTATION never reach the compiler too.
|
||||||
|
;
|
||||||
|
; - EINK_DISPLAY_MODEL GxEPD2 driver for this panel (GxEPD2 1.6.2+)
|
||||||
|
; - DISPLAY_ROTATION 0: the panel's native landscape
|
||||||
|
; - RAM: the 800x480 framebuffer is 48 KB (the stock 2.13" needs ~4 KB),
|
||||||
|
; and UiCore plus every screen is allocated from the heap at startup --
|
||||||
|
; a failed allocation there crashes into a boot loop. So:
|
||||||
|
; OFFLINE_QUEUE_SIZE 32 (stock e-ink: 128) frees ~17 KB
|
||||||
|
; MAX_CONTACTS 250 (stock: 350) frees ~18 KB
|
||||||
|
; - EINK_LARGE_FONT 2: the 4.2" build's 9x15 font (and its 9 px icons) --
|
||||||
|
; on hardware it reads much better here than the 6x9 scaled up, which goes
|
||||||
|
; blocky at 4x.
|
||||||
|
; - EINK_FONT_SCALE 2: that font at 2x (18x30 cells, ~44 characters x 16
|
||||||
|
; lines). Also adds Settings > Display > Text size (1x-5x), which starts here.
|
||||||
|
; - STATUS_ICON_SCALE turns on scalable Home status/page icons and adds
|
||||||
|
; Settings > Display > Icon size (Auto = 1.5x the text: 3x at 2x text, 1x-8x).
|
||||||
|
;
|
||||||
|
; Build: pio run -e WioTrackerL1_GDEQ0426T82_companion_solo_dual
|
||||||
|
[env:WioTrackerL1_GDEQ0426T82_companion_solo_dual]
|
||||||
|
extends = WioTrackerL1Eink
|
||||||
|
build_unflags = -Ofast
|
||||||
|
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
|
||||||
|
-I lib/GxEPD2-patch/src
|
||||||
|
-D WIO_TRACKER_L1
|
||||||
|
-D WIO_TRACKER_L1_EINK
|
||||||
|
-D USE_SX1262
|
||||||
|
-D RADIO_CLASS=CustomSX1262
|
||||||
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
|
-D LORA_TX_POWER=22
|
||||||
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
|
-D EINK_DISPLAY_MODEL=GxEPD2_426_GDEQ0426T82
|
||||||
|
-D DISPLAY_ROTATION=0
|
||||||
|
-D JOYSTICK_UPRIGHT_ROTATION=0
|
||||||
|
-D EINK_LARGE_FONT=2
|
||||||
|
-D EINK_FONT_SCALE=2
|
||||||
|
-D STATUS_ICON_SCALE=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=250
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
-D OFFLINE_QUEUE_SIZE=32
|
||||||
|
-D DISPLAY_CLASS=GxEPDDisplay
|
||||||
|
-D BUTTON_USE_INTERRUPTS=1
|
||||||
|
-D UI_HAS_JOYSTICK=1
|
||||||
|
-D UI_HAS_JOYSTICK_UPDOWN=1
|
||||||
|
-D PIN_BUZZER=12
|
||||||
|
-D QSPIFLASH=1
|
||||||
|
-D FIRMWARE_SOLO_BUILD=1
|
||||||
|
-D MESHCORE_VERSION='"1.17.1"'
|
||||||
|
-D UI_SENSORS_PAGE=1
|
||||||
|
-D BLE_PIN_CODE=123456
|
||||||
|
-D DUAL_SERIAL=1
|
||||||
|
-D ENABLE_SCREENSHOT
|
||||||
|
-D KEEP_DISPLAY_ON_USB
|
||||||
|
-D EINK_FAST_WRITES=1
|
||||||
|
-D EINK_SPI_HZ=8000000
|
||||||
|
-Os
|
||||||
|
extra_scripts = post:create-uf2.py
|
||||||
@@ -536,6 +536,7 @@ public:
|
|||||||
virtual void setBrightness(uint8_t level) { } // level 0-4 (min to max), no-op default
|
virtual void setBrightness(uint8_t level) { } // level 0-4 (min to max), no-op default
|
||||||
virtual void setDisplayRotation(uint8_t rot) { } // 0-3, no-op for fixed-orientation displays
|
virtual void setDisplayRotation(uint8_t rot) { } // 0-3, no-op for fixed-orientation displays
|
||||||
virtual void setFullRefreshInterval(uint8_t n) { } // e-ink: do full refresh every n partial refreshes (0=never)
|
virtual void setFullRefreshInterval(uint8_t n) { } // e-ink: do full refresh every n partial refreshes (0=never)
|
||||||
|
virtual void setUiScale(uint8_t font_scale) { } // e-ink: text pixel multiplier, 0 = build default
|
||||||
// Sideways slide between two frames, for screens that page left/right.
|
// Sideways slide between two frames, for screens that page left/right.
|
||||||
// slideBegin() keeps rows y0.. of the frame on screen now; call it before the
|
// slideBegin() keeps rows y0.. of the frame on screen now; call it before the
|
||||||
// next frame is drawn. slideCompose(dx), at the end of drawing that frame,
|
// next frame is drawn. slideCompose(dx), at the end of drawing that frame,
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class EinkGfxDisplay : public DisplayDriver {
|
|||||||
uint16_t _curr;
|
uint16_t _curr;
|
||||||
const GFXfont* _font;
|
const GFXfont* _font;
|
||||||
int _cell_w = 6, _cell_h = 9; // the font's cell, before _px and the text size
|
int _cell_w = 6, _cell_h = 9; // the font's cell, before _px and the text size
|
||||||
int _px = 1; // pixel doubling (landscape without EINK_LARGE_FONT)
|
int _px = 1; // pixel multiplier (landscape: EINK_FONT_PX, or Text size)
|
||||||
|
uint8_t _px_override = 0; // Settings > Display > Text size (0 = build default)
|
||||||
int _text_sz = 1;
|
int _text_sz = 1;
|
||||||
int _cx = 0, _cy = 0; // cursor, top of the text row
|
int _cx = 0, _cy = 0; // cursor, top of the text row
|
||||||
uint32_t _hash = 0, _shown_hash = 0;
|
uint32_t _hash = 0, _shown_hash = 0;
|
||||||
@@ -74,6 +75,13 @@ public:
|
|||||||
dest[dest_size - 1] = '\0';
|
dest[dest_size - 1] = '\0';
|
||||||
}
|
}
|
||||||
bool isEink() override { return true; }
|
bool isEink() override { return true; }
|
||||||
|
// Runtime text scale (Settings > Display > Text size): re-lays out and forces
|
||||||
|
// a full redraw. 0 = the build default (EINK_FONT_SCALE).
|
||||||
|
void setUiScale(uint8_t font_scale) override {
|
||||||
|
if (font_scale == _px_override) return; // unchanged: no full redraw
|
||||||
|
_px_override = font_scale;
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
void startFrame(Color bkg = DARK) override;
|
void startFrame(Color bkg = DARK) override;
|
||||||
void setTextSize(int sz) override;
|
void setTextSize(int sz) override;
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
// the font tables below are static arrays.
|
// the font tables below are static arrays.
|
||||||
|
|
||||||
#include "EinkGfxDisplay.h"
|
#include "EinkGfxDisplay.h"
|
||||||
|
// EINK_FONT_SCALE=<n>: pixel multiplier for the landscape font -- the 6x9, or
|
||||||
|
// the EINK_LARGE_FONT one if the build picks it. 4 on an 800x480 panel with the
|
||||||
|
// 6x9 = 24x36 cells, ~33 characters x 13 lines. Setting it also adds Settings >
|
||||||
|
// Display > Text size, which overrides it at run time. Unset: stock, the 6x9 at
|
||||||
|
// 2x or the large font at its own size.
|
||||||
|
#ifdef EINK_FONT_SCALE
|
||||||
|
#define EINK_FONT_PX EINK_FONT_SCALE
|
||||||
|
#else
|
||||||
|
#define EINK_FONT_PX (EINK_LARGE_FONT ? 1 : 2)
|
||||||
|
#endif
|
||||||
#include "MiscFixedRenderer.h"
|
#include "MiscFixedRenderer.h"
|
||||||
#if EINK_LARGE_FONT == 2
|
#if EINK_LARGE_FONT == 2
|
||||||
#include "MiscFixed9x15Font.h"
|
#include "MiscFixed9x15Font.h"
|
||||||
@@ -20,7 +30,8 @@ EinkGfxDisplay::EinkGfxDisplay(int w, int h, Adafruit_GFX& gfx, uint16_t ink, ui
|
|||||||
void EinkGfxDisplay::updateLayout() {
|
void EinkGfxDisplay::updateLayout() {
|
||||||
const bool landscape = width() >= height();
|
const bool landscape = width() >= height();
|
||||||
_font = (EINK_LARGE_FONT && landscape) ? &EINK_LARGE_GFXFONT : &MiscFixed;
|
_font = (EINK_LARGE_FONT && landscape) ? &EINK_LARGE_GFXFONT : &MiscFixed;
|
||||||
_px = (!EINK_LARGE_FONT && landscape) ? 2 : 1;
|
_px = landscape ? EINK_FONT_PX : 1;
|
||||||
|
if (_px_override && landscape) _px = _px_override; // portrait screens are laid out for 1x
|
||||||
_cell_w = miscFixedCellW(*_font);
|
_cell_w = miscFixedCellW(*_font);
|
||||||
_cell_h = _font->yAdvance;
|
_cell_h = _font->yAdvance;
|
||||||
_vw_dirty = true;
|
_vw_dirty = true;
|
||||||
@@ -87,7 +98,7 @@ void EinkGfxDisplay::fillRect(int x, int y, int w, int h) {
|
|||||||
void EinkGfxDisplay::drawRect(int x, int y, int w, int h) {
|
void EinkGfxDisplay::drawRect(int x, int y, int w, int h) {
|
||||||
mix<int>(x); mix<int>(y); mix<int>(w); mix<int>(h);
|
mix<int>(x); mix<int>(y); mix<int>(w); mix<int>(h);
|
||||||
_gfx.drawRect(x, y, w, h, _curr);
|
_gfx.drawRect(x, y, w, h, _curr);
|
||||||
if (_px == 2 && w > 2 && h > 2) _gfx.drawRect(x + 1, y + 1, w - 2, h - 2, _curr);
|
if (_px >= 2 && w > 2 && h > 2) _gfx.drawRect(x + 1, y + 1, w - 2, h - 2, _curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EinkGfxDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
void EinkGfxDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
// Faster image writes for large GxEPD2 panels.
|
||||||
|
//
|
||||||
|
// GxEPD2 sends the framebuffer to the panel one byte at a time: every byte is
|
||||||
|
// its own SPI.transfer(uint8_t) call, and on nRF52 each of those is a complete
|
||||||
|
// EasyDMA transaction with several microseconds of fixed setup overhead --
|
||||||
|
// independent of the SPI clock. A partial refresh here writes the full screen
|
||||||
|
// five times (GxEPDDisplay::endFrame's inverse-redrive pass, then
|
||||||
|
// GxEPD2_BW::display()), so on an 800x480 panel (48 KB a screen) that overhead
|
||||||
|
// alone blocks the main loop for well over a second per refresh.
|
||||||
|
//
|
||||||
|
// These subclasses override only the image writes, sending each row as one
|
||||||
|
// bulk SPI.transfer(buf, nullptr, n) -- the same bytes, in the same order, to
|
||||||
|
// the same RAM window -- so the cost becomes just the wire time. Everything
|
||||||
|
// else (init, refresh, power) is the stock driver. The first write (panel init
|
||||||
|
// and the initial screen clear) and the rarely used mirror/PGM variants go
|
||||||
|
// through the stock path unchanged.
|
||||||
|
//
|
||||||
|
// Use by naming the subclass as the panel: -D EINK_DISPLAY_MODEL=GxEPD2_426_GDEQ0426T82_Fast
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _GxEPD2_426_GDEQ0426T82_H_
|
||||||
|
class GxEPD2_426_GDEQ0426T82_Fast : public GxEPD2_426_GDEQ0426T82 {
|
||||||
|
typedef GxEPD2_426_GDEQ0426T82 Base;
|
||||||
|
uint8_t _row[WIDTH / 8]; // one panel row, in RAM (EasyDMA can't read flash)
|
||||||
|
|
||||||
|
bool canFast(bool mirror_y, bool pgm) const {
|
||||||
|
return _init_display_done && !_initial_write && !mirror_y && !pgm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same RAM window as the stock driver's (private) _setPartialRamArea():
|
||||||
|
// the gates are reversed on this panel, so y is mirrored and entered decreasing.
|
||||||
|
// Copied from GxEPD2 1.6.2 (src/gdeq/GxEPD2_426_GDEQ0426T82.cpp) -- when
|
||||||
|
// bumping GxEPD2, check the library's version still matches this one.
|
||||||
|
void setRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||||
|
y = HEIGHT - y - h;
|
||||||
|
_writeCommand(0x11);
|
||||||
|
_writeData(0x01);
|
||||||
|
_writeCommand(0x44);
|
||||||
|
_writeData(x % 256);
|
||||||
|
_writeData(x / 256);
|
||||||
|
_writeData((x + w - 1) % 256);
|
||||||
|
_writeData((x + w - 1) / 256);
|
||||||
|
_writeCommand(0x45);
|
||||||
|
_writeData((y + h - 1) % 256);
|
||||||
|
_writeData((y + h - 1) / 256);
|
||||||
|
_writeData(y % 256);
|
||||||
|
_writeData(y / 256);
|
||||||
|
_writeCommand(0x4e);
|
||||||
|
_writeData(x % 256);
|
||||||
|
_writeData(x / 256);
|
||||||
|
_writeCommand(0x4f);
|
||||||
|
_writeData((y + h - 1) % 256);
|
||||||
|
_writeData((y + h - 1) / 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The stock _writeImage(), with the per-byte loop replaced by one bulk
|
||||||
|
// transfer per row (clipping and indexing unchanged). Also from GxEPD2 1.6.2.
|
||||||
|
void fastWrite(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert) {
|
||||||
|
delay(1); // as the stock driver: yield
|
||||||
|
int16_t wb = (w + 7) / 8;
|
||||||
|
x -= x % 8;
|
||||||
|
w = wb * 8;
|
||||||
|
int16_t x1 = x < 0 ? 0 : x;
|
||||||
|
int16_t y1 = y < 0 ? 0 : y;
|
||||||
|
int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x;
|
||||||
|
int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y;
|
||||||
|
int16_t dx = x1 - x;
|
||||||
|
int16_t dy = y1 - y;
|
||||||
|
w1 -= dx;
|
||||||
|
h1 -= dy;
|
||||||
|
if ((w1 <= 0) || (h1 <= 0)) return;
|
||||||
|
setRamArea(x1, y1, w1, h1);
|
||||||
|
_writeCommand(command);
|
||||||
|
_startTransfer();
|
||||||
|
const int16_t nb = w1 / 8;
|
||||||
|
for (int16_t i = 0; i < h1; i++) {
|
||||||
|
const uint8_t* src = &bitmap[dx / 8 + int32_t(i + dy) * wb];
|
||||||
|
if (invert) { for (int16_t j = 0; j < nb; j++) _row[j] = ~src[j]; }
|
||||||
|
else { memcpy(_row, src, nb); }
|
||||||
|
_pSPIx->transfer(_row, nullptr, nb);
|
||||||
|
}
|
||||||
|
_endTransfer();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
GxEPD2_426_GDEQ0426T82_Fast(int16_t cs, int16_t dc, int16_t rst, int16_t busy) : Base(cs, dc, rst, busy) {}
|
||||||
|
using Base::writeImage; // keep the other overloads visible
|
||||||
|
|
||||||
|
void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h,
|
||||||
|
bool invert = false, bool mirror_y = false, bool pgm = false) override {
|
||||||
|
if (!canFast(mirror_y, pgm)) { Base::writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
||||||
|
fastWrite(0x24, bitmap, x, y, w, h, invert);
|
||||||
|
}
|
||||||
|
void writeImageForFullRefresh(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h,
|
||||||
|
bool invert = false, bool mirror_y = false, bool pgm = false) override {
|
||||||
|
if (!canFast(mirror_y, pgm)) { Base::writeImageForFullRefresh(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
||||||
|
fastWrite(0x26, bitmap, x, y, w, h, invert); // set previous
|
||||||
|
fastWrite(0x24, bitmap, x, y, w, h, invert); // set current
|
||||||
|
}
|
||||||
|
void writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h,
|
||||||
|
bool invert = false, bool mirror_y = false, bool pgm = false) override {
|
||||||
|
if (!canFast(mirror_y, pgm)) { Base::writeImageAgain(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
||||||
|
fastWrite(0x26, bitmap, x, y, w, h, invert); // set previous
|
||||||
|
fastWrite(0x24, bitmap, x, y, w, h, invert); // set current
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
@@ -14,8 +14,16 @@
|
|||||||
SPIClass SPI1 = SPIClass(FSPI);
|
SPIClass SPI1 = SPIClass(FSPI);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// SPI clock for the panel. 4 MHz is the conservative default every board has
|
||||||
|
// always used; a short FPC run can go faster (SSD16xx-family controllers are
|
||||||
|
// rated ~20 MHz for writes). On nRF52840 the Adafruit core puts SPI1 on SPIM2,
|
||||||
|
// which caps at 8 MHz -- higher requests are silently clamped to 8 MHz there.
|
||||||
|
#ifndef EINK_SPI_HZ
|
||||||
|
#define EINK_SPI_HZ 4000000
|
||||||
|
#endif
|
||||||
|
|
||||||
bool GxEPDDisplay::begin() {
|
bool GxEPDDisplay::begin() {
|
||||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
display.epd2.selectSPI(SPI1, SPISettings(EINK_SPI_HZ, MSBFIRST, SPI_MODE0));
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS);
|
SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS);
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -18,6 +18,18 @@
|
|||||||
#include <GxEPD2_7C.h>
|
#include <GxEPD2_7C.h>
|
||||||
|
|
||||||
#include "EinkGfxDisplay.h"
|
#include "EinkGfxDisplay.h"
|
||||||
|
#include "GxEPD2_FastWrite.h"
|
||||||
|
|
||||||
|
// EINK_FAST_WRITES=1: drive the panel through its <model>_Fast subclass
|
||||||
|
// (GxEPD2_FastWrite.h) -- the same bytes, sent a row at a time in bulk instead
|
||||||
|
// of one SPI call per byte. Only for panels that have one.
|
||||||
|
#define EINK_CAT_(a, b) a##b
|
||||||
|
#define EINK_CAT(a, b) EINK_CAT_(a, b)
|
||||||
|
#if defined(EINK_DISPLAY_MODEL) && defined(EINK_FAST_WRITES) && EINK_FAST_WRITES
|
||||||
|
#define EINK_PANEL_TYPE EINK_CAT(EINK_DISPLAY_MODEL, _Fast)
|
||||||
|
#elif defined(EINK_DISPLAY_MODEL)
|
||||||
|
#define EINK_PANEL_TYPE EINK_DISPLAY_MODEL
|
||||||
|
#endif
|
||||||
|
|
||||||
// This driver calls callBusyPump() during its BUSY-pin waits; app code keys
|
// This driver calls callBusyPump() during its BUSY-pin waits; app code keys
|
||||||
// its setBusyPumpFn() wiring off this so it isn't compiled for displays (or
|
// its setBusyPumpFn() wiring off this so it isn't compiled for displays (or
|
||||||
@@ -53,8 +65,8 @@
|
|||||||
// The panel object, as a base so it's built before EinkGfxDisplay draws on it.
|
// The panel object, as a base so it's built before EinkGfxDisplay draws on it.
|
||||||
struct GxEPDPanel {
|
struct GxEPDPanel {
|
||||||
#if defined(EINK_DISPLAY_MODEL)
|
#if defined(EINK_DISPLAY_MODEL)
|
||||||
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> display;
|
GxEPD2_BW<EINK_PANEL_TYPE, EINK_PANEL_TYPE::HEIGHT> display;
|
||||||
GxEPDPanel() : display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {}
|
GxEPDPanel() : display(EINK_PANEL_TYPE(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {}
|
||||||
#else
|
#else
|
||||||
GxEPD2_BW<GxEPD2_150_BN, 200> display;
|
GxEPD2_BW<GxEPD2_150_BN, 200> display;
|
||||||
GxEPDPanel() : display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {}
|
GxEPDPanel() : display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user