kopia lustrzana https://github.com/meshtastic/firmware
Async full-refresh for EInkDynamicDisplay (#3339)
* Move Wireless Paper V1.1 custom hibernate behavior to GxEPD2 * Async full-refresh for EInkDynamicDisplay * initial config for T-Echo * formatting responds to https://github.com/meshtastic/firmware/pull/3339#discussion_r1518175434 * increase fast-refresh limit for T-Echo https://github.com/meshtastic/firmware/pull/3339#issuecomment-1986245727 * change dependency from private repo to meshtastic/GxEPD2 --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>pull/3374/head
rodzic
892223a297
commit
cf4753f7fd
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
||||
#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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
|
@ -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,
|
||||
|
@ -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?
|
||||
|
@ -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
|
|
@ -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
|
|
@ -5,7 +5,6 @@
|
|||
#define I2C_SCL SCL
|
||||
|
||||
#define USE_EINK
|
||||
#define EINK_NO_HIBERNATE
|
||||
|
||||
/*
|
||||
* eink display pins
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue