Files
Crosspoint/lib/hal/HalDisplay.cpp
T
Justin Mitchell e66de575a1 Add async display refresh support to HalDisplay
Introduces non-blocking refresh capability with displayBufferAsync() that starts panel waveform and returns immediately, plus waitRefreshComplete() to block until refresh finishes. Panels without async support fall back to blocking refresh. Requires framebuffer to remain untouched during refresh and differential baseline rebuild before next update.
2026-07-13 16:20:20 -04:00

143 lines
5.5 KiB
C++

#include <HalDisplay.h>
#include <HalGPIO.h>
// Global HalDisplay instance
HalDisplay display;
#define SD_SPI_MISO 7
HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY) {}
HalDisplay::~HalDisplay() {}
void HalDisplay::begin(bool seamless) {
// Set X3-specific panel mode before initializing.
if (gpio.deviceIsX3()) {
einkDisplay.setDisplayX3();
}
einkDisplay.begin();
if (seamless) {
// Defuse the SDK's X3 _x3InitialFullSyncsRemaining counter (no-op on X4)
// so the first paint isn't promoted to FULL (~770ms). Skips the wakeup-
// gated requestResync() below for the same reason.
einkDisplay.skipInitialResync();
return;
}
// Request resync after specific wakeup events to ensure clean display state.
const auto wakeupReason = gpio.getWakeupReason();
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
wakeupReason == HalGPIO::WakeupReason::Other) {
einkDisplay.requestResync();
}
}
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
bool fromProgmem) const {
einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem);
}
void HalDisplay::drawImageTransparent(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
bool fromProgmem) const {
einkDisplay.drawImageTransparent(imageData, x, y, w, h, fromProgmem);
}
EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) {
switch (mode) {
case HalDisplay::FULL_REFRESH:
return EInkDisplay::FULL_REFRESH;
case HalDisplay::HALF_REFRESH:
return EInkDisplay::HALF_REFRESH;
case HalDisplay::FAST_REFRESH:
default:
return EInkDisplay::FAST_REFRESH;
}
}
void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen) {
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
einkDisplay.requestResync(1);
}
einkDisplay.displayBuffer(convertRefreshMode(mode), turnOffScreen);
}
void HalDisplay::displayBufferAsync(HalDisplay::RefreshMode mode) {
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
einkDisplay.requestResync(1);
}
einkDisplay.displayBufferAsyncNoShadow(convertRefreshMode(mode));
}
void HalDisplay::waitRefreshComplete() { einkDisplay.waitRefreshComplete(); }
bool HalDisplay::supportsAsyncRefresh() const { return einkDisplay.supportsAsyncRefresh(); }
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
einkDisplay.requestResync(1);
}
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
}
void HalDisplay::deepSleep() { einkDisplay.deepSleep(); }
uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
uint8_t* HalDisplay::lendFrameBufferStorage(uint32_t* sizeOut) { return einkDisplay.lendBuildStorage(sizeOut); }
void HalDisplay::returnFrameBufferStorage() { einkDisplay.returnBuildStorage(); }
void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) {
einkDisplay.copyGrayscaleBuffers(lsbBuffer, msbBuffer);
}
void HalDisplay::displayGrayscaleBase(RefreshMode fallback, bool turnOffScreen) {
// X3: a HALF fallback means the caller wants a clean base (e.g. the sleep
// cover, a full-screen swap from arbitrary prior content). Without this, the
// X3 grayscale base takes its gentle differential happy path and the prior
// home/reader frame ghosts through the soft aa_pre_bw_mid waveform. Forcing a
// resync makes displayGrayscaleBase clear first, matching displayBuffer(HALF).
// The reader's FAST path is deliberately left on the differential path so
// per-page grayscale stays cheap.
if (gpio.deviceIsX3() && fallback == RefreshMode::HALF_REFRESH) {
einkDisplay.requestResync(1);
}
einkDisplay.displayGrayscaleBase(convertRefreshMode(fallback), turnOffScreen);
}
void HalDisplay::preconditionGrayscale() { einkDisplay.preconditionGrayscale(); }
void HalDisplay::preconditionGrayscale(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
einkDisplay.preconditionGrayscale(x, y, w, h);
}
void HalDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { einkDisplay.copyGrayscaleLsbBuffers(lsbBuffer); }
void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay.copyGrayscaleMsbBuffers(msbBuffer); }
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }
void HalDisplay::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, uint16_t yStart, uint16_t numRows) {
einkDisplay.writeGrayscalePlaneStrip(lsbPlane ? EInkDisplay::GRAY_PLANE_LSB : EInkDisplay::GRAY_PLANE_MSB, rows,
yStart, numRows);
}
bool HalDisplay::supportsStripGrayscale() const { return einkDisplay.supportsStripGrayscale(); }
uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); }
uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); }
uint16_t HalDisplay::getDisplayWidthBytes() const { return einkDisplay.getDisplayWidthBytes(); }
uint32_t HalDisplay::getBufferSize() const { return einkDisplay.getBufferSize(); }