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

+23
View File
@@ -2,6 +2,7 @@
#include <driver/gpio.h>
#include <esp_attr.h>
#include <esp_cpu.h>
#include <esp_heap_caps.h>
#include <esp_intr_alloc.h>
#include <esp_rom_gpio.h>
@@ -176,6 +177,17 @@ void EL512Panel::show() {
// The ISR copies _fb to _front just before it encodes line 0 of a frame.
_swapPending = true;
for (int i = 0; i < 40 && _swapPending; i++) vTaskDelay(1);
if (_swapPending) _showTimeouts++;
}
EL512Panel::Stats EL512Panel::takeStats() {
Stats s{_refills, _maxGap, _swaps, _showTimeouts, _underruns};
_underruns = 0;
_refills = 0;
_maxGap = 0;
_swaps = 0;
_showTimeouts = 0;
return s;
}
void IRAM_ATTR EL512Panel::encodeRow(uint16_t line, uint8_t* out) const {
@@ -206,6 +218,7 @@ void IRAM_ATTR EL512Panel::fillChunk(uint16_t chunk) {
uint32_t* d = (uint32_t*)_front;
for (size_t w = 0; w < FRAMEBUFFER_BYTES / 4; w++) d[w] = s[w];
_swapPending = false;
_swaps = _swaps + 1;
}
encodeRow(line, out + (size_t)i * ROW_BYTES);
line = (line + 1 == LINES_PER_FRAME) ? 0 : line + 1;
@@ -214,6 +227,16 @@ void IRAM_ATTR EL512Panel::fillChunk(uint16_t chunk) {
void IRAM_ATTR EL512Panel::isr(void* arg) {
auto* self = static_cast<EL512Panel*>(arg);
const uint32_t now = esp_cpu_get_cycle_count();
if (self->_lastIsrCycle != 0 && now - self->_lastIsrCycle > self->_maxGap) self->_maxGap = now - self->_lastIsrCycle;
self->_lastIsrCycle = now;
self->_refills = self->_refills + 1;
// Health counters (see takeStats): TX FIFO ran empty / transmitter hung.
const uint32_t raw = I2S0.int_raw.val;
if (raw & (I2S_TX_REMPTY_INT_RAW_M | I2S_TX_HUNG_INT_RAW_M)) {
self->_underruns = self->_underruns + 1;
I2S0.int_clr.val = I2S_TX_REMPTY_INT_CLR_M | I2S_TX_HUNG_INT_CLR_M;
}
const uint32_t status = I2S0.int_st.val;
I2S0.int_clr.val = status;
if (!(status & I2S_OUT_EOF_INT_ST_M)) return;