Quellcode durchsuchen

Reset the watchdog directly instead of through esphome's rate-limited feed_wdt()

esphome::App.feed_wdt()'s rate-limiting state (last_wdt_feed_) is
global, shared across every caller, on the assumption only the main
loop task ever calls it - fine when that's true, but this code now
also runs on gxepd2's own update task. The main loop's own frequent
feed_wdt() calls kept satisfying that shared rate limit, silently
suppressing this task's calls to it - esp_task_wdt_reset() only resets
whichever task is actually calling it, so those suppressed calls never
touched this task's subscription at all. Confirmed on hardware: ESP-IDF
named "gxepd2_update" as the task that didn't reset in time.

Call esp_task_wdt_reset() directly in both spots that need it (the
busy-wait in Arduino.h, the per-256-byte throttle in SPI.h) instead -
it isn't rate-limited and always resets the real calling task, so it
composes correctly regardless of which task is running this code.
ZiB vor 1 Monat
Ursprung
Commit
5300f73932
2 geänderte Dateien mit 25 neuen und 10 gelöschten Zeilen
  1. 14 4
      components/gxepd2/arduino_compat/Arduino.h
  2. 11 6
      components/gxepd2/arduino_compat/SPI.h

+ 14 - 4
components/gxepd2/arduino_compat/Arduino.h

@@ -10,7 +10,8 @@
 #include <sstream>
 #include <sstream>
 #include <string>
 #include <string>
 
 
-#include "esphome/core/application.h"
+#include <esp_task_wdt.h>
+
 #include "esphome/core/hal.h"
 #include "esphome/core/hal.h"
 #include "esphome/core/log.h"
 #include "esphome/core/log.h"
 #include "esphome/components/gxepd2/pin_registry.h"
 #include "esphome/components/gxepd2/pin_registry.h"
@@ -42,17 +43,26 @@ inline int digitalRead(int16_t pin) {
 // deliberately up to tens of seconds (_busy_timeout), since a real 3/4-color
 // deliberately up to tens of seconds (_busy_timeout), since a real 3/4-color
 // refresh can legitimately take that long. Neither esphome::delay() nor
 // refresh can legitimately take that long. Neither esphome::delay() nor
 // esphome::yield() resets the ESP-IDF task watchdog on their own, so
 // esphome::yield() resets the ESP-IDF task watchdog on their own, so
-// without an explicit feed here, a real (or a misconfigured-polarity, thus
+// without an explicit reset here, a real (or a misconfigured-polarity, thus
 // permanently "busy") wait outlasts the watchdog and the whole device
 // permanently "busy") wait outlasts the watchdog and the whole device
 // reboots mid-refresh instead of the loop's own timeout ever getting a
 // reboots mid-refresh instead of the loop's own timeout ever getting a
 // chance to fire.
 // chance to fire.
+//
+// This calls esp_task_wdt_reset() directly, not esphome::App.feed_wdt():
+// feed_wdt()'s rate-limiting state is global, shared by every caller, on
+// the assumption only the main loop task ever calls it. This code runs on
+// gxepd2's own task (see gxepd2_display.h), so the main loop's frequent
+// feed_wdt() calls kept satisfying that shared rate limit and silently
+// swallowing this task's - esp_task_wdt_reset() always resets whichever
+// task is actually calling it, isn't rate-limited, and is cheap enough to
+// call unconditionally here.
 inline void delay(uint32_t ms) {
 inline void delay(uint32_t ms) {
-  esphome::App.feed_wdt();
+  esp_task_wdt_reset();
   esphome::delay(ms);
   esphome::delay(ms);
 }
 }
 inline uint32_t micros() { return esphome::micros(); }
 inline uint32_t micros() { return esphome::micros(); }
 inline void yield() {
 inline void yield() {
-  esphome::App.feed_wdt();
+  esp_task_wdt_reset();
   esphome::yield();
   esphome::yield();
 }
 }
 
 

+ 11 - 6
components/gxepd2/arduino_compat/SPI.h

@@ -8,7 +8,8 @@
 
 
 #include <cstdint>
 #include <cstdint>
 
 
-#include "esphome/core/application.h"
+#include <esp_task_wdt.h>
+
 #include "esphome/core/hal.h"
 #include "esphome/core/hal.h"
 #include "esphome/components/spi/spi.h"
 #include "esphome/components/spi/spi.h"
 
 
@@ -44,13 +45,17 @@ class SPIClass : public esphome::spi::SPIDevice<esphome::spi::BIT_ORDER_MSB_FIRS
     // polling-mode SPI transaction doesn't yield the scheduler while it
     // polling-mode SPI transaction doesn't yield the scheduler while it
     // waits either. Left unchecked, that starves this core's idle task
     // waits either. Left unchecked, that starves this core's idle task
     // long enough to trip ITS watchdog - a hard crash, not just a stall.
     // 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
+    // Resetting our own task's watchdog subscription doesn't stop the
+    // idle-task starvation on its own, 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
     // real blocking delay actually lets a lower-priority task like idle
-    // run, so force one periodically.
+    // run, so force one periodically. esp_task_wdt_reset() (not
+    // esphome::App.feed_wdt(), whose rate-limiting is global and gets
+    // satisfied by the main loop task's own frequent calls regardless of
+    // whether this task's subscription has actually been reset) keeps
+    // this task itself from tripping the watchdog across a long burst.
     if ((this->transfer_count_++ & 0xFF) == 0) {
     if ((this->transfer_count_++ & 0xFF) == 0) {
-      esphome::App.feed_wdt();
+      esp_task_wdt_reset();
       esphome::delay(1);
       esphome::delay(1);
     }
     }
     this->write_byte(data);
     this->write_byte(data);