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
Touch-Display-Firmware (ESP32-P4)
Client-Firmware für die Touch-Displays, die als UART-Bedienung für die Dual-PID-Siebträgersteuerung dienen:
- Guition JC4880P443C-I-W (4,3", ST7701, 480×800)
- Guition JC1060P470C-I-W (7,0", JD9165, 1024×600)
- Waveshare ESP32-P4-Pico + Waveshare 7inch DSI LCD (H) (1280×720)
Alle drei teilen sich denselben Sketch; das Zielpanel wird über JC_PANEL_TYPE in
config.h gewählt.
Die Hauptplatine (ESP32-S3, Dual_PID_FastHeatUp.ino) bleibt die Steuerzentrale
inkl. WLAN/Webinterface. Dieses Display ist ein reiner UART-Client: es empfängt den
Zustand und sendet Bedienkommandos. Protokoll: siehe
../Doku/P4-Display_UART-Protokoll.md.
Panel-Auswahl
In config.h:
#define JC_PANEL_TYPE WS_PANEL_7H // Waveshare P4-Pico + 7inch DSI LCD (H) (Vorgabe)
#define JC_PANEL_TYPE JC_PANEL_43 // 4,3" JC4880P443C-I-W
#define JC_PANEL_TYPE JC_PANEL_70 // 7,0" JC1060P470C-I-W
Der Schalter steuert Panel-Treiber, Auflösung, Rotation, Reset-/Backlight-Ansteuerung, die Touch-Umrechnung und die Farbtiefe von LVGL; UI und Protokoll bleiben identisch.
Nur hier umschalten, nicht per Compiler-Flag.
lv_conf.hliestJC_PANEL_TYPEausconfig.h, weil das Waveshare-Panel RGB888 verlangt und LVGL deshalb mit 24 statt 16 Bit übersetzt werden muss. Ein-D-Flag der IDE erreicht die LVGL-Bibliothek nicht — Sketch und Bibliothek liefen dann mit verschiedenen Farbtiefen.display_hal.cppbricht den Build mit einerstatic_assertab, falls das doch passiert.
Hardware
Vendor-Pakete: 4,3" im Ordner JC4880P443C_I_W/, 7,0" im Repo-Ordner
../JC_Display_Firmware_7zoll/.
| Komponente | 4,3" JC4880P443C-I-W | 7,0" JC1060P470C-I-W | Waveshare P4-Pico + 7" DSI (H) |
|---|---|---|---|
| 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 — 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 × 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 |
Waveshare 7" (H) — Besonderheiten des Bringups
Das Panel hängt hinter einer Waveshare-Bridge und braucht keinen Hersteller-Panel-Treiber;
board_bringup.c legt DSI-Bus, DBI-Kommandokanal und DPI-Panel direkt an:
- Referenztakt der D-PHY =
PLL_F20M(Legacy). Mit der Standardquelle bleibt das Bild auf ESP32-P4 Rev. 1.3 / ECO2 schwarz. - 2 Lanes à 1250 Mbit/s, DPI-Takt 80 MHz, Austastlücken 64/64/64 (≈ 60 Hz).
Alle Werte stehen in
pins_config.h(WS7_*) und lassen sich dort ändern, falls das Bild reißt oder flimmert — 1280×720 in RGB888 belastet den PSRAM spürbar. - Reihenfolge: Bridge-Grundinitialisierung (
C0/C2/AC) → DSI → DCSMADCTL/Sleep Out/Display On→ DPI-Start → erst dann Hintergrundbeleuchtung. Wird das Licht früher eingeschaltet, blitzt beim Start Bildrauschen auf. - Einbaulage:
WS7_ROTATE_180inpins_config.hdreht Bild und Touch um 180°, falls das Panel auf dem Kopf montiert wird. Vorgabe ist 0 — in der jetzigen Einbaulage steht das Bild richtig herum. Die Drehung macht der PPA-Grafikbeschleuniger; die Software-Rotation des LVGL-Ports beherrscht nur RGB565. - Touch-Spiegelung: Der Digitizer sitzt gegenüber dem Bild um die Hochachse gedreht.
WS7_TOUCH_MIRROR_RAW_Xsteht 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 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_REFRESHnutzbar würde, müsste der LVGL-Port die geänderten Bereiche zwischen beiden Puffern abgleichen. Die Maschinerie dafür liegt inlvgl_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.
Verkabelung zur Hauptplatine (UART, gekreuzt)
Für beide JC-Panels dieselben P4-Pins (4,3": Header JP1, Schaltplan
JC4880P443C_I_W/5-Schematic/4_USB&IO.png · 7,0": FPC-Buchse FPC4):
S3 GPIO43 (TX) ----> P4 GPIO31 (RX) (DISPLAY_UART_RX_PIN)
S3 GPIO44 (RX) <---- P4 GPIO33 (TX) (DISPLAY_UART_TX_PIN)
S3 GND ------ P4 GND
Waveshare ESP32-P4-Pico (40-poliger Header):
S3 GPIO43 (TX) ----> P4 GPIO21 (RX) (DISPLAY_UART_RX_PIN)
S3 GPIO44 (RX) <---- P4 GPIO20 (TX) (DISPLAY_UART_TX_PIN)
S3 GND ------ P4 GND
Die rechte JP1-Spalte (
C6_U01TXD/C6_I10RXD/…) gehört zum ESP32-C6 — nicht verwenden. 3,3-V-Logik beidseitig → keine Pegelwandlung nötig.
Projektstruktur (eigener Code)
| Datei | Zweck |
|---|---|
P4_Display_Firmware.ino |
setup()/loop(), verdrahtet die Module |
config.h |
Pins, UART, Protokoll-Parameter |
machine_state.h |
Spiegel des state-Objekts der S3 |
protocol_client.h/.cpp |
UART-Protokoll: hello/state/ack/profiles/… + Heartbeat |
ui.h/.cpp |
LVGL-Dashboard (hardwareunabhängig) |
display_hal.h/.cpp |
LVGL-/UI-Verdrahtung + Lock-Wrapper |
board_bringup.c |
esp_lcd-Hardware-Bringup (C; nur bei JC_USE_REAL_PANEL=1) |
Abhängigkeiten
- Arduino-ESP32 ≥ 3.1 (mit ESP32-P4-Support), Board: ESP32P4 Dev Module, PSRAM aktiviert.
- LVGL 9.x. Die
lv_conf.him Sketch-Ordner ist die maßgebliche; sie wird über den Include-Pfad des Sketches gefunden und leitetLV_COLOR_DEPTHausJC_PANEL_TYPEab. Liegt zusätzlich eine altelv_conf.hneben demlvgl-Ordner der Bibliotheken, wird sie nicht benutzt — sie darf dort aber auch nicht stören. - ArduinoJson 7.x.
Zwei Betriebsmodi (JC_USE_REAL_PANEL in display_hal.h)
0 = Stub-Modus (Standard)
Kompiliert ohne Display-Treiber (eigenes Minimal-LVGL, rendert ins Leere). Ideal, um
Protokoll/UART/Logik am Schreibtisch zu testen (Debug über USB-CDC Serial). Läuft auf
jedem ESP32 mit LVGL+ArduinoJson.
1 = echte Hardware (Hersteller-BSP)
display_hal.cpp baut auf der erprobten Hersteller-Portierung auf. Dazu folgende
Dateien aus JC4880P443C_I_W/1-Demo/arduino_examples/lvgl_v9_sw_rotation/ in diesen
Sketch-Ordner kopieren:
pins_config.h
lvgl_port_v9.h
lvgl_port_v9.c
src/lcd/ (esp_lcd_st7701*.c/.h + st7701_lcd.* sowie esp_lcd_jd9165.c/.h)
src/touch/ (komplett: esp_lcd_touch*.c/.h, gt911_touch.*)
Diese Dateien liegen bereits im Sketch. esp_lcd_jd9165.* stammt aus dem
7-Zoll-Vendor-Paket
(../JC_Display_Firmware_7zoll/1-Demo/Demo_Arduino/1_2_Lvgl_V9/.../src/lcd/); je nach
JC_PANEL_TYPE wird nur einer der beiden Panel-Treiber eingebunden.
Der Init-Ablauf (MIPI-DSI-PHY-LDO, ST7701 bzw. JD9165, GT911, LEDC-Backlight,
lvgl_port_init) ist 1:1 aus dem Vendor-Beispiel lvgl_sw_rotation.c übernommen — nur
der Demo-Aufruf (lv_demo_widgets()) wurde durch ui_init() ersetzt.
Die Touch-Skalierung des 7-Zöllers hängt in board_bringup.c am
process_coordinates-Hook von esp_lcd_touch, damit die Vendor-Datei
esp_lcd_touch_gt911.c unverändert bleibt (im Vendor-Demo ist die Umrechnung direkt in
den Treiber gepatcht).
Wichtig: Im realen Modus läuft LVGL in einem eigenen FreeRTOS-Task. UI-Zugriffe aus
der Arduino-loop() (z. B. ui_update bei jedem state-Push) sind daher in
hal_lock()/hal_unlock() (= lvgl_port_lock/unlock) gekapselt — ist in der .ino
bereits umgesetzt.
Ausrichtung
Das Dashboard ist Querformat. Beim 4,3-Zöller (physisch 480×800) wird das per
LVGL-Rotation 270° mit PPA-Beschleunigung erreicht → 800×480. Der 7-Zöller ist nativ
1024×600, dort steht die Rotation auf 0 und PPA ist abgeschaltet. Beides setzt
pins_config.h automatisch anhand von JC_PANEL_TYPE.
Das Layout arbeitet fast durchgehend mit LV_PCT/LV_SIZE_CONTENT und skaliert
deshalb mit. Feste Pixelmaße (Rundinstrumente, Thermometer, Buttons) bleiben gleich groß
— auf dem 7-Zöller entsteht dadurch mehr Luft. Die Schriftgrößen bleiben ebenfalls
unverändert: 4,3" ≈ 213 dpi, 7,0" ≈ 170 dpi, gleiche Pixelhöhe wirkt dort physisch
größer.
Status
Fertig & hardwareunabhängig:
- Vollständiger UART-Protokoll-Client (v2): hello-Handshake, state-Parsing, ack/error, Profile, Heartbeat, Link-Timeout.
- Dashboard: Live-Temperaturen Wasser/Dampf (ist/soll), Duty-Balken, Status-Banner
(Farbe nach
statusKey), Verbindungs- & WLAN-Anzeige, Bezugs-Timer, Bedien-Buttons. - Senden für
setPid,startAutotune/stopAutotune,getProfileDetailsvorhanden.
Board-Bringup: auf die konkreten JC-Pins/Treiber gesetzt (ST7701 bzw. JD9165, GT911,
Backlight, I²C) — sobald die BSP-Dateien kopiert und JC_USE_REAL_PANEL=1 gesetzt ist,
lauffähig.
Noch offen (UI-Ausbau): weitere Screens — Einstellungen (Brew/Service/Sensor), Profil-Auswahl/-Editor, PID/AutoTune-Bedienung.
Inbetriebnahme / Fehlersuche (Lessons learned)
- Stromversorgung: Jedes Board eigene Versorgung. Zwischen P4 und S3 nur GND + TX + RX — kein 3V3/5V! Den S3 nicht über den 3V3-Header des Displays speisen (reicht nicht / Regler-Rückspeisung).
- Pins nach Silkscreen: Die UART-Pads am Board nach Aufdruck
GPIO50/GPIO51identifizieren, nicht nach Header-Pin-Nummern. - S3-Seite: muss mit
TOUCH_UART_ENABLED 1geflasht sein und laufen. DaSerial(Debug) und der Touch-UART beide auf GPIO43/44 liegen, ggf. „USB CDC On Boot: Enabled" verwenden, damit der Debug-Port nicht die UART-Pins blockiert.
Diagnose-Schalter (in config.h)
SHOW_DEBUG_OVERLAY 1→ blendet unten ein grünes Panel ein: gesendetehello, empfangene Bytes/Zeilen, Parse-Fehler, letzte Rohzeile, Link-Status.UART_SELFTEST_INTERNAL_LOOPBACK 1→ legt TX chip-intern auf RX (ohne Draht/Pins). Läuft „RX byte" hoch, sind Firmware+UART ok → Problem liegt an Pins/Verkabelung. Nach dem Test wieder auf 0!