Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async full-refresh for EInkDynamicDisplay #3339

Merged
merged 9 commits into from
Mar 11, 2024
25 changes: 11 additions & 14 deletions src/graphics/EInkDisplay2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,24 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
}
}

// Trigger the refresh in GxEPD2
LOG_DEBUG("Updating E-Paper... ");

#if false
// Currently unused; rescued from commented-out line during a refactor
// Use a meaningful macro here if variant doesn't want fast refresh

// Full update mode (slow)
adafruitDisplay->display(false)
#else
// Fast update mode
adafruitDisplay->nextPage();
#endif

#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory
// Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
adafruitDisplay->hibernate();
#endif
// End the update process
endUpdate();

LOG_DEBUG("done\n");
return true;
}

// End the update process - virtual method, overriden in derived class
void EInkDisplay::endUpdate()
{
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
adafruitDisplay->hibernate();
}

// Write the buffer to the display memory
void EInkDisplay::display(void)
{
Expand Down Expand Up @@ -188,6 +184,7 @@ bool EInkDisplay::connect()
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
}
#elif defined(PCA10059)
{
Expand Down
7 changes: 7 additions & 0 deletions src/graphics/EInkDisplay2.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ class EInkDisplay : public OLEDDisplay
*/
virtual bool forceDisplay(uint32_t msecLimit = 1000);

/**
* Run any code needed to complete an update, after the physical refresh has completed.
* Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
*
*/
virtual void endUpdate();

/**
* shim to make the abstraction happy
*
Expand Down
90 changes: 81 additions & 9 deletions src/graphics/EInkDynamicDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,29 @@ void EInkDynamicDisplay::adjustRefreshCounters()
// Trigger the display update by calling base class
bool EInkDynamicDisplay::update()
{
// Detemine the refresh mode to use, and start the update
bool refreshApproved = determineMode();
if (refreshApproved)
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
return refreshApproved; // (Unutilized) Base class promises to return true if update ran

#if defined(HAS_EINK_ASYNCFULL)
if (refreshApproved)
endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh)
#endif

return refreshApproved; // (Unutilized) Base class promises to return true if update ran
}

// Assess situation, pick a refresh type
bool EInkDynamicDisplay::determineMode()
{
checkWasFlooded();
checkForPromotion();
#if defined(HAS_EINK_ASYNCFULL)
checkAsyncFullRefresh();
#endif
checkRateLimiting();

// If too soon for a new time, abort here
// If too soon for a new frame, or display busy, abort early
if (refresh == SKIPPED) {
storeAndReset();
return false; // No refresh
Expand All @@ -116,7 +126,7 @@ bool EInkDynamicDisplay::determineMode()

resetRateLimiting(); // Once determineMode() ends, will have to wait again
hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry
LOG_DEBUG("determineMode(): "); // Begin log entry

// Once mode determined, any remaining checks will bypass
checkCosmetic();
Expand Down Expand Up @@ -151,13 +161,25 @@ bool EInkDynamicDisplay::determineMode()
}
}

// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh?
void EInkDynamicDisplay::checkWasFlooded()
// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
void EInkDynamicDisplay::checkForPromotion()
{
if (previousReason == EXCEEDED_RATELIMIT_FAST) {
// If so, allow a BACKGROUND frame to draw as RESPONSIVE
// Because we DID want a RESPONSIVE frame last time, we just didn't get it
// If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
// Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it

switch (previousReason) {
case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
setFrameFlag(DEMAND_FAST);
break;
case ASYNC_REFRESH_BLOCKED_COSMETIC:
setFrameFlag(COSMETIC);
break;
case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
case EXCEEDED_RATELIMIT_FAST:
setFrameFlag(RESPONSIVE);
break;
default:
break;
}
}

Expand Down Expand Up @@ -381,4 +403,54 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
}
#endif // EINK_LIMIT_GHOSTING_PX

#ifdef HAS_EINK_ASYNCFULL
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready
void EInkDynamicDisplay::checkAsyncFullRefresh()
{
// No refresh taking place, continue with determineMode()
if (!asyncRefreshRunning)
return;

// Full refresh still running
if (adafruitDisplay->epd2.isBusy()) {
// No refresh
refresh = SKIPPED;

// Set the reason, marking what type of frame we're skipping
if (frameFlags & DEMAND_FAST)
reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
else if (frameFlags & COSMETIC)
reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
else if (frameFlags & RESPONSIVE)
reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
else
reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;

return;
}

// If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
LOG_DEBUG("Async full-refresh complete\n");

// Note: this code only works because of a modification to meshtastic/GxEPD2.
// It is only equipped to intercept calls to nextPage()
}

// Figure out who runs the post-update code
void EInkDynamicDisplay::endOrDetach()
{
if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop.
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop.
LOG_DEBUG("Async full-refresh begins\n");
}

// Fast Refresh
else
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
}
#endif // HAS_EINK_ASYNCFULL

#endif // USE_EINK_DYNAMICDISPLAY
15 changes: 14 additions & 1 deletion src/graphics/EInkDynamicDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class EInkDynamicDisplay : public EInkDisplay
};
enum reasonTypes : uint8_t { // How was the decision reached
NO_OBJECTIONS,
ASYNC_REFRESH_BLOCKED_DEMANDFAST,
ASYNC_REFRESH_BLOCKED_COSMETIC,
ASYNC_REFRESH_BLOCKED_RESPONSIVE,
ASYNC_REFRESH_BLOCKED_BACKGROUND,
DISPLAY_NOT_READY_FOR_FULL,
EXCEEDED_RATELIMIT_FAST,
EXCEEDED_RATELIMIT_FULL,
FLAGGED_COSMETIC,
Expand All @@ -64,7 +69,7 @@ class EInkDynamicDisplay : public EInkDisplay
bool update(); // Trigger the display update - determine mode, then call base class

// Checks as part of determineMode()
void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC?
void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
void checkRateLimiting(); // Is this frame too soon?
void checkCosmetic(); // Was the COSMETIC flag set?
void checkDemandingFast(); // Was the DEMAND_FAST flag set?
Expand Down Expand Up @@ -99,6 +104,14 @@ class EInkDynamicDisplay : public EInkDisplay
uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
#endif

// Conditional - async full refresh - only with modified meshtastic/GxEPD2
#if defined(HAS_EINK_ASYNCFULL)
void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh()
void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh()
#endif
};

#endif
2 changes: 1 addition & 1 deletion variants/heltec_wireless_paper/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ build_flags =
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
lib_deps =
${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2
https://github.com/meshtastic/GxEPD2/
adafruit/Adafruit BusIO@^1.13.2
lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200
1 change: 0 additions & 1 deletion variants/heltec_wireless_paper/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#define I2C_SCL SCL

#define USE_EINK
#define EINK_NO_HIBERNATE

/*
* eink display pins
Expand Down
8 changes: 7 additions & 1 deletion variants/t-echo/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo
-DEINK_DISPLAY_MODEL=GxEPD2_154_D67
-DEINK_WIDTH=200
-DEINK_HEIGHT=200
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
-DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted
-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
-DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo>
lib_deps =
${nrf52840_base.lib_deps}
https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61
https://github.com/meshtastic/GxEPD2
adafruit/Adafruit BusIO@^1.13.2
lewisxhe/PCF8563_Library@^1.0.1
;upload_protocol = fs
Loading