ソースを参照

Fix idle-task watchdog crash from long uninterrupted SPI write bursts

A full-frame refresh calls SPIClass::transfer() thousands of times
back-to-back (tens of thousands for a 4-color panel), with no yield
anywhere in GxEPD2's own per-byte write loops, and ESP-IDF's
polling-mode SPI transaction doesn't yield the scheduler while it
waits either. That starves this core's idle task long enough to trip
its own watchdog - crashing with a backtrace rooted in prvIdleTask,
not in our code, which is what made this one hard to place.

The earlier App.feed_wdt() fix in Arduino.h's delay()/yield() (for
_waitWhileBusy()'s spin) doesn't cover this: it resets our own task's
watchdog subscription, not the idle task's, and a plain yield() just
hands the CPU straight back to us since nothing else made us
not-ready. Only a real blocking delay lets a lower-priority task like
idle actually run, so force one every 256 bytes transferred.
ZiB 1 ヶ月 前
親
コミット
f85e83fd6d
1 ファイル変更、20 行追加、0 行削除
  1. 20 0
      components/gxepd2/arduino_compat/SPI.h

+ 20 - 0
components/gxepd2/arduino_compat/SPI.h

@@ -8,6 +8,8 @@
 
 #include <cstdint>
 
+#include "esphome/core/application.h"
+#include "esphome/core/hal.h"
 #include "esphome/components/spi/spi.h"
 
 class SPISettings {
@@ -36,9 +38,27 @@ class SPIClass : public esphome::spi::SPIDevice<esphome::spi::BIT_ORDER_MSB_FIRS
   // every panel command. write_byte() only ever asks to write, so it works
   // whether or not MISO is wired.
   uint8_t transfer(uint8_t data) {
+    // A full-frame refresh calls this thousands of times back-to-back (a
+    // 4-color panel's write loop alone is tens of thousands of bytes), with
+    // no yield anywhere in GxEPD2's own per-byte loops, and ESP-IDF's
+    // polling-mode SPI transaction doesn't yield the scheduler while it
+    // waits either. Left unchecked, that starves this core's idle task
+    // long enough to trip ITS watchdog - a hard crash, not just a stall.
+    // App.feed_wdt() alone doesn't fix this: it resets our own task's
+    // watchdog, not the idle task's, and a plain yield() just hands the
+    // CPU right back to us since nothing else made us not-ready. Only a
+    // real blocking delay actually lets a lower-priority task like idle
+    // run, so force one periodically.
+    if ((this->transfer_count_++ & 0xFF) == 0) {
+      esphome::App.feed_wdt();
+      esphome::delay(1);
+    }
     this->write_byte(data);
     return 0;
   }
+
+ private:
+  uint32_t transfer_count_{0};
 };
 
 // Unused: every gxepd2 instance overrides this via selectSPI() with its own