Skip to content

ADR-045 display never refreshes — LVGL tick never advances (CONFIG_LV_TICK_CUSTOM unset, no lv_tick_inc()) #889

@markt-heximal

Description

@markt-heximal

Summary

The ADR-045 on-device LVGL display draws its initial frame at boot and then never repaints. Live data (vitals / CSI / activity / clock) is written into the LVGL object tree every loop, but it never reaches the panel — the screen is effectively static after the first frame.

Root cause: LVGL's tick never advances, so lv_timer_handler()'s display-refresh timer (LV_DISP_DEF_REFR_PERIOD = 30 ms) is never "due" and no flush is ever performed.

Root cause detail

  • main/lv_conf.h sets LV_TICK_CUSTOM 1 with esp_timer_get_time() — which would be headless-safe — but the managed lvgl/lvgl component is Kconfig-configured (LV_CONF_SKIP via lv_conf_internal.h), so lv_conf.h is ignored.
  • The generated sdkconfig contains # CONFIG_LV_TICK_CUSTOM is not set.
  • Nothing in firmware/esp32-csi-node/main/ ever calls lv_tick_inc() (grep-confirmed).
  • Net effect: lv_tick is stuck → lv_timer_handler() returns with no timer ready → the display refresh callback never runs → the panel is never updated after the first paint.

This is the same Kconfig-vs-lv_conf.h trap that also silently drops lv_conf.h font enables (fonts must be set via CONFIG_LV_FONT_MONTSERRAT_*).

Impact

Affects all ADR-045 display builds, independent of panel — the SH8601/RM67162 QSPI AMOLED HAL included. The on-device UI appears to "work" only in the sense that the first frame renders; none of the live readouts ever update.

How it was isolated

A backlight "breathe" beacon (LEDC PWM driven straight from the display task loop, bypassing LVGL and SPI entirely) kept pulsing while the on-screen content stayed frozen. That cleanly separates the layers:

Backlight (LEDC) On-screen content Conclusion
pulsing frozen task loop alive; LVGL refresh dead → tick

(Verified on a Waveshare ESP32-S3-Touch-LCD-1.69 / ST7789V2, but the defect is panel-independent.)

Fix

Register an esp_timer that drives the tick, in display_task_start() after lv_init():

static void lvgl_tick_cb(void *arg) { lv_tick_inc(2); }
...
const esp_timer_create_args_t tick_args = { .callback = &lvgl_tick_cb, .name = "lvgl_tick" };
esp_timer_handle_t t = NULL;
esp_timer_create(&tick_args, &t);
esp_timer_start_periodic(t, 2000);  /* 2 ms — host-independent, works headless */

After this, lv_timer_handler() refreshes every 30 ms and the panel updates live (heartbeat blinks, charts/bars track data) on USB and on a bare power supply.

Related (same PR)

  1. No ST7789 SPI LCD HALdisplay_hal.c only implements the SH8601/RM67162 QSPI AMOLED path. Added display_hal_st7789.c (ESP-IDF built-in esp_lcd_new_panel_st7789 + CST816 touch + LEDC backlight) selected via a new DISPLAY_PANEL Kconfig choice, plus a compact 240×280 UI (display_ui_st7789.c) since the 4-view AMOLED UI is laid out for 368×448.
  2. Deployment note: the LCD board boot-loops from a startup-inrush brownout on a single host-USB port (full-screen draws + WiFi connect at once). Mitigated by avoiding full-white init frames + a dimmable backlight; a ≥2 A supply resolves it fully.

Environment

  • ESP-IDF v5.4 (Docker espressif/idf:release-v5.4), target esp32s3
  • lvgl/lvgl ~8.3 (managed component), LV_DISP_DEF_REFR_PERIOD=30
  • firmware esp32-csi-node v0.7.0, ADR-045

PR with the tick fix + ST7789 HAL incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions