From 66931df8c1f6f53b325d1305af2cb9fa9d776d99 Mon Sep 17 00:00:00 2001 From: Alexander Matthes Date: Sat, 15 Aug 2026 19:46:20 +0200 Subject: [PATCH 1/2] feat: Add FreeInkDisplay functions to work with absolute grayscale luts This is needed for using the absolute factory lut of the SS1677 on X4 without device specific information in crosspoint reader. Renamed supportsFactoryGrayscale to supportsAbsoluteGrayscale to make the difference more to the default differential approach more clear --- .../FreeInkDisplay/include/FreeInkDisplay.h | 3 +++ .../FreeInkDisplay/src/FreeInkDisplay.cpp | 17 +++++++++++++++++ .../FreeInkDisplay/src/driver/PanelDriver.h | 9 +++++++++ .../FreeInkDisplay/src/driver/Ssd1677Driver.cpp | 5 +++++ .../FreeInkDisplay/src/driver/Ssd1677Driver.h | 2 ++ 5 files changed, 36 insertions(+) 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..57e184a 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp @@ -635,6 +635,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..7729317 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h +++ b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h @@ -85,11 +85,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 true; } 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 From 2e766400544060e7f2cf50f6e4934c08f7f0cc21 Mon Sep 17 00:00:00 2001 From: Alexander Matthes Date: Sun, 16 Aug 2026 20:05:26 +0200 Subject: [PATCH 2/2] fix: enable absolute grayscale lut support only for X4 with SSD1677, not for sticky --- libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp | 2 ++ libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.cpp index 57e184a..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; } diff --git a/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h b/libs/display/FreeInkDisplay/src/driver/Ssd1677Driver.h index 7729317..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,7 +86,7 @@ class Ssd1677Driver : public PanelDriver { void seedPreviousFrame(EpdBus& bus, const uint8_t* buf) override; bool supportsStripGrayscale() const override { return true; } - bool supportsAbsoluteGrayscale() 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,