mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-14 17:12:08 +00:00
E-Ink: additional conditions for "Dynamic Partial" mode (#3256)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
parent
eb8a12e5a2
commit
78b4a65635
@ -362,6 +362,12 @@ void EInkDisplay::lowPriority()
|
|||||||
isHighPriority = false;
|
isHighPriority = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Full-refresh is explicitly requested for next one update - no skipping please
|
||||||
|
void EInkDisplay::demandFullRefresh()
|
||||||
|
{
|
||||||
|
demandingFull = true;
|
||||||
|
}
|
||||||
|
|
||||||
// configure display for partial-refresh
|
// configure display for partial-refresh
|
||||||
void EInkDisplay::configForPartialRefresh()
|
void EInkDisplay::configForPartialRefresh()
|
||||||
{
|
{
|
||||||
@ -384,6 +390,49 @@ void EInkDisplay::configForFullRefresh()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef EINK_PARTIAL_ERASURE_LIMIT
|
||||||
|
// Count black pixels in an image. Used for "erasure tracking"
|
||||||
|
int32_t EInkDisplay::countBlackPixels()
|
||||||
|
{
|
||||||
|
int32_t blackCount = 0; // Signed, to avoid underflow when comparing
|
||||||
|
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
|
||||||
|
for (uint8_t i = 0; i < 7; i++) {
|
||||||
|
// Check if each bit is black or white
|
||||||
|
blackCount += (buffer[b] >> i) & 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blackCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the (rough) amount of black->white pixel change since last full refresh
|
||||||
|
bool EInkDisplay::tooManyErasures()
|
||||||
|
{
|
||||||
|
// Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes
|
||||||
|
// but that would require substantially more "code tampering"
|
||||||
|
|
||||||
|
// Get the black pixel stats for this image
|
||||||
|
int32_t blackCount = countBlackPixels();
|
||||||
|
int32_t blackDifference = blackCount - prevBlackCount;
|
||||||
|
|
||||||
|
// Update the running total of "erasures" - black pixels which have become white, since last full-refresh
|
||||||
|
if (blackDifference < 0)
|
||||||
|
erasedSinceFull -= blackDifference;
|
||||||
|
|
||||||
|
// Store black pixel count for next time
|
||||||
|
prevBlackCount = blackCount;
|
||||||
|
|
||||||
|
// Log the running total - help devs setup new boards
|
||||||
|
LOG_DEBUG("Dynamic Partial: erasedSinceFull=%hu, EINK_PARTIAL_ERASURE_LIMIT=%hu\n", erasedSinceFull,
|
||||||
|
EINK_PARTIAL_ERASURE_LIMIT);
|
||||||
|
|
||||||
|
// Check if too many pixels have been erased
|
||||||
|
if (erasedSinceFull > EINK_PARTIAL_ERASURE_LIMIT)
|
||||||
|
return true; // Too many
|
||||||
|
else
|
||||||
|
return false; // Still okay
|
||||||
|
}
|
||||||
|
#endif // ifdef EINK_PARTIAL_BRIGHTEN_LIMIT_PX
|
||||||
|
|
||||||
bool EInkDisplay::newImageMatchesOld()
|
bool EInkDisplay::newImageMatchesOld()
|
||||||
{
|
{
|
||||||
uint32_t newImageHash = 0;
|
uint32_t newImageHash = 0;
|
||||||
@ -416,16 +465,20 @@ bool EInkDisplay::determineRefreshMode()
|
|||||||
missedHighPriorityUpdate = false;
|
missedHighPriorityUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort: if too soon for a new frame
|
// Abort: if too soon for a new frame (unless demanding full)
|
||||||
if (isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) {
|
if (!demandingFull && isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) {
|
||||||
LOG_DEBUG("Update skipped: exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n");
|
LOG_DEBUG("Dynamic Partial: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n");
|
||||||
missedHighPriorityUpdate = true;
|
missedHighPriorityUpdate = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!isHighPriority && sinceLast < lowPriorityLimitMsec) {
|
if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If demanded full refresh: give it to them
|
||||||
|
if (demandingFull)
|
||||||
|
needsFull = true;
|
||||||
|
|
||||||
// Check if old image (partial) should be redrawn (as full), for image quality
|
// Check if old image (partial) should be redrawn (as full), for image quality
|
||||||
if (partialRefreshCount > 0 && !isHighPriority)
|
if (partialRefreshCount > 0 && !isHighPriority)
|
||||||
needsFull = true;
|
needsFull = true;
|
||||||
@ -434,7 +487,14 @@ bool EInkDisplay::determineRefreshMode()
|
|||||||
if (partialRefreshCount >= partialRefreshLimit)
|
if (partialRefreshCount >= partialRefreshLimit)
|
||||||
needsFull = true;
|
needsFull = true;
|
||||||
|
|
||||||
|
#ifdef EINK_PARTIAL_ERASURE_LIMIT
|
||||||
|
// Some displays struggle with erasing black pixels to white, during partial refresh
|
||||||
|
if (tooManyErasures())
|
||||||
|
needsFull = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
// If image matches
|
// If image matches
|
||||||
|
// (Block must run, even if full already selected, to store hash for next time)
|
||||||
if (newImageMatchesOld()) {
|
if (newImageMatchesOld()) {
|
||||||
// If low priority: limit rate
|
// If low priority: limit rate
|
||||||
// otherwise, every loop() will run the hash method
|
// otherwise, every loop() will run the hash method
|
||||||
@ -453,9 +513,11 @@ bool EInkDisplay::determineRefreshMode()
|
|||||||
if (partialRefreshCount > 0)
|
if (partialRefreshCount > 0)
|
||||||
configForFullRefresh();
|
configForFullRefresh();
|
||||||
|
|
||||||
LOG_DEBUG("Conditions met for full-refresh\n");
|
LOG_DEBUG("Dynamic Partial: conditions met for full-refresh\n");
|
||||||
partialRefreshCount = 0;
|
partialRefreshCount = 0;
|
||||||
needsFull = false;
|
needsFull = false;
|
||||||
|
demandingFull = false;
|
||||||
|
erasedSinceFull = 0; // Reset the count for EINK_PARTIAL_ERASURE_LIMIT - tracks ghosting buildup
|
||||||
}
|
}
|
||||||
|
|
||||||
// If options allow a partial refresh
|
// If options allow a partial refresh
|
||||||
@ -463,7 +525,7 @@ bool EInkDisplay::determineRefreshMode()
|
|||||||
if (partialRefreshCount == 0)
|
if (partialRefreshCount == 0)
|
||||||
configForPartialRefresh();
|
configForPartialRefresh();
|
||||||
|
|
||||||
LOG_DEBUG("Conditions met for partial-refresh\n");
|
LOG_DEBUG("Dynamic Partial: conditions met for partial-refresh\n");
|
||||||
partialRefreshCount++;
|
partialRefreshCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +64,10 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
|
|
||||||
// Use full refresh if EITHER:
|
// Use full refresh if EITHER:
|
||||||
// * lowPriority() was set
|
// * lowPriority() was set
|
||||||
|
// * demandFullRefresh() was called - (single shot)
|
||||||
// * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT)
|
// * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT)
|
||||||
// * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
// * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
||||||
|
// * (optional) too many "erasures" since full-refresh (black pixels cleared to white)
|
||||||
|
|
||||||
// Rate limit if:
|
// Rate limit if:
|
||||||
// * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
// * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS)
|
||||||
@ -74,6 +76,7 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
// Skip update entirely if ALL criteria met:
|
// Skip update entirely if ALL criteria met:
|
||||||
// * new image matches old image
|
// * new image matches old image
|
||||||
// * lowPriority()
|
// * lowPriority()
|
||||||
|
// * no call to demandFullRefresh()
|
||||||
// * not redrawing for image quality
|
// * not redrawing for image quality
|
||||||
// * not refreshing for display health
|
// * not refreshing for display health
|
||||||
|
|
||||||
@ -89,24 +92,33 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
#define EINK_LOWPRIORITY_LIMIT_SECONDS 30
|
#define EINK_LOWPRIORITY_LIMIT_SECONDS 30
|
||||||
#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
|
#define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
|
||||||
#define EINK_PARTIAL_REPEAT_LIMIT 5
|
#define EINK_PARTIAL_REPEAT_LIMIT 5
|
||||||
|
#define EINK_PARTIAL_ERASURE_LIMIT 300 // optional
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void highPriority(); // Suggest partial refresh
|
void highPriority(); // Suggest partial refresh
|
||||||
void lowPriority(); // Suggest full refresh
|
void lowPriority(); // Suggest full refresh
|
||||||
|
void demandFullRefresh(); // For next update: explicitly request full refresh
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void configForPartialRefresh(); // Display specific code to select partial refresh mode
|
void configForPartialRefresh(); // Display specific code to select partial refresh mode
|
||||||
void configForFullRefresh(); // Display specific code to return to full refresh mode
|
void configForFullRefresh(); // Display specific code to return to full refresh mode
|
||||||
bool newImageMatchesOld(); // Is the new update actually different to the last image?
|
bool newImageMatchesOld(); // Is the new update actually different to the last image?
|
||||||
bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update
|
bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update
|
||||||
|
#ifdef EINK_PARTIAL_ERASURE_LIMIT
|
||||||
|
int32_t countBlackPixels(); // Calculate the number of black pixels in the new image
|
||||||
|
bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh?
|
||||||
|
#endif
|
||||||
|
|
||||||
bool isHighPriority = true; // Does the method calling update believe that this is urgent?
|
bool isHighPriority = true; // Does the method calling update believe that this is urgent?
|
||||||
bool needsFull = false; // Is a full refresh forced? (display health)
|
bool needsFull = false; // Is a full refresh forced? (display health)
|
||||||
|
bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc)
|
||||||
bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting?
|
bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting?
|
||||||
uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh?
|
uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh?
|
||||||
uint32_t lastUpdateMsec = 0; // When did the last update occur?
|
uint32_t lastUpdateMsec = 0; // When did the last update occur?
|
||||||
uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not)
|
uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not)
|
||||||
|
int32_t prevBlackCount = 0; // How many black pixels were in the previous image
|
||||||
|
uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly)
|
||||||
|
|
||||||
// Set in variant.h
|
// Set in variant.h
|
||||||
const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes
|
const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes
|
||||||
@ -117,5 +129,6 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
// Tolerate calls to these methods anywhere, just to be safe
|
// Tolerate calls to these methods anywhere, just to be safe
|
||||||
void highPriority() {}
|
void highPriority() {}
|
||||||
void lowPriority() {}
|
void lowPriority() {}
|
||||||
|
void demandFullRefresh() {}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user