mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 14:54:44 -05:00
fast refresh for everything
This commit is contained in:
1 parent
acc5afd75b
commit
8d7d5cdd18
16 files changed
+191
-142
No files matched your search
@@ -1,4 +1,4 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) && !defined(MESHTASTIC_INCLUDE_INKHUD)
|
||||
|
||||
#include "./BaseUIEInkDisplay.h"
|
||||
#include "UptimeClock.h"
|
||||
|
||||
@@ -11,7 +11,7 @@ Replaces the per-board branching in EInkDisplay2 / EInkDynamicDisplay / EInkPara
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#if defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) && !defined(MESHTASTIC_INCLUDE_INKHUD)
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
|
||||
@@ -140,15 +140,15 @@ class SafeFastEPD : public FASTEPD
|
||||
_state.pfnEinkPower = safeEPDiyV7EinkPower;
|
||||
#endif
|
||||
}
|
||||
int partialUpdateWithWhiteFinish(uint32_t whitePixels)
|
||||
int partialUpdateWithWhiteFinish(uint32_t whitePixels, bool cleanup = false)
|
||||
{
|
||||
if (dmaFault || _state.mode != BB_MODE_1BPP || _state.native_width != 960 || _state.native_height != 540 ||
|
||||
_state.rotation != 0 || (_state.iFlags & (BB_PANEL_FLAG_MIRROR_X | BB_PANEL_FLAG_MIRROR_Y)) ||
|
||||
_state.panelDef.iLinePadding || !_state.pTemp || !_state.dma_buf)
|
||||
return BBEP_ERROR_BAD_PARAMETER;
|
||||
int rc = FASTEPD::partialUpdate(true, 0, 539);
|
||||
if (rc == BBEP_SUCCESS && whitePixels)
|
||||
rc = finishWhitePixels(_state.pTemp, whiteFinishPasses);
|
||||
if (rc == BBEP_SUCCESS && (whitePixels || cleanup))
|
||||
rc = finishMonochromePixels(_state.pTemp, whiteFinishPasses, cleanup);
|
||||
if (rc == BBEP_SUCCESS)
|
||||
rc = writePulseFrame(nullptr);
|
||||
const int offRc = einkPower(0);
|
||||
@@ -342,9 +342,9 @@ class SafeFastEPD : public FASTEPD
|
||||
#endif
|
||||
bool dmaFault = false;
|
||||
|
||||
int finishWhitePixels(uint8_t *plane, unsigned passes)
|
||||
int finishMonochromePixels(uint8_t *plane, unsigned passes, bool cleanup = false)
|
||||
{
|
||||
// Keep only the black-to-white pulses prepared by the monochrome partial update.
|
||||
// Maintenance restores both endpoints without inverting the visible image.
|
||||
#if INKHUD_BOOKREADER_PERF_LOG
|
||||
const uint32_t prepareStart = millis();
|
||||
uint32_t whitePixels = 0;
|
||||
@@ -352,23 +352,36 @@ class SafeFastEPD : public FASTEPD
|
||||
uint8_t active = 0;
|
||||
for (size_t i = 0; i < 960U * 540U / 4U; ++i) {
|
||||
uint8_t finish = plane[i] & 0xAA;
|
||||
if (cleanup) {
|
||||
const uint8_t pixels = (_state.pCurrent[i / 2U] >> ((i & 1U) ? 0U : 4U)) & 15U;
|
||||
finish = ((pixels & 8U) << 4U) | ((pixels & 4U) << 3U) | ((pixels & 2U) << 2U) | ((pixels & 1U) << 1U);
|
||||
#if INKHUD_BOOKREADER_PERF_LOG
|
||||
whitePixels += __builtin_popcount(static_cast<unsigned>(finish));
|
||||
#endif
|
||||
finish |= (finish ^ 0xAAU) >> 1U;
|
||||
}
|
||||
plane[i] = finish;
|
||||
active |= plane[i];
|
||||
#if INKHUD_BOOKREADER_PERF_LOG
|
||||
whitePixels += __builtin_popcount(static_cast<unsigned>(finish));
|
||||
if (!cleanup)
|
||||
whitePixels += __builtin_popcount(static_cast<unsigned>(finish));
|
||||
#endif
|
||||
}
|
||||
int rc = BBEP_SUCCESS;
|
||||
#if INKHUD_BOOKREADER_PERF_LOG
|
||||
LOG_INFO("[BookReaderDraw] white-only finish BEGIN at=%lu pixels=%lu passes=%u prepareMs=%lu mode=%d",
|
||||
LOG_INFO("[BookReaderDraw] monochrome finish BEGIN at=%lu whitePixels=%lu passes=%u prepareMs=%lu mode=%d cleanup=%u",
|
||||
static_cast<unsigned long>(millis()), static_cast<unsigned long>(whitePixels), active ? passes : 0U,
|
||||
static_cast<unsigned long>(millis() - prepareStart), _state.mode);
|
||||
static_cast<unsigned long>(millis() - prepareStart), _state.mode, cleanup);
|
||||
#endif
|
||||
if (active)
|
||||
for (unsigned pass = 0; pass < passes && rc == BBEP_SUCCESS; ++pass)
|
||||
for (unsigned pass = 0; pass < passes && rc == BBEP_SUCCESS; ++pass) {
|
||||
if (cleanup && pass == static_cast<unsigned>(_state.iFullPasses))
|
||||
for (size_t i = 0; i < 960U * 540U / 4U; ++i)
|
||||
plane[i] &= 0xAAU;
|
||||
rc = writePulseFrame(plane);
|
||||
}
|
||||
#if INKHUD_BOOKREADER_PERF_LOG
|
||||
LOG_INFO("[BookReaderDraw] white-only finish END rc=%d", rc);
|
||||
LOG_INFO("[BookReaderDraw] monochrome finish END rc=%d", rc);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
@@ -573,7 +586,8 @@ void ED047TC1::update(uint8_t *imageData, UpdateTypes type)
|
||||
memcpy(dstRow, srcRow, srcRowBytes);
|
||||
}
|
||||
|
||||
if (type != FULL && monochromeBaselineValid && memcmp(cur, epaper->previousBuffer(), dstRowBytes * dstTotalRows) == 0) {
|
||||
if (type != FULL && type != CLEANUP && monochromeBaselineValid &&
|
||||
memcmp(cur, epaper->previousBuffer(), dstRowBytes * dstTotalRows) == 0) {
|
||||
failed = false;
|
||||
return;
|
||||
}
|
||||
@@ -601,10 +615,12 @@ int ED047TC1::driveUpdate()
|
||||
whiteToBlack += __builtin_popcount(static_cast<unsigned>(~cur[i] & previous[i]));
|
||||
}
|
||||
auto *safeEpaper = static_cast<SafeFastEPD *>(epaper);
|
||||
const int rc = safeEpaper->partialUpdateWithWhiteFinish(blackToWhite);
|
||||
LOG_DEBUG("[EInkPerf] FAST blackToWhite=%lu whiteToBlack=%lu whiteExtraPasses=%u ms=%lu rc=%d",
|
||||
static_cast<unsigned long>(blackToWhite), static_cast<unsigned long>(whiteToBlack),
|
||||
blackToWhite ? SafeFastEPD::whiteFinishPasses : 0U, static_cast<unsigned long>(millis() - startMs), rc);
|
||||
const bool cleanup = pendingType == CLEANUP;
|
||||
const int rc = safeEpaper->partialUpdateWithWhiteFinish(blackToWhite, cleanup);
|
||||
LOG_DEBUG("[EInkPerf] %s blackToWhite=%lu whiteToBlack=%lu whiteExtraPasses=%u ms=%lu rc=%d",
|
||||
cleanup ? "CLEANUP" : "FAST", static_cast<unsigned long>(blackToWhite),
|
||||
static_cast<unsigned long>(whiteToBlack), (blackToWhite || cleanup) ? SafeFastEPD::whiteFinishPasses : 0U,
|
||||
static_cast<unsigned long>(millis() - startMs), rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ED047TC1 : public EInk
|
||||
static constexpr uint8_t V_OFFSET_TOP = 16; // visual RIGHT : 16px physical top margin
|
||||
static constexpr uint8_t V_OFFSET_BOTTOM = 16; // visual LEFT : 16px physical bottom margin
|
||||
|
||||
static constexpr UpdateTypes supported = static_cast<UpdateTypes>(FULL | FAST);
|
||||
static constexpr UpdateTypes supported = static_cast<UpdateTypes>(FULL | FAST | CLEANUP);
|
||||
|
||||
public:
|
||||
ED047TC1() : EInk(DISPLAY_WIDTH, DISPLAY_HEIGHT, supported) {}
|
||||
|
||||
@@ -45,9 +45,11 @@ void EInk::beginPolling(uint32_t interval, uint32_t expectedDuration)
|
||||
// This is what allows us to update the display asynchronously
|
||||
int32_t EInk::runOnce()
|
||||
{
|
||||
// A long CPU task may delay polling after the hardware has already finished.
|
||||
const bool updateDone = !failed && isUpdateDone();
|
||||
// Check for polling timeout
|
||||
// Manually set at 10 seconds, in case some big task holds up the firmware's cooperative multitasking
|
||||
if (millis() - pollingBegunAt > 10000)
|
||||
if (!updateDone && millis() - pollingBegunAt > 10000)
|
||||
failed = true;
|
||||
|
||||
// Handle failure
|
||||
@@ -61,7 +63,7 @@ int32_t EInk::runOnce()
|
||||
}
|
||||
|
||||
// If update not yet done
|
||||
if (!isUpdateDone())
|
||||
if (!updateDone)
|
||||
return pollingInterval; // Poll again in a few ms
|
||||
|
||||
// If update done
|
||||
@@ -83,4 +85,4 @@ void EInk::await()
|
||||
yield();
|
||||
}
|
||||
}
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
@@ -24,7 +24,8 @@ class EInk : private concurrency::OSThread
|
||||
enum UpdateTypes : uint8_t {
|
||||
UNSPECIFIED = 0,
|
||||
FULL = 1 << 0,
|
||||
FAST = 1 << 1, // "Partial Refresh"
|
||||
FAST = 1 << 1, // "Partial Refresh"
|
||||
CLEANUP = 1 << 2, // Non-inverting maintenance, where supported
|
||||
};
|
||||
|
||||
EInk(uint16_t width, uint16_t height, UpdateTypes supported);
|
||||
|
||||
@@ -33,8 +33,8 @@ void setupNicheGraphics()
|
||||
- [`await()`](#await)
|
||||
- [`supports(UpdateTypes type)`](#supportsupdatetypes-type)
|
||||
- [`busy()`](#busy)
|
||||
- [`width()`](#width)
|
||||
- [`height()`](#height)
|
||||
- [`width`](#width)
|
||||
- [`height`](#height)
|
||||
- [Supporting New Displays](#supporting-new-displays)
|
||||
- [Controller IC](#controller-ic)
|
||||
- [Finding Information](#finding-information)
|
||||
@@ -49,28 +49,33 @@ Update the image on the display
|
||||
- _`type`_ which type of update to perform.
|
||||
- `FULL`
|
||||
- `FAST` (partial refresh)
|
||||
- (Other custom types may be possible)
|
||||
- `CLEANUP` (optional non-inverting maintenance refresh)
|
||||
|
||||
The imageData is a 1-bit image. X-Pixels are 8-per byte, with the MSB being the leftmost pixel. This was not an InkHUD design decision; it is the raw format accepted by the E-Ink display controllers ICs.
|
||||
Not every driver supports every update type. Check `driver->supports(EInk::CLEANUP)` before requesting maintenance; drivers advertise supported types as capability bits. `CLEANUP` does not imply support for grayscale.
|
||||
|
||||
_To-do: add a helper method to `InkHUD::Drivers::EInk` to do this arithmetic for you._
|
||||
The image data is a row-major 1-bit image, with eight horizontal pixels per byte and the MSB representing the leftmost pixel. A set bit is white; a cleared bit is black. Each row occupies `(width + 7) / 8` bytes, including padding when the width is not divisible by eight. Keep the buffer alive and unchanged until the update completes.
|
||||
|
||||
```cpp
|
||||
uint16_t w = driver::width();
|
||||
uint16_t h = driver::height();
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
uint8_t image[ (w/8) * h ]; // X pixels are 8-per-byte
|
||||
const uint16_t w = driver->width;
|
||||
const uint16_t h = driver->height;
|
||||
const std::size_t rowBytes = (static_cast<std::size_t>(w) + 7) / 8;
|
||||
std::vector<uint8_t> image(rowBytes * h, 0xFF); // Start with white pixels.
|
||||
|
||||
image[0] |= (1 << 7); // Set pixel x=0, y=0
|
||||
image[0] |= (1 << 0); // Set pixel x=7, y=0
|
||||
image[1] |= (1 << 7); // Set pixel x=8, y=0
|
||||
const uint16_t x = 12;
|
||||
const uint16_t y = 2;
|
||||
if (x < w && y < h) {
|
||||
const std::size_t yBytes = static_cast<std::size_t>(y) * rowBytes;
|
||||
const std::size_t xBytes = x / 8;
|
||||
const uint8_t mask = static_cast<uint8_t>(1U << (7 - (x % 8)));
|
||||
image[yBytes + xBytes] &= static_cast<uint8_t>(~mask); // Draw black.
|
||||
}
|
||||
|
||||
uint8_t x = 12;
|
||||
uint8_t y = 2;
|
||||
uint8_t yBytes = y * (w/8);
|
||||
uint8_t xBytes = x / 8;
|
||||
uint8_t xBits = (7-x) % 8;
|
||||
image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2
|
||||
driver->update(image.data(), NicheGraphics::Drivers::EInk::FULL);
|
||||
driver->await(); // Finish before the local vector is destroyed or reused.
|
||||
```
|
||||
|
||||
### `await()`
|
||||
@@ -87,19 +92,19 @@ Check if display supports a specific update type. `true` if supported.
|
||||
|
||||
Check if display is already performing an `update()`. `true` if already updating.
|
||||
|
||||
### `width()`
|
||||
### `width`
|
||||
|
||||
Width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
||||
Read-only instance field (`driver->width`): width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
||||
|
||||
### `height()`
|
||||
### `height`
|
||||
|
||||
Height of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
||||
Read-only instance field (`driver->height`): height of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
||||
|
||||
## Supporting New Displays
|
||||
|
||||
_This topic is not covered in depth, but these notes may be helpful._
|
||||
|
||||
The `InkHUD::Drivers::EInk` class contains only the mechanism for implementing an E-Ink driver on-top of Meshtastic's `OSThread`. A driver for a specific display needs to extend this class.
|
||||
The `NicheGraphics::Drivers::EInk` class contains only the mechanism for implementing an E-Ink driver on-top of Meshtastic's `OSThread`. A driver for a specific display needs to extend this class.
|
||||
|
||||
### Controller IC
|
||||
|
||||
|
||||
@@ -260,12 +260,16 @@ struct ZipCentralEntry {
|
||||
struct EpubArchiveCache {
|
||||
std::string epubPath;
|
||||
std::vector<ZipCentralEntry> entries;
|
||||
uint64_t fileSize = 0;
|
||||
int64_t lastWrite = 0;
|
||||
bool ready = false;
|
||||
|
||||
void clear()
|
||||
{
|
||||
epubPath.clear();
|
||||
entries.clear();
|
||||
fileSize = 0;
|
||||
lastWrite = 0;
|
||||
ready = false;
|
||||
}
|
||||
};
|
||||
@@ -405,7 +409,7 @@ struct ImageRenderContext {
|
||||
uint16_t srcHeight = 0;
|
||||
std::vector<uint8_t> *monoBitmap = nullptr;
|
||||
std::vector<uint8_t> *sourceBitmap = nullptr;
|
||||
bool skipScreenWrite = false; // when true, caller writes the decoded bitmap with writeBitmap()
|
||||
bool skipScreenWrite = false; // Bitmap-only rendering; the caller blits the finished image.
|
||||
bool inlineRaster = false;
|
||||
bool *prefetchInterrupted = nullptr;
|
||||
};
|
||||
@@ -1174,7 +1178,7 @@ bool drawRenderedImageCache(InkHUD::BookReaderApplet *applet, const std::string
|
||||
const uint16_t sourceY = static_cast<uint16_t>(visibleTop - imageTop);
|
||||
|
||||
// Convert applet-local image position to screen coords, then use the
|
||||
// renderer's fast 1-bpp blit path.
|
||||
// renderer's fast bitmap blit path.
|
||||
InkHUD::Tile *tile = applet->getTile();
|
||||
const int16_t tileLeft = tile ? tile->getLeft() : 0;
|
||||
const int16_t tileTop = tile ? tile->getTop() : 0;
|
||||
@@ -1896,6 +1900,11 @@ bool ensureEpubArchiveCached(const std::string &epubPath)
|
||||
|
||||
gEpubArchiveCache.epubPath = epubPath;
|
||||
gEpubArchiveCache.entries = std::move(entries);
|
||||
{
|
||||
concurrency::LockGuard guard(spiLock);
|
||||
gEpubArchiveCache.fileSize = gEpubArchiveFile.size();
|
||||
gEpubArchiveCache.lastWrite = gEpubArchiveFile.getLastWrite();
|
||||
}
|
||||
gEpubArchiveCache.ready = true;
|
||||
return true;
|
||||
#endif
|
||||
@@ -4371,9 +4380,7 @@ std::string pickPreferredTocPath(const std::vector<std::string> &tocPaths)
|
||||
void InkHUD::BookReaderApplet::setDisplayRefreshActive(bool active)
|
||||
{
|
||||
#if INKHUD_BOOKREADER_HAS_SD && INKHUD_BOOKREADER_ENABLE_INLINE_IMAGES && INKHUD_BOOKREADER_USE_JPEGDEC
|
||||
const bool previous = gReaderDisplayRefreshActive.exchange(active);
|
||||
if (previous != active && gJpegPrefetchJob && !gJpegPrefetchJob->done.load())
|
||||
BR_PERF_LOG("JPEG prefetch display priority active=%u", active);
|
||||
gReaderDisplayRefreshActive.store(active);
|
||||
#else
|
||||
(void)active;
|
||||
#endif
|
||||
@@ -4406,6 +4413,8 @@ uint64_t InkHUD::BookReaderApplet::pageIndexFingerprint() const
|
||||
hash = (hash ^ p[i]) * 1099511628211ULL;
|
||||
};
|
||||
bytes(bookPath.data(), bookPath.size());
|
||||
bytes(&gEpubArchiveCache.fileSize, sizeof(gEpubArchiveCache.fileSize));
|
||||
bytes(&gEpubArchiveCache.lastWrite, sizeof(gEpubArchiveCache.lastWrite));
|
||||
const uint8_t readingHeight = fontSmall.lineHeight();
|
||||
const uint8_t footerHeight = fontSmall.lineHeight();
|
||||
bytes(&readingHeight, sizeof(readingHeight));
|
||||
@@ -4935,6 +4944,8 @@ void InkHUD::BookReaderApplet::updateBackgroundReaderPending()
|
||||
|
||||
void InkHUD::BookReaderApplet::queueDeferredPartExpansion(int32_t partIndex)
|
||||
{
|
||||
if (deferredPartLoad.partIndex != partIndex)
|
||||
deferredPartLoad.clear();
|
||||
deferredPartLoad.partIndex = partIndex;
|
||||
backgroundReaderWorkArmed = true;
|
||||
updateBackgroundReaderPending();
|
||||
@@ -4967,17 +4978,24 @@ bool InkHUD::BookReaderApplet::runBackgroundReaderPass()
|
||||
if (deferredPartLoad.partIndex >= 0) {
|
||||
const int32_t targetPartIndex = deferredPartLoad.partIndex;
|
||||
const int32_t savedScrollLine = scrollLine;
|
||||
deferredPartLoad.clear();
|
||||
updateBackgroundReaderPending();
|
||||
if (pagedBookMode && pagedPartIndex == targetPartIndex) {
|
||||
if (deferredPartLoad.failures && !Throttle::hasElapsed(deferredPartLoad.lastFailureMs, 1000U))
|
||||
return false;
|
||||
if (!loadPagedPart(targetPartIndex, false)) {
|
||||
queueDeferredPartExpansion(targetPartIndex);
|
||||
deferredPartLoad.lastFailureMs = Time::getMillis();
|
||||
if (++deferredPartLoad.failures >= 3U) {
|
||||
// Leave the already displayed partial chapter available after storage failure.
|
||||
deferredPartLoad.clear();
|
||||
}
|
||||
updateBackgroundReaderPending();
|
||||
backgroundReaderWorkArmed = false;
|
||||
return false;
|
||||
}
|
||||
scrollLine = std::clamp<int32_t>(savedScrollLine, 0, readerLastPageStart(wrappedLines.size(), linesPerPage));
|
||||
return true;
|
||||
}
|
||||
deferredPartLoad.clear();
|
||||
updateBackgroundReaderPending();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5306,18 +5324,32 @@ BOOKREADER_NOINLINE bool InkHUD::BookReaderApplet::loadEpubContainerAsBook(const
|
||||
|
||||
BOOKREADER_NOINLINE bool InkHUD::BookReaderApplet::loadFile(const BrowserEntry &entry)
|
||||
{
|
||||
const bool reuseEpubCaches = fileLoaded && bookPath == entry.path && entry.format == BookFormat::EPUB_CONTAINER &&
|
||||
gEpubArchiveCache.ready && gEpubArchiveCache.epubPath == entry.path;
|
||||
#if INKHUD_BOOKREADER_HAS_SD && INKHUD_BOOKREADER_ENABLE_INLINE_IMAGES && INKHUD_BOOKREADER_USE_JPEGDEC
|
||||
clearJpegPrefetchJob();
|
||||
#endif
|
||||
bool reuseEpubCaches = false;
|
||||
#if INKHUD_BOOKREADER_HAS_SD
|
||||
if (ensureSDReady()) {
|
||||
concurrency::LockGuard guard(spiLock);
|
||||
// Check a fresh handle only at explicit opens. Equal size and timestamp cannot detect every replacement.
|
||||
File current = SD.open(entry.path.c_str(), FILE_READ);
|
||||
if (current && !current.isDirectory()) {
|
||||
reuseEpubCaches = fileLoaded && bookPath == entry.path && entry.format == BookFormat::EPUB_CONTAINER &&
|
||||
gEpubArchiveCache.ready && gEpubArchiveCache.epubPath == entry.path &&
|
||||
gEpubArchiveCache.fileSize == current.size() &&
|
||||
gEpubArchiveCache.lastWrite == current.getLastWrite();
|
||||
}
|
||||
if (current)
|
||||
current.close();
|
||||
}
|
||||
#endif
|
||||
if (!reuseEpubCaches) {
|
||||
closeCachedEpubFile();
|
||||
gEpubArchiveCache.clear();
|
||||
gExternalCssCaches.clear();
|
||||
clearRenderedImageCaches();
|
||||
gImageProbeCache.clear();
|
||||
}
|
||||
#if INKHUD_BOOKREADER_HAS_SD && INKHUD_BOOKREADER_ENABLE_INLINE_IMAGES && INKHUD_BOOKREADER_USE_JPEGDEC
|
||||
else {
|
||||
clearJpegPrefetchJob();
|
||||
}
|
||||
#endif
|
||||
BR_PERF_LOG("book image caches reused=%u path=%s", reuseEpubCaches, entry.path.c_str());
|
||||
resetPageIndex();
|
||||
contents.clear();
|
||||
@@ -6660,7 +6692,7 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
|
||||
BR_LOAD_SCOPE("jpeg-codec-and-raster");
|
||||
decodeRc = gBookReaderJpegDec.decode(0, 0, options);
|
||||
}
|
||||
rendered = (decodeRc != 0);
|
||||
rendered = decodeRc != 0 && gBookReaderJpegDec.getLastError() == JPEG_SUCCESS;
|
||||
BR_PERF_LOG("JPEG decoded path=%s result=%d error=%d", imagePath.c_str(), decodeRc,
|
||||
gBookReaderJpegDec.getLastError());
|
||||
epubStream.finishReadAhead();
|
||||
@@ -6674,6 +6706,7 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
|
||||
gBookReaderJpegDec.close();
|
||||
}
|
||||
epubStream.finishReadAhead();
|
||||
// JPEGDEC may return success after a callback abort; honor the explicit interruption before retrying or caching.
|
||||
if (prefetchInterrupted && *prefetchInterrupted) {
|
||||
BR_PERF_LOG("image prefetch interrupted path=%s", imagePath.c_str());
|
||||
return false;
|
||||
@@ -6691,7 +6724,7 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
|
||||
if (gBookReaderJpegDec.openRAM(imageBytes.data(), static_cast<int>(imageBytes.size()), jpegDecDrawCallback)) {
|
||||
gBookReaderJpegDec.setPixelType(sourceBitmap ? EIGHT_BIT_GRAYSCALE : RGB565_LITTLE_ENDIAN);
|
||||
gBookReaderJpegDec.setUserPointer(&ctx);
|
||||
rendered = gBookReaderJpegDec.decode(0, 0, options) != 0;
|
||||
rendered = gBookReaderJpegDec.decode(0, 0, options) != 0 && gBookReaderJpegDec.getLastError() == JPEG_SUCCESS;
|
||||
BR_PERF_LOG("RAM retry: path=%s bytes=%u rendered=%u error=%d", imagePath.c_str(),
|
||||
static_cast<unsigned>(imageBytes.size()), rendered, gBookReaderJpegDec.getLastError());
|
||||
gBookReaderJpegDec.close();
|
||||
|
||||
@@ -106,8 +106,10 @@ class BookReaderApplet : public Applet, private concurrency::OSThread
|
||||
};
|
||||
struct DeferredPartLoad {
|
||||
int32_t partIndex = -1;
|
||||
uint8_t failures = 0;
|
||||
uint32_t lastFailureMs = 0;
|
||||
|
||||
void clear() { partIndex = -1; }
|
||||
void clear() { *this = DeferredPartLoad{}; }
|
||||
};
|
||||
BookReaderApplet();
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
#include "./DisplayHealth.h"
|
||||
#include "DisplayHealth.h"
|
||||
#include "./InkHUD.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// Timing for "maintenance"
|
||||
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
||||
// Restore the display after heavy interaction, then space any remaining maintenance an hour apart.
|
||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
||||
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
||||
|
||||
@@ -42,7 +42,7 @@ void InkHUD::DisplayHealth::forceUpdateType(Drivers::EInk::UpdateTypes type)
|
||||
// Calling this method consumes the result, and resets for the next update
|
||||
Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::decideUpdateType()
|
||||
{
|
||||
LOG_DEBUG("FULL-update debt:%f", debt);
|
||||
LOG_DEBUG("Refresh maintenance debt:%f", debt);
|
||||
|
||||
// For convenience
|
||||
typedef Drivers::EInk::UpdateTypes UpdateTypes;
|
||||
@@ -59,11 +59,13 @@ Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::decideUpdateType()
|
||||
if (OSThread::enabled && debt == 0.0)
|
||||
endMaintenance();
|
||||
|
||||
// Explicitly requested FULL
|
||||
if (finalDecision == UpdateTypes::FULL) {
|
||||
LOG_DEBUG("Explicit FULL");
|
||||
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
|
||||
return UpdateTypes::FULL;
|
||||
// Explicit full refresh or panel-specific cleanup.
|
||||
if (finalDecision == UpdateTypes::FULL || finalDecision == UpdateTypes::CLEANUP) {
|
||||
LOG_DEBUG("Explicit %s", finalDecision == UpdateTypes::FULL ? "FULL" : "CLEANUP");
|
||||
debt = max(debt - 1.0, 0.0);
|
||||
if (debt == 0.0)
|
||||
endMaintenance();
|
||||
return finalDecision;
|
||||
}
|
||||
|
||||
// Explicitly requested FAST
|
||||
@@ -95,18 +97,20 @@ Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::decideUpdateType()
|
||||
return UpdateTypes::FAST;
|
||||
}
|
||||
|
||||
// In debt: suggest FULL
|
||||
// In debt: use the driver's maintenance operation.
|
||||
else {
|
||||
LOG_DEBUG("UNSPECIFIED: using FULL");
|
||||
LOG_DEBUG("UNSPECIFIED: using %s", maintenanceUpdateType == UpdateTypes::FULL ? "FULL" : "CLEANUP");
|
||||
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
|
||||
|
||||
// When maintenance begins, the first refresh happens shortly after user interaction ceases (a minute or so)
|
||||
// If we *are* given an opportunity to refresh before that, we'll skip that initial maintenance refresh
|
||||
// We were intending to use that initial refresh to redraw the screen as FULL, but we're doing that now, organically
|
||||
if (OSThread::enabled && OSThread::interval == MAINTENANCE_MS_INITIAL)
|
||||
// This update takes the place of the initial maintenance refresh.
|
||||
if (debt == 0.0)
|
||||
endMaintenance();
|
||||
else if (OSThread::enabled && OSThread::interval == MAINTENANCE_MS_INITIAL)
|
||||
OSThread::setInterval(MAINTENANCE_MS); // Note: not intervalFromNow
|
||||
|
||||
return UpdateTypes::FULL;
|
||||
return maintenanceUpdateType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +125,10 @@ Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::prioritize(Drivers::EInk::Upda
|
||||
return type2;
|
||||
|
||||
case Drivers::EInk::UpdateTypes::FAST:
|
||||
return (type2 == Drivers::EInk::UpdateTypes::FULL) ? Drivers::EInk::UpdateTypes::FULL : Drivers::EInk::UpdateTypes::FAST;
|
||||
return (type2 == Drivers::EInk::UpdateTypes::FULL || type2 == Drivers::EInk::UpdateTypes::CLEANUP) ? type2 : type1;
|
||||
|
||||
case Drivers::EInk::UpdateTypes::CLEANUP:
|
||||
return type2 == Drivers::EInk::UpdateTypes::FULL ? type2 : type1;
|
||||
|
||||
case Drivers::EInk::UpdateTypes::FULL:
|
||||
return type1;
|
||||
@@ -131,7 +138,7 @@ Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::prioritize(Drivers::EInk::Upda
|
||||
}
|
||||
|
||||
// We're using the timer to perform "maintenance"
|
||||
// If significant FULL-refresh debt has accumulated, we will occasionally run FULL refreshes unprovoked.
|
||||
// If significant debt has accumulated, periodically request the driver's maintenance operation.
|
||||
// This prevents gradual build-up of debt,
|
||||
// in case we aren't doing enough UNSPECIFIED refreshes to pay the debt back organically.
|
||||
// The first refresh takes place shortly after user finishes interacting with the device; this does the bulk of the restoration
|
||||
@@ -144,12 +151,9 @@ int32_t InkHUD::DisplayHealth::runOnce()
|
||||
|
||||
// Ask WindowManager to redraw everything, purely for the refresh
|
||||
// Todo: optimize? Could update without re-rendering
|
||||
InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
||||
InkHUD::getInstance()->forceUpdate(maintenanceUpdateType);
|
||||
|
||||
// Record that we have paid back (some of) the FULL refresh debt
|
||||
debt = max(debt - 1.0, 0.0);
|
||||
|
||||
// Next maintenance refresh scheduled - long wait (an hour?)
|
||||
// The renderer credits this request once; schedule any remaining maintenance an hour later.
|
||||
return MAINTENANCE_MS;
|
||||
}
|
||||
|
||||
@@ -159,8 +163,7 @@ int32_t InkHUD::DisplayHealth::runOnce()
|
||||
|
||||
// Begin periodically refreshing the display, to repay FULL-refresh debt
|
||||
// We do this in case user doesn't have enough activity to repay it organically, with UpdateTypes::UNSPECIFIED
|
||||
// After an initial refresh, to redraw as FULL, we only perform these maintenance refreshes very infrequently
|
||||
// This gives the display a chance to heal by evaluating UNSPECIFIED as FULL, which is preferable
|
||||
// After the initial maintenance refresh, subsequent requests are much less frequent.
|
||||
void InkHUD::DisplayHealth::beginMaintenance()
|
||||
{
|
||||
OSThread::setIntervalFromNow(MAINTENANCE_MS_INITIAL);
|
||||
@@ -173,4 +176,4 @@ int32_t InkHUD::DisplayHealth::endMaintenance()
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
/*
|
||||
|
||||
Responsible for maintaining display health, by optimizing the ratio of FAST vs FULL refreshes
|
||||
Maintains display health by balancing fast updates with panel-specific cleanup or full refreshes.
|
||||
|
||||
- counts number of FULL vs FAST refresh
|
||||
- suggests whether to use FAST or FULL, when not explicitly specified
|
||||
- tracks maintenance debt from fast updates
|
||||
- chooses fast refresh or maintenance when no update type is specified
|
||||
- periodically requests update unprovoked, if required for display health
|
||||
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ Responsible for maintaining display health, by optimizing the ratio of FAST vs F
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "InkHUD.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
#include "graphics/niche/Drivers/EInk/EInk.h"
|
||||
|
||||
@@ -30,8 +30,9 @@ class DisplayHealth : protected concurrency::OSThread
|
||||
void forceUpdateType(Drivers::EInk::UpdateTypes type);
|
||||
Drivers::EInk::UpdateTypes decideUpdateType();
|
||||
|
||||
uint8_t fastPerFull = 5; // Ideal number of fast refreshes between full refreshes
|
||||
uint8_t fastPerFull = 5; // Ideal number of fast refreshes between maintenance refreshes
|
||||
float stressMultiplier = 2.0; // How bad for the display are extra fast refreshes beyond fastPerFull?
|
||||
Drivers::EInk::UpdateTypes maintenanceUpdateType = Drivers::EInk::UpdateTypes::FULL;
|
||||
|
||||
private:
|
||||
int32_t runOnce() override;
|
||||
@@ -45,9 +46,9 @@ class DisplayHealth : protected concurrency::OSThread
|
||||
bool forced = false;
|
||||
Drivers::EInk::UpdateTypes workingDecision = Drivers::EInk::UpdateTypes::UNSPECIFIED;
|
||||
|
||||
float debt = 0.0; // How many full refreshes are due
|
||||
float debt = 0.0; // How many maintenance refreshes are due
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@ build_src_filter =
|
||||
-<graphics/EInkDisplay2.cpp> ; InkHUD drives e-ink through niche drivers
|
||||
-<graphics/EInkDynamicDisplay.cpp>
|
||||
-<graphics/EInkParallelDisplay.cpp>
|
||||
-<graphics/BaseUIEInkDisplay.cpp>
|
||||
-<graphics/EmoteRenderer.cpp>
|
||||
-<graphics/emotes.cpp>
|
||||
-<graphics/Panel_sdl.cpp>
|
||||
|
||||
@@ -52,6 +52,9 @@ void InkHUD::Renderer::setDriver(Drivers::EInk *driver)
|
||||
|
||||
// Store the driver which was created in setupNicheGraphics()
|
||||
this->driver = driver;
|
||||
displayHealth.maintenanceUpdateType = driver->supports(Drivers::EInk::UpdateTypes::CLEANUP)
|
||||
? Drivers::EInk::UpdateTypes::CLEANUP
|
||||
: Drivers::EInk::UpdateTypes::FULL;
|
||||
|
||||
// Determine the dimensions of the image buffer, in bytes.
|
||||
// Along rows, pixels are stored 8 per byte.
|
||||
@@ -817,6 +820,8 @@ void InkHUD::Renderer::clearTile(Tile *t)
|
||||
xEnd = driver->width;
|
||||
if (yEnd > driver->height)
|
||||
yEnd = driver->height;
|
||||
if (xStart >= xEnd || yStart >= yEnd)
|
||||
return;
|
||||
#if INKHUD_BOOKREADER_GRAYSCALE && INKHUD_BOOKREADER_PERF_LOG
|
||||
LOG_INFO("[BookReaderDraw] clear tile xy=%u,%u-%u,%u gray=%u", xStart, yStart, xEnd, yEnd, grayBuffer != nullptr);
|
||||
#endif
|
||||
@@ -855,21 +860,20 @@ void InkHUD::Renderer::clearTile(Tile *t)
|
||||
if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear
|
||||
memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth);
|
||||
} else {
|
||||
const uint16_t byteStart = (xStart / 8) + 1;
|
||||
const uint16_t byteEnd = xEnd / 8;
|
||||
const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8));
|
||||
const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF;
|
||||
for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) {
|
||||
// Set the leading byte
|
||||
imageBuffer[i + byteStart - 1] |= leadingByte;
|
||||
|
||||
// Set the continuous bytes
|
||||
if (byteStart < byteEnd)
|
||||
memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart);
|
||||
|
||||
// Set the trailing byte
|
||||
if (byteEnd != imageBufferWidth)
|
||||
const uint16_t byteStart = xStart / 8U;
|
||||
const uint16_t byteEnd = (xEnd - 1U) / 8U;
|
||||
const uint8_t leadingByte = 0xFFU >> (xStart & 7U);
|
||||
const uint8_t trailingByte = 0xFFU << (7U - ((xEnd - 1U) & 7U));
|
||||
for (size_t i = static_cast<size_t>(yStart) * imageBufferWidth; i < static_cast<size_t>(yEnd) * imageBufferWidth;
|
||||
i += imageBufferWidth) {
|
||||
if (byteStart == byteEnd)
|
||||
imageBuffer[i + byteStart] |= leadingByte & trailingByte;
|
||||
else {
|
||||
imageBuffer[i + byteStart] |= leadingByte;
|
||||
if (byteStart + 1U < byteEnd)
|
||||
memset(imageBuffer + i + byteStart + 1U, 0xFF, byteEnd - byteStart - 1U);
|
||||
imageBuffer[i + byteEnd] |= trailingByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ A haphazard collection of notes which _might_ be helpful for developers.
|
||||
- [`InkHUD::Persistence::LatestMessage`](#inkhudpersistencelatestmessage)
|
||||
- [`InkHUD::WindowManager`](#inkhudwindowmanager)
|
||||
- [`InkHUD::Renderer`](#inkhudrenderer)
|
||||
- [`InkHUD::Renderer::DisplayHealth`](#inkhudrendererdisplayhealth)
|
||||
- [`InkHUD::DisplayHealth`](#inkhuddisplayhealth)
|
||||
- [`InkHUD::Events`](#inkhudevents)
|
||||
- [`InkHUD::Applet`](#inkhudapplet)
|
||||
- [`InkHUD::SystemApplet`](#inkhudsystemapplet)
|
||||
@@ -336,6 +336,8 @@ Resource limits have overridable defaults in `BookReaderApplet.cpp`. Set overrid
|
||||
|
||||
Opening a book or an uncached chapter/page uses a centered book icon and loading label. Chapter reads, wrapping, pagination, and image prefetch are deferred where possible so the reader can show content before preparing the entire book. A cached transition need not show a loading indicator. E-paper refresh time is separate from CPU rendering time and depends on the display driver.
|
||||
|
||||
Explicit book opens check the archive's current size and last-write time before reusing caches. Changed metadata invalidates archive, stylesheet, image, and pagination caches. A replacement with identical size and timestamp is not distinguishable by this check. Background chapter expansion retries storage failures at most three times, at least one second apart, retaining the already displayed partial chapter if expansion fails.
|
||||
|
||||
| Component | Responsibility |
|
||||
| --------- | -------------- |
|
||||
| `BookReaderApplet.cpp` / `.h` | Library, EPUB access, parsing, views, input, persistence, and background work |
|
||||
@@ -656,47 +658,27 @@ The exact size / position / rotation of InkHUD applets is configurable by the us
|
||||
|
||||
---
|
||||
|
||||
### `InkHUD::Renderer::DisplayHealth`
|
||||
### `InkHUD::DisplayHealth`
|
||||
|
||||
_`src/graphics/niche/InkHUD/DisplayHealth.h`_
|
||||
|
||||
Responsible for maintaining display health, by optimizing the ratio of FAST vs FULL refreshes
|
||||
|
||||
- count number of FAST vs FULL refreshes (debt)
|
||||
- suggest either FAST or FULL type
|
||||
- periodically FULL refresh the display unprovoked, if needed
|
||||
Balances fast updates with the maintenance operation supported by the display driver.
|
||||
|
||||
#### Background Info
|
||||
|
||||
When the image on an E-Ink display is updated, different procedures can be used to move the pixels to their new states. We have defined two procedures: `FAST` and `FULL`.
|
||||
`FAST` requests a partial refresh. `FULL` requests the driver's full refresh sequence. Drivers can additionally advertise `CLEANUP`, a panel-specific maintenance operation that does not invert the entire image. The renderer selects `CLEANUP` for automatic maintenance only when the driver supports it; other displays continue using `FULL`.
|
||||
|
||||
A `FAST` update moves pixels directly from their old position, to their new position. This is aesthetically pleasing, and quick, _but_ it is challenging for the display hardware. If used excessively, pixels can build up residual charge, which negatively impacts the display's lifespan and image quality.
|
||||
|
||||
A `FULL` update first moves all pixels between black and white, before letting them eventually settle at their final position. This causes an unpleasant flashing of the display image, but is best for the display health and image quality.
|
||||
|
||||
Most displays readily tolerate `FAST` updates, so long as a `FULL` update is occasionally performed. How often this `FULL` update is required depends on the display model.
|
||||
On the T5S3 ED047TC1, monochrome cleanup reinforces white pixels and existing black content within the short cleanup sequence. Startup, explicit full-refresh requests, and invalid-baseline recovery retain the original full-wipe path. Grayscale image updates use a separate driver path and the panel's grayscale waveform.
|
||||
|
||||
#### Debt
|
||||
|
||||
`InkHUD::DisplayHealth` records how many `FAST` refreshes have occurred since the previous `FULL` refresh.
|
||||
Fast updates accumulate maintenance debt. `InkHUD::setDisplayResilience` configures the target ratio and extra debt for prolonged fast-only interaction in the variant's `nicheGraphics.h`.
|
||||
|
||||
This is referred to as the "full refresh debt".
|
||||
|
||||
If an update of a specific type (`FULL` / `FAST`) is requested / forced, this will be granted.
|
||||
|
||||
If an update is requested / forced _without_ a specified type (`UpdateTypes::UNSPECIFIED`), `DisplayHealth` will select either `FAST` or `FULL`, in an attempt to maintain a target ratio of fast to full updates.
|
||||
|
||||
This target is set by `InkHUD::setDisplayResilience`, when setting up in `nichegraphics.h`
|
||||
|
||||
If an _excessive_ amount of `FAST` refreshes are performed back-to-back, `DisplayHealth` will begin artificially inflating the full refresh debt. This will cause the next few `UNSPECIFIED` updates to _all_ be performed as `FULL`, while the debt is paid down.
|
||||
|
||||
This system of "full refresh debt" allows us to increase perceived responsiveness by tolerating additional strain on the display during periods of user interaction, and attempting to "repair the damage" later, once user interaction ceases.
|
||||
An `UNSPECIFIED` update uses fast refresh while debt is low, then the driver's maintenance operation when cleanup is due. Explicit requests are prioritized as `FULL`, `CLEANUP`, `FAST`, then `UNSPECIFIED`. Each consumed maintenance request reduces debt once.
|
||||
|
||||
#### Maintenance
|
||||
|
||||
The system of "full refresh debt" assumes that the display will perform many updates of `UNSPECIFIED` type between periods of user interaction. Depending on the amount of mesh traffic / applet selection, this may not be the case.
|
||||
|
||||
If debt is particularly high, and no updates are taking place organically, `DisplayHealth` will begin infrequently performing `FULL` updates, purely to pay down the full refresh debt.
|
||||
If explicit fast updates accumulate enough debt, a timer requests maintenance after interaction settles. The initial delay is one minute; further maintenance requests are one hour apart. The timer stops when the debt is cleared. Maintenance uses `CLEANUP` or `FULL` according to driver capability, without disabling cleanup on fast-refresh panels.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -63,8 +63,7 @@ void setupNicheGraphics()
|
||||
// Set the driver
|
||||
inkhud->setDriver(driver);
|
||||
|
||||
// Set how many FAST updates per FULL update
|
||||
// Set how unhealthy additional FAST updates beyond this number are
|
||||
// The driver supplies quick cleanup for scheduled maintenance; explicit full wipes remain available.
|
||||
inkhud->setDisplayResilience(7, 1.5);
|
||||
|
||||
// Prepare fonts - use larger sizes to suit the 4.7" screen at ~234 DPI
|
||||
|
||||
@@ -21,9 +21,6 @@ build_flags = -fno-strict-aliasing
|
||||
-D TOUCH_THRESHOLD_X=40
|
||||
-D TOUCH_THRESHOLD_Y=40
|
||||
-D TIME_LONG_PRESS=500
|
||||
; -D EINK_LIMIT_GHOSTING_PX=5000
|
||||
-D EPD_FULLSLOW_PERIOD=100
|
||||
-D FAST_EPD_PARTIAL_UPDATE_BUG ; use rect area update instead of partial
|
||||
|
||||
build_src_filter =
|
||||
${esp32s3_base.build_src_filter}
|
||||
@@ -39,7 +36,8 @@ lib_deps =
|
||||
|
||||
; This board's 3.3V octal (AP_3v3) 8MB PSRAM is unreliable at the qio_opi default 80MHz
|
||||
; (Total PSRAM reads 0); 40MHz makes it enumerate. Forces a from-source build as no
|
||||
; precompiled 40MHz-octal libs exist; bandwidth is immaterial for an e-paper node.
|
||||
; precompiled 40MHz-octal libs exist. PSRAM bandwidth affects reader rendering and
|
||||
; image processing; retain 40MHz for reliable PSRAM initialization on this board.
|
||||
custom_sdkconfig =
|
||||
${esp32s3_base.custom_sdkconfig}
|
||||
CONFIG_SPIRAM_SPEED_40M=y
|
||||
@@ -51,13 +49,11 @@ board_level = release
|
||||
custom_meshtastic_display_name = LilyGo T5 E-paper S3 Pro V2 InkHUD
|
||||
custom_meshtastic_key = t5s3_epaper_inkhud
|
||||
custom_meshtastic_variant = H752-01 (V2) InkHUD
|
||||
custom_meshtastic_has_ink_hud = true
|
||||
build_flags =
|
||||
${t5s3_epaper_base.build_flags}
|
||||
${inkhud.build_flags}
|
||||
-D MESHTASTIC_INCLUDE_INKHUD_BOOKREADER
|
||||
-D INKHUD_BOOKREADER_GRAYSCALE=1
|
||||
-D SDCARD_USE_SPI1
|
||||
-D INKHUD_BOOKREADER_ENABLE_INLINE_IMAGES=1
|
||||
; Board-specific resource limits; other reader variants can set their own.
|
||||
-D INKHUD_BOOKREADER_MAX_FILE_BYTES=196608U
|
||||
@@ -98,6 +94,8 @@ custom_meshtastic_variant = H752 (V1)
|
||||
build_flags =
|
||||
${t5s3_epaper_base.build_flags}
|
||||
-D T5_S3_EPAPER_PRO_V1
|
||||
-D EPD_FULLSLOW_PERIOD=100
|
||||
-D FAST_EPD_PARTIAL_UPDATE_BUG ; use rect area update instead of partial
|
||||
-D GPS_DEFAULT_NOT_PRESENT=1
|
||||
|
||||
[env:t5s3-epaper-v2] ; H752-01
|
||||
@@ -110,4 +108,6 @@ build_flags =
|
||||
${t5s3_epaper_base.build_flags}
|
||||
-D T5_S3_EPAPER_PRO_V2
|
||||
-D SDCARD_USE_SPI1
|
||||
-D EPD_FULLSLOW_PERIOD=100
|
||||
-D FAST_EPD_PARTIAL_UPDATE_BUG ; use rect area update instead of partial
|
||||
-D GPS_POWER_TOGGLE
|
||||
Reference in new issue
Block a user