el512: refresh health counters and quiet warnings

The panel driver counts ring refills, the largest gap between refills,
frame swaps, swap timeouts and I2S TX FIFO underruns (takeStats()). The
ESPHome component checks them every 10 s and logs a warning only when the
ESP32 side of the signal had a problem, plus a debug summary per minute.

Measured with Wi-Fi on: ~4600 refills/s, max gap < 0.4 ms (lead 1.7 ms),
no underruns or timeouts, so remaining blinks come from the signal being
disturbed on the wires (Wi-Fi transmit bursts), not from the refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-18 20:52:10 +02:00
1 parent 963590cdfe
commit f3212ea969
4 files changed
+80 -1

No files matched your search

+36 -1
View File
@@ -1,6 +1,7 @@
#include "el512_display.h"
#include <esp_heap_caps.h>
#include <esp_private/esp_clk.h>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -22,7 +23,10 @@ void EL512Display::setup() {
}
void EL512Display::loop() {
if (this->still_alive_page_ == nullptr || this->panel_ == nullptr || !this->panel_->running())
if (this->panel_ == nullptr || !this->panel_->running())
return;
this->check_refresh_health_();
if (this->still_alive_page_ == nullptr)
return;
const bool active = this->is_still_alive_playing();
const uint32_t now = millis();
@@ -44,6 +48,37 @@ void EL512Display::loop() {
this->update();
}
// Refresh health, checked every 10 s: warn when the ESP32 side of the video
// signal had a problem (DMA FIFO underrun, a refill later than the ring's lead,
// or a frame swap that timed out); otherwise a debug summary once a minute.
// No warnings while the panel misbehaves means the signal left the ESP32
// intact and was disturbed on the wires (e.g. by Wi-Fi transmit bursts).
void EL512Display::check_refresh_health_() {
const uint32_t now = millis();
if (this->health_checked_ == 0) {
// Skip the start-up period (the FIFO is empty once while the DMA starts).
this->panel_->takeStats();
this->health_checked_ = now;
return;
}
if (now - this->health_checked_ < 10000)
return;
const uint32_t period_ms = now - this->health_checked_;
this->health_checked_ = now;
const auto s = this->panel_->takeStats();
const float max_gap_us = s.maxGapCycles / (float) (esp_clk_cpu_freq() / 1000000);
const float lead_us = 1e6f * EL512Panel::RING_LINES * EL512Panel::CLOCKS_PER_ROW / EL512Panel::PIXEL_CLOCK_HZ;
if (s.underruns > 0 || s.showTimeouts > 0 || max_gap_us > lead_us * 0.75f) {
ESP_LOGW(TAG, "Refresh problem in the last %us: %u FIFO underruns, %u swap timeouts, max refill gap %.0f us",
(unsigned) (period_ms / 1000), (unsigned) s.underruns, (unsigned) s.showTimeouts, max_gap_us);
}
if (now - this->health_logged_ >= 60000) {
this->health_logged_ = now;
ESP_LOGD(TAG, "Refresh OK: %.0f refills/s, max gap %.0f us, %u swaps", s.refills * 1000.0f / period_ms,
max_gap_us, (unsigned) s.swaps);
}
}
void EL512Display::play_still_alive() {
if (this->still_alive_page_ == nullptr)
return;