diff --git a/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp b/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp index eb77f4a..e7c20c3 100644 --- a/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp +++ b/libs/display/FreeInkDisplay/src/FreeInkDisplay.cpp @@ -1,12 +1,16 @@ #include "FreeInkDisplay.h" #include +#include #include #ifndef ARDUINO #include #include #endif +#if defined(ARDUINO) && CONFIG_PM_ENABLE +#include +#endif #if FREEINK_FB_PSRAM #include @@ -61,6 +65,46 @@ RefreshMode toInternal(FreeInkDisplay::RefreshMode m) { } } +#if defined(ARDUINO) && CONFIG_PM_ENABLE +esp_pm_lock_handle_t displayNoLightSleepLock() { + static esp_pm_lock_handle_t lock = nullptr; + static bool initialized = false; + if (!initialized) { + initialized = true; + if (esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "freeink-display", &lock) != ESP_OK) { + lock = nullptr; + } + } + return lock; +} + +class DisplayPmLock { + public: + DisplayPmLock() { + _lock = displayNoLightSleepLock(); + _acquired = _lock != nullptr && esp_pm_lock_acquire(_lock) == ESP_OK; + } + + ~DisplayPmLock() { + if (_acquired) esp_pm_lock_release(_lock); + } + + DisplayPmLock(const DisplayPmLock&) = delete; + DisplayPmLock& operator=(const DisplayPmLock&) = delete; + + private: + esp_pm_lock_handle_t _lock = nullptr; + bool _acquired = false; +}; +#else +class DisplayPmLock { + public: + DisplayPmLock() = default; + DisplayPmLock(const DisplayPmLock&) = delete; + DisplayPmLock& operator=(const DisplayPmLock&) = delete; +}; +#endif + void invertBytes(uint8_t* buffer, const uint32_t size) { if (!buffer) return; for (uint32_t i = 0; i < size; ++i) { @@ -175,6 +219,7 @@ void FreeInkDisplay::selectDriver() { } void FreeInkDisplay::begin() { + DisplayPmLock pmLock; selectDriver(); // External-library drivers (e.g. M5GFX) own the SPI/display hardware; only @@ -537,6 +582,7 @@ void FreeInkDisplay::syncPendingAsync() { // pipeline (X3 DTM1 sync + conditioning). A plain waitBusy would skip that // and leave the controller mid-pipeline. if (!_refreshPending) return; + DisplayPmLock pmLock; #ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE _driver->displayFinish(_bus, frameBuffer); #else @@ -553,10 +599,13 @@ bool FreeInkDisplay::refreshBusy() { // Does NOT clear the pending state on completion: the driver's post-waveform // work (X3 DTM1 sync) must run through displayFinish(). When this returns // false, call waitRefreshComplete() (or any blocking display op) to drain it. + if (!_refreshPending) return false; + DisplayPmLock pmLock; return _refreshPending && _bus.isBusy(); } void FreeInkDisplay::displayBuffer(RefreshMode mode, bool turnOffScreen) { + DisplayPmLock pmLock; #if defined(SSD1677_PROBE_DEBUG) && SSD1677_PROBE_DEBUG Serial.printf("[EPD] displayBuffer mode=%d off=%d\n", (int)mode, (int)turnOffScreen); #endif @@ -608,6 +657,7 @@ void FreeInkDisplay::displayAsyncImpl(RefreshMode mode, bool turnOffScreen, bool displayBuffer(mode, turnOffScreen); return; } + DisplayPmLock pmLock; syncPendingAsync(); #ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE if (noShadow) { @@ -663,6 +713,7 @@ void FreeInkDisplay::triggerDisplay(RefreshMode mode, bool turnOffScreen) { displayBuffer(mode, turnOffScreen); return; } + DisplayPmLock pmLock; syncPendingAsync(); // finish any prior split/async refresh before starting another #ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE const bool deferred = _driver->displayStart(_bus, frameBuffer, nullptr, toInternal(mode), turnOffScreen); @@ -706,6 +757,7 @@ void FreeInkDisplay::syncRedRamFromFrameBuffer() { // X3 has no host-managed previous-frame plane (its baseline lives in DTM1); the // advisory flag is meaningless there. if (_panelSel == PanelSel::X3) return; + DisplayPmLock pmLock; #ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE // Single-buffer builds: the driver reseeds RED from the framebuffer after every // refresh (displayImpl prev==nullptr path), so RED already holds the on-screen @@ -742,6 +794,7 @@ void FreeInkDisplay::displayBufferAsyncNoShadow(RefreshMode mode) { } void FreeInkDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool turnOffScreen) { + DisplayPmLock pmLock; #if defined(SSD1677_PROBE_DEBUG) && SSD1677_PROBE_DEBUG Serial.printf("[EPD] displayWindow %u,%u %ux%u\n", x, y, w, h); #endif @@ -762,6 +815,7 @@ void FreeInkDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t } void FreeInkDisplay::displayGrayBuffer(bool turnOffScreen, const unsigned char* lut, bool factoryMode) { + DisplayPmLock pmLock; #if defined(SSD1677_PROBE_DEBUG) && SSD1677_PROBE_DEBUG Serial.printf("[EPD] displayGrayBuffer\n"); #endif @@ -778,6 +832,7 @@ void FreeInkDisplay::refreshDisplay(RefreshMode mode, bool turnOffScreen) { disp void FreeInkDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); // RAM writes must not race a deferred refresh _driver->copyGrayscaleLsb(_bus, lsbBuffer); _driver->copyGrayscaleMsb(_bus, msbBuffer); @@ -788,6 +843,7 @@ void FreeInkDisplay::displayGrayscaleBase(RefreshMode fallback, bool turnOffScre displayBuffer(fallback, turnOffScreen); return; } + DisplayPmLock pmLock; syncPendingAsync(); _shadowValid = false; _driver->displayGrayscaleBase(_bus, frameBuffer, toInternal(fallback), turnOffScreen); @@ -795,24 +851,28 @@ void FreeInkDisplay::displayGrayscaleBase(RefreshMode fallback, bool turnOffScre void FreeInkDisplay::preconditionGrayscale() { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); _driver->preconditionGrayscale(_bus, 0, 0, getDisplayWidth(), getDisplayHeight()); } void FreeInkDisplay::preconditionGrayscale(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); _driver->preconditionGrayscale(_bus, x, y, w, h); } void FreeInkDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); _driver->copyGrayscaleLsb(_bus, lsbBuffer); } void FreeInkDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); _driver->copyGrayscaleMsb(_bus, msbBuffer); } @@ -820,6 +880,7 @@ void FreeInkDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { void FreeInkDisplay::writeGrayscalePlaneStrip(GrayPlane plane, const uint8_t* rows, uint16_t yStart, uint16_t numRows) { if (_inverted) return; + DisplayPmLock pmLock; syncPendingAsync(); // no-op in the reader flow (it waits first); guards misuse _driver->writeGrayscalePlaneStrip(_bus, plane == GRAY_PLANE_LSB ? freeink::GrayPlane::Lsb : freeink::GrayPlane::Msb, rows, yStart, numRows); @@ -830,6 +891,7 @@ bool FreeInkDisplay::supportsStripGrayscale() const { } void FreeInkDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { + DisplayPmLock pmLock; syncPendingAsync(); if (!_inverted) { _driver->cleanupGrayscaleBuffers(_bus, bwBuffer); @@ -841,6 +903,7 @@ void FreeInkDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { } #ifndef EINK_DISPLAY_SINGLE_BUFFER_MODE void FreeInkDisplay::cleanupGrayscaleWithPreviousBuffer() { + DisplayPmLock pmLock; const uint8_t* baseline = frameBufferActive ? frameBufferActive : frameBuffer; if (!_inverted) { _driver->cleanupGrayscaleBuffers(_bus, baseline); @@ -871,14 +934,18 @@ uint16_t FreeInkDisplay::fastRefreshCutoffMs() const { } void FreeInkDisplay::grayscaleRevert() { - if (!_inverted && _driver) _driver->grayscaleRevert(_bus, frameBuffer); + if (_inverted || !_driver) return; + DisplayPmLock pmLock; + _driver->grayscaleRevert(_bus, frameBuffer); } void FreeInkDisplay::setCustomLUT(bool enabled, const unsigned char* lutData) { + DisplayPmLock pmLock; if (_driver) _driver->setCustomLut(_bus, enabled, lutData); } void FreeInkDisplay::deepSleep() { + DisplayPmLock pmLock; syncPendingAsync(); if (_driver) _driver->deepSleep(_bus); } diff --git a/libs/display/FreeInkDisplay/src/bus/EpdBus.cpp b/libs/display/FreeInkDisplay/src/bus/EpdBus.cpp index 406cd5a..05bb245 100644 --- a/libs/display/FreeInkDisplay/src/bus/EpdBus.cpp +++ b/libs/display/FreeInkDisplay/src/bus/EpdBus.cpp @@ -1,6 +1,9 @@ #include "EpdBus.h" #include +#if defined(ARDUINO) && defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE +#include +#endif #include #include @@ -23,7 +26,74 @@ static void IRAM_ATTR epdBusyIsr() { if (woken) portYIELD_FROM_ISR(); } +EpdBus::Transaction::Transaction(EpdBus& bus) : _bus(&bus), _pmLock(bus), _active(true) { + SPI.beginTransaction(_bus->_spi); +} + +EpdBus::Transaction::~Transaction() { end(); } + +EpdBus::Transaction::Transaction(Transaction&& other) noexcept + : _bus(other._bus), _pmLock(std::move(other._pmLock)), _active(other._active) { + other._bus = nullptr; + other._active = false; +} + +EpdBus::Transaction& EpdBus::Transaction::operator=(Transaction&& other) noexcept { + if (this != &other) { + end(); + _bus = other._bus; + _pmLock = std::move(other._pmLock); + _active = other._active; + other._bus = nullptr; + other._active = false; + } + return *this; +} + +void EpdBus::Transaction::end() { + if (!_active || _bus == nullptr) { + return; + } + digitalWrite(_bus->_pins.cs, HIGH); + SPI.endTransaction(); + _pmLock.release(); + _active = false; +} + +uint8_t EpdBus::Transaction::transfer(uint8_t d) { return SPI.transfer(d); } + +void EpdBus::Transaction::cmd(uint8_t c) { + digitalWrite(_bus->_pins.dc, LOW); + SPI.transfer(c); + digitalWrite(_bus->_pins.dc, HIGH); +} + +void EpdBus::Transaction::data(uint8_t d) { + digitalWrite(_bus->_pins.dc, HIGH); + SPI.transfer(d); +} + +void EpdBus::Transaction::writeBytes(const uint8_t* d, uint16_t len) { + digitalWrite(_bus->_pins.dc, HIGH); + SPI.writeBytes(d, len); +} + +EpdBus::~EpdBus() { +#if defined(ARDUINO) && defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE + if (_spiApbLock != nullptr) { + esp_pm_lock_delete(_spiApbLock); + _spiApbLock = nullptr; + } + if (_noLightSleepLock != nullptr) { + esp_pm_lock_delete(_noLightSleepLock); + _noLightSleepLock = nullptr; + } +#endif +} + void EpdBus::begin(const EpdPins& pins, uint32_t spiHz, BusyPolarity busy, int8_t spiMiso, int8_t coCs) { + createPmLocks(); + NoLightSleepLockGuard noLightSleepLock(*this); _pins = pins; _spiHz = spiHz; _busy = busy; @@ -45,7 +115,12 @@ void EpdBus::begin(const EpdPins& pins, uint32_t spiHz, BusyPolarity busy, int8_ delay(100); } - SPI.begin(pins.sclk, spiMiso, pins.mosi, pins.cs); + { + // SPI.begin() configures the peripheral/dividers from the current APB clock; + // keep APB fixed here so the setup matches the requested display SPI rate. + SpiApbLockGuard spiPmLock(*this); + SPI.begin(pins.sclk, spiMiso, pins.mosi, pins.cs); + } pinMode(pins.cs, OUTPUT); pinMode(pins.dc, OUTPUT); @@ -64,6 +139,7 @@ void EpdBus::begin(const EpdPins& pins, uint32_t spiHz, BusyPolarity busy, int8_ } void EpdBus::reset(uint16_t extraSettleMs) { + NoLightSleepLockGuard noLightSleepLock(*this); digitalWrite(_pins.rst, HIGH); delay(20); digitalWrite(_pins.rst, LOW); @@ -75,44 +151,47 @@ void EpdBus::reset(uint16_t extraSettleMs) { } } +EpdBus::Transaction EpdBus::transaction() { return Transaction(*this); } + +EpdBus::Transaction EpdBus::beginTxn() { + if (_coCs >= 0) { + digitalWrite(_coCs, HIGH); + } + auto txn = transaction(); + digitalWrite(_pins.cs, LOW); + return txn; +} + void EpdBus::cmd(uint8_t c) { - SPI.beginTransaction(_spi); + auto txn = transaction(); digitalWrite(_pins.dc, LOW); digitalWrite(_pins.cs, LOW); - SPI.transfer(c); - digitalWrite(_pins.cs, HIGH); - SPI.endTransaction(); + txn.transfer(c); } void EpdBus::data(uint8_t d) { - SPI.beginTransaction(_spi); + auto txn = transaction(); digitalWrite(_pins.dc, HIGH); digitalWrite(_pins.cs, LOW); - SPI.transfer(d); - digitalWrite(_pins.cs, HIGH); - SPI.endTransaction(); + txn.transfer(d); } void EpdBus::data(const uint8_t* d, uint16_t len) { - SPI.beginTransaction(_spi); + auto txn = transaction(); digitalWrite(_pins.dc, HIGH); digitalWrite(_pins.cs, LOW); SPI.writeBytes(d, len); - digitalWrite(_pins.cs, HIGH); - SPI.endTransaction(); } void EpdBus::cmdData(uint8_t c, const uint8_t* d, uint16_t len) { - SPI.beginTransaction(_spi); + auto txn = transaction(); digitalWrite(_pins.cs, LOW); digitalWrite(_pins.dc, LOW); - SPI.transfer(c); + txn.transfer(c); if (len > 0 && d != nullptr) { digitalWrite(_pins.dc, HIGH); SPI.writeBytes(d, len); } - digitalWrite(_pins.cs, HIGH); - SPI.endTransaction(); } void EpdBus::cmdData2(uint8_t c, uint8_t d0, uint8_t d1) { @@ -120,33 +199,19 @@ void EpdBus::cmdData2(uint8_t c, uint8_t d0, uint8_t d1) { cmdData(c, d, 2); } -void EpdBus::beginTxn() { - if (_coCs >= 0) { - digitalWrite(_coCs, HIGH); +void EpdBus::createPmLocks() { +#if defined(ARDUINO) && defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE + if (_spiApbLock == nullptr) { + if (esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "freeink-epd-spi", &_spiApbLock) != ESP_OK) { + _spiApbLock = nullptr; + } } - SPI.beginTransaction(_spi); - digitalWrite(_pins.cs, LOW); -} - -void EpdBus::endTxn() { - digitalWrite(_pins.cs, HIGH); - SPI.endTransaction(); -} - -void EpdBus::rawCmd(uint8_t c) { - digitalWrite(_pins.dc, LOW); - SPI.transfer(c); - digitalWrite(_pins.dc, HIGH); -} - -void EpdBus::rawData(uint8_t d) { - digitalWrite(_pins.dc, HIGH); - SPI.transfer(d); -} - -void EpdBus::rawWriteBytes(const uint8_t* d, uint16_t len) { - digitalWrite(_pins.dc, HIGH); - SPI.writeBytes(d, len); + if (_noLightSleepLock == nullptr) { + if (esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "freeink-epd-bus", &_noLightSleepLock) != ESP_OK) { + _noLightSleepLock = nullptr; + } + } +#endif } void EpdBus::waitBusy(const char* tag) { waitBusy(_busy, tag); } @@ -246,6 +311,7 @@ void EpdBus::waitRefreshComplete(const char* tag) { waitBusy(tag); return; } + // Levels/edge by polarity. X4 (ActiveHigh): working HIGH, done on the HIGH->LOW // (FALLING) edge. X3 (X3TwoPhase) / ActiveLow: working LOW, done on the LOW->HIGH // (RISING) edge. @@ -293,11 +359,10 @@ void EpdBus::waitRefreshComplete(const char* tag) { void EpdBus::sendPlaneFlipped(uint8_t ramCmd, const uint8_t* plane, uint16_t height, uint16_t widthBytes) { cmd(ramCmd); // own CS pulse - beginTxn(); // single CS-low burst for the whole plane + auto txn = beginTxn(); // single CS-low burst for the whole plane for (int y = static_cast(height) - 1; y >= 0; y--) { - rawWriteBytes(plane + static_cast(y) * widthBytes, widthBytes); + txn.writeBytes(plane + static_cast(y) * widthBytes, widthBytes); } - endTxn(); } void EpdBus::fillPlane(uint8_t ramCmd, uint8_t fillByte, uint16_t height, uint16_t widthBytes) { @@ -306,16 +371,15 @@ void EpdBus::fillPlane(uint8_t ramCmd, uint8_t fillByte, uint16_t height, uint16 uint8_t chunk[128]; memset(chunk, fillByte, sizeof(chunk)); cmd(ramCmd); - beginTxn(); + auto txn = beginTxn(); for (uint16_t y = 0; y < height; y++) { uint16_t remaining = widthBytes; while (remaining) { const uint16_t n = remaining < sizeof(chunk) ? remaining : static_cast(sizeof(chunk)); - rawWriteBytes(chunk, n); + txn.writeBytes(chunk, n); remaining = static_cast(remaining - n); } } - endTxn(); } } // namespace freeink diff --git a/libs/display/FreeInkDisplay/src/bus/EpdBus.h b/libs/display/FreeInkDisplay/src/bus/EpdBus.h index 579a578..82b7cef 100644 --- a/libs/display/FreeInkDisplay/src/bus/EpdBus.h +++ b/libs/display/FreeInkDisplay/src/bus/EpdBus.h @@ -9,6 +9,10 @@ #include #include +#include +#if defined(ARDUINO) && defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE +#include +#endif namespace freeink { @@ -33,9 +37,107 @@ struct EpdPins { }; class EpdBus { + private: +#if defined(ARDUINO) && defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE + // Holds APB at its maximum while SPI timing is configured or bytes are on the bus. + // Use for SPI.begin() and every SPI transaction so DFS cannot change the APB clock mid-operation. + esp_pm_lock_handle_t _spiApbLock = nullptr; + + // Blocks automatic light sleep while panel rails, reset GPIO, or BUSY polling are active. + // Use around non-SPI panel control sequences that must not be paused by light sleep. + esp_pm_lock_handle_t _noLightSleepLock = nullptr; + + template + class PmLockGuard { + public: + PmLockGuard() = default; + explicit PmLockGuard(EpdBus& bus) : _bus(&bus) { acquire(); } + ~PmLockGuard() { release(); } + + PmLockGuard(const PmLockGuard&) = delete; + PmLockGuard& operator=(const PmLockGuard&) = delete; + PmLockGuard(PmLockGuard&& other) noexcept : _bus(other._bus), _acquired(other._acquired) { + other._bus = nullptr; + other._acquired = false; + } + PmLockGuard& operator=(PmLockGuard&& other) noexcept { + if (this != &other) { + release(); + _bus = other._bus; + _acquired = other._acquired; + other._bus = nullptr; + other._acquired = false; + } + return *this; + } + + void acquire() { + if (_acquired || _bus == nullptr) { + return; + } + const esp_pm_lock_handle_t lock = _bus->*Lock; + _acquired = lock != nullptr && esp_pm_lock_acquire(lock) == ESP_OK; + } + + void release() { + if (!_acquired || _bus == nullptr) { + return; + } + const esp_pm_lock_handle_t lock = _bus->*Lock; + if (lock != nullptr) { + esp_pm_lock_release(lock); + } + _acquired = false; + } + + private: + EpdBus* _bus = nullptr; + bool _acquired = false; + }; + + using SpiApbLockGuard = PmLockGuard<&EpdBus::_spiApbLock>; + using NoLightSleepLockGuard = PmLockGuard<&EpdBus::_noLightSleepLock>; +#else + class SpiApbLockGuard { + public: + SpiApbLockGuard() = default; + explicit SpiApbLockGuard(EpdBus&) {} + void release() {} + }; + + class NoLightSleepLockGuard { + public: + explicit NoLightSleepLockGuard(EpdBus&) {} + }; +#endif + public: + class Transaction { + public: + Transaction() = default; + explicit Transaction(EpdBus& bus); + ~Transaction(); + + Transaction(const Transaction&) = delete; + Transaction& operator=(const Transaction&) = delete; + Transaction(Transaction&& other) noexcept; + Transaction& operator=(Transaction&& other) noexcept; + + void end(); + void cmd(uint8_t c); + void data(uint8_t d); + uint8_t transfer(uint8_t d); + void writeBytes(const uint8_t* d, uint16_t len); + + private: + EpdBus* _bus = nullptr; + SpiApbLockGuard _pmLock; + bool _active = false; + }; + // coCs: a co-resident chip-select (e.g. the SD card sharing the SPI bus on // M5 PaperColor) that must be held de-asserted during panel transactions. + ~EpdBus(); void begin(const EpdPins& pins, uint32_t spiHz, BusyPolarity busy, int8_t spiMiso = -1, int8_t coCs = -1); // Hardware reset pulse; extraSettleMs adds a post-reset settle (X3 needs 50 ms). @@ -50,12 +152,8 @@ class EpdBus { void cmdData(uint8_t c, const uint8_t* d, uint16_t len); void cmdData2(uint8_t c, uint8_t d0, uint8_t d1); - // Grouped transaction primitives (used by multi-step sequences, e.g. M5). - void beginTxn(); - void endTxn(); - void rawCmd(uint8_t c); // assumes a transaction is open - void rawData(uint8_t d); // assumes a transaction is open - void rawWriteBytes(const uint8_t* d, uint16_t len); // bulk data, transaction open + // Scoped SPI/APB transaction that selects the panel and de-selects it on exit. + [[nodiscard]] Transaction beginTxn(); // Wait for a refresh/operation to finish using the configured (or given) polarity. void waitBusy(const char* tag = nullptr); @@ -123,6 +221,9 @@ class EpdBus { delay(fallbackDelayMs); } + void createPmLocks(); + [[nodiscard]] Transaction transaction(); + EpdPins _pins{-1, -1, -1, -1, -1, -1}; SPISettings _spi; BusyPolarity _busy = BusyPolarity::ActiveHigh; diff --git a/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.cpp b/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.cpp index b3be385..9552206 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.cpp @@ -93,23 +93,22 @@ void Ed2208M5Driver::initController(EpdBus& bus) { 0x84, 1, 0x01, }; - bus.beginTxn(); + auto txn = bus.beginTxn(); for (size_t i = 0; i < sizeof(initCommands);) { const uint8_t command = initCommands[i++]; const uint8_t length = initCommands[i++]; waitBusy(bus); - bus.rawCmd(command); + txn.cmd(command); for (uint8_t j = 0; j < length; ++j) { - bus.rawData(initCommands[i++]); + txn.data(initCommands[i++]); } } waitBusy(bus); - bus.rawCmd(0x61); - bus.rawData(static_cast((PANEL_WIDTH >> 8) & 0xFF)); - bus.rawData(static_cast(PANEL_WIDTH & 0xFF)); - bus.rawData(static_cast((PANEL_HEIGHT >> 8) & 0xFF)); - bus.rawData(static_cast(PANEL_HEIGHT & 0xFF)); - bus.endTxn(); + txn.cmd(0x61); + txn.data(static_cast((PANEL_WIDTH >> 8) & 0xFF)); + txn.data(static_cast(PANEL_WIDTH & 0xFF)); + txn.data(static_cast((PANEL_HEIGHT >> 8) & 0xFF)); + txn.data(static_cast(PANEL_HEIGHT & 0xFF)); } void Ed2208M5Driver::begin(EpdBus& bus) { @@ -139,8 +138,8 @@ void Ed2208M5Driver::writeFrame(EpdBus& bus, const uint8_t* fb) { #endif uint8_t packedRow[PANEL_WIDTH / 2]; - bus.beginTxn(); - bus.rawCmd(0x10); + auto txn = bus.beginTxn(); + txn.cmd(0x10); for (uint16_t panelY = 0; panelY < PANEL_HEIGHT; ++panelY) { for (uint16_t panelX = 0; panelX < PANEL_WIDTH; panelX += 2) { const uint16_t leftLogicalX = panelY; @@ -157,12 +156,11 @@ void Ed2208M5Driver::writeFrame(EpdBus& bus, const uint8_t* fb) { packedRow[panelX >> 1] = static_cast(((leftWhite ? EPD_WHITE : EPD_BLACK) << 4) | (rightWhite ? EPD_WHITE : EPD_BLACK)); } - bus.rawWriteBytes(packedRow, sizeof(packedRow)); + txn.writeBytes(packedRow, sizeof(packedRow)); } - bus.endTxn(); } -void Ed2208M5Driver::setPartialWindow(EpdBus& bus, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { +void Ed2208M5Driver::setPartialWindow(EpdBus::Transaction& txn, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { if (w == 0 || h == 0) return; const uint16_t xEnd = static_cast(x + w - 1); const uint16_t yEnd = static_cast(y + h - 1); // inclusive, matching xEnd @@ -173,26 +171,24 @@ void Ed2208M5Driver::setPartialWindow(EpdBus& bus, uint16_t x, uint16_t y, uint1 static_cast(yEnd >> 8), static_cast(yEnd & 0xFF), 0x01, }; - bus.rawCmd(0x83); - for (uint8_t v : window) bus.rawData(v); + txn.cmd(0x83); + for (uint8_t v : window) txn.data(v); } void Ed2208M5Driver::powerOn(EpdBus& bus) { if (_panelPowerOn) return; - bus.beginTxn(); - bus.rawCmd(0x04); + auto txn = bus.beginTxn(); + txn.cmd(0x04); waitBusy(bus); - bus.endTxn(); _panelPowerOn = true; } void Ed2208M5Driver::powerOff(EpdBus& bus) { if (!_panelPowerOn) return; - bus.beginTxn(); - bus.rawCmd(0x02); - bus.rawData(0x00); + auto txn = bus.beginTxn(); + txn.cmd(0x02); + txn.data(0x00); waitBusy(bus); - bus.endTxn(); _panelPowerOn = false; } @@ -275,20 +271,20 @@ void Ed2208M5Driver::refresh(EpdBus& bus, uint16_t dirtyX, uint16_t dirtyY, uint _completeNextRefresh = false; powerOn(bus); - bus.beginTxn(); - bus.rawCmd(0x06); - bus.rawData(0x6F); - bus.rawData(0x1F); - bus.rawData(0x17); - bus.rawData(0x27); - setPartialWindow(bus, dirtyX, dirtyY, dirtyW, dirtyH); - bus.rawCmd(0x50); + auto txn = bus.beginTxn(); + txn.cmd(0x06); + txn.data(0x6F); + txn.data(0x1F); + txn.data(0x17); + txn.data(0x27); + setPartialWindow(txn, dirtyX, dirtyY, dirtyW, dirtyH); + txn.cmd(0x50); // Complete refresh uses the vendor VCOM/CDI for full contrast; the fast // interrupt path keeps the dark-hack value. - bus.rawData(completeWaveform ? 0x3F : DARK_DISPLAY_CTRL); - bus.rawCmd(0x12); - bus.rawData(0x00); - bus.endTxn(); + txn.data(completeWaveform ? 0x3F : DARK_DISPLAY_CTRL); + txn.cmd(0x12); + txn.data(0x00); + txn.end(); if (completeWaveform) { // Run the full OTP waveform to completion. waitBusy()'s generic 100 ms @@ -308,11 +304,10 @@ void Ed2208M5Driver::refresh(EpdBus& bus, uint16_t dirtyX, uint16_t dirtyY, uint delay(10); } delay(BUSY_SETTLE_MS); - bus.beginTxn(); - bus.rawCmd(0x02); // POWER_OFF - bus.rawData(0x00); + auto powerOffTxn = bus.beginTxn(); + powerOffTxn.cmd(0x02); // POWER_OFF + powerOffTxn.data(0x00); waitBusy(bus); - bus.endTxn(); _panelPowerOn = false; } else { interruptRefresh(bus); diff --git a/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.h b/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.h index 7113f44..72ba329 100644 --- a/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.h +++ b/libs/display/FreeInkDisplay/src/driver/Ed2208M5Driver.h @@ -54,7 +54,7 @@ class Ed2208M5Driver : public PanelDriver { void initController(EpdBus& bus); void waitBusy(EpdBus& bus); void writeFrame(EpdBus& bus, const uint8_t* fb); - void setPartialWindow(EpdBus& bus, uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void setPartialWindow(EpdBus::Transaction& txn, uint16_t x, uint16_t y, uint16_t w, uint16_t h); void powerOn(EpdBus& bus); void powerOff(EpdBus& bus); void interruptRefresh(EpdBus& bus); diff --git a/libs/display/FreeInkDisplay/src/driver/Uc8253MurphyDriver.cpp b/libs/display/FreeInkDisplay/src/driver/Uc8253MurphyDriver.cpp index ede448c..b2ea833 100644 --- a/libs/display/FreeInkDisplay/src/driver/Uc8253MurphyDriver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Uc8253MurphyDriver.cpp @@ -72,7 +72,7 @@ void Uc8253MurphyDriver::loadLut(EpdBus& bus, const Uc8253MurphyLutBank& bank) { // (240x416). Controller pixel (cx,cy) maps to framebuffer (srcX=cy, srcY=fbH-1-cx). void Uc8253MurphyDriver::writePlane(EpdBus& bus, uint8_t command, const uint8_t* fb) { bus.cmd(command); - bus.beginTxn(); + auto txn = bus.beginTxn(); uint8_t row[CTRL_WB]; for (uint16_t cy = 0; cy < CTRL_H; cy++) { const uint16_t srcX = cy; // 0..415 -> framebuffer column @@ -82,9 +82,8 @@ void Uc8253MurphyDriver::writePlane(EpdBus& bus, uint8_t command, const uint8_t* const uint8_t bit = (fb[srcY * _fbWb + (srcX >> 3)] >> (7 - (srcX & 7))) & 0x01; if (bit) row[cx >> 3] |= static_cast(1 << (7 - (cx & 7))); } - bus.rawWriteBytes(row, CTRL_WB); + txn.writeBytes(row, CTRL_WB); } - bus.endTxn(); } void Uc8253MurphyDriver::triggerRefresh(EpdBus& bus, bool turnOff) { diff --git a/libs/display/FreeInkDisplay/src/driver/Uc8253X3Driver.cpp b/libs/display/FreeInkDisplay/src/driver/Uc8253X3Driver.cpp index 83a352b..0871162 100644 --- a/libs/display/FreeInkDisplay/src/driver/Uc8253X3Driver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Uc8253X3Driver.cpp @@ -398,11 +398,11 @@ void Uc8253X3Driver::writeGrayscalePlaneStrip(EpdBus& bus, GrayPlane plane, cons bus.cmd(CMD_PARTIAL_IN); bus.cmdData(CMD_PARTIAL_WINDOW, win, 9); bus.cmd(ramCmd); - bus.beginTxn(); + auto txn = bus.beginTxn(); for (int r = static_cast(numRows) - 1; r >= 0; r--) { - bus.rawWriteBytes(rows + static_cast(r) * _wb, _wb); + txn.writeBytes(rows + static_cast(r) * _wb, _wb); } - bus.endTxn(); + txn.end(); bus.cmd(CMD_PARTIAL_OUT); if (plane == GrayPlane::Lsb) _grayState.lsbValid = true; } diff --git a/libs/display/FreeInkDisplay/src/driver/Uc8279Driver.cpp b/libs/display/FreeInkDisplay/src/driver/Uc8279Driver.cpp index fbd12e9..f09212a 100644 --- a/libs/display/FreeInkDisplay/src/driver/Uc8279Driver.cpp +++ b/libs/display/FreeInkDisplay/src/driver/Uc8279Driver.cpp @@ -270,11 +270,10 @@ void Uc8279Driver::writeGrayscalePlaneStrip(EpdBus& bus, GrayPlane plane, const bus.cmd(CMD_PARTIAL_IN); bus.cmdData(CMD_PARTIAL_WINDOW, win, 9); bus.cmd(ramCmd); - bus.beginTxn(); + auto txn = bus.beginTxn(); for (int r = static_cast(numRows) - 1; r >= 0; r--) { - bus.rawWriteBytes(rows + static_cast(r) * _wb, _wb); + txn.writeBytes(rows + static_cast(r) * _wb, _wb); } - bus.endTxn(); bus.cmd(CMD_PARTIAL_OUT); if (plane == GrayPlane::Lsb) _lsbValid = true; }