Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Opus 5 8db8e166a2 fix(P4): Nach Kaltstart einmal selbst neu starten (1.6.22)
Der DSI-Ab- und Wiederaufbau aus 1.6.21 hat nicht geholfen, sondern das
Bild um einen Schritt verschlechtert: Einschalten - gar nichts, erster
Reset - nur Beleuchtung, zweiter Reset - Bild. Ein Neuaufbau im laufenden
Betrieb ist einem echten Chip-Reset also nicht gleichwertig.
WS7_DSI_RESTART steht wieder auf 0, der Schalter bleibt dokumentiert.

Stattdessen der Weg, den die Messungen stützen: Nach einem Kaltstart
startet der P4 sich genau einmal selbst neu, sobald der erste Hochlauf
durch ist. Ein Reset hat in jedem Versuch zuverlässig geholfen.

- Bedingung ist esp_reset_reason() == ESP_RST_POWERON, beim zweiten
  Durchlauf greift sie nicht mehr - keine Schleife möglich.
- Der Frühstart-Zähler der Absturzsicherung wird vorher zurückgesetzt,
  damit der gewollte Neustart nicht in den Update-Modus führt.
- Schalter WS7_COLD_BOOT_RESTART, kostet ~3 s beim Einschalten.

Das ist eine Umgehung, keine Erklärung: Warum das Panel den ersten Anlauf
nicht annimmt, bleibt offen - der Hochlauf meldet durchgehend Erfolg.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 02:44:06 +02:00
thomas 1def9e45dc Merge pull request 'fix(P4): DSI-Strecke beim Hochlauf einmal neu aufbauen (1.6.21)' (#49) from fix/dsi-strecke-neu-aufbauen into main 2026-09-02 02:27:49 +02:00
raw-designsandClaude Opus 5 c2a8b21917 fix(P4): DSI-Strecke beim Hochlauf einmal neu aufbauen (1.6.21)
Der Hochlauf lief zuletzt vollständig fehlerfrei durch - Bridge zweimal
eingestellt, Panel zweimal geweckt, alle I2C-Bausteine erreichbar - und
trotzdem kam beim ersten Einschalten kein Bild. Erst ein Reset half, und
der zuverlässig.

Es fehlte also kein Befehl mehr, sondern der Vorgang selbst: Ein Reset
unterscheidet sich an dieser Stelle in genau einem Punkt von unserem
Ablauf - die DSI-Verbindung geht einmal weg und neu auf, während Bridge
und Panel bereits versorgt und eingestellt sind. Die Wiederholungen aus
1.6.19/1.6.20 liefen dagegen über eine durchgehend bestehende Verbindung.

- Der DSI-Aufbau (Bus, Kommandokanal, Bildausgabe, Weckbefehle) steckt
  jetzt in ws7_dsi_bringup() und ist damit wiederholbar.
- Nach dem ersten Durchlauf werden esp_lcd_panel_del(),
  esp_lcd_panel_io_del() und esp_lcd_del_dsi_bus() aufgerufen und die
  Strecke neu angelegt. Schalter WS7_DSI_RESTART, Pause
  WS7_DSI_RESTART_MS.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 02:26:24 +02:00
thomas 6d7d544ac7 Merge pull request 'fix(P4): Bridge-Grundeinstellung nach dem DSI-Start wiederholen (1.6.20)' (#48) from fix/bridge-grundeinstellung-wiederholen into main 2026-09-02 02:12:53 +02:00
raw-designsandClaude Opus 5 992796345c fix(P4): Bridge-Grundeinstellung nach dem DSI-Start wiederholen (1.6.20)
Beobachtung am Gerät: Beim Einschalten geht nach 2-3 s die Beleuchtung an,
dann kommt nichts. Nach einem Reset geht die Beleuchtung kurz aus, danach
erscheint Beleuchtung samt Bild - reproduzierbar in zwei von zwei Fällen.

Dass die Beleuchtung ausgeht, zeigt: Die Bridge folgt dem DSI-Signal. Und
dass der zweite Durchlauf klappt, zeigt warum - die Bridge hatte ihre
Grundeinstellung aus dem ersten Durchlauf noch und fand diesmal ein
DSI-Signal vor. Beim ersten Start bekommt sie ihre Einstellung dagegen,
bevor die DSI-Strecke überhaupt existiert.

ws7_bridge_core_init() ist aus ws7_bridge_pre_init() herausgelöst und wird
jetzt ein zweites Mal aufgerufen, sobald esp_lcd_panel_init() läuft -
dasselbe Vorgehen wie bei ws7_panel_wake() in 1.6.19. Die Register sind
wiederholbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 02:11:29 +02:00
thomas 5e2d0f7af8 Merge pull request 'fix(P4): Panel-Weckbefehle nach dem Start der Videoausgabe wiederholen (1.6.19)' (#47) from fix/panel-weckbefehle-wiederholen into main 2026-09-02 01:41:26 +02:00
raw-designsandClaude Opus 5 b6fdaa9ad7 fix(P4): Panel-Weckbefehle nach dem Start der Videoausgabe wiederholen (1.6.19)
Die Wartezeit aus 1.6.18 hat den Start deutlich verbessert, aber noch nicht
zuverlässig gemacht: beim ersten Anstecken nur Beleuchtung, nach Reset zwei
von drei Versuchen erfolgreich.

Aus dem Fehlerbild folgt, wo es hakt: Die Beleuchtung ging an. Sie hängt an
der Bridge und wird über I2C geschaltet, die Kommunikation steht also.
Verloren gehen die DCS-Weckbefehle an das Panel selbst - sie wurden genau
einmal geschickt, bevor die Videoausgabe lief. War der Panel-Controller
dann noch nicht aufnahmebereit, blieb der Bildschirm dunkel, während jeder
Schritt Erfolg meldete.

- ws7_panel_wake() gebündelt und wird jetzt zweimal aufgerufen: vor und
  nach esp_lcd_panel_init(), getrennt durch WS7_PANEL_SETTLE_MS.
- Jeder Anlauf ist im Start-Log einzeln benannt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 01:40:01 +02:00
thomas 08d07bd1a0 Merge pull request 'fix(P4): Anlaufzeit des Displaymoduls abwarten (1.6.18)' (#46) from fix/panel-anlaufzeit into main 2026-09-02 01:27:04 +02:00
raw-designsandClaude Opus 5 ee74c700bf fix(P4): Anlaufzeit des Displaymoduls abwarten (1.6.18)
Entscheidender Hinweis vom Gerät: "Sobald der serielle Monitor läuft,
startet es fast immer einwandfrei." Dessen USB-Anmeldung verzögert den
Start - und ersetzte damit unbeabsichtigt eine Wartezeit, die der
Werks-Testsketch explizit hat: delay(2000) am Anfang von setup().

Unsere Firmware sprach Bridge und Panel sofort nach dem Einschalten an.
Das Displaymodul ist dann noch nicht bereit: Die Bridge nimmt ihre
Register nicht an oder das Panel zeigt trotz korrekt gesendeter DSI-Daten
nichts - beides ohne Fehlermeldung, weil aus Sicht des P4 jeder Schritt
geklappt hat. Daher das sprunghafte Verhalten und der Fehlstart beim
ersten Anstecken.

jc_board_bringup() wartet für WS_PANEL_7H jetzt WS7_PANEL_WARMUP_MS
(Vorgabe 2000 ms), bevor es beginnt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 01:25:40 +02:00
thomas 750a6fb78c Merge pull request 'diag(P4): Prüfbild hält an, statt von LVGL überschrieben zu werden (1.6.17)' (#45) from diag/pruefbild-vor-lvgl into main 2026-09-01 17:25:48 +02:00
raw-designsandClaude Opus 5 80b6eb810f diag(P4): Prüfbild hält an, statt von LVGL überschrieben zu werden (1.6.17)
Der Störimpulsfilter aus 1.6.16 wirkt - nach dem Panel-Start melden sich
wieder alle drei I2C-Bausteine, keine Bridge- oder Touch-Fehler mehr.

Das Prüfbild aus 1.6.15 war als Test jedoch untauglich: gesetzt bei
esp_lcd_dpi_panel_set_pattern(), aber unmittelbar danach lief
lvgl_port_init() und schrieb in den Framebuffer, womit das Muster wieder
verschwand. Der Test sah damit genauso aus wie der Fehler, den er finden
soll.

Mit WS7_TEST_PATTERN = 1 endet jc_board_bringup() jetzt direkt nach dem
Muster: Backlight an, Rückgabe false, kein LVGL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 17:24:25 +02:00
thomas 6a9048411e Merge pull request 'fix(P4): I2C-Störimpulsfilter - Ursache des schwarzen Bildes (1.6.16)' (#44) from fix/i2c-stoerimpulsfilter into main 2026-09-01 17:06:15 +02:00
raw-designsandClaude Opus 5 ecb9836510 fix(P4): I2C-Störimpulsfilter - Ursache des schwarzen Bildes (1.6.16)
Der I2C-Scan vor und nach dem Panel-Start war entscheidend:

  vor:   0x14 (Touch)  0x18 (Audio)  0x45 (Bridge)
  nach:  0x18

Es fallen genau die beiden Bausteine weg, die am Displaykabel hängen; der
Audio-Baustein auf der Platine bleibt erreichbar, der Bus arbeitet also.
Sobald die DSI-Ausgabe läuft, stören deren Signale auf das Flachbandkabel
ein. Die Bridge liess sich danach nicht mehr ansprechen
(ESP_ERR_INVALID_STATE), das Panel blieb dunkel.

i2c_master_bus_config_t bekommt für WS_PANEL_7H jetzt glitch_ignore_cnt = 7
und enable_internal_pullup - beides setzt auch das Waveshare-Beispiel. In
unserem Bringup fehlte es, weil die Bus-Einrichtung von den JC-Panels
übernommen wurde, die kein Flachbandkabel dieser Länge haben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 17:04:51 +02:00
thomas c4cb6eb90a Merge pull request 'diag(P4): Schwarzes Bild eingrenzen - Prüfbild und lückenlose Meldungen (1.6.15)' (#43) from diag/schwarzes-bild-eingrenzen into main 2026-09-01 16:52:28 +02:00
raw-designsandClaude Opus 5 c58b901f26 diag(P4): Schwarzes Bild eingrenzen - Prüfbild und lückenlose Meldungen (1.6.15)
Kaltstart, kein einziger Fehler im Log, alle I2C-Bausteine da - und trotzdem
keine Anzeige. Zwei Lücken in der Diagnose:

- Die DCS-Weckbefehle (MADCTL, Sleep Out, Display On) liefen ungeprüft
  durch. Jetzt über BSP_STEP gemeldet.
- Nach dem Panel-Start endete die Ausgabe. "Bringup fertig" vor und
  "LVGL laeuft" nach lvgl_port_init() trennen jetzt ein Hängenbleiben in
  LVGL von einem fehlenden Bild.

Neuer Schalter WS7_TEST_PATTERN nutzt esp_lcd_dpi_panel_set_pattern() für
Farbbalken aus dem DSI-Baustein, ohne Framebuffer und ohne LVGL - wie im
funktionierenden Werks-Testsketch. Erscheinen sie, arbeitet die Kette bis
zum Panel und der Fehler liegt weiter oben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 16:51:04 +02:00
thomas d83619904f Merge pull request 'fix(P4): Touch erst nach Panel-Freigabe initialisieren (1.6.14)' (#42) from fix/touch-nach-panelfreigabe into main 2026-09-01 16:31:55 +02:00
raw-designsandClaude Opus 5 50540179c7 fix(P4): Touch erst nach Panel-Freigabe initialisieren (1.6.14)
Der GT9271 meldete "i2c transaction failed" auch beim Kaltstart, während
alle Panel-Schritte fehlerfrei durchliefen und die Bus-Freitaktung aus 1.6.13
den Bus als frei meldete.

Ursache ist die Reihenfolge: Im funktionierenden Werks-Testsketch steht
initTouch() hinter enableBacklight(). Der GT9271 hängt an derselben
Versorgung wie das Panel und antwortet erst, wenn die Bridge über 0xAD
freigegeben ist. Beim Übertragen in die Firmware war das verloren gegangen -
jc_backlight_set() lief erst nach jc_board_bringup().

- Für WS_PANEL_7H wird die Beleuchtung jetzt innerhalb des Bringups vor der
  Touch-Initialisierung eingeschaltet, mit WS7_TOUCH_POWER_MS Pause.
- Neue Diagnose ws7_i2c_scan() listet die antwortenden I2C-Adressen vor und
  nach dem Panel-Start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 16:30:31 +02:00
thomas 9bbd7d72aa Merge pull request 'fix(P4): Hängenden I2C-Bus freitakten - Ursache des schwarzen Bildes (1.6.13)' (#41) from fix/i2c-bus-freitakten into main 2026-09-01 16:17:42 +02:00
raw-designsandClaude Opus 5 43c5d5a41a fix(P4): Hängenden I2C-Bus freitakten - Ursache des schwarzen Bildes (1.6.13)
Der Log nach einem Reset zeigte alle Panel-Schritte fehlerfrei, aber
"i2c transaction failed" beim GT911. Bridge und Touch teilen sich den Bus:
Trifft ein Reset des P4 mitten in eine Übertragung, bleibt der Baustein in
seiner Bitausgabe stehen und hält SDA dauerhaft low. Beide hängen an
Dauerstrom, der Zustand überlebt also Reset und Flashen.

- ws7_i2c_bus_recover() taktet SCL vor dem Anlegen des Busses bis zu neunmal
  von Hand, bis SDA freigegeben wird, und erzeugt dann eine Stop-Bedingung.
  Bei freiem Bus läuft die Funktion wirkungslos durch.
- ws7_bridge_write() prüft den Rückgabewert. Bisher liefen fehlgeschlagene
  Schreibzugriffe stillschweigend ins Leere - der eigentliche Grund, warum
  das schwarze Bild keinerlei Spur hinterließ.
- pins_config.h: "#elsealle" statt "#else" seit 1.6.10 korrigiert. Der
  Tippfehler traf nur den JC_PANEL_43-Zweig, der dadurch nicht mehr
  übersetzbar war. Beide Panel-Varianten wieder gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 16:16:09 +02:00
thomas dfd2aaddd1 Merge pull request 'fix(P4): Abschaltfolge zurücknehmen, Startgrund und LDO prüfen (1.6.12)' (#40) from fix/bridge-abschaltfolge-zuruecknehmen into main 2026-09-01 15:45:24 +02:00
raw-designsandClaude Opus 5 6c783c55de fix(P4): Abschaltfolge zurücknehmen, Startgrund und LDO prüfen (1.6.12)
Die Bridge-Abschaltfolge aus 1.6.11 hat den Fehler nicht behoben, sondern
verschlimmert: Nach C0/C2/AC = 0x00 kam das Panel auch direkt nach dem
Flashen nicht mehr hoch. Der Bringup entspricht wieder 1.6.10.

Stattdessen zwei Diagnosen zur Eingrenzung:
- esp_reset_reason() wird beim Hochlauf ausgegeben, um Kalt- von Warmstart
  unterscheiden zu können.
- esp_ldo_acquire_channel() läuft nicht mehr ungeprüft durch, sondern meldet
  einen Fehler über BSP_STEP. Es war der einzige Schritt ohne Prüfung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 15:44:02 +02:00
raw-designs f38ea3045b Revert "Merge pull request 'fix(P4): Schwarzes Bild nach Reset am Waveshare-Panel (1.6.11)' (#39) from fix/schwarzes-bild-nach-reset into main"
This reverts commit 0a6c6ed869, reversing
changes made to edb1e333f0.
2026-09-01 15:37:29 +02:00
thomas 0a6c6ed869 Merge pull request 'fix(P4): Schwarzes Bild nach Reset am Waveshare-Panel (1.6.11)' (#39) from fix/schwarzes-bild-nach-reset into main 2026-09-01 15:23:35 +02:00
raw-designsandClaude Opus 5 e8308a9182 fix(P4): Schwarzes Bild nach Reset am Waveshare-Panel (1.6.11)
Nach einem Reset des P4 blieb das Bild schwarz, die Hintergrundbeleuchtung
brannte; nur direkt nach dem Flashen lief es.

Die Bridge des Panels hat keine Reset-Leitung und hängt an Dauerstrom. Ein
Reset des P4 setzt sie nicht zurück - ihre Register standen noch auf den
Werten der vorigen Sitzung, während der P4 seine DSI-Strecke komplett neu
aufbaut. Beim Flashen wird die Versorgung getrennt, deshalb trat der Fehler
dort nicht auf.

ws7_bridge_pre_init() schaltet die Bridge jetzt zuerst definiert ab
(AB/AA, AD, dann AC/C2/C0), wartet WS7_BRIDGE_RESET_MS und schreibt erst
danach die Grundinitialisierung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 15:22:11 +02:00
thomas edb1e333f0 Merge pull request 'perf(P4): Teil-Neuzeichnen als Vorgabe, Messeinblendung, deckende Seiten (1.6.10)' (#38) from perf/scrollen-messen-und-vorgaben into main 2026-09-01 14:46:56 +02:00
raw-designsandClaude Opus 5 528c8d5500 perf(P4): Teil-Neuzeichnen als Vorgabe, Messeinblendung, deckende Seiten (1.6.10)
- WS7_PARTIAL_REFRESH = 1 auch im Auslieferungsstand. Am Gerät geprüft: kein
  Flackern; von den drei Tempo-Maßnahmen aus 1.6.4 ist es der größte Gewinn.
- make_page() setzt einen deckenden Hintergrund in COL_BG statt LV_OPA_TRANSP.
  Optisch identisch, spart LVGL beim Scrollen aber das Zeichnen und Mischen
  der darunterliegenden Ebenen.
- Neuer Schalter WS7_PERF_MONITOR schaltet LV_USE_SYSMON/LV_USE_PERF_MONITOR
  ein und blendet Bilder je Sekunde und Prozessorlast ein. Vorgabe 0, mit 1
  gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 14:45:33 +02:00
thomas 117715dc3b Merge pull request 'fix(P4): Tipp auf einen Knopf löst nicht mehr mehrfach aus (1.6.9)' (#37) from fix/touch-mehrfachausloesung into main 2026-09-01 14:19:51 +02:00
raw-designsandClaude Opus 5 328dc06983 fix(P4): Tipp auf einen Knopf löst nicht mehr mehrfach aus (1.6.9)
Der GT9271 lässt gelegentlich einen Messzyklus aus oder liefert ein
unplausibles Paket. Der Hersteller-Treiber kann "gerade nichts Neues" nicht
von "Finger weg" unterscheiden und meldet beides als losgelassen. Seit die
Abtastrate mit 1.6.8 von 33 auf 16 ms gestiegen ist, wurde daraus sichtbar
Drücken-Loslassen-Drücken-Loslassen.

Vor den Treiber ist jetzt ein eigener get_xy-Aufsatz gesetzt: Er verwirft
unplausible Rohwerte und hält den letzten gültigen Punkt noch
WS7_TOUCH_HOLD_MS lang (Vorgabe 40 ms). Der Aufsatz wird zur Laufzeit in das
Touch-Handle eingehängt, die Herstellerdateien bleiben unverändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 14:18:28 +02:00
thomas 480708c992 Merge pull request 'perf(P4): Scrollen am Waveshare-Panel flüssiger (1.6.8)' (#36) from perf/scrollen-taktrate into main 2026-09-01 14:02:46 +02:00
raw-designsandClaude Opus 5 9a0761f6da perf(P4): Scrollen am Waveshare-Panel flüssiger (1.6.8)
LV_DEF_REFR_PERIOD steuert nicht nur die Bildrate, sondern auch die
Abtastrate des Touch und die Schrittweite von Animationen. Mit 33 ms waren
das 30 Schritte je Sekunde - beim Ziehen mit dem Finger sichtbar ruckelig.

Für das Waveshare-Panel jetzt 16 ms, passend zu dessen 60 Hz. Kommt das
Zeichnen nicht hinterher, wird ein Bild später fertig; kaputt geht nichts.
Die JC-Panels bleiben unverändert bei 33 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 14:01:23 +02:00
thomas e6c09b7a9a Merge pull request 'fix(P4): Menü-Knopf fängt Tipps auch knapp daneben ab (1.6.7)' (#35) from fix/menue-knopf-trefferflaeche into main 2026-09-01 13:49:17 +02:00
raw-designsandClaude Opus 5 4fa0c81087 fix(P4): Menü-Knopf fängt Tipps auch knapp daneben ab (1.6.7)
Eine Messung mit WS7_TOUCH_DEBUG hat gezeigt, dass die Touch-Zuordnung stimmt:
Der Tipp auf den Menü-Knopf landete bei X=66/Y=27, der Knopf liegt aber bei
X 8..56. Mit 48x36 Pixeln ist er auf dem 7-Zoll-Panel ein sehr kleines Ziel.

lv_obj_set_ext_click_area vergrößert die Trefferfläche um 20 Pixel, ohne den
Knopf optisch zu ändern. Die Vergrößerung wirkt nur innerhalb der Kopfleiste,
weil diese ihre Kinder beschneidet - dem Seiteninhalt darunter wird nichts
weggenommen, und bei offenem Menü liegt ohnehin der Vorhang darüber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 13:47:54 +02:00
thomas 04eb77fb2d Merge pull request 'feat(P4): Touch-Messausgabe zum Einmessen (1.6.6)' (#34) from diag/touch-einmessen into main 2026-09-01 13:25:39 +02:00
raw-designsandClaude Opus 5 7878f1bafd feat(P4): Touch-Messausgabe zum Einmessen (1.6.6)
Der Menü-Knopf oben links reagiert am Waveshare-Panel nicht. Ob der Touch die
48x36 Pixel grosse Flaeche in der 44 Pixel hohen Kopfleiste ueberhaupt trifft,
liess sich bisher nicht nachsehen.

WS7_TOUCH_DEBUG in pins_config.h gibt jeden Berührpunkt roh und umgerechnet
über esp_rom_printf aus, also auch bei Core-Debug-Level "none". Vorgabe 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 13:24:18 +02:00
thomas 60bb859fc9 Merge pull request 'fix(P4): Menü-Vorhang wieder öffenbar, RGB888 bleibt Vorgabe (1.6.5)' (#33) from fix/waveshare-vollbild-neuzeichnen into main 2026-09-01 13:14:52 +02:00
raw-designsandClaude Opus 5 e8fdc60fea fix(P4): Menü-Vorhang wieder öffenbar, RGB888 bleibt Vorgabe (1.6.5)
Zwei der drei Tempo-Maßnahmen aus 1.6.4 haben sich am Gerät als untauglich
erwiesen und stehen wieder auf ihrem alten Wert:

- WS7_COLOR_BITS = 24: Die Bridge dieses Panels nimmt kein RGB565 an.
- WS7_PARTIAL_REFRESH = 0: Der Menü-Vorhang ist halbtransparent und wird in
  den gerade aktiven Bildpuffer eingemischt. Die nächste Zustandsmeldung der
  S3 zeichnet in den zweiten Puffer, der ihn nie bekommen hat - der Vorhang
  war damit sofort wieder weg und der Knopf schien wirkungslos. Nutzbar wäre
  der Modus erst, wenn der LVGL-Port die geänderten Bereiche zwischen beiden
  Puffern abgleicht.

Beide bleiben als Schalter erhalten, mit Notiz woran sie gescheitert sind.
WS7_PARALLEL_RENDER bleibt eingeschaltet - von den drei Maßnahmen trägt nur
diese.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 13:13:28 +02:00
thomas 8b7978507c Merge pull request 'perf(P4): Darstellung am Waveshare-Panel beschleunigen (1.6.4)' (#32) from perf/waveshare-darstellung-beschleunigen into main 2026-09-01 12:52:55 +02:00
raw-designsandClaude Opus 5 a8ae034911 perf(P4): Darstellung am Waveshare-Panel beschleunigen (1.6.4)
1280x720 sind 2,4-mal so viele Pixel wie beim 4,3-Zöller, und bis 1.6.3 wurde
bei jeder Änderung das komplette Bild in 24 Bit neu gezeichnet. Drei Schalter
in pins_config.h, einzeln zurückstellbar:

- WS7_COLOR_BITS = 16: RGB565 statt RGB888, halbe Datenmenge je Bild.
  lv_conf.h liest den Wert aus pins_config.h, damit Sketch und LVGL-Bibliothek
  dieselbe Farbtiefe benutzen; display_hal.cpp sichert das per static_assert.
- WS7_PARTIAL_REFRESH = 1: Avoid-Tear-Mode 3 (Direct-Mode, zwei Puffer) statt
  Vollbild-Neuzeichnen.
- WS7_PARALLEL_RENDER = 1: LV_USE_OS = FREERTOS und zwei Zeichen-Threads.

Framebuffer-Bedarf sinkt dadurch von rund 8,3 MB auf 3,7 MB PSRAM. Die
JC-Panels sind von allen drei Schaltern nicht betroffen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 12:51:24 +02:00
thomas ee5888ed97 Merge pull request 'fix(P4): UART auf GPIO20/21, Touch oben/unten spiegeln (1.6.3)' (#31) from fix/waveshare-uart-pins-und-touchspiegelung into main 2026-09-01 12:35:54 +02:00
9 changed files with 771 additions and 69 deletions
+233
View File
@@ -1,3 +1,236 @@
Version 1.6.22:
- Der Ab- und Wiederaufbau der DSI-Strecke aus 1.6.21 hat nicht geholfen, sondern das Bild
um einen Schritt verschlechtert: Nach dem Einschalten kam gar nichts mehr, nach dem
ersten Reset nur die Beleuchtung, erst nach dem zweiten das Bild. Ein Ab- und Wiederaufbau
im laufenden Betrieb ist einem echten Chip-Reset also nicht gleichwertig. WS7_DSI_RESTART
steht deshalb wieder auf 0; der Schalter bleibt samt Begruendung erhalten.
- Stattdessen der Weg, den die Messungen eindeutig stuetzen: Nach einem Kaltstart startet
der P4 sich genau einmal selbst neu, sobald der erste Hochlauf durch ist. Damit passiert
genau das, was bei jedem Versuch zuverlaessig geholfen hat - ohne dass jemand die
Reset-Taste druecken muss.
- Eine Schleife ist ausgeschlossen: Beim zweiten Durchlauf ist der Startgrund kein Kaltstart
mehr, die Bedingung greift nicht erneut. Der Fruehstart-Zaehler der Absturzsicherung wird
vor dem gewollten Neustart zurueckgesetzt, damit dieser nicht als Absturz zaehlt.
- Kostet rund drei Sekunden, und nur beim Einschalten. Schalter WS7_COLD_BOOT_RESTART in
pins_config.h.
- Das ist eine Umgehung, keine Erklaerung: Warum das Panel den ersten Anlauf nach dem
Einschalten nicht annimmt, ist weiterhin offen. Der Hochlauf meldet durchgehend Erfolg.
Version 1.6.21:
- Der Hochlauf lief zuletzt vollstaendig fehlerfrei durch - Bridge zweimal eingestellt,
Panel zweimal geweckt, alle I2C-Bausteine erreichbar - und trotzdem kam beim ersten
Einschalten kein Bild. Erst ein Reset half, und der zuverlaessig. Es fehlte also kein
Befehl mehr; es fehlte der Vorgang selbst.
- Ein Reset unterscheidet sich an dieser Stelle in genau einem Punkt: Die DSI-Verbindung
geht einmal weg und neu auf, waehrend Bridge und Panel bereits versorgt und eingestellt
sind. Weder die wiederholte Grundeinstellung noch die wiederholten Weckbefehle ersetzen
das - beide laufen ueber eine Verbindung, die durchgehend bestand.
- Der Hochlauf baut die DSI-Strecke jetzt einmal komplett ab und wieder auf: Bildausgabe,
Kommandokanal und Bus werden freigegeben und neu angelegt. Damit passiert ohne Reset,
was vorher erst der zweite Start brachte. Schalter WS7_DSI_RESTART in pins_config.h,
Pause dazwischen WS7_DSI_RESTART_MS (Vorgabe 200 ms).
- Der DSI-Aufbau steckt dafuer in einer eigenen Funktion, damit der Vorgang wiederholbar
ist statt zweimal im Quelltext zu stehen.
Version 1.6.20:
- Nach 1.6.19 lief es reproduzierbar so ab: Beim Einschalten ging nach zwei bis drei
Sekunden die Hintergrundbeleuchtung an, dann kam nichts mehr; nach einem Reset ging die
Beleuchtung kurz aus und danach erschien Beleuchtung samt Bild. Daraus folgt der letzte
fehlende Schritt: Auch die Grundeinstellung der Bridge lief bisher nur einmal, und zwar
bevor die DSI-Strecke ueberhaupt existierte. Beim Reset half es nur deshalb, weil die
Bridge ihre Einstellung aus dem vorigen Durchlauf noch hatte und diesmal ein DSI-Signal
vorfand.
- Die Grundeinstellung der Bridge wird jetzt ein zweites Mal geschrieben, sobald die
Videoausgabe laeuft - dasselbe Vorgehen wie bei den Weckbefehlen des Panels in 1.6.19.
Damit wird ohne Reset erreicht, was vorher erst der zweite Durchlauf brachte.
- Im Start-Log sind beide Durchgaenge benannt ("Bridge-Grundeinstellung vor dem DSI-Start"
und "... nach dem Start der Videoausgabe").
Version 1.6.19:
- Die Wartezeit aus 1.6.18 hat den Start deutlich verbessert, aber noch nicht zuverlässig
gemacht. Aus dem verbliebenen Fehlerbild folgt der nächste Schritt: Die Beleuchtung ging
an, das Bild fehlte. Die Beleuchtung hängt an der Bridge und wird über I2C geschaltet -
sie funktioniert also. Verloren gehen die Weckbefehle an das Panel selbst.
- Diese Befehle wurden bisher genau einmal geschickt, bevor die Videoausgabe lief. War der
Panel-Controller in dem Moment noch nicht aufnahmebereit, waren sie weg, und danach hat
nie wieder jemand nachgefragt. Sie werden jetzt ein zweites Mal gesendet, sobald die
Videoausgabe steht. Die Befehle sind wiederholbar, der zweite Anlauf kostet nichts.
- Die Pause dazwischen steht als WS7_PANEL_SETTLE_MS in pins_config.h (Vorgabe 200 ms).
- Im Start-Log ist jeder Anlauf einzeln zu sehen ("Weckbefehle vor/nach dem Start der
Videoausgabe").
Version 1.6.18:
- Ursache des unzuverlässigen Starts gefunden: Der P4 sprach Bridge und Panel sofort nach
dem Einschalten an. Das Displaymodul braucht danach aber einen Moment, bis es bereit ist.
Kam der Hochlauf zu früh, nahm die Bridge ihre Register nicht an oder das Panel zeigte
trotz korrekt gesendeter DSI-Daten nichts - beides ohne Fehlermeldung, weil aus Sicht des
P4 jeder Schritt geklappt hatte. Das Ergebnis war ein Display, das mal ansprang und mal
nicht, und beim ersten Anstecken meist gar nicht.
- Der Hochlauf wartet jetzt WS7_PANEL_WARMUP_MS (Vorgabe 2000 ms), bevor er beginnt. Der
Werks-Testsketch wartet aus demselben Grund 2 Sekunden am Anfang.
- Damit erklärt sich auch, warum es mit geöffnetem seriellen Monitor fast immer klappte:
Dessen USB-Anmeldung verzögert den Start und ersetzte damit unbeabsichtigt die Wartezeit.
- Der Wert steht in pins_config.h. Läuft der Start zuverlässig, lässt er sich vorsichtig
senken; springt die Anzeige nicht an, ist er der erste Wert zum Erhöhen.
Version 1.6.17:
- Der I2C-Stoerimpulsfilter aus 1.6.16 wirkt: Nach dem Panel-Start melden sich wieder alle
drei Bausteine, Bridge und Touch bleiben ansprechbar.
- Das Pruefbild aus 1.6.15 war allerdings wirkungslos und damit als Test untauglich: Es
wurde zwar gesetzt, doch unmittelbar danach startete LVGL und schrieb in den Bildspeicher,
womit das Pruefbild wieder verschwand. Der Test sah deshalb genauso aus wie der Fehler,
den er finden sollte.
- Mit WS7_TEST_PATTERN = 1 endet der Hochlauf jetzt direkt nach dem Pruefbild: Beleuchtung
an, Farbbalken stehen, kein LVGL. Damit ist die Frage eindeutig zu beantworten, ob die
Anzeigekette bis zum Panel arbeitet.
Version 1.6.16:
- Ursache des schwarzen Bildes gefunden. Der Start-Log zeigte es eindeutig: Vor dem
Panel-Start meldeten sich alle drei I2C-Bausteine (Touch 0x14, Audio 0x18, Bridge 0x45),
danach nur noch der Audio-Baustein. Es fielen also genau die beiden weg, die am
Displaykabel haengen - der Bus selbst arbeitete weiter. Sobald die DSI-Ausgabe laeuft,
stoeren deren schnelle Signale auf das lange Flachbandkabel ein, und ohne Filterung
bringen diese Stoerspitzen die I2C-Uebertragung aus dem Tritt. Die Bridge liess sich
daraufhin nicht mehr ansprechen, das Panel blieb dunkel.
- Der I2C-Bus laeuft fuer das Waveshare-Panel jetzt mit Stoerimpulsfilter und internen
Abschlusswiderstaenden, so wie es auch das Waveshare-Beispiel tut. Beides fehlte, weil
die Bus-Einrichtung urspruenglich von den JC-Panels uebernommen wurde, die kein
Flachbandkabel dieser Laenge haben.
- Das erklaert rueckwirkend auch, warum sich der Fehler so sprunghaft verhielt: Ob eine
Uebertragung durchkam, hing von der Stoerlage ab.
Version 1.6.15:
- Diagnose fuer das schwarze Bild, das der Log bisher nicht erklaeren konnte: Alle Schritte
liefen fehlerfrei durch, trotzdem blieb die Anzeige leer.
- Die drei Weckbefehle an das Panel (MADCTL, Sleep Out, Display On) liefen bislang
ungeprueft durch und sind jetzt in die Fehlerausgabe einbezogen.
- Neuer Schalter WS7_TEST_PATTERN in pins_config.h: zeigt statt der Oberflaeche
Prueffarbbalken an. Sie entstehen im DSI-Baustein selbst und benutzen weder Bildspeicher
noch LVGL. Damit laesst sich in einem Durchgang trennen, ob die Anzeigekette bis zum
Panel arbeitet oder ob der Fehler weiter oben liegt.
- Das Ende des Hochlaufs wird gemeldet ("Bringup fertig" / "LVGL laeuft"). Bisher endete
die Ausgabe nach dem Panel-Start, und ein Haengenbleiben in LVGL war von einem
fehlenden Bild nicht zu unterscheiden.
Version 1.6.14:
- Der Touch-Controller wird jetzt erst angesprochen, nachdem die Bridge freigegeben und die
Hintergrundbeleuchtung eingeschaltet ist. Der GT9271 haengt an derselben Versorgung und
antwortet vorher nicht auf I2C - deshalb schlug seine Initialisierung bisher schon beim
Kaltstart fehl. In der Werks-Testskizze steht die Touch-Initialisierung aus demselben
Grund hinter dem Einschalten der Beleuchtung; beim Uebertragen in die Firmware war diese
Reihenfolge verloren gegangen.
- Die Wartezeit dazwischen steht als WS7_TOUCH_POWER_MS in pins_config.h (Vorgabe 120 ms).
- Neu im Start-Log: eine Liste aller Bausteine am I2C-Bus, einmal vor und einmal nach dem
Panel-Start. Erwartet werden 0x45 (Bridge) und 0x14 (Touch). Damit ist auf einen Blick zu
sehen, ob ein Baustein gar nicht antwortet, statt aus Treiberfehlern raten zu muessen.
Version 1.6.13:
- Ursache des schwarzen Bildes gefunden: ein haengender I2C-Bus. Trifft ein Reset des P4
mitten in eine laufende Uebertragung, bleibt der angesprochene Baustein in seiner
Bitausgabe stehen und haelt die Datenleitung dauerhaft auf Masse. Damit schlaegt jede
weitere Uebertragung fehl. Bridge und Touch-Controller haengen an Dauerstrom, weshalb
dieser Zustand jeden Reset und sogar das Flashen ueberlebt - nur echtes Stromabziehen
loest ihn. Die Folge: Die Bridge wird nie initialisiert, das Bild bleibt schwarz, und
weil sie ihre Register behaelt, brennt die Hintergrundbeleuchtung weiter.
- Vor dem Anlegen des I2C-Busses wird die Taktleitung jetzt von Hand getaktet, bis der
Baustein die Datenleitung freigibt, danach folgt eine Stopp-Bedingung. Das ist der in
der I2C-Spezifikation vorgesehene Weg und laeuft bei freiem Bus wirkungslos durch.
- Fehlerbehebung: Schreibzugriffe auf die Bridge liefen stillschweigend ins Leere. Genau
deshalb war der Fehler so schwer zu finden - das Bild blieb ohne jeden Hinweis schwarz.
Sie melden Fehler jetzt im Klartext.
- Fehlerbehebung: In pins_config.h stand seit 1.6.10 "#elsealle" statt "#else". Der
Tippfehler traf nur den Zweig des 4,3-Zoll-Panels, das damit nicht mehr uebersetzbar war.
Beide Panel-Varianten sind jetzt wieder gegengebaut.
Version 1.6.12:
- Die Abschaltfolge für die Bridge aus 1.6.11 ist zurückgenommen. Sie hat den Fehler nicht
behoben, sondern verschlimmert: Nach dem Nullsetzen von C0/C2/AC kam das Panel auch
direkt nach dem Flashen nicht mehr hoch, die normale Einschaltfolge holt es aus diesem
Zustand nicht zurück. Der Start entspricht damit wieder 1.6.10.
- Stattdessen zwei Diagnosen, um die Ursache des schwarzen Bildes nach einem Reset
einzugrenzen: Der Startgrund wird beim Hochlauf ausgegeben (1 = Kaltstart, alles andere
Warmstart), und das Anfordern der Stromversorgung für die MIPI-Strecke wird jetzt auf
Fehler geprüft - bislang lief dieser Schritt als einziger ungeprüft durch.
Version 1.6.10:
- WS7_PARTIAL_REFRESH ist jetzt auch im Auslieferungsstand 1. Am Gerät geprüft: kein
Flackern, und der Gewinn ist der größte der drei Tempo-Maßnahmen - vor allem während
eines Bezugs, wo die Zeitanzeige zehnmal je Sekunde weiterläuft.
- Der Hintergrund der Seiten ist deckend statt durchsichtig. Sichtbar ändert sich nichts,
die Ebenen darunter haben dieselbe Farbe; LVGL muss sie beim Scrollen aber nicht mehr
mitzeichnen und durchmischen.
- Neuer Schalter WS7_PERF_MONITOR in pins_config.h: blendet unten rechts Bilder je Sekunde
und Prozessorlast ein. Damit lässt sich beantworten, ob weitere Mühe beim Zeichnen
überhaupt noch etwas bringt, statt ins Blaue zu optimieren.
Version 1.6.9:
- Fehlerbehebung: Ein Tipp auf einen Knopf löste oft mehrfach aus. Der GT9271 lässt
gelegentlich einen Messzyklus aus oder liefert ein unplausibles Paket; der
Hersteller-Treiber kann "gerade nichts Neues" nicht von "Finger weg" unterscheiden und
meldet beides als losgelassen. Seit die Oberfläche mit 1.6.8 doppelt so oft abtastet,
wurde daraus sichtbar Drücken-Loslassen-Drücken-Loslassen.
- Vor den Treiber ist deshalb ein Filter gesetzt: Er verwirft unplausible Rohwerte und hält
den letzten gültigen Berührpunkt noch WS7_TOUCH_HOLD_MS lang (Vorgabe 40 ms). Losgelassen
wird erst gemeldet, wenn wirklich nichts mehr kommt. Der Wert steht in pins_config.h;
0 schaltet die Überbrückung ab. Zu groß gewählt verzögert er das Loslassen und dämpft
den Schwung beim Wischen.
- Der Filter hängt sich zur Laufzeit in den Treiber ein, die Herstellerdateien bleiben
unverändert.
Version 1.6.8:
- Scrollen und Wischen laufen am Waveshare-Panel flüssiger: LVGL rechnet dort jetzt im
16-Millisekunden-Takt statt alle 33 ms. Dieser Wert steuert nicht nur die Bildrate,
sondern auch, wie oft der Touch abgetastet und wie fein eine Animation gerechnet wird -
mit 30 Schritten je Sekunde war vor allem das Ziehen mit dem Finger sichtbar ruckelig.
Das Panel läuft mit 60 Hz, mehr als 16 ms bringt also nichts.
- Kommt das Zeichnen einmal nicht hinterher, wird ein Bild später fertig; kaputt geht
dabei nichts. Die JC-Panels bleiben bei 33 ms.
Version 1.6.7:
- Der Menue-Knopf oben links fängt Tipps jetzt auch knapp daneben ab. Mit 48x36 Pixeln war
er auf dem 7-Zoll-Panel ein sehr kleines Ziel; gemessen wurde ein Fehlgriff von rund
10 Pixeln neben der Fläche, weshalb sich das Menü scheinbar nicht öffnen ließ. Optisch
bleibt der Knopf unverändert, die Trefferfläche wächst um 20 Pixel nach allen Seiten.
Sie wirkt nur innerhalb der Kopfleiste - dem Seiteninhalt darunter wird nichts genommen.
- Die Touch-Zuordnung selbst war korrekt: Eine Messung mit WS7_TOUCH_DEBUG hat gezeigt,
dass Achsen und Spiegelung stimmen.
Version 1.6.6:
- Neuer Schalter WS7_TOUCH_DEBUG in pins_config.h: Auf 1 gesetzt, gibt jeder Berührpunkt
seine Rohwerte und die daraus errechnete Bildposition auf der seriellen Konsole aus.
Gedacht zum Einmessen des Touch, wenn Bedienelemente nicht reagieren - insbesondere am
oberen Bildrand, wo die 44 Pixel hohe Kopfleiste sitzt.
Version 1.6.5:
- Zwei der drei Tempo-Maßnahmen aus 1.6.4 haben sich am Gerät als untauglich erwiesen und
stehen wieder auf ihrem alten Wert. Sie bleiben als Schalter erhalten, mit Notiz, woran
sie gescheitert sind.
* WS7_COLOR_BITS wieder 24: Die Bridge dieses Panels nimmt kein RGB565 an, mit 16 Bit
kommt kein brauchbares Bild.
* WS7_PARTIAL_REFRESH wieder 0: Der Menü-Vorhang ließ sich nicht mehr öffnen. Er ist
halbtransparent und wird deshalb in den gerade aktiven Bildpuffer eingemischt; die
nächste Zustandsmeldung der S3 zeichnet aber in den zweiten Puffer, der ihn nie
bekommen hat - der Vorhang war also sofort wieder weg. Nutzbar wäre der Modus erst,
wenn der LVGL-Port die geänderten Bereiche zwischen beiden Puffern abgleicht.
- WS7_PARALLEL_RENDER bleibt eingeschaltet: LVGL zeichnet weiterhin mit zwei Threads auf
beiden Prozessorkernen. Von den drei Maßnahmen trägt nur diese.
Version 1.6.4:
- Waveshare 7" (H): Drei Maßnahmen gegen die träge Bedienung am großen Panel. 1280x720
sind 2,4-mal so viele Pixel wie beim 4,3-Zöller, und bis 1.6.3 wurde bei jeder Änderung
das komplette Bild in 24 Bit neu gezeichnet.
* Farbtiefe RGB565 statt RGB888 (WS7_COLOR_BITS): halbiert die Datenmenge je Bild.
Feinere Farbabstufungen entfallen, sichtbar allenfalls in den dunklen Verläufen der
Rundanzeige - die JC-Panels laufen seit jeher so.
* Nur geänderte Bildbereiche neu zeichnen statt des ganzen Bildes
(WS7_PARTIAL_REFRESH, LVGL-Direct-Mode mit zwei Puffern). Eine sich ändernde
Temperaturanzeige kostet damit ein paar tausend statt 921.600 Pixel.
* LVGL zeichnet mit zwei Threads auf beiden Prozessorkernen (WS7_PARALLEL_RENDER).
- Jeder der drei Schalter steht in pins_config.h und lässt sich einzeln zurückstellen;
die README nennt zu jedem, woran man erkennt, dass er zurück muss. Die JC-Panels sind
von allen dreien nicht betroffen.
- Nebeneffekt: Der Framebuffer-Bedarf sinkt von rund 8,3 MB auf 3,7 MB PSRAM.
Version 1.6.3:
- Waveshare ESP32-P4-Pico: UART zur Hauptplatine liegt jetzt auf TX = GPIO20 und
RX = GPIO21 (vorher GPIO17/GPIO18). Die JC-Panels bleiben bei GPIO33/GPIO31.
@@ -24,6 +24,7 @@
#include "protocol_client.h"
#include "ui.h"
#include "display_hal.h"
#include "pins_config.h"
#include <ArduinoJson.h>
#include <Update.h>
#include <Preferences.h>
@@ -296,6 +297,31 @@ void setup() {
g_client.onOta(onOta);
hal_init(&g_client); // LVGL + Panel/Touch + Dashboard (ruft ui_init intern auf)
// --- Waveshare-Panel: einmaliger Neustart nach dem Einschalten ---
//
// Das Panel kommt nach dem Anlegen der Versorgung im ersten Anlauf nicht zuverlaessig
// hoch: Die Beleuchtung geht an, ein Bild bleibt aus - obwohl jeder Schritt des
// Hochlaufs Erfolg meldet. Ein Reset behebt es zuverlaessig, und zwar nur ein echter
// Chip-Reset; die DSI-Strecke im laufenden Betrieb ab- und wieder aufzubauen genuegt
// nicht (siehe WS7_DSI_RESTART).
//
// Deshalb startet der P4 sich nach einem Kaltstart genau einmal selbst neu, nachdem
// der erste Hochlauf durch ist. Beim zweiten Durchlauf ist der Startgrund kein
// Kaltstart mehr, die Bedingung greift also nicht erneut - eine Schleife ist
// ausgeschlossen. Kostet rund drei Sekunden, aber nur beim Einschalten.
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_COLD_BOOT_RESTART
if (esp_reset_reason() == ESP_RST_POWERON) {
// Der Fruehstart-Zaehler wurde oben bereits erhoeht. Diesen gewollten Neustart
// darf er nicht mitzaehlen, sonst laeuft die Absturzsicherung nach ein paar
// Einschaltvorgaengen in den Update-Modus.
{ Preferences p; p.begin("bootguard", false); p.putUShort("early", 0); p.end(); }
DBG_SERIAL.println(F("[Panel] Kaltstart - einmaliger Neustart fuer das Displaypanel"));
DBG_SERIAL.flush();
delay(50);
esp_restart();
}
#endif
g_client.begin(); // UART starten, hello folgt automatisch in loop()
DBG_SERIAL.println(F("Bereit. Warte auf Verbindung zur Hauptplatine..."));
+21 -2
View File
@@ -44,13 +44,13 @@ Vendor-Pakete: 4,3" im Ordner `JC4880P443C_I_W/`, 7,0" im Repo-Ordner
| Haupt-MCU (UI) | ESP32-P4 | ESP32-P4 | ESP32-P4 (32 MB PSRAM, 32 MB Flash) |
| WLAN/BT (hier ungenutzt) | ESP32-C6 | ESP32-C6 | — (kein Funkmodul) |
| Display | ST7701, 480×800 IPS, MIPI-DSI | JD9165, 1024×600 IPS, MIPI-DSI | 1280×720 IPS, MIPI-DSI 2 Lanes über Waveshare-Bridge |
| Farbformat | RGB565 | RGB565 | **RGB888** (Bridge nimmt nichts anderes) |
| Farbformat | RGB565 | RGB565 | **RGB888** — die Bridge nimmt kein RGB565 (geprüft) |
| LVGL-Ausrichtung | Rotation **270°** (PPA) → 800×480 | **keine** Rotation, nativ 1024×600 | **keine** Rotation, nativ 1280×720 |
| Touch | GT911, I²C SDA=GPIO7 / SCL=GPIO8 | GT911, I²C SDA=GPIO7 / SCL=GPIO8 | **GT9271** (I²C-Adresse **0x14**), SDA=GPIO7 / SCL=GPIO8 |
| Touch-Rohkoordinaten | 480×800 (= Panelraster) | **800×480** → wird auf 1024×600 skaliert | **720×1280** (Hochformat) → X/Y werden getauscht |
| LCD-Reset | GPIO5 | **GPIO27** | kein Reset-Pin (macht die Bridge) |
| Backlight | GPIO23 (LEDC-PWM) | GPIO23 (LEDC-PWM) | **Bridge-Register** auf I²C **0x45** (`AD`/`AB`/`AA`) |
| Framebuffer (3×) | 3 × 750 KB ≈ 2,3 MB PSRAM | 3 × 1,2 MB ≈ 3,6 MB PSRAM | 3 × 2,8 MB ≈ **8,3 MB** PSRAM |
| Framebuffer | 3 × 750 KB ≈ 2,3 MB PSRAM | 3 × 1,2 MB ≈ 3,6 MB PSRAM | 3 × 2,8 MB ≈ 8,3 MB PSRAM |
| Freie Expansion-IO | Stiftleiste **JP1**: GPIO 52/51/50/49/35/34/33/32/31/30/29/28 | FPC 1,0 mm 16-pol. **FPC4**: GPIO 34/33/32/31/30/29/28 · **FPC3**: 48/47/46/45/5/4/3/2 | 40-poliger Header im Raspberry-Pi-Pico-Format; belegt sind I²C (7/8), TF-Karte (9/10/11/12/13/53) und Debug-UART0 (37/38) |
| UART zur Hauptplatine | TX GPIO33 / RX GPIO31 | TX GPIO33 / RX GPIO31 | **TX GPIO20 / RX GPIO21** |
@@ -75,6 +75,25 @@ Das Panel hängt hinter einer Waveshare-Bridge und braucht **keinen** Hersteller
`WS7_TOUCH_MIRROR_RAW_X` steht deshalb auf **1** (dreht oben/unten), unabhängig von der
Bilddrehung.
### Waveshare 7" (H) — Tempo-Schalter
1280×720 bedeutet 2,4-mal so viele Pixel wie beim 4,3-Zöller. Drei Schalter in
[`pins_config.h`](pins_config.h) halten die Bedienung flüssig; jeder ist einzeln
zurückstellbar, falls er Ärger macht:
| Schalter | Vorgabe | Wirkung | Stand am Gerät |
|---|---|---|---|
| `WS7_COLOR_BITS` | `24` | `16` (RGB565) würde die Datenmenge je Bild halbieren. | **Geht an diesem Panel nicht** — die Bridge nimmt nur RGB888 |
| `WS7_PARTIAL_REFRESH` | `0` | `1` zeichnet nur geänderte Bereiche neu (LVGL-Direct-Mode, zwei wechselnde Puffer). Wäre der größte Gewinn. | **Geht so nicht** — der halbtransparente Menü-Vorhang landet nur in einem der beiden Puffer und ist sofort wieder weg |
| `WS7_PARALLEL_RENDER` | `1` | LVGL zeichnet mit zwei Threads auf beiden Prozessorkernen. | Aktiv |
> Damit `WS7_PARTIAL_REFRESH` nutzbar würde, müsste der LVGL-Port die geänderten Bereiche
> zwischen beiden Puffern abgleichen. Die Maschinerie dafür liegt in `lvgl_port_v9.c`,
> wird aber nur bei gedrehter Ausgabe übersetzt.
Die Farbtiefe muss für Sketch **und** LVGL-Bibliothek dieselbe sein; `lv_conf.h` liest
`WS7_COLOR_BITS` deshalb direkt aus `pins_config.h`.
> Beim 7-Zöller ist die Expansion-IO **kein Stiftleisten-Header**, sondern eine
> FPC-Buchse (1,0 mm Raster, 16-polig) — für die UART-Verdrahtung wird ein
> FPC-Breakout/Adapterkabel gebraucht. Schaltplan: `../JC_Display_Firmware_7zoll/5-Schematic/4_CONN.png`.
+356 -54
View File
@@ -29,6 +29,8 @@
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "esp_rom_sys.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_ldo_regulator.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
@@ -59,16 +61,92 @@
#define BSP_LCD_BACKLIGHT ((gpio_num_t)LCD_LED)
#define LCD_LEDC_CH LEDC_CHANNEL_0
// Bytes je Pixel im Framebuffer. Das Waveshare-Panel laeuft in RGB888 (LVGL ebenfalls,
// siehe lv_conf.h), die JC-Panels in RGB565.
// Bytes je Pixel im Framebuffer. Beim Waveshare-Panel waehlbar ueber WS7_COLOR_BITS
// (pins_config.h); LVGL rechnet mit derselben Tiefe (lv_conf.h). JC-Panels: RGB565.
#if JC_PANEL_TYPE == WS_PANEL_7H
#define BSP_LCD_FB_BYTES_PER_PX (3)
#define BSP_LCD_FB_BYTES_PER_PX (WS7_COLOR_BITS / 8)
#else
#define BSP_LCD_FB_BYTES_PER_PX (2)
#endif
static i2c_master_bus_handle_t s_i2c_handle = NULL;
#if JC_PANEL_TYPE == WS_PANEL_7H
// ------------------------------------------------------------------------------------
// Haengenden I2C-Bus befreien
//
// Trifft ein Reset des P4 mitten in eine laufende I2C-Uebertragung, bleibt der
// angesprochene Baustein in seiner Bitausgabe stehen und haelt die Datenleitung dauerhaft
// auf Masse. Der Bus ist damit tot: Jede weitere Uebertragung schlaegt fehl. Da Bridge und
// Touch-Controller an Dauerstrom haengen, ueberlebt dieser Zustand jeden Reset und sogar
// das Flashen - nur echtes Stromabziehen loest ihn.
//
// Folge: Die Bridge wird nie initialisiert, das Bild bleibt schwarz, und weil sie ihre
// Register behaelt, brennt die Hintergrundbeleuchtung weiter. Genau dieses Bild.
//
// Abhilfe ist der uebliche Weg aus der I2C-Spezifikation: Vor dem Anlegen des Busses die
// Taktleitung von Hand takten, bis der Baustein sein letztes Bit ausgegeben hat und die
// Datenleitung wieder loslaesst, danach eine Stopp-Bedingung erzeugen.
static void ws7_i2c_bus_recover(void)
{
gpio_config_t io = {
.pin_bit_mask = BIT64(BSP_I2C_SDA) | BIT64(BSP_I2C_SCL),
.mode = GPIO_MODE_INPUT_OUTPUT_OD, // offener Kollektor, Zustand lesbar
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io);
gpio_set_level(BSP_I2C_SDA, 1);
gpio_set_level(BSP_I2C_SCL, 1);
esp_rom_delay_us(10);
if (gpio_get_level(BSP_I2C_SDA) != 0) {
return; // Bus frei - nichts zu tun
}
esp_rom_printf("[Panel] I2C-Bus haengt - wird freigetaktet\n");
// Bis zu neun Takte: so viele braucht ein Baustein hoechstens, um ein angefangenes
// Byte samt Bestaetigungsbit zu Ende zu schieben.
for (int i = 0; i < 9 && gpio_get_level(BSP_I2C_SDA) == 0; i++) {
gpio_set_level(BSP_I2C_SCL, 0);
esp_rom_delay_us(5);
gpio_set_level(BSP_I2C_SCL, 1);
esp_rom_delay_us(5);
}
// Stopp-Bedingung: Datenleitung geht bei hoher Taktleitung von Masse auf High.
gpio_set_level(BSP_I2C_SDA, 0);
esp_rom_delay_us(5);
gpio_set_level(BSP_I2C_SCL, 1);
esp_rom_delay_us(5);
gpio_set_level(BSP_I2C_SDA, 1);
esp_rom_delay_us(10);
esp_rom_printf("[Panel] I2C-Bus nach Freitakten: %s\n",
gpio_get_level(BSP_I2C_SDA) ? "frei" : "weiterhin belegt");
// Pins wieder freigeben, damit der I2C-Treiber sie uebernehmen kann.
gpio_reset_pin(BSP_I2C_SDA);
gpio_reset_pin(BSP_I2C_SCL);
}
// Zeigt, welche Bausteine sich am I2C-Bus melden. Erwartet werden 0x45 (Bridge) und
// 0x14 (GT9271-Touch); 0x18 waere der Audio-Baustein. Fehlt einer, sagt das mehr aus als
// jede Fehlermeldung des Treibers.
static void ws7_i2c_scan(const char *wann)
{
esp_rom_printf("[Panel] I2C-Bausteine (%s):", wann);
for (uint8_t addr = 1; addr < 127; addr++) {
if (i2c_master_probe(s_i2c_handle, addr, 50) == ESP_OK) {
esp_rom_printf(" 0x%02X", addr);
}
}
esp_rom_printf("\n");
}
#endif // JC_PANEL_TYPE == WS_PANEL_7H
// Klartext-Meldung auf der seriellen Konsole. Bewusst esp_rom_printf: das laeuft auch bei
// abgeschaltetem Core-Debug-Level und ohne Heap, also genau dann, wenn es darauf ankommt.
#define BSP_STEP(call, what) \
@@ -106,11 +184,33 @@ static void ws7_bridge_write(uint8_t reg, uint8_t value)
{
if (s_bridge_handle == NULL) return;
uint8_t buf[2] = { reg, value };
i2c_master_transmit(s_bridge_handle, buf, sizeof(buf), 100);
esp_err_t err = i2c_master_transmit(s_bridge_handle, buf, sizeof(buf), 100);
if (err != ESP_OK) {
// Frueher lief das stillschweigend ins Leere. Schlaegt es fehl, wird die Bridge
// nie initialisiert und das Bild bleibt schwarz - ohne jeden Hinweis darauf.
esp_rom_printf("[Panel] Bridge-Register 0x%02X liess sich nicht schreiben: %s\n",
reg, esp_err_to_name(err));
}
}
// Bridge am I2C-Bus anmelden und Grundinitialisierung schreiben. Muss VOR dem
// DSI-Bringup laufen; die Hintergrundbeleuchtung bleibt dabei noch aus.
// Grundeinstellung der Bridge. Bewusst mehrfach aufrufbar, siehe unten.
//
// Warum zweimal: Beim ersten Aufruf existiert die DSI-Strecke noch gar nicht - die Bridge
// bekommt ihre Einstellung also, bevor ein Signal anliegt. Beim ersten Einschalten reichte
// das nicht: Die Beleuchtung ging an (die haengt an der Bridge), ein Bild kam aber nicht.
// Erst ein Reset half - und dann nur deshalb, weil die Bridge ihre Einstellung aus dem
// vorigen Durchlauf noch hatte und diesmal ein DSI-Signal vorfand. Genau das holt der
// zweite Aufruf nach, ohne dass ein Reset noetig ist. Die Register sind wiederholbar.
static void ws7_bridge_core_init(const char *wann)
{
esp_rom_printf("[Panel] Bridge-Grundeinstellung %s\n", wann);
ws7_bridge_write(0xC0, 0x01);
ws7_bridge_write(0xC2, 0x01);
ws7_bridge_write(0xAC, 0x01);
}
// Bridge am I2C-Bus anmelden und erstmalig einstellen. Laeuft VOR dem DSI-Bringup;
// die Hintergrundbeleuchtung bleibt dabei noch aus.
static void ws7_bridge_pre_init(void)
{
i2c_device_config_t dev_cfg = {
@@ -122,9 +222,7 @@ static void ws7_bridge_pre_init(void)
s_bridge_handle = NULL;
return;
}
ws7_bridge_write(0xC0, 0x01);
ws7_bridge_write(0xC2, 0x01);
ws7_bridge_write(0xAC, 0x01);
ws7_bridge_core_init("vor dem DSI-Start");
}
void jc_backlight_init(void)
@@ -184,6 +282,63 @@ IRAM_ATTR static bool on_vsync(esp_lcd_panel_handle_t panel,
}
#if JC_PANEL_TYPE == WS_PANEL_7H
// ------------------------------------------------------------------------------------
// Aussetzer des GT9271 ueberbruecken
//
// Der Controller laesst gelegentlich einen Messzyklus aus und liefert ab und zu ein
// unplausibles Paket - die Werks-Testskizze hat solche Pakete ebenfalls verworfen. Der
// Hersteller-Treiber kann "gerade nichts Neues" nicht von "Finger weg" unterscheiden und
// meldet beides als losgelassen. Seit die Oberflaeche doppelt so oft abtastet, faellt das
// auf: Aus einem Tipp wird Druecken-Loslassen-Druecken-Loslassen, der Knopf loest also
// mehrfach aus.
//
// Deshalb liegt hier ein eigener get_xy-Aufsatz vor dem Treiber: Er verwirft unplausible
// Rohwerte und haelt den letzten gueltigen Punkt kurz fest. Losgelassen wird erst
// gemeldet, wenn WS7_TOUCH_HOLD_MS lang wirklich nichts mehr kam.
// ------------------------------------------------------------------------------------
static bool (*s_touch_get_xy_orig)(esp_lcd_touch_handle_t, uint16_t *, uint16_t *,
uint16_t *, uint8_t *, uint8_t) = NULL;
static uint16_t s_touch_last_x = 0;
static uint16_t s_touch_last_y = 0;
static int64_t s_touch_last_us = 0;
static bool s_touch_down = false;
static bool ws7_touch_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
bool got = s_touch_get_xy_orig(tp, x, y, strength, point_num, max_point_num);
int64_t now = esp_timer_get_time();
// Unplausible Rohwerte gelten als Aussetzer, nicht als Beruehrung.
if (got && *point_num > 0 &&
(x[0] > WS7_TOUCH_RAW_X_MAX || y[0] > WS7_TOUCH_RAW_Y_MAX)) {
got = false;
*point_num = 0;
}
if (got && *point_num > 0) {
s_touch_last_x = x[0];
s_touch_last_y = y[0];
s_touch_last_us = now;
s_touch_down = true;
return true;
}
#if WS7_TOUCH_HOLD_MS > 0
if (s_touch_down && (now - s_touch_last_us) < ((int64_t)WS7_TOUCH_HOLD_MS * 1000)) {
x[0] = s_touch_last_x;
y[0] = s_touch_last_y;
if (strength) strength[0] = 0;
*point_num = 1;
return true;
}
#endif
s_touch_down = false;
return false;
}
// Der GT9271 des Waveshare-Panels meldet im Hochformat (Roh-X 0..720, Roh-Y 0..1280),
// das Bild laeuft aber im Querformat 1280x720. Achsentausch (und bei Bedarf Spiegelung)
// deshalb ueber den process_coordinates-Hook, der VOR den swap/mirror-Flags von
@@ -211,6 +366,10 @@ static void ws7_touch_map(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
// Bild wird um 180 Grad gedreht ausgegeben -> Touch muss mitdrehen.
px = (uint16_t)(LCD_H_RES - 1 - px);
py = (uint16_t)(LCD_V_RES - 1 - py);
#endif
#if WS7_TOUCH_DEBUG
esp_rom_printf("[Touch] roh X=%u Y=%u -> Bild X=%u Y=%u\n",
(unsigned)x[i], (unsigned)y[i], (unsigned)px, (unsigned)py);
#endif
x[i] = px;
y[i] = py;
@@ -238,45 +397,43 @@ static void jc_touch_scale(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
}
#endif
// Bringt Panel + Touch + LVGL-Port hoch (LVGL laeuft danach in eigenem Task).
// Rueckgabe false: Panel kam nicht hoch - der Aufrufer darf dann KEINE LVGL-Funktion
// benutzen, sonst folgt ein zweiter Absturz, der die eigentliche Ursache ueberdeckt.
bool jc_board_bringup(void)
{
jc_backlight_init();
i2c_master_bus_config_t i2c_bus_conf = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
.i2c_port = BSP_I2C_NUM,
};
i2c_new_master_bus(&i2c_bus_conf, &s_i2c_handle);
#if JC_PANEL_TYPE == WS_PANEL_7H
// Bridge des Waveshare-Panels vorbereiten (noch ohne Hintergrundbeleuchtung).
ws7_bridge_pre_init();
// Panel wecken. Bewusst mehrfach, siehe unten.
//
// Ein reines DPI-Panel kennt keine reset-Funktion, esp_lcd_panel_reset() wuerde ins Leere
// greifen - deshalb nur die DCS-Befehle.
//
// Warum mehrfach: Verpasst der Panel-Controller diese Befehle, weil er nach dem
// Einschalten noch nicht bereit war, bleibt der Bildschirm dunkel, obwohl der P4 alles
// korrekt gesendet hat und jeder Schritt Erfolg meldet. Die Hintergrundbeleuchtung geht
// trotzdem an, weil sie an der Bridge haengt und nicht am Panel - genau dieses Bild.
// Die Befehle sind wiederholbar, ein zweiter Anlauf nach dem Start der Videoausgabe
// kostet nichts und faengt den Fall ab.
static void ws7_panel_wake(esp_lcd_panel_io_handle_t io, const char *wann)
{
uint8_t zero = 0x00;
uint8_t madctl = WS7_MADCTL;
esp_rom_printf("[Panel] Weckbefehle %s\n", wann);
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x36, &madctl, 1), "Panel-Befehl MADCTL");
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x11, &zero, 1), "Panel-Befehl Sleep Out");
vTaskDelay(pdMS_TO_TICKS(120));
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x29, &zero, 1), "Panel-Befehl Display On");
vTaskDelay(pdMS_TO_TICKS(20));
}
#endif
static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
esp_ldo_channel_config_t ldo_cfg = {
.chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
#if JC_PANEL_TYPE == WS_PANEL_7H
// Baut die komplette DSI-Strecke auf: Bus, Kommandokanal, Bildausgabe, und weckt das
// Panel. Bewusst als eigene Funktion, damit der ganze Vorgang wiederholbar ist.
static bool ws7_dsi_bringup(esp_lcd_dsi_bus_handle_t *out_bus,
esp_lcd_panel_io_handle_t *out_io,
esp_lcd_panel_handle_t *out_panel,
esp_lcd_dpi_panel_config_t *out_dpi_config)
{
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
esp_lcd_panel_io_handle_t io = NULL;
esp_lcd_panel_handle_t disp_panel = NULL;
#if JC_PANEL_TYPE == WS_PANEL_7H
// ---------------- Waveshare 7inch DSI LCD (H) (1280x720) ----------------
// Kein Hersteller-Panel-Treiber: DSI-Bus, DBI-Kommandokanal und DPI-Panel werden
// direkt angelegt. Entscheidend ist die Referenztaktquelle PLL_F20M - mit der
// Standardquelle bleibt das Bild auf ESP32-P4 Rev. 1.3 / ECO2 schwarz.
bsp_report_psram((size_t)BSP_LCD_H_RES * BSP_LCD_V_RES * BSP_LCD_FB_BYTES_PER_PX
* LVGL_PORT_LCD_BUFFER_NUMS);
esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = WS7_DSI_LANES,
@@ -296,7 +453,11 @@ bool jc_board_bringup(void)
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = WS7_DPI_CLOCK_MHZ,
#if WS7_COLOR_BITS == 24
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888,
#else
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
#endif
.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS,
.video_timing = {
.h_size = BSP_LCD_H_RES,
@@ -321,19 +482,142 @@ bool jc_board_bringup(void)
return false;
}
// Panel wecken (MADCTL / Sleep Out / Display On). Ein Reset gibt es hier nicht:
// ein reines DPI-Panel kennt keine reset-Funktion, esp_lcd_panel_reset() wuerde
// ins Leere greifen.
{
uint8_t zero = 0x00;
uint8_t madctl = WS7_MADCTL;
esp_lcd_panel_io_tx_param(io, 0x36, &madctl, 1); // MADCTL
esp_lcd_panel_io_tx_param(io, 0x11, &zero, 1); // Sleep Out
vTaskDelay(pdMS_TO_TICKS(120));
esp_lcd_panel_io_tx_param(io, 0x29, &zero, 1); // Display On
vTaskDelay(pdMS_TO_TICKS(20));
}
ws7_panel_wake(io, "vor dem Start der Videoausgabe");
BSP_STEP(esp_lcd_panel_init(disp_panel), "DPI-Videoausgabe starten");
// Zweiter Anlauf, jetzt bei laufender Videoausgabe: faengt den Fall ab, dass das
// Panel beim ersten Mal noch nicht aufnahmebereit war.
vTaskDelay(pdMS_TO_TICKS(WS7_PANEL_SETTLE_MS));
ws7_bridge_core_init("nach dem Start der Videoausgabe");
ws7_panel_wake(io, "nach dem Start der Videoausgabe");
*out_bus = mipi_dsi_bus;
*out_io = io;
*out_panel = disp_panel;
*out_dpi_config = dpi_config;
return true;
}
#endif
// Bringt Panel + Touch + LVGL-Port hoch (LVGL laeuft danach in eigenem Task).
// Rueckgabe false: Panel kam nicht hoch - der Aufrufer darf dann KEINE LVGL-Funktion
// benutzen, sonst folgt ein zweiter Absturz, der die eigentliche Ursache ueberdeckt.
bool jc_board_bringup(void)
{
#if JC_PANEL_TYPE == WS_PANEL_7H
// Anlaufzeit abwarten, BEVOR Bridge und Panel angesprochen werden.
//
// Das Displaymodul braucht nach dem Anlegen der Versorgung einen Moment, bis Bridge
// und Panel bereit sind. Faengt der P4 sofort an, laeuft der Hochlauf ins Leere: Die
// Bridge nimmt ihre Register nicht an oder das Panel zeigt trotz korrekt gesendeter
// DSI-Daten nichts - beides ohne Fehlermeldung, weil aus Sicht des P4 alles geklappt
// hat. Das Ergebnis war ein Display, das mal ansprang und mal nicht.
//
// Der Werks-Testsketch wartet aus demselben Grund 2 Sekunden am Anfang von setup().
// Dass es mit geoeffnetem seriellen Monitor fast immer klappte, hatte dieselbe
// Ursache: Dessen USB-Anmeldung verzoegert den Start und ersetzte damit die Wartezeit.
vTaskDelay(pdMS_TO_TICKS(WS7_PANEL_WARMUP_MS));
#endif
jc_backlight_init();
#if JC_PANEL_TYPE == WS_PANEL_7H
ws7_i2c_bus_recover(); // haengenden Bus vom letzten Reset befreien
#endif
i2c_master_bus_config_t i2c_bus_conf = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
.i2c_port = BSP_I2C_NUM,
#if JC_PANEL_TYPE == WS_PANEL_7H
// Stoerimpulsfilter und interne Abschlusswiderstaende - beides setzt auch das
// Waveshare-Beispiel. Ohne den Filter brechen Bridge und Touch weg, sobald die
// DSI-Ausgabe laeuft: Deren schnelle Signale stoeren auf das lange Flachbandkabel
// ein, und ungefilterte Stoerspitzen bringen die I2C-Uebertragung aus dem Tritt.
// Der Audio-Baustein auf der Platine bleibt dabei erreichbar, weil er nicht am
// Displaykabel haengt - genau dieses Muster war im Start-Log zu sehen.
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
#endif
};
i2c_new_master_bus(&i2c_bus_conf, &s_i2c_handle);
#if JC_PANEL_TYPE == WS_PANEL_7H
ws7_i2c_scan("vor dem Panel-Start");
// Bridge des Waveshare-Panels vorbereiten (noch ohne Hintergrundbeleuchtung).
ws7_bridge_pre_init();
#endif
// Reset-Grund mitschreiben: 1 = Kaltstart (Strom weg), alles andere ist ein Warmstart.
// Nur beim Warmstart behalten die Bausteine ihren alten Zustand - genau dann traten
// die schwarzen Bilder auf.
esp_rom_printf("[Panel] Startgrund: %d (1 = Kaltstart)\n", (int)esp_reset_reason());
static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
esp_ldo_channel_config_t ldo_cfg = {
.chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
BSP_STEP(esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan), "Stromversorgung MIPI-D-PHY");
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
esp_lcd_panel_io_handle_t io = NULL;
esp_lcd_panel_handle_t disp_panel = NULL;
#if JC_PANEL_TYPE == WS_PANEL_7H
esp_lcd_dpi_panel_config_t dpi_config;
#endif
#if JC_PANEL_TYPE == WS_PANEL_7H
// ---------------- Waveshare 7inch DSI LCD (H) (1280x720) ----------------
// Kein Hersteller-Panel-Treiber: DSI-Bus, DBI-Kommandokanal und DPI-Panel werden
// direkt angelegt. Entscheidend ist die Referenztaktquelle PLL_F20M - mit der
// Standardquelle bleibt das Bild auf ESP32-P4 Rev. 1.3 / ECO2 schwarz.
bsp_report_psram((size_t)BSP_LCD_H_RES * BSP_LCD_V_RES * BSP_LCD_FB_BYTES_PER_PX
* LVGL_PORT_LCD_BUFFER_NUMS);
if (!ws7_dsi_bringup(&mipi_dsi_bus, &io, &disp_panel, &dpi_config)) {
return false;
}
#if WS7_DSI_RESTART
// Die Strecke einmal komplett ab- und wieder aufbauen.
//
// Beim ersten Einschalten kam trotz fehlerfreiem Hochlauf kein Bild; erst ein Reset
// half, und zwar zuverlaessig. Ein Reset unterscheidet sich an dieser Stelle nur in
// einem Punkt von unserem Ablauf: Die DSI-Verbindung geht einmal weg und neu auf,
// waehrend Bridge und Panel bereits versorgt und eingestellt sind. Weder die
// wiederholte Grundeinstellung der Bridge noch die wiederholten Weckbefehle ersetzen
// das. Genau dieser Ab- und Wiederaufbau wird hier nachgeholt - ohne Reset.
esp_rom_printf("[Panel] DSI-Strecke wird neu aufgebaut\n");
esp_lcd_panel_del(disp_panel);
esp_lcd_panel_io_del(io);
esp_lcd_del_dsi_bus(mipi_dsi_bus);
vTaskDelay(pdMS_TO_TICKS(WS7_DSI_RESTART_MS));
if (!ws7_dsi_bringup(&mipi_dsi_bus, &io, &disp_panel, &dpi_config)) {
return false;
}
#endif
#if WS7_TEST_PATTERN
// Prueffarbbalken: Sie entstehen im DSI-Baustein selbst und benutzen weder den
// Bildspeicher noch LVGL. Erscheinen sie, arbeitet die Anzeigekette bis zum Panel und
// der Fehler liegt weiter oben. Bleibt es schwarz, liegt es an DSI, Zeitbasis oder
// Panel.
//
// Der Hochlauf endet hier bewusst: Wuerde LVGL danach starten, schriebe es sofort in
// den Bildspeicher und das Pruefbild waere wieder weg - der Test saehe dann genauso
// aus wie der Fehler, den er finden soll.
jc_backlight_set(100); // Beleuchtung an, sonst ist nichts zu sehen
BSP_STEP(esp_lcd_dpi_panel_set_pattern(disp_panel, MIPI_DSI_PATTERN_BAR_VERTICAL),
"Prueffarbbalken");
esp_rom_printf("[Panel] Pruefbild steht - erwartet werden senkrechte Farbbalken.\n");
esp_rom_printf("[Panel] Hochlauf endet hier (WS7_TEST_PATTERN = 1, keine Oberflaeche).\n");
return false; // ohne LVGL: Anzeige bleibt beim Pruefbild
#endif
#elif JC_PANEL_TYPE == JC_PANEL_70
// ---------------- 7,0" JD9165 (1024x600) ----------------
esp_lcd_dsi_bus_config_t bus_config = JD9165_PANEL_BUS_DSI_2CH_CONFIG();
@@ -423,6 +707,16 @@ bool jc_board_bringup(void)
};
esp_lcd_dpi_panel_register_event_callbacks(disp_panel, &cbs, NULL);
#if JC_PANEL_TYPE == WS_PANEL_7H
// Bridge freigeben und Beleuchtung einschalten, BEVOR der Touch angesprochen wird.
// Der GT9271 antwortet erst danach: Die Freigabe der Bridge (Register 0xAD) versorgt
// offenbar auch ihn. In der Werks-Testskizze steht die Touch-Initialisierung aus
// demselben Grund hinter dem Einschalten der Beleuchtung.
jc_backlight_set(100);
vTaskDelay(pdMS_TO_TICKS(WS7_TOUCH_POWER_MS));
ws7_i2c_scan("nach dem Panel-Start");
#endif
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
// MUSS vorbelegt sein: schlaegt die Touch-Initialisierung fehl, gibt der Treiber einen
// unbrauchbaren Zeiger zurueck. Ohne Vorbelegung landet Muell im LVGL-Port.
@@ -459,11 +753,19 @@ bool jc_board_bringup(void)
esp_rom_printf("[Panel] Touch-Controller nicht gefunden - Anzeige laeuft ohne Bedienung.\n");
tp_handle = NULL;
}
#if JC_PANEL_TYPE == WS_PANEL_7H
if (tp_handle) { // Aussetzer-Filter vor den Treiber haengen
s_touch_get_xy_orig = tp_handle->get_xy;
tp_handle->get_xy = ws7_touch_get_xy;
}
#endif
lvgl_port_interface_t interface =
(dpi_config.flags.use_dma2d) ? LVGL_PORT_INTERFACE_MIPI_DSI_DMA
: LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA;
esp_rom_printf("[Panel] Bringup fertig - LVGL wird gestartet\n");
lvgl_port_init(disp_panel, tp_handle, interface);
esp_rom_printf("[Panel] LVGL laeuft\n");
return true;
}
+1 -1
View File
@@ -126,7 +126,7 @@
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.6.3" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
#define DISPLAY_FW_VERSION "1.6.10" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// -------------------------------------------------------------------------------------
// Protokoll
+3 -1
View File
@@ -10,12 +10,14 @@
#include "display_hal.h"
#include "ui.h"
#include "config.h"
#include "pins_config.h"
// LVGL muss mit derselben Farbtiefe uebersetzt sein wie das Panel erwartet. Zieht der
// Build versehentlich eine fremde lv_conf.h (z.B. eine alte Datei neben dem lvgl-Ordner),
// faellt das hier auf - sonst erst am schwarzen oder farbverfaelschten Bild.
#if JC_PANEL_TYPE == WS_PANEL_7H
static_assert(LV_COLOR_DEPTH == 24, "Waveshare 7\" (H) braucht RGB888 - falsche lv_conf.h?");
static_assert(LV_COLOR_DEPTH == WS7_COLOR_BITS,
"Farbtiefe passt nicht zu WS7_COLOR_BITS - falsche lv_conf.h?");
#else
static_assert(LV_COLOR_DEPTH == 16, "JC-Panels brauchen RGB565 - falsche lv_conf.h?");
#endif
+39 -10
View File
@@ -26,18 +26,18 @@
COLOR SETTINGS
*====================*/
/* Panel-Typ des Projekts. Liegt im selben Ordner wie diese Datei, ein Include mit
* Anfuehrungszeichen findet also immer genau diese config.h - auch dann, wenn die
* LVGL-Bibliothek uebersetzt wird. Nur so bekommen Sketch UND Bibliothek dieselbe
* Farbtiefe; ein Compiler-Flag allein wuerde die Bibliothek nicht erreichen. */
#include "config.h"
/* Panel- und Tempo-Einstellungen des Projekts. Die Datei liegt im selben Ordner wie
* diese hier, ein Include mit Anfuehrungszeichen findet also immer genau sie - auch
* dann, wenn die LVGL-Bibliothek uebersetzt wird. Nur so bekommen Sketch UND Bibliothek
* dieselben Werte; ein Compiler-Flag allein wuerde die Bibliothek nicht erreichen. */
#include "pins_config.h"
/*Color depth: 1 (I1), 8 (L8), 16 (RGB565), 24 (RGB888), 32 (XRGB8888)*/
/* Waveshare 7inch DSI LCD (H): Die Bridge des Panels nimmt ausschliesslich RGB888
* entgegen, deshalb muss auch LVGL in 24 Bit rechnen. Die JC-Panels laufen wie
* bisher mit RGB565. */
/* Waveshare 7inch DSI LCD (H): Farbtiefe kommt aus WS7_COLOR_BITS (pins_config.h) -
* 16 Bit halbiert die Datenmenge je Bild, 24 Bit bringt feinere Verlaeufe.
* Die JC-Panels laufen unveraendert mit RGB565. */
#if JC_PANEL_TYPE == WS_PANEL_7H
#define LV_COLOR_DEPTH 24
#define LV_COLOR_DEPTH WS7_COLOR_BITS
#else
#define LV_COLOR_DEPTH 16
#endif
@@ -91,7 +91,17 @@
*====================*/
/*Default display refresh, input device read and animation step period.*/
/* Der Wert bestimmt nicht nur die Bildrate, sondern auch, wie oft der Touch abgetastet
* und wie fein eine Animation gerechnet wird. 33 ms sind 30 Schritte je Sekunde - beim
* Ziehen mit dem Finger deutlich als Stocken zu sehen. Das Waveshare-Panel laeuft mit
* 60 Hz, deshalb dort 16 ms; mehr als das Panel zeigen kann, bringt nichts. Kommt das
* Zeichnen nicht hinterher, wird es einfach seltener fertig - kaputt geht dabei nichts.
* Die JC-Panels bleiben bei 33 ms, damit sich ihr Verhalten nicht aendert. */
#if JC_PANEL_TYPE == WS_PANEL_7H
#define LV_DEF_REFR_PERIOD 16 /*[ms]*/
#else
#define LV_DEF_REFR_PERIOD 33 /*[ms]*/
#endif
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
*(Not so important, you can adjust it to modify default sizes and spaces)*/
@@ -109,7 +119,14 @@
* - LV_OS_WINDOWS
* - LV_OS_MQX
* - LV_OS_CUSTOM */
/* Nur mit einem Betriebssystem kann LVGL auf mehreren Kernen zeichnen. Beim
* Waveshare-Panel wird das gebraucht (WS7_PARALLEL_RENDER), sonst bleibt es aus -
* die JC-Panels sollen sich nicht aendern. */
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_PARALLEL_RENDER
#define LV_USE_OS LV_OS_FREERTOS
#else
#define LV_USE_OS LV_OS_NONE
#endif
#if LV_USE_OS == LV_OS_CUSTOM
#define LV_OS_CUSTOM_INCLUDE <stdint.h>
@@ -175,7 +192,14 @@
/* Set the number of draw unit.
* > 1 requires an operating system enabled in `LV_USE_OS`
* > 1 means multiple threads will render the screen in parallel */
/* Der ESP32-P4 hat zwei Kerne. Beim grossen Waveshare-Panel lohnt es sich, beide
* zum Zeichnen zu benutzen; jeder Zeichen-Thread kostet einen eigenen Stack im
* knappen internen RAM. */
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_PARALLEL_RENDER
#define LV_DRAW_SW_DRAW_UNIT_CNT 2
#else
#define LV_DRAW_SW_DRAW_UNIT_CNT 1
#endif
/* Use Arm-2D to accelerate the sw render */
#define LV_USE_DRAW_ARM2D_SYNC 0
@@ -891,14 +915,19 @@
#define LV_USE_SNAPSHOT 0
/*1: Enable system monitor component*/
/* Nur fuer die Messeinblendung des Waveshare-Panels, siehe WS7_PERF_MONITOR. */
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_PERF_MONITOR
#define LV_USE_SYSMON 1
#else
#define LV_USE_SYSMON 0
#endif
#if LV_USE_SYSMON
/*Get the idle percentage. E.g. uint32_t my_get_idle(void);*/
#define LV_SYSMON_GET_IDLE lv_timer_get_idle
/*1: Show CPU usage and FPS count
* Requires `LV_USE_SYSMON = 1`*/
#define LV_USE_PERF_MONITOR 0
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
+82
View File
@@ -10,6 +10,31 @@
// -------------------------------------------------------------------------------------
#define WS7_ROTATE_180 0
// -------------------------------------------------------------------------------------
// Waveshare 7" (H): Tempo-Schalter
//
// 1280x720 ist gegenueber den JC-Panels ein Vielfaches an Pixeln. Die drei Schalter
// senken die Last und lassen sich einzeln zurueckstellen, falls einer Aerger macht.
//
// WS7_COLOR_BITS 24 = RGB888. GEPRUEFT: Die Bridge dieses Panels nimmt KEIN RGB565
// an - mit 16 kommt kein brauchbares Bild. Einstellbar bleibt es
// nur, weil andere Waveshare-Panels an derselben Firmware
// haengen koennten.
// WS7_PARTIAL_REFRESH 1 = nur geaenderte Bildbereiche neu zeichnen statt jedes Mal des
// gesamten Bildes (LVGL-Direct-Mode, zwei wechselnde Puffer).
// Groesster Tempogewinn, besonders waehrend eines Bezugs: Die
// Zeitanzeige laeuft zehnmal je Sekunde weiter, und mit 0 wuerde
// dafuer jedes Mal das ganze Bild neu gezeichnet. Am Geraet
// geprueft, kein Flackern - am 4,3-Zoll-Panel hatte derselbe
// Modus dagegen geflackert.
// WS7_PARALLEL_RENDER 1 = LVGL zeichnet mit zwei Threads auf beiden Prozessorkernen.
// Kostet zwei zusaetzliche Task-Stacks im knappen internen RAM;
// startet der P4 nicht mehr, hier zuerst auf 0 stellen.
// -------------------------------------------------------------------------------------
#define WS7_COLOR_BITS 24
#define WS7_PARTIAL_REFRESH 1
#define WS7_PARALLEL_RENDER 1
#define EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS 500 //range 2 to 2000
#define EXAMPLE_LVGL_PORT_TASK_MIN_DELAY_MS 5 //range 1 to 100
#define EXAMPLE_LVGL_PORT_TASK_PRIORITY 4
@@ -24,7 +49,13 @@
// Bild, daher zeigen statische Flaechen (z.B. Tab-Leiste) nie alten Bufferinhalt.
// (Mode 3 = Direct-Mode flackert bei nie neu gezeichneten Bereichen.)
// Bei Rotation 90 nutzen beide Modi ohnehin 3 Framebuffer -> kein Mehrverbrauch.
// Beim Waveshare-Panel waere Vollbild-Neuzeichnen bei 1280x720 zu teuer -> Mode 3
// (Direct-Mode, zwei Puffer, nur geaenderte Bereiche), siehe WS7_PARTIAL_REFRESH.
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_PARTIAL_REFRESH
#define EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE 3 //range 1 to 3
#else
#define EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE 2 //range 1 to 3
#endif
#if JC_PANEL_TYPE == WS_PANEL_7H
// Waveshare 7" (H): Panel ist nativ 1280x720 Querformat. Bei Einbau auf dem Kopf wird um
@@ -95,6 +126,57 @@
#define WS7_TOUCH_MIRROR_RAW_X 1
#define WS7_TOUCH_MIRROR_RAW_Y 0
// Zeigt unten rechts Bilder je Sekunde und Prozessorlast ein. Nur zum Messen gedacht:
// Der Wert sagt, ob sich weitere Muehe beim Zeichnen ueberhaupt lohnt.
#define WS7_PERF_MONITOR 0
// Zum Einmessen des Touch: 1 gibt jeden Beruehrpunkt roh und umgerechnet auf der
// seriellen Konsole aus. Danach wieder auf 0, sonst laeuft das Log dauernd mit.
#define WS7_TOUCH_DEBUG 0
// So lange gilt eine Beruehrung nach dem letzten gueltigen Messwert noch als anliegend.
// Ueberbrueckt Aussetzer des GT9271, die sonst als Loslassen durchgehen und einen Tipp in
// mehrere Klicks zerlegen. 0 schaltet die Ueberbrueckung ab. Nicht zu gross waehlen: Der
// Wert verzoegert das Loslassen und daempft dadurch den Schwung beim Wischen.
#define WS7_TOUCH_HOLD_MS 40
// Wartezeit zwischen dem Freigeben der Bridge und dem Ansprechen des Touch. Der GT9271
// haengt an derselben Versorgung und braucht nach dem Einschalten einen Moment, bis er
// auf I2C antwortet. Meldet sich 0x14 beim Start nicht, diesen Wert erhoehen.
#define WS7_TOUCH_POWER_MS 120
// Prueffarbbalken statt Oberflaeche anzeigen. Sie entstehen im DSI-Baustein selbst und
// benutzen weder Bildspeicher noch LVGL. Damit laesst sich trennen, ob die Anzeigekette
// bis zum Panel arbeitet (Balken sichtbar) oder nicht (schwarz). Nur zur Fehlersuche.
// Anlaufzeit des Displaymoduls, bevor Bridge und Panel angesprochen werden. Ohne diese
// Pause startet die Anzeige nur zufaellig - der Werks-Testsketch wartet aus demselben
// Grund 2 Sekunden. Springt das Display beim Einschalten nicht zuverlaessig an, ist das
// der erste Wert zum Erhoehen; laeuft es sicher, kann er vorsichtig gesenkt werden.
#define WS7_PANEL_WARMUP_MS 2000
// Pause zwischen dem Start der Videoausgabe und dem zweiten Anlauf der Weckbefehle.
// Verpasst das Panel den ersten Anlauf, bleibt der Bildschirm dunkel, waehrend die
// Beleuchtung brennt - sie haengt an der Bridge, nicht am Panel.
#define WS7_PANEL_SETTLE_MS 200
// Die DSI-Strecke beim Hochlauf einmal ab- und wieder aufbauen. Beim ersten Einschalten
// kam sonst trotz fehlerfreiem Ablauf kein Bild; erst ein Reset half. Der einzige
// Unterschied dabei: Die DSI-Verbindung geht einmal weg und neu auf, waehrend Bridge und
// Panel schon versorgt und eingestellt sind. Genau das holt der zweite Aufbau nach.
// Kostet WS7_DSI_RESTART_MS zusaetzliche Startzeit.
#define WS7_DSI_RESTART 0
#define WS7_DSI_RESTART_MS 200
// Nach einem Kaltstart einmal selbst neu starten, sobald der erste Hochlauf durch ist.
// Das Panel kommt beim ersten Anlauf nach dem Einschalten nicht zuverlaessig hoch;
// ein echter Chip-Reset behebt es zuverlaessig, der Ab- und Wiederaufbau der
// DSI-Strecke im Betrieb dagegen nicht. Kostet rund drei Sekunden beim Einschalten.
// Eine Schleife ist ausgeschlossen: Beim zweiten Durchlauf ist der Startgrund kein
// Kaltstart mehr.
#define WS7_COLD_BOOT_RESTART 1
#define WS7_TEST_PATTERN 0
#elif JC_PANEL_TYPE == JC_PANEL_70
#define LCD_H_RES 1024
+10 -1
View File
@@ -453,7 +453,11 @@ static lv_obj_t* make_page(lv_obj_t* parent) {
lv_obj_t* p = lv_obj_create(parent);
lv_obj_set_size(p, LV_PCT(100), LV_PCT(100));
lv_obj_set_pos(p, 0, 0);
lv_obj_set_style_bg_opa(p, LV_OPA_TRANSP, 0);
// Deckend in der Hintergrundfarbe statt durchsichtig. Sieht identisch aus - die Ebenen
// darunter haben dieselbe Farbe -, erspart LVGL beim Scrollen aber, sie mitzuzeichnen
// und durchzumischen.
lv_obj_set_style_bg_color(p, COL_BG, 0);
lv_obj_set_style_bg_opa(p, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(p, 0, 0);
lv_obj_set_style_pad_all(p, 8, 0);
lv_obj_set_style_pad_row(p, 6, 0);
@@ -3171,6 +3175,11 @@ void ui_init(ProtocolClient* client) {
lv_obj_t* burger = th_button(header, LV_SYMBOL_LIST, COL_CARD2, COL_TEXT);
lv_obj_set_size(burger, 48, 36);
// Der Knopf bleibt optisch klein, faengt Tipps aber auch knapp daneben ab. 48x36 Pixel
// sind auf dem 7-Zoll-Panel ein sehr kleines Ziel; gemessen wurde ein Fehlgriff von
// rund 10 Pixeln. Die Vergroesserung wirkt nur innerhalb der Kopfleiste - darunter
// beschneidet sie die Leiste selbst, sodass dem Seiteninhalt nichts weggenommen wird.
lv_obj_set_ext_click_area(burger, 20);
lv_obj_add_event_cb(burger, hamburger_cb, LV_EVENT_CLICKED, nullptr);
g_hdrTitle = lv_label_create(header);