From 97b36f3783ce06b949cd47901268131e2c94ec09 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Mon, 24 Mar 2025 13:22:40 +1300 Subject: [PATCH] fix: allow timeout if display update fails Result is not graceful, but avoids total display lockup requiring power cycle. Typical cause of failure is poor wiring / power supply. --- src/graphics/niche/Drivers/EInk/EInk.cpp | 22 +++++++++++++--- src/graphics/niche/Drivers/EInk/EInk.h | 4 ++- src/graphics/niche/Drivers/EInk/SSD16XX.cpp | 29 +++++++++++++++++++-- src/graphics/niche/Drivers/EInk/SSD16XX.h | 2 +- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/graphics/niche/Drivers/EInk/EInk.cpp b/src/graphics/niche/Drivers/EInk/EInk.cpp index 043788b13..bdb69bff8 100644 --- a/src/graphics/niche/Drivers/EInk/EInk.cpp +++ b/src/graphics/niche/Drivers/EInk/EInk.cpp @@ -6,7 +6,7 @@ using namespace NicheGraphics::Drivers; // Separate from EInk::begin method, as derived class constructors can probably supply these parameters as constants EInk::EInk(uint16_t width, uint16_t height, UpdateTypes supported) - : concurrency::OSThread("E-Ink Driver"), width(width), height(height), supportedUpdateTypes(supported) + : concurrency::OSThread("EInkDriver"), width(width), height(height), supportedUpdateTypes(supported) { OSThread::disable(); } @@ -31,8 +31,9 @@ bool EInk::supports(UpdateTypes type) void EInk::beginPolling(uint32_t interval, uint32_t expectedDuration) { updateRunning = true; - updateBegunAt = millis(); pollingInterval = interval; + pollingBegunAt = millis(); + pollingExpectedDuration = expectedDuration; // To minimize load, we can choose to delay polling for a few seconds, if we know roughly how long the update will take // By default, expectedDuration is 0, and we'll start polling immediately @@ -45,10 +46,25 @@ void EInk::beginPolling(uint32_t interval, uint32_t expectedDuration) // This is what allows us to update the display asynchronously int32_t EInk::runOnce() { + // Check for polling timeout + if (millis() - pollingBegunAt > pollingExpectedDuration * 3) + failed = true; + + // Handle failure + // - polling timeout + // - other error (derived classes) + if (failed) { + LOG_WARN("Display update failed. Check wiring & power supply."); + updateRunning = false; + failed = false; + return disable(); + } + + // If update not yet done if (!isUpdateDone()) return pollingInterval; // Poll again in a few ms - // If update done: + // If update done finalizeUpdate(); // Any post-update code: power down panel hardware, hibernate, etc updateRunning = false; // Change what we report via EInk::busy() return disable(); // Stop polling diff --git a/src/graphics/niche/Drivers/EInk/EInk.h b/src/graphics/niche/Drivers/EInk/EInk.h index 84222a744..da81f6b4f 100644 --- a/src/graphics/niche/Drivers/EInk/EInk.h +++ b/src/graphics/niche/Drivers/EInk/EInk.h @@ -41,14 +41,16 @@ class EInk : private concurrency::OSThread void beginPolling(uint32_t interval, uint32_t expectedDuration); // Begin checking repeatedly if update finished virtual bool isUpdateDone() = 0; // Check once if update finished virtual void finalizeUpdate() {} // Run any post-update code + bool failed = false; // If an error occurred during update private: int32_t runOnce() override; // Repeated checking if update finished const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class bool updateRunning = false; // see EInk::busy() - uint32_t updateBegunAt = 0; // For initial pause before polling for update completion uint32_t pollingInterval = 0; // How often to check if update complete (ms) + uint32_t pollingBegunAt = 0; // To timeout during polling + uint32_t pollingExpectedDuration = 0; // To timeout during polling }; } // namespace NicheGraphics::Drivers diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.cpp b/src/graphics/niche/Drivers/EInk/SSD16XX.cpp index 6b4bb7245..5a5397dbd 100644 --- a/src/graphics/niche/Drivers/EInk/SSD16XX.cpp +++ b/src/graphics/niche/Drivers/EInk/SSD16XX.cpp @@ -37,11 +37,26 @@ void SSD16XX::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_b reset(); } -void SSD16XX::wait() +// Poll the displays busy pin until an operation is complete +// Timeout and set fail flag if something went wrong and the display got stuck +void SSD16XX::wait(uint32_t timeout) { + // Don't bother waiting if part of the update sequence failed + // In that situation, we're now just failing-through the process, until we can try again with next update. + if (failed) + return; + + uint32_t startMs = millis(); + // Busy when HIGH - while (digitalRead(pin_busy) == HIGH) + while (digitalRead(pin_busy) == HIGH) { + // Check for timeout + if (millis() - startMs > timeout) { + failed = true; + break; + } yield(); + } } void SSD16XX::reset() @@ -62,6 +77,11 @@ void SSD16XX::reset() void SSD16XX::sendCommand(const uint8_t command) { + // Abort if part of the update sequence failed + // This will unlock again once we have failed-through the entire process + if (failed) + return; + spi->beginTransaction(spiSettings); digitalWrite(pin_dc, LOW); // DC pin low indicates command digitalWrite(pin_cs, LOW); @@ -78,6 +98,11 @@ void SSD16XX::sendData(uint8_t data) void SSD16XX::sendData(const uint8_t *data, uint32_t size) { + // Abort if part of the update sequence failed + // This will unlock again once we have failed-through the entire process + if (failed) + return; + spi->beginTransaction(spiSettings); digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command digitalWrite(pin_cs, LOW); diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.h b/src/graphics/niche/Drivers/EInk/SSD16XX.h index 88fe4dc25..799a378c0 100644 --- a/src/graphics/niche/Drivers/EInk/SSD16XX.h +++ b/src/graphics/niche/Drivers/EInk/SSD16XX.h @@ -27,7 +27,7 @@ class SSD16XX : public EInk virtual void update(uint8_t *imageData, UpdateTypes type) override; protected: - virtual void wait(); + virtual void wait(uint32_t timeout = 1000); virtual void reset(); virtual void sendCommand(const uint8_t command); virtual void sendData(const uint8_t data);