mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-15 16:01:19 +10:00
* Add shared e-ink hardware layer (graphics/eink) alongside legacy drivers Foundation for a target-by-target migration off the GxEPD2-based EInkDisplay2/EInkDynamicDisplay/EInkParallelDisplay stack: - src/graphics/eink/: chipset drivers, panel profiles, backlight helper (promoted from the InkHUD driver set, shared by BaseUI and InkHUD) - src/graphics/BaseUIEInkDisplay: OLEDDisplay adapter driving the new layer, with EINK_* compat macros matching EInkDynamicDisplay - [niche] build helper in platformio.ini; graphics/eink/ excluded from arduino_base so unconverted targets are unaffected - Screen/CannedMessageModule dispatch between the two stacks per env - InkHUD-specific touch code in TouchScreenImpl1 guarded with MESHTASTIC_INCLUDE_INKHUD (no-op today, required once BaseUI variants define MESHTASTIC_INCLUDE_NICHE_GRAPHICS without InkHUD) No variant is converted and no legacy file is removed; every existing env builds identical firmware. * Fix clang-format comment alignment in Screen.cpp * Address review findings in the e-ink driver layer - Screen.cpp: exclude InkHUD builds from all NicheGraphics BaseUI guards - BaseUIEInkDisplay: size the OLEDDisplay buffer from its actual indexing - EInkParallel: defer update() while an async refresh is in flight, honor the selected clear mode in the async task, never delete a live task - ED047TC1: clean up on failed initPanel, fix inverted bbepI2CWrite checks - UC8175: drop bogus 0x12 soft reset (0x12 is display refresh on UC8175) - LCMEN2R13EFC1: guard absent reset pin, bound the busy wait - SSD16XX/SSD1682: build the RAM window from the instance, not statics - Doc corrections in driver banners and Drivers/README * SSD16XX/SSD1682: send inclusive Y-end address (height - 1) * LCMEN213EFC1: adopt the shared wait timeout / fail-through pattern wait() now bounds the busy poll via Throttle and sets the EInk failed flag on timeout; sendCommand/sendData fail through like the SSD16XX and UC8175 drivers. EInk::runOnce clears the flag after the failed cycle.
209 lines
6.1 KiB
C++
209 lines
6.1 KiB
C++
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
|
|
#include "./BaseUIEInkDisplay.h"
|
|
|
|
#include "configuration.h"
|
|
#include "main.h"
|
|
|
|
using namespace NicheGraphics;
|
|
|
|
BaseUIEInkDisplay::BaseUIEInkDisplay(Drivers::EInk *driver, uint8_t rotation) : driver(driver), rotation(rotation & 0x3)
|
|
{
|
|
this->geometry = GEOMETRY_RAWMODE;
|
|
|
|
// BaseUI draws in UI orientation. Physical panel dimensions are swapped for 90°/270°.
|
|
const bool swap = (this->rotation == 1) || (this->rotation == 3);
|
|
this->displayWidth = swap ? driver->height : driver->width;
|
|
this->displayHeight = swap ? driver->width : driver->height;
|
|
|
|
// OLEDDisplay indexes buffer[x + (y/8) * displayWidth]
|
|
this->displayBufferSize = displayWidth * ((displayHeight + 7) / 8);
|
|
|
|
// Panel-native row-major buffer
|
|
panelRowBytes = ((driver->width - 1) / 8) + 1;
|
|
panelBufferSize = panelRowBytes * driver->height;
|
|
panelBuffer = new uint8_t[panelBufferSize];
|
|
memset(panelBuffer, 0xFF, panelBufferSize); // All white
|
|
}
|
|
|
|
BaseUIEInkDisplay::~BaseUIEInkDisplay()
|
|
{
|
|
delete[] panelBuffer;
|
|
}
|
|
|
|
bool BaseUIEInkDisplay::connect()
|
|
{
|
|
LOG_INFO("Init BaseUI E-Ink (%u x %u, rot %u)", driver->width, driver->height, rotation);
|
|
return true;
|
|
}
|
|
|
|
void BaseUIEInkDisplay::addFrameFlag(frameFlagTypes flag)
|
|
{
|
|
frameFlags = (frameFlagTypes)(frameFlags | flag);
|
|
}
|
|
|
|
void BaseUIEInkDisplay::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
|
|
{
|
|
this->fastPerFull = (fastPerFull == 0) ? 1 : fastPerFull;
|
|
this->stressMultiplier = stressMultiplier;
|
|
}
|
|
|
|
void BaseUIEInkDisplay::joinAsyncRefresh()
|
|
{
|
|
if (driver->busy())
|
|
driver->await();
|
|
}
|
|
|
|
// OLEDDisplayUi tick path. Honours rate-limit unless flags demand otherwise.
|
|
void BaseUIEInkDisplay::display()
|
|
{
|
|
const bool demandFast = frameFlags & DEMAND_FAST;
|
|
const bool cosmetic = frameFlags & COSMETIC;
|
|
const bool unlimitedFast = frameFlags & UNLIMITED_FAST;
|
|
|
|
if (!demandFast && !cosmetic && !unlimitedFast) {
|
|
if (!forceDisplay(lastDrawMsec == 0 ? 0 : 1000))
|
|
return;
|
|
return;
|
|
}
|
|
|
|
forceDisplay(0);
|
|
}
|
|
|
|
// Keyframe path. Returns true if a frame was pushed (sets lastDrawMsec).
|
|
bool BaseUIEInkDisplay::forceDisplay(uint32_t msecLimit)
|
|
{
|
|
const uint32_t now = millis();
|
|
if (lastDrawMsec != 0 && (now - lastDrawMsec) < msecLimit)
|
|
return false;
|
|
|
|
const bool blocking = frameFlags & BLOCKING;
|
|
Drivers::EInk::UpdateTypes type = decide();
|
|
|
|
// Don't pile frames on top of a running update - wait it out.
|
|
if (driver->busy())
|
|
driver->await();
|
|
|
|
const bool pushed = commit(type, blocking);
|
|
if (pushed)
|
|
lastDrawMsec = now;
|
|
|
|
// Reset flags for next frame
|
|
frameFlags = BACKGROUND;
|
|
return pushed;
|
|
}
|
|
|
|
bool BaseUIEInkDisplay::commit(Drivers::EInk::UpdateTypes type, bool blocking)
|
|
{
|
|
uint32_t hash = repack();
|
|
|
|
// Skip if frame unchanged. Exception: caller explicitly wants a refresh (COSMETIC or FULL).
|
|
if (hash == lastHash && type != Drivers::EInk::UpdateTypes::FULL && lastDrawMsec != 0)
|
|
return false;
|
|
lastHash = hash;
|
|
|
|
// Fall back to FULL on panels that don't advertise FAST support.
|
|
if (type == Drivers::EInk::UpdateTypes::FAST && !driver->supports(Drivers::EInk::UpdateTypes::FAST))
|
|
type = Drivers::EInk::UpdateTypes::FULL;
|
|
|
|
driver->update(panelBuffer, type);
|
|
|
|
if (blocking)
|
|
driver->await();
|
|
return true;
|
|
}
|
|
|
|
Drivers::EInk::UpdateTypes BaseUIEInkDisplay::decide()
|
|
{
|
|
typedef Drivers::EInk::UpdateTypes UT;
|
|
|
|
const bool unlimitedFast = frameFlags & UNLIMITED_FAST;
|
|
|
|
// Explicit flag wins outright
|
|
if (frameFlags & COSMETIC) {
|
|
fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
|
|
return UT::FULL;
|
|
}
|
|
if (frameFlags & DEMAND_FAST) {
|
|
if (!unlimitedFast) {
|
|
fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
|
|
}
|
|
return UT::FAST;
|
|
}
|
|
|
|
const bool explicitFast = frameFlags & RESPONSIVE;
|
|
|
|
if (explicitFast || unlimitedFast) {
|
|
if (!unlimitedFast) {
|
|
fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
|
|
}
|
|
return UT::FAST;
|
|
}
|
|
|
|
// BACKGROUND / unspecified: let debt decide
|
|
if (fullRefreshDebt >= 1.0f) {
|
|
fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
|
|
return UT::FULL;
|
|
}
|
|
fullRefreshDebt += 1.0f / fastPerFull;
|
|
return UT::FAST;
|
|
}
|
|
|
|
uint32_t BaseUIEInkDisplay::repack()
|
|
{
|
|
memset(panelBuffer, 0xFF, panelBufferSize); // start all-white
|
|
|
|
const uint16_t pw = driver->width;
|
|
const uint16_t ph = driver->height;
|
|
|
|
// OLEDDisplay buffer: byte = buffer[x + (y/8) * displayWidth]; bit = 1 << (y & 7); 1 = black
|
|
// Niche buffer: byte = (y * panelRowBytes) + (x/8); bit = 1 << (7 - x%8); 1 = white
|
|
for (uint16_t oy = 0; oy < displayHeight; oy++) {
|
|
for (uint16_t ox = 0; ox < displayWidth; ox++) {
|
|
const uint8_t b = buffer[ox + (oy / 8) * displayWidth];
|
|
const bool isBlack = b & (1 << (oy & 7));
|
|
|
|
uint16_t px, py;
|
|
switch (rotation) {
|
|
case 1: // 90° CW: OLED (ox,oy) → panel (pw-1-oy, ox)
|
|
px = pw - 1 - oy;
|
|
py = ox;
|
|
break;
|
|
case 2: // 180°
|
|
px = pw - 1 - ox;
|
|
py = ph - 1 - oy;
|
|
break;
|
|
case 3: // 270° CW
|
|
px = oy;
|
|
py = ph - 1 - ox;
|
|
break;
|
|
case 0:
|
|
default:
|
|
px = ox;
|
|
py = oy;
|
|
break;
|
|
}
|
|
|
|
if (px >= pw || py >= ph)
|
|
continue;
|
|
|
|
const uint32_t byteNum = (py * panelRowBytes) + (px / 8);
|
|
const uint8_t bitNum = 7 - (px % 8);
|
|
if (isBlack)
|
|
panelBuffer[byteNum] &= ~(1 << bitNum);
|
|
else
|
|
panelBuffer[byteNum] |= (1 << bitNum);
|
|
}
|
|
}
|
|
|
|
// FNV-1a
|
|
uint32_t h = 2166136261u;
|
|
for (uint32_t i = 0; i < panelBufferSize; i++) {
|
|
h ^= panelBuffer[i];
|
|
h *= 16777619u;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|