#include "el512_display.h" #include #include #include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome::el512 { static const char *const TAG = "el512"; void EL512Display::setup() { this->panel_ = new EL512Panel(this->pins_); // NOLINT(cppcoreguidelines-owning-memory) if (!this->panel_->begin()) { ESP_LOGE(TAG, "Panel init failed (out of DMA memory or no free interrupt)"); this->mark_failed(); return; } // Apply states that switches may have restored before setup ran. this->panel_->setLowPower(this->low_power_); this->panel_->setEnabled(this->enabled_); } void EL512Display::loop() { 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(); if (active && !this->still_alive_was_active_) { // Remember where we came from, then start the show from the top. this->still_alive_return_ = this->previous_page_ != this->still_alive_page_ ? this->previous_page_ : nullptr; this->still_alive_.start(now); ESP_LOGI(TAG, "Still Alive started; start the song in %u ms", (unsigned) this->still_alive_song_start_ms()); } this->still_alive_was_active_ = active; if (!active) return; if (this->still_alive_.finished(now) && this->still_alive_return_ != nullptr) { this->show_page(this->still_alive_return_); this->update(); return; } if (this->still_alive_.update(now)) 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; if (this->is_still_alive_playing()) { this->still_alive_.start(millis()); // already showing: just restart } else { this->show_page(this->still_alive_page_); // loop() starts it } } void EL512Display::draw_still_alive() { if (this->panel_ != nullptr) this->still_alive_.draw(*this->panel_); } void EL512Display::update() { if (this->panel_ == nullptr || !this->panel_->running()) return; this->do_update_(); this->panel_->show(); } void EL512Display::fill(Color color) { // The panel clear is a memset; with clipping active, fall back to per-pixel. if (this->panel_ == nullptr || this->get_clipping().is_set()) { Display::fill(color); return; } this->panel_->clear(color.is_on()); } void EL512Display::draw_absolute_pixel_internal(int x, int y, Color color) { if (this->panel_ != nullptr) this->panel_->setPixel(x, y, color.is_on()); } void EL512Display::set_enabled(bool on) { this->enabled_ = on; if (this->panel_ != nullptr) this->panel_->setEnabled(on); } void EL512Display::set_low_power(bool on) { this->low_power_ = on; if (this->panel_ != nullptr) this->panel_->setLowPower(on); } void EL512Display::dump_config() { LOG_DISPLAY("", "EL512.256-H", this); ESP_LOGCONFIG(TAG, " Pins: VCLK %d, VID %d, TVID %d, HS %d, VS %d, ENABLE %d, _LOWPWR %d\n" " Refresh: %.1f Hz, DMA ring %u bytes\n" " Free internal heap: %u bytes", this->pins_.vclk, this->pins_.vid, this->pins_.tvid, this->pins_.hs, this->pins_.vs, this->pins_.enable, this->pins_.lowpwr_n, EL512Panel::refreshHz(), (unsigned) EL512Panel::RING_BYTES, (unsigned) heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); LOG_UPDATE_INTERVAL(this); if (this->is_failed()) ESP_LOGE(TAG, " Init failed"); } } // namespace esphome::el512