diff --git a/libs/display/FreeInkDisplay/include/FreeInkDisplay.h b/libs/display/FreeInkDisplay/include/FreeInkDisplay.h index 075a2a4..ef91c47 100644 --- a/libs/display/FreeInkDisplay/include/FreeInkDisplay.h +++ b/libs/display/FreeInkDisplay/include/FreeInkDisplay.h @@ -123,6 +123,8 @@ class FreeInkDisplay { bool supportsBusyGrayscaleStaging() const; void prepareGrayscaleTarget(); bool supportsStripGrayscale() const; + // True only when the runtime-selected panel driver accepts absolute LUT plane encoding. + bool supportsAbsoluteGrayscale() const; // True when displayGrayscaleBase() defers the base activation so the gray // planes join it in one waveform (Paper Mono) - see PanelDriver. bool combinesGrayscaleBase() const; @@ -244,6 +246,7 @@ class FreeInkDisplay { // EXPERIMENTAL: Windowed update - display only a rectangular region void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool turnOffScreen = false); void displayGrayBuffer(bool turnOffScreen = false, const unsigned char* lut = nullptr, bool factoryMode = false); + void displayAbsoluteGrayBuffer(bool turnOffScreen = false); void displayGrayCalibration(uint16_t customX, uint16_t customY, uint16_t customW, uint16_t customH); void refreshDisplay(RefreshMode mode = FAST_REFRESH, bool turnOffScreen = false); diff --git a/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp b/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp index 0f12000..80a14df 100644 --- a/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp +++ b/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp @@ -783,6 +783,19 @@ void FreeInkDisplay::displayGrayBuffer(bool turnOffScreen, const unsigned char* _driver->displayGray(_bus, frameBuffer, turnOffScreen, lut, factoryMode); } +void FreeInkDisplay::displayAbsoluteGrayBuffer(bool turnOffScreen) { +#if defined(SSD1677_PROBE_DEBUG) && SSD1677_PROBE_DEBUG + Serial.printf("[EPD] displayAbsoluteGrayBuffer\n"); +#endif + // Inverted mode deliberately renders a crisp BW page. Writing normal + // grayscale planes afterward would partially undo the output inversion. + if (_inverted) return; + syncPendingAsync(); + _shadowValid = false; + _redRamSynced = false; // grayscale leaves RED holding a gray plane, not the BW baseline + _driver->displayGrayAbsolute(_bus, frameBuffer, turnOffScreen); +} + void FreeInkDisplay::displayGrayCalibration(uint16_t customX, uint16_t customY, uint16_t customW, uint16_t customH) { if (_inverted) return; syncPendingAsync(); @@ -859,6 +872,10 @@ bool FreeInkDisplay::supportsStripGrayscale() const { return !_inverted && _driver && _driver->supportsStripGrayscale(); } +bool FreeInkDisplay::supportsAbsoluteGrayscale() const { + return !_inverted && _driver && _driver->supportsAbsoluteGrayscale(); +} + bool FreeInkDisplay::combinesGrayscaleBase() const { return _driver && _driver->combinesGrayscaleBase(); } void FreeInkDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { diff --git a/libs/display/FreeInkDisplay/src/driver/PanelDriver.h b/libs/display/FreeInkDisplay/src/driver/PanelDriver.h index 6213623..4a33ccb 100644 --- a/libs/display/FreeInkDisplay/src/driver/PanelDriver.h +++ b/libs/display/FreeInkDisplay/src/driver/PanelDriver.h @@ -101,6 +101,11 @@ class PanelDriver { // --- grayscale (dual-plane LSB/MSB) --- virtual bool supportsStripGrayscale() const { return false; } + + // True when this controller accepts absolute selector planes via displayGrayAbsolute() + // This is a runtime capability because one firmware image may include several drivers + // and driver configurations and select the controller during boot. + virtual bool supportsAbsoluteGrayscale() const { return false; } // True when displayGrayscaleBase() DEFERS the base activation so the gray // planes join it in a single waveform (Paper Mono). Hosts should then route the // grayscale base through displayGrayscaleBase() instead of display(): a @@ -139,6 +144,10 @@ class PanelDriver { (void)factoryMode; display(bus, fb, nullptr, RefreshMode::Fast, turnOff); } + virtual void displayGrayAbsolute(EpdBus& bus, const uint8_t* fb, bool turnOff) + { + displayGray(bus, fb, turnOff, nullptr, false); + } // Diagnostic four-gray comparison. The full frame is first rendered with // the controller's flashing OTP waveform; `custom*` is then rebuilt with the // driver's non-flashing grayscale path. Default drivers keep the OTP frame. diff --git a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp index 5b251c7..5423bb5 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp @@ -66,6 +66,8 @@ const Ssd1677Config& ssd1677DefaultConfig() { 0xC0, // borderWaveformGray: written explicitly with the external AA LUT (vendor // reference stage 2); same value the B/W paths leave in the register, so // the wire state is unchanged — just no longer relying on carry-over + false, // grayPowerUpFirst + true, // enableAbsoluteLut }; return cfg; } @@ -635,6 +637,11 @@ void Ssd1677Driver::displayGray(EpdBus& bus, const uint8_t* fb, bool turnOff, co setCustomLut(bus, false, nullptr); } +void Ssd1677Driver::displayGrayAbsolute(EpdBus& bus, const uint8_t* fb, bool turnOff) +{ + displayGray(bus, fb, turnOff, lut_factory_quality, true); +} + void Ssd1677Driver::cleanupGrayscaleBuffers(EpdBus& bus, const uint8_t* bw) { if (!bw) return; setRamArea(bus, 0, 0, _w, _h); diff --git a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h index e7303de..03610bd 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h +++ b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h @@ -53,6 +53,7 @@ struct Ssd1677Config { // collapsing toward B/W). The X4 keeps the panel powered between fast // refreshes, so it never needs this and keeps stock behavior. bool grayPowerUpFirst = false; + bool enableAbsoluteLut = false; }; // Standard config (Xteink X4 / GDEQ0426T82). Panel mounting (mirror/180°) is NOT @@ -85,11 +86,13 @@ class Ssd1677Driver : public PanelDriver { void seedPreviousFrame(EpdBus& bus, const uint8_t* buf) override; bool supportsStripGrayscale() const override { return true; } + bool supportsAbsoluteGrayscale() const override { return _cfg.enableAbsoluteLut; } void copyGrayscaleLsb(EpdBus& bus, const uint8_t* lsb) override; void copyGrayscaleMsb(EpdBus& bus, const uint8_t* msb) override; void writeGrayscalePlaneStrip(EpdBus& bus, GrayPlane plane, const uint8_t* rows, uint16_t yStart, uint16_t numRows) override; void displayGray(EpdBus& bus, const uint8_t* fb, bool turnOff, const unsigned char* lut, bool factoryMode) override; + void displayGrayAbsolute(EpdBus& bus, const uint8_t* fb, bool turnOff) override; void cleanupGrayscaleBuffers(EpdBus& bus, const uint8_t* bw) override; // No grayscaleRevert override: stock parity — the OEM firmware has no revert