Просмотр исходного кода

Let update() return once the draw lambda finishes, not after the refresh

update() previously returned immediately on notifying update_task_,
giving no signal at all for when content generation is actually done.
Add a binary semaphore the task gives right after the first
do_update_() call (the draw lambda) and update() waits on before
returning - so the caller gets synchronous "your content has been
drawn" semantics for the fast part, while the slow part (SPI write +
physical busy-wait refresh) still runs fully in the background.

For a single-page model (height_divisor 1, ~80 of 101 catalog entries)
that first do_update_() is the *only* one, so this covers the entire
draw phase. For a paged model the lambda runs again per subsequent
page, interleaved with that page's hardware write, entirely in the
background after we've returned - so there this only guarantees the
first page's content is drawn, not all of it. Documented rather than
solved further for now.
ZiB 1 месяц назад
Родитель
Сommit
c979e40789
1 измененных файлов с 20 добавлено и 0 удалено
  1. 20 0
      components/gxepd2/gxepd2_display.h

+ 20 - 0
components/gxepd2/gxepd2_display.h

@@ -3,6 +3,7 @@
 #include <atomic>
 
 #include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
 #include <freertos/task.h>
 
 #include "esphome/core/component.h"
@@ -136,6 +137,7 @@ class GxEPD2Display : public display::DisplayBuffer {
     //    shared spi: bus should mostly be fine - but that's about the
     //    write phase, not this task boundary, and hasn't been stress-tested
     //    against another device on the same bus mid-refresh.
+    this->lambda_done_sem_ = xSemaphoreCreateBinary();
     xTaskCreate(&GxEPD2Display::update_task_trampoline_, "gxepd2_update", 4096, this, 1, &this->update_task_);
   }
 
@@ -146,6 +148,14 @@ class GxEPD2Display : public display::DisplayBuffer {
       return;
     }
     xTaskNotifyGive(this->update_task_);
+    // Block only through this frame's draw lambda - not through the SPI
+    // write / physical refresh that follows, which continues on
+    // update_task_ after we return. For a paged (height_divisor > 1) model
+    // this only covers the first page: the lambda runs again per
+    // subsequent page, interleaved with that page's hardware write, fully
+    // in the background. Most models are single-page, where this is the
+    // entire draw phase.
+    xSemaphoreTake(this->lambda_done_sem_, portMAX_DELAY);
   }
 
   void dump_config() override {
@@ -206,8 +216,17 @@ class GxEPD2Display : public display::DisplayBuffer {
       uint32_t start = millis();
       this->driver_->setFullWindow();
       this->driver_->firstPage();
+      bool first_page = true;
       do {
         this->do_update_();
+        if (first_page) {
+          // update() is waiting on exactly this: the draw lambda for the
+          // first (often only) page has finished, so the caller can
+          // proceed while the SPI write and physical refresh below run
+          // without it.
+          xSemaphoreGive(this->lambda_done_sem_);
+          first_page = false;
+        }
       } while (this->driver_->nextPage());
       ESP_LOGD(TAG, "Display update took %u ms", (unsigned) (millis() - start));
       this->update_running_.store(false, std::memory_order_release);
@@ -215,6 +234,7 @@ class GxEPD2Display : public display::DisplayBuffer {
   }
 
   TaskHandle_t update_task_{nullptr};
+  SemaphoreHandle_t lambda_done_sem_{nullptr};
   std::atomic<bool> update_running_{false};
 };