Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Opus 5 ff23e0e614 diag(P4): Gezeichnete Fläche messen, direktes Zeichnen zurücknehmen (1.9.1)
WS7_DIRECT_FB hat die Bildaufbauzeit nicht verkürzt (weiterhin 240-300 ms)
und Bildfehler erzeugt - in denselben Speicher zu zeichnen, aus dem gerade
angezeigt wird, geht ohne zweiten Framebuffer nicht sauber. Zurück auf 0,
Schalter mit Begründung erhalten.

Damit ist belegt: Der Flaschenhals liegt nicht im Kopieren, sondern im
Rendern selbst. Offen ist, ob LVGL kleine Bereiche langsam zeichnet oder
jedes Mal die volle Fläche - zwei verschiedene Ursachen.

refr_start_cb summiert deshalb jetzt die nicht zusammengefassten
inv_areas und meldet Anzahl und Flächenanteil zusammen mit der Dauer.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 01:42:05 +02:00
thomas 09b5231357 Merge pull request 'perf(P4): LVGL zeichnet direkt in den Bildspeicher (1.9.0)' (#69) from perf/direkt-in-bildspeicher-zeichnen into main 2026-09-03 01:27:32 +02:00
raw-designsandClaude Opus 5 16d8465b0c perf(P4): LVGL zeichnet direkt in den Bildspeicher (1.9.0)
Die Messung aus 1.8.8 hat es gezeigt: Jeder Bildaufbau kostete 240-300 ms,
dauerhaft, auch im Leerlauf. Die fünf Sekunden für den Aufweckdialog waren
rund zwanzig solcher Durchgänge - meine bisherigen Erklärungen (Fades,
Scrims) waren Symptome, nicht die Ursache.

Grund: zwei Durchgänge durch je 2,7 MB PSRAM pro Bild. LVGL rendert in einen
eigenen Draw-Buffer, esp_lcd_panel_draw_bitmap kopiert ihn danach komplett
in den Framebuffer.

WS7_DIRECT_FB holt den Framebuffer über esp_lcd_dpi_panel_get_frame_buffer
und übergibt ihn als LVGL-Draw-Buffer. Der IDF-Treiber erkennt das
(draw_buffer im Framebuffer-Bereich) und macht nur noch Cache-Writeback statt
Kopie; dazu LV_DISPLAY_RENDER_MODE_DIRECT, sodass nur geänderte Bereiche neu
gezeichnet werden.

Außerdem:
- Shot-Timer und Waage behalten während eines Bezugs die normale Schrift;
  der Wechsel auf den fetten 96er ist entfernt.
- Größe der Standby-Uhr einstellbar (120/180/240 px, Info-Seite,
  NVS ui/uhrgr). Wirkt sofort - sie hängt an einem einzigen Label.
- Die Knöpfe des Aufweckdialogs skalieren mit der Schriftgröße.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 01:27:14 +02:00
thomas 407d938031 Merge pull request 'diag(P4): Bildaufbau messen, Dialogknöpfe skalieren (1.8.8)' (#68) from diag/bildaufbau-messen into main 2026-09-03 01:01:36 +02:00
7 changed files with 9386 additions and 14 deletions
+30
View File
@@ -1,3 +1,33 @@
Version 1.9.1:
- Das direkte Zeichnen in den Bildspeicher aus 1.9.0 ist zurückgenommen: Es hat die
Bildaufbauzeit nicht verkürzt (weiterhin 240 bis 300 ms) und stattdessen Bildfehler
erzeugt, weil in denselben Speicher gezeichnet wird, aus dem gerade angezeigt wird.
Damit ist auch klar, dass der Flaschenhals nicht im Kopieren liegt, sondern im Zeichnen
selbst. Der Schalter WS7_DIRECT_FB bleibt samt Begründung erhalten.
- Die Messung sagt jetzt zusätzlich, WIE VIEL neu gezeichnet wurde: Anzahl der Bereiche und
ihr Anteil an der Gesamtfläche. Erst damit lässt sich unterscheiden, ob LVGL nur kleine
Bereiche anfasst - dann ist das Zeichnen an sich zu langsam - oder ob jedes Mal der ganze
Bildschirm neu entsteht, weil ihn etwas unnötig ungültig macht. Das sind zwei völlig
verschiedene Ursachen mit verschiedenen Gegenmitteln.
Version 1.9.0:
- Die Messung aus 1.8.8 hat die Ursache der Trägheit gefunden: Jeder Bildaufbau kostete
240 bis 300 Millisekunden - dauerhaft, auch im Leerlauf. Fünf Sekunden für den
Aufweckdialog waren also rund zwanzig solcher Durchgänge.
- Grund war, dass zweimal durch den Bildspeicher gearbeitet wurde: LVGL zeichnete in einen
eigenen Puffer, und dessen Inhalt wurde anschließend vollständig in den Bildspeicher
kopiert. Bei 1280x720 in 24 Bit sind das zwei Durchgänge durch je 2,7 MB.
- LVGL zeichnet jetzt direkt in den Bildspeicher. Der Treiber lässt das Kopieren dann weg,
und LVGL zeichnet nur noch die geänderten Bereiche statt jedes Mal die ganze Fläche.
Schalter WS7_DIRECT_FB in pins_config.h.
- Die Anzeigen von Shot-Timer und Waage bleiben während eines Bezugs in der normalen
Schrift; der fette Wechsel ist entfernt. Der Bereich bekommt weiterhin mehr Platz.
- Neu: Die Größe der Standby-Uhr lässt sich einstellen (Klein, Mittel, Groß - 120, 180 oder
240 Pixel), zu finden auf der Info-Seite unter Darstellung. Anders als die allgemeine
Schriftgröße wirkt sie sofort, weil sie an einem einzigen Textfeld hängt.
- Die Knöpfe des Aufweckdialogs berücksichtigen jetzt ebenfalls die eingestellte
Schriftgröße und wachsen mit.
Version 1.8.8:
- Der Aufweckdialog berücksichtigt jetzt die eingestellte Schriftgröße vollständig: Die
Knöpfe bekommen dieselbe Schrift wie der Text und wachsen mit (150 / 210 / 280 Pixel).
+2
View File
@@ -569,6 +569,8 @@
LV_FONT_DECLARE(lv_font_maven_pro_48) \
LV_FONT_DECLARE(lv_font_maven_pro_bold_96) \
LV_FONT_DECLARE(lv_font_maven_pro_bold_140) \
LV_FONT_DECLARE(lv_font_clock_120) \
LV_FONT_DECLARE(lv_font_clock_180) \
LV_FONT_DECLARE(lv_font_clock_240)
/*Always set a default font*/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+27 -1
View File
@@ -496,6 +496,32 @@ static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
// buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
ESP_LOGI(TAG, "LVGL buffer size: %dKB", buffer_size * sizeof(lv_color_t) / 1024);
#if WS7_DIRECT_FB
// PROJEKTAENDERUNG: LVGL zeichnet direkt in den Bildspeicher.
//
// Der Hersteller-Port legt einen eigenen Zeichenpuffer an; der fertige Inhalt wird
// danach in den Bildspeicher kopiert. Bei 1280x720 in 24 Bit sind das zwei Durchgaenge
// durch je 2,7 MB PSRAM - gemessen rund 250 bis 300 ms pro Bild, und das dauerhaft.
// Genau deshalb hing die Oberflaeche eine gute Viertelsekunde hinterher, und Vorgaenge
// mit mehreren Bildern brauchten Sekunden.
//
// Ist der Zeichenpuffer derselbe Speicher wie der Bildspeicher, laesst der
// esp_lcd-Treiber das Kopieren weg und schreibt nur den Prozessor-Cache zurueck. Dazu
// kommt LV_DISPLAY_RENDER_MODE_DIRECT: LVGL zeichnet dann nur die geaenderten Bereiche
// neu, statt jedes Mal die ganze Flaeche.
//
// Preis: Gezeichnet wird in den Speicher, der gerade angezeigt wird - bei schnellen
// Wechseln kann kurz eine Kante durchs Bild laufen. Mit nur einem Bildspeicher (den das
// Panel verlangt, siehe WS7_SINGLE_FB) ist das ohnehin nicht zu vermeiden.
void* fb0 = NULL;
if (esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb0) == ESP_OK && fb0) {
buf1 = fb0;
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
ESP_LOGI(TAG, "LVGL zeichnet direkt in den Bildspeicher (%dKB)",
(int)(buffer_size * sizeof(lv_color_t) / 1024));
}
#endif
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
ESP_LOGD(TAG, "Register display driver to LVGL");
@@ -511,7 +537,7 @@ static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
display, buf1, buf2, buffer_size * sizeof(lv_color_t),
#if LVGL_PORT_FULL_REFRESH
LV_DISPLAY_RENDER_MODE_FULL
#elif LVGL_PORT_DIRECT_MODE
#elif LVGL_PORT_DIRECT_MODE || WS7_DIRECT_FB
LV_DISPLAY_RENDER_MODE_DIRECT
#else
LV_DISPLAY_RENDER_MODE_PARTIAL
+6
View File
@@ -39,6 +39,12 @@
// nicht - siehe die ausfuehrliche Begruendung weiter unten beim Avoid-Tear-Schalter.
// Auf 0 nur, wenn das Startverhalten erneut untersucht wird.
#define WS7_SINGLE_FB 1
// LVGL zeichnet direkt in den Bildspeicher statt in einen eigenen Puffer, dessen Inhalt
// danach kopiert wird. GEPRUEFT: bringt an diesem Panel nichts (die Bildaufbauzeit blieb bei
// 240-300 ms) und erzeugt Bildfehler, weil in denselben Speicher gezeichnet wird, aus dem
// gerade angezeigt wird. Der Flaschenhals liegt also nicht im Kopieren, sondern im Zeichnen
// selbst. Bleibt als Schalter erhalten, damit der Versuch nicht erneut gemacht wird.
#define WS7_DIRECT_FB 0
#define WS7_PARALLEL_RENDER 1
#define EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS 500 //range 2 to 2000
+77 -13
View File
@@ -24,6 +24,7 @@
#include "esp_system.h"
#include "display_hal.h"
#include "esp_rom_sys.h"
#include "lvgl_private.h"
// ------------------------------------------------------------------------------------
// Schriftgroesse
@@ -198,8 +199,6 @@ static lv_obj_t* g_profPickerList = nullptr; // Button-Liste im Overlay
static lv_obj_t* g_profPickerInfo = nullptr; // "Lade Profile..."-Hinweis im Overlay
static lv_obj_t* g_infoRow = nullptr; // untere Zeile; waechst waehrend eines Bezugs
static bool g_dashBrewMode = false;
static const lv_font_t* g_shotFontNormal = nullptr;
static const lv_font_t* g_bezugFontNormal = nullptr;
static lv_obj_t* cellShot; static lv_obj_t* lblShotTimer; static lv_obj_t* g_bbtBar = nullptr;
static lv_obj_t* cellSteam; static lv_obj_t* lblSteamTimer; static lv_obj_t* g_sbtBar = nullptr;
static lv_obj_t* cellBezug; static lv_obj_t* lblBezug; static lv_obj_t* g_bbwBar = nullptr;
@@ -692,6 +691,24 @@ static void stat_list_finish(int used) {
}
}
// Groesse der Standby-Uhr. Anders als die allgemeine Schriftgroesse braucht diese keinen
// Neustart: Es haengt an genau EINEM Textfeld, das sich sofort umstellen laesst.
enum { UHR_KLEIN = 0, UHR_MITTEL = 1, UHR_GROSS = 2 };
static uint8_t g_clockSize = UHR_GROSS;
static lv_obj_t* g_clockSizeBtn[3] = {};
static const lv_font_t* clock_font() {
switch (g_clockSize) {
case UHR_KLEIN: return &lv_font_clock_120;
case UHR_MITTEL: return &lv_font_clock_180;
default: return &lv_font_clock_240;
}
}
static void clock_size_apply() {
if (g_standbyClock) lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0);
}
static void scroll_ohne_nachlauf(lv_obj_t* o) {
#if JC_PANEL_TYPE == WS_PANEL_7H
lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLL_MOMENTUM);
@@ -1301,10 +1318,6 @@ static void build_dashboard(lv_obj_t* p) {
lv_bar_set_range(g_bbwBar, 0, 100);
lv_bar_set_value(g_bbwBar, 0, LV_ANIM_OFF);
lv_obj_add_flag(g_bbwBar, LV_OBJ_FLAG_HIDDEN);
// Normalschriften merken, damit der Bezugsmodus sie wieder herstellen kann
g_shotFontNormal = lv_obj_get_style_text_font(lblShotTimer, LV_PART_MAIN);
g_bezugFontNormal = lv_obj_get_style_text_font(lblBezug, LV_PART_MAIN);
cellFlow = infoCell(ir, "FLOW", &lblFlow, lv_color_hex(0x64b5f6));
lv_obj_add_flag(cellFlow, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Brew-Control
lv_obj_add_event_cb(cellFlow, brew_cell_cb, LV_EVENT_CLICKED, nullptr);
@@ -3042,6 +3055,18 @@ static void design_btn_cb(lv_event_t* e) {
// Live-Bezugsschirm ein-/ausschalten (P4-lokal in NVS gespeichert)
// Temperaturzahl einfaerben oder weiss lassen. Wirkt sofort - die Farbe wird bei jeder
// Zustandsmeldung neu gesetzt, es muss nichts neu aufgebaut werden.
// Groesse der Standby-Uhr waehlen. Wirkt sofort - es haengt an einem einzigen Textfeld.
static void clock_size_cb(lv_event_t* e) {
g_clockSize = (uint8_t)(intptr_t)lv_event_get_user_data(e);
{ Preferences prefs; prefs.begin("ui", false); prefs.putUChar("uhrgr", g_clockSize); prefs.end(); }
clock_size_apply();
for (int i = 0; i < 3; i++) if (g_clockSizeBtn[i]) {
bool an = (i == g_clockSize);
lv_obj_set_style_bg_color(g_clockSizeBtn[i], an ? COL_ACCENT : COL_CARD2, 0);
lv_obj_set_style_text_color(g_clockSizeBtn[i], an ? lv_color_hex(0x000000) : COL_TEXT, 0);
}
}
static void temp_color_sw_cb(lv_event_t* e) {
g_tempColored = sw_get((lv_obj_t*)lv_event_get_target(e));
{ Preferences prefs; prefs.begin("ui", false); prefs.putBool("tempcol", g_tempColored); prefs.end(); }
@@ -3084,6 +3109,20 @@ static void build_info(lv_obj_t* p) {
lv_obj_add_event_cb(fb, font_pick_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
}
lv_obj_t* cl = lv_label_create(dcard);
lv_label_set_text(cl, "Uhrgröße im Standby");
lv_obj_set_style_text_color(cl, COL_TEXT_DIM, 0);
lv_obj_t* crow = row(dcard, nullptr);
lv_obj_set_style_pad_column(crow, 8, 0);
static const char* UHR_NAMEN[3] = { "Klein", "Mittel", "Groß" };
for (uint8_t i = 0; i < 3; i++) {
lv_obj_t* b = (i == g_clockSize) ? th_accent_button(crow, UHR_NAMEN[i])
: th_button(crow, UHR_NAMEN[i], COL_CARD2, COL_TEXT);
lv_obj_set_flex_grow(b, 1);
lv_obj_add_event_cb(b, clock_size_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
g_clockSizeBtn[i] = b;
}
lv_obj_t* swTempCol = add_switch(dcard, "Temperaturzahl in der Zustandsfarbe");
sw_set(swTempCol, g_tempColored);
lv_obj_add_event_cb(swTempCol, temp_color_sw_cb, LV_EVENT_VALUE_CHANGED, nullptr);
@@ -3426,11 +3465,33 @@ static bool g_uiReady = false;
static uint32_t g_refrStart = 0;
static void refr_start_cb(lv_event_t*) { g_refrStart = lv_tick_get(); }
static uint32_t g_refrPixels = 0;
static uint32_t g_refrAreas = 0;
// Beim Start eines Zeichenzyklus festhalten, WIE VIEL neu gezeichnet wird. Erst damit laesst
// sich unterscheiden, ob LVGL nur kleine Bereiche anfasst - dann ist das Zeichnen an sich zu
// langsam - oder ob jedes Mal der ganze Bildschirm neu entsteht, weil ihn etwas unnoetig
// ungueltig macht.
static void refr_start_cb(lv_event_t*) {
g_refrStart = lv_tick_get();
g_refrPixels = 0;
g_refrAreas = 0;
lv_display_t* d = lv_display_get_default();
if (!d) return;
for (uint16_t i = 0; i < d->inv_p; i++) {
if (d->inv_area_joined[i]) continue;
const lv_area_t* a = &d->inv_areas[i];
g_refrPixels += (uint32_t)(a->x2 - a->x1 + 1) * (uint32_t)(a->y2 - a->y1 + 1);
g_refrAreas++;
}
}
static void refr_ready_cb(lv_event_t*) {
uint32_t d = lv_tick_get() - g_refrStart;
if (d >= REFR_WARN_MS) esp_rom_printf("[UI] Bildaufbau %u ms\n", (unsigned)d);
if (d < REFR_WARN_MS) return;
unsigned pct = (unsigned)((uint64_t)g_refrPixels * 100ULL / ((uint64_t)LCD_H_RES * LCD_V_RES));
esp_rom_printf("[UI] Bildaufbau %u ms, %u Bereiche, %u %% der Flaeche\n",
(unsigned)d, (unsigned)g_refrAreas, pct);
}
static void refr_monitor_install() {
@@ -3458,6 +3519,8 @@ void ui_init(ProtocolClient* client) {
if (g_tempDesign >= TD_DESIGN_COUNT) g_tempDesign = TD_BAND;
g_brewLiveEnabled = prefs.getBool("brewlive", true);
g_tempColored = prefs.getBool("tempcol", false);
g_clockSize = prefs.getUChar("uhrgr", UHR_GROSS);
if (g_clockSize > UHR_GROSS) g_clockSize = UHR_GROSS;
g_chartWin = prefs.getUChar("chartwin", 0);
if (g_chartWin > 3) g_chartWin = 0;
g_chartDuty = prefs.getBool("chartduty", false);
@@ -3657,7 +3720,7 @@ void ui_init(ProtocolClient* client) {
g_standbyClock = lv_label_create(g_standbyScreen);
lv_label_set_text(g_standbyClock, "--:--");
lv_obj_set_style_text_color(g_standbyClock, COL_TEXT, 0);
lv_obj_set_style_text_font(g_standbyClock, &lv_font_clock_240, 0); // grosse Uhr im Standby
lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0); // Groesse einstellbar
lv_obj_center(g_standbyClock);
lv_obj_add_event_cb(g_standbyScreen, standby_screen_cb, LV_EVENT_CLICKED, nullptr); // Tippen -> Aufweck-Abfrage
lv_obj_t* standbyHint = lv_label_create(g_standbyScreen); // dezenter Hinweis am unteren Rand
@@ -3935,10 +3998,10 @@ static void dash_brew_mode(bool on) {
if (!g_tempRow || !g_infoRow) return;
lv_obj_set_flex_grow(g_tempRow, on ? 1 : 3);
lv_obj_set_flex_grow(g_infoRow, on ? 3 : 2);
if (lblShotTimer && g_shotFontNormal)
lv_obj_set_style_text_font(lblShotTimer, on ? &lv_font_maven_pro_bold_96 : g_shotFontNormal, 0);
if (lblBezug && g_bezugFontNormal)
lv_obj_set_style_text_font(lblBezug, on ? &lv_font_maven_pro_bold_96 : g_bezugFontNormal, 0);
// Die Schrift von Shot-Timer und Waage bleibt unveraendert. Frueher wechselte sie
// waehrend des Bezugs auf den grossen fetten Schnitt - das wirkte gegenueber dem
// restlichen Dashboard zu laut. Der Bereich bekommt weiterhin mehr Platz, die Zahlen
// stehen dadurch freier, aber in derselben Schrift wie sonst auch.
}
static void dash_brew_mode_tick(const MachineState& st) {
@@ -3951,6 +4014,7 @@ static void dash_brew_mode_tick(const MachineState& st) {
}
// ------------------------------------------------------------------------------------
// Uhr läuft lokal weiter
//