Compare commits

..
Author SHA1 Message Date
raw-designsandClaude Opus 5 326af4288a perf(P4): Auch feste Höhen - LVGL markiert sonst das Layout als dirty (1.9.5)
Die festen Breiten aus 1.9.4 haben nicht gereicht. lv_label_set_text ruft
lv_obj_refresh_self_size(); ist eines der beiden Maße LV_SIZE_CONTENT, gilt
das Parent-Layout als dirty und der Container wird komplett invalidiert.

- tempField(): big und setp bekommen zusätzlich eine feste Höhe aus
  lv_font_get_line_height().
- infoCell(): val ebenso, mit Platz für zwei Zeilen.
- g_clock: feste Breite/Höhe, rechtsbündig. Erklärt den Bereich
  "x 6..57, y 2..41" (Menü-Knopf), der bei jedem Minutenwechsel durch das
  Header-Relayout mit invalidiert wurde.

Zusätzlich eine gezielte Diagnose: LV_EVENT_INVALIDATE_AREA auf g_content
und der Dashboard-Seite meldet, wenn ein ganzer Container invalidiert wird -
damit ist "einzelnes Label" von "kompletter Bereich" zu unterscheiden.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 03:10:40 +02:00
thomas 2e8651bc00 Merge pull request 'perf(P4): Feste Breiten für Anzeigewerte - kein Neulayout mehr (1.9.4)' (#73) from perf/feste-breiten-kein-neulayout into main 2026-09-03 02:56:24 +02:00
raw-designsandClaude Opus 5 b13e5fdaf1 perf(P4): Feste Breiten für Anzeigewerte - kein Neulayout mehr (1.9.4)
Die Koordinaten haben den Verursacher benannt: Der invalidierte Bereich war
"x 0..1279, y 44..719" - der komplette Content-Container, bei jeder
Zustandsmeldung.

Grund: Die Wert-Labels hatten LV_SIZE_CONTENT. Ändert sich ihre Breite mit
dem Inhalt (92,4 °C -> 100,1 °C), löst LVGL ein Flex-Relayout der ganzen
Seite aus und invalidiert sie komplett. Change-checked Text allein reicht
also nicht - der Text darf beim Ändern auch die Breite nicht ändern.

tempField() und infoCell() setzen die Wert-Labels jetzt auf LV_PCT(100) mit
zentriertem Text. Optisch identisch, da die Werte ohnehin mittig standen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 02:56:05 +02:00
thomas 671a6696ce Merge pull request 'fix(P4): Menüvorhang wieder durchscheinend, weitere Invalidierung entfernt (1.9.3)' (#72) from fix/menuevorhang-und-weitere-invalidierung into main 2026-09-03 02:36:48 +02:00
raw-designsandClaude Opus 5 288f61447d fix(P4): Menüvorhang wieder durchscheinend, weitere Invalidierung entfernt (1.9.3)
- Regression aus 1.8.7: Der deckende Drawer-Scrim verdeckte den
  Seiteninhalt, der dahinter sichtbar bleiben soll. Zurück auf LV_OPA_50.
- ready_band_update() setzte Text, Textfarbe und Hintergrundfarbe des
  Zustandsbands bei jeder Zustandsmeldung neu. Das Band spannt sich über die
  volle Breite - allein dadurch wurde ein Streifen über den ganzen Bildschirm
  ungültig. Jetzt über die change-checked Helfer.
- temp_progress_set() setzte Balkenwert und -farbe unbedingt; jetzt nur bei
  echter Änderung.
- Das WLAN-Symbol ebenso.
- Die Messung gibt zusätzlich die Koordinaten der invalidierten Bereiche aus.
  Die Fläche allein sagt nicht, welcher Teil der UI den Redraw auslöst.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 02:36:31 +02:00
thomas a91c792491 Merge pull request 'perf(P4): Nur bei echter Änderung neu zeichnen (1.9.2)' (#71) from perf/nur-bei-aenderung-neu-zeichnen into main 2026-09-03 02:17:20 +02:00
raw-designsandClaude Opus 5 e32c654b8d perf(P4): Nur bei echter Änderung neu zeichnen (1.9.2)
Die erweiterte Messung war eindeutig: "1 Bereich, 100 % der Fläche" - LVGL
zeichnete bei jeder Zustandsmeldung den kompletten Bildschirm neu, also
einmal pro Sekunde 240-300 ms lang. Kein Rendering-Tempoproblem, sondern
massiv zu viel Invalidierung.

lv_label_set_text invalidiert den Label-Bereich unbedingt, auch bei
identischem Text; lv_obj_set_style_text_color ebenso. Auf dem Dashboard
wurden pro Sekunde Werte quer über den Bildschirm gesetzt (Uhr oben,
Temperaturen Mitte, Bezugswerte unten) - LVGL joint die Bereiche, und die
Bounding-Box ist die ganze Fläche.

- Neue Helfer label_fmt_if_changed() und label_color_if_changed() neben dem
  vorhandenen label_set_if_changed().
- Alle 13 zyklischen Textzuweisungen und alle 10 Farbzuweisungen in
  ui_update darauf umgestellt.

Da sich Temperaturen selten um ein Zehntelgrad ändern und Sollwerte
praktisch nie, entfallen die meisten Neuzeichnungen ganz.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 02:17:03 +02:00
thomas 484812cb4c Merge pull request 'diag(P4): Gezeichnete Fläche messen, direktes Zeichnen zurücknehmen (1.9.1)' (#70) from diag/gezeichnete-flaeche-messen into main 2026-09-03 01:42:21 +02:00
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
raw-designsandClaude Opus 5 33ce2b3ab9 diag(P4): Bildaufbau messen, Dialogknöpfe skalieren (1.8.8)
Der Aufweckdialog braucht weiterhin ~5 s. Zwei Erklärungsversuche lagen
daneben (Fade-Animationen, transparenter Scrim), also wird ab jetzt
gemessen statt geraten.

- refr_monitor_install() hängt sich über LV_EVENT_REFR_START/READY an die
  Anzeige und meldet jeden Zeichenzyklus, der 200 ms überschreitet. Im
  Normalbetrieb bleibt die Konsole still.
- Der Aufweckdialog berücksichtigt die Schriftgröße jetzt vollständig:
  Knöpfe mit font_text() statt geerbter Grundschrift, Breite 150/210/280 je
  Stufe. Bisher wirkten sie neben dem größeren Text verloren.
- Die große Standby-Uhr bleibt bewusst fest: Sie ist auf die Bildschirmhöhe
  abgestimmt.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 01:01:19 +02:00
thomas d4d4f2b9d2 Merge pull request 'fix(P4): Aufweckdialog sofort statt nach fünf Sekunden (1.8.7)' (#67) from fix/vorhaenge-deckend-und-dialogbreite into main 2026-09-03 00:47:35 +02:00
raw-designsandClaude Opus 5 9fef4f8a6e fix(P4): Aufweckdialog sofort statt nach fünf Sekunden (1.8.7)
Vom Fingertipp auf die Standby-Uhr bis zum Dialog vergingen ~5 s mit
schwarzem Bildschirm. Ursache war der Scrim mit LV_OPA_70 über den ganzen
Bildschirm: Ein durchscheinender Vollbild-Layer zwingt LVGL zu einem
Zwischenpuffer und zum Mischen der gesamten Fläche - bei 1280x720 in 24 Bit
die teuerste Operation überhaupt.

- Wake-Scrim auf WS_PANEL_7H jetzt LV_OPA_COVER. Verloren geht nichts: Im
  Standby liegt dahinter ohnehin nur Schwarz.
- Der Drawer-Scrim (LV_OPA_50) hat dasselbe Problem und ist ebenfalls
  deckend, in 0x0d0d0d - auf dunklem Grund optisch praktisch identisch.
- Die JC-Panels behalten in beiden Fällen die Transparenz.
- Die Dialogkarte wächst mit der Schriftgröße (420 / 640 / 900 px), damit
  der Text nicht an unglücklichen Stellen umbricht.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:47:16 +02:00
thomas 0f5b681421 Merge pull request 'feat(P4): Uhr läuft lokal weiter, wenn die S3 keine Zeit liefert (1.8.6)' (#66) from feat/uhr-laeuft-lokal-weiter into main 2026-09-03 00:34:07 +02:00
raw-designsandClaude Opus 5 01d960258a feat(P4): Uhr läuft lokal weiter, wenn die S3 keine Zeit liefert (1.8.6)
Die Uhrzeit kommt von der Hauptplatine und ist leer, solange dort keine
Zeitquelle synchronisiert ist. Bei einem erneuten Abgleich ist sie kurz weg,
und die Uhr verschwand für ein paar Sekunden - im Standby unruhig.

Der P4 merkt sich jetzt die zuletzt empfangene Zeit (Minuten seit
Mitternacht plus lv_tick zum Empfangszeitpunkt) und rechnet zwischen den
Meldungen selbst weiter. Sobald die S3 wieder eine Zeit schickt, gilt deren
Wert - der P4 stellt nichts richtig und ersetzt keine Zeitquelle, er
überbrückt nur die Lücke. Ohne je empfangene Zeit bleibt die Anzeige leer
wie bisher.

Gilt für die Kopfleisten-Uhr und die große Standby-Uhr; beide setzen den
Text jetzt über label_set_if_changed, sparen also unnötige Neuzeichnungen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:33:50 +02:00
thomas 3c333f0280 Merge pull request 'fix(P4): Standby-Verzögerung, Dialog-Timeout, Kurvenhintergrund, Bindestrich (1.8.5)' (#65) from fix/standby-verzoegerung-und-feinschliff into main 2026-09-03 00:26:30 +02:00
raw-designsandClaude Opus 5 b6c6214b41 fix(P4): Standby-Verzögerung, Dialog-Timeout, Kurvenhintergrund, Bindestrich (1.8.5)
- Die ~2 s Verzögerung rund um den Standby lag nicht an der Helligkeit:
  lv_obj_move_foreground(g_standbyScreen) lief bei JEDER Zustandsmeldung.
  Das ordnet die Ebenen neu und lässt LVGL den ganzen Bildschirm neu
  zeichnen - einmal pro Sekunde, 1280x720 in 24 Bit. Nach vorn geholt wird
  die Uhr jetzt nur beim Einblenden.
- Der Aufweckdialog schließt sich nach 30 s ohne Auswahl selbst und wird wie
  Abbrechen gewertet (lv_timer, repeat_count 1). Sonst bliebe das Display
  hell stehen, weil der Dialog auf normale Helligkeit schaltet.
- Die Verlaufskurve hat keinen eigenen Hintergrund mehr. Die abgesetzte
  Fläche war nur nötig, solange die Kurve vor dem ersten Messwert leer war -
  seit sie beim aktuellen Wert startet, ist immer eine Linie da.
- Statistik: Bei fehlendem Gewicht stand ein En-Dash, den der Zeichensatz
  nicht enthält - erschien als leeres Rechteck. Jetzt ASCII-Bindestrich.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:26:12 +02:00
thomas 63290afa07 Merge pull request 'fix(P4): Übergänge ohne Animation, kein blaues Aufblitzen, Temperaturfarbe wählbar (1.8.4)' (#64) from fix/uebergaenge-und-temperaturfarbe into main 2026-09-03 00:04:43 +02:00
raw-designsandClaude Opus 5 cf57e11ccb fix(P4): Übergänge ohne Animation, kein blaues Aufblitzen, Temperaturfarbe wählbar (1.8.4)
- Standby-Uhr und Aufweckdialog kamen spürbar verzögert. Ursache waren die
  Fade-Animationen: Eine Deckkraft-Animation ist bei einem Bildspeicher in
  RGB888 das Teuerste überhaupt, weil pro Einzelbild die ganze Fläche unter
  der Ebene neu gezeichnet und gemischt werden muss. UI_FADE_MS ist für
  WS_PANEL_7H auf 0; ui_fade_in/ui_fade_out_hide zeigen bzw. verstecken dann
  direkt. Die JC-Panels behalten die Animation.
- Beim Wechsel der Schriftgröße blitzte Blau auf. Das ist das Panel selbst -
  ohne Videosignal zeigt es Blau, und der nötige Neustart reißt das Signal
  ab. hal_backlight(0) vor esp_restart().
- Neue Einstellung "Temperaturzahl in der Zustandsfarbe" (Info-Seite,
  Abschnitt Darstellung, NVS ui/tempcol, Vorgabe aus). Aus bleibt die Zahl
  weiß; die Zustandsfarbe steckt dann allein im Balken. Warnungen bleiben
  immer rot.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:03:58 +02:00
thomas 6d345d2145 Merge pull request 'fix(P4): Fehlendes °C, unsichtbarer Verlauf, fette Zahlen (1.8.3)' (#63) from fix/dashboard-schrift-und-verlauf into main 2026-09-02 20:12:52 +02:00
raw-designsandClaude Opus 5 a410da6e72 fix(P4): Fehlendes °C, unsichtbarer Verlauf, fette Zahlen (1.8.3)
Drei Punkte am neuen Dashboard:

- Hinter der Temperatur stand ein leeres Rechteck statt des C. Die großen
  Schriften waren auf Ziffern, Komma, Punkt, Minus und Gradzeichen
  beschränkt; C fehlte. Jetzt enthalten, ebenso g und s für die Anzeige
  während eines Bezugs.
- Bei "Verlauf" war keine Kurve zu sehen: Ein frisches lv_chart hat lauter
  LV_CHART_POINT_NONE und zeichnet nichts, und ohne Hintergrund war der
  Bereich unsichtbar, bis nach einer Minute Messwerte zusammenkamen. Die
  Kurve startet jetzt beim aktuellen Wert (lv_chart_set_all_value), Bereich
  und Fläche sind sofort gesetzt.
- Die großen Zahlen stehen im fetten Schnitt und mit engerem Zeichenabstand,
  wie im Entwurf. Erzeugt aus MavenPro-VariableFont bei wght=700 via
  fontTools varLib.instancer; dieselbe Schriftart, nur kräftiger. Die
  Dateien heißen entsprechend lv_font_maven_pro_bold_96/_140.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:12:32 +02:00
thomas f0a214d712 Merge pull request 'feat(P4): Letzte Bezüge als Tabelle statt Textblock (1.8.2)' (#62) from feat/statistik-tabelle into main 2026-09-02 19:52:14 +02:00
raw-designsandClaude Opus 5 21f273aae4 feat(P4): Letzte Bezüge als Tabelle statt Textblock (1.8.2)
Die Liste war ein einziges mehrzeiliges Label: Zeitpunkt, Dauer und Gewicht
durch Leerzeichen getrennt aneinandergereiht. Weil Zahlen unterschiedlich
breit sind, stand nichts untereinander - zwei Bezüge ließen sich nicht
vergleichen, ohne jede Zeile einzeln zu lesen.

Jetzt eine Tabelle mit festen Spalten (Zeitpunkt / Dauer / Gewicht / kalt),
Ziffern gleicher Breite, Kopfzeile und leicht abgesetzten wechselnden
Zeilen statt Trennlinien. Ohne Waage bezogen bleibt die Gewichtsspalte leer
statt eine Null vorzutäuschen.

Die Zeilen werden einmal angelegt und danach nur gefüllt oder ausgeblendet -
kein Neuanlegen bei jeder Aktualisierung.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:51:57 +02:00
thomas e42d2b3de2 Merge pull request 'feat(P4): Bezugsanzeige für alle vier Darstellungen (1.8.1)' (#61) from feat/bezugsanzeige into main 2026-09-02 19:41:07 +02:00
raw-designsandClaude Opus 5 785f64331d feat(P4): Bezugsanzeige für alle vier Darstellungen (1.8.1)
Sobald ein Bezug oder Dampfbezug läuft, schrumpft der Temperaturbereich
(flex-grow 3 -> 1) und die untere Zeile wächst (2 -> 3); Shot-Timer und
Gewicht wechseln auf 96 px. Während eines Bezugs zählen Zeit und Gewicht,
die Kesseltemperaturen sind dann Nebensache.

Keine zweite Ansicht: Die Seite verschiebt nur das Gewicht zwischen ihren
beiden Bereichen. Nichts doppelt zu pflegen, nichts springt an eine andere
Stelle.

Der Wechsel hält nach dem Bezug noch vier Sekunden an - ohne dieses
Nachhalten würde die Seite bei jeder kurzen Unterbrechung hin- und
herspringen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:40:51 +02:00
thomas 40aabe48bc Merge pull request 'feat(P4): Vier neue Dashboard-Darstellungen (1.8.0)' (#60) from feat/dashboard-vier-darstellungen into main 2026-09-02 19:30:11 +02:00
raw-designsandClaude Opus 5 ca3a9c1264 feat(P4): Vier neue Dashboard-Darstellungen (1.8.0)
Ersetzt die bisherigen fünf (Klassisch, Instrumente, Minimal, Soft-3D,
Thermometer) durch vier, die nicht nur den Temperaturbereich anders zeichnen,
sondern die Fläche unterschiedlich nutzen:

- Gewichtet: Wasser mit flex-grow 2, Dampf mit 1.
- Verlauf: lv_chart-Sparkline je Kessel, gespeist aus der bestehenden
  Sekundenabtastung; Ausschnitt am Sollwert ausgerichtet.
- Flächen: ohne Kartenkanten, zwei Felder mit leicht verschiedenem Grund.
- Band: Zustandsband über volle Breite (bereit / heizt / Bezug / Cold
  Extraction / Standby). Neue Vorgabe.

Zwei neue Schriftgrade (96 und 140 px), erzeugt mit lv_font_conv aus
MavenPro-Regular.ttf, beschränkt auf Ziffern, Komma, Punkt, Minus und
Gradzeichen - die größte Textschrift hatte 48 px und reichte für die großen
Zahlen nicht.

Ein gespeicherter Wert außerhalb des neuen Bereichs fällt auf die Vorgabe
zurück. Die fünf alten Builder sind entfernt.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 19:29:54 +02:00
thomas bcd9ecf0c8 Merge pull request 'refactor(P4): Schriftgröße auf die Info-Seite verschieben (1.7.3)' (#59) from refactor/schriftgroesse-auf-info-seite into main 2026-09-02 18:09:36 +02:00
raw-designsandClaude Opus 5 e5c61d42d4 refactor(P4): Schriftgröße auf die Info-Seite verschieben (1.7.3)
Die Info-Seite hat bereits einen Abschnitt "Darstellung" mit der Wahl der
Temperaturanzeige und dem Live-Bezugsschirm. Die Schriftgröße gehört dort
hinein - sie ist eine Einstellung dieses Displays, keine Wartungsaufgabe.

- Die eigene Karte auf der Wartungsseite entfällt; diese hat damit wieder
  vier Karten in zwei Spalten.
- Die Auswahl selbst ist unverändert: drei Stufen, Neustart danach.
- Sie gilt für alle Panels, nicht nur den 7-Zöller.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFRMCnijNEA7obGAq5Jz9v
2026-09-02 18:09:21 +02:00
thomas d77b0795ea Merge pull request 'feat(P4): Schriftgröße einstellbar (1.7.2)' (#58) from feat/schriftgroesse-einstellbar into main 2026-09-02 18:02:40 +02:00
raw-designsandClaude Opus 5 7ebffdab8f feat(P4): Schriftgröße einstellbar (1.7.2)
Klein, Mittel oder Groß - Karte "Darstellung" auf der Seite Reinigung &
Wartung.

LVGL-Schriften sind fest einkompilierte Bilddaten und existieren nur in den
Größen, die im Projekt liegen (14/18/28/40/48). Stufenloses Skalieren gibt
es also nicht. Stattdessen drei Rollen - font_small(), font_text(),
font_title() -, die je nach Einstellung eine Stufe höher greifen; die 45
festen Schriftverweise in ui.cpp sind darauf umgestellt.

- Die großen Anzeigen (Temperatur 40/48, Uhr) bleiben fest: Sie sind auf die
  Bildschirmgröße abgestimmt.
- Die Grundschrift wird am Bildschirmobjekt gesetzt, damit alles ohne eigene
  Schrift sie erbt.
- Die Einstellung liegt lokal im P4 (NVS "anzeige"), nicht auf der S3.
- Nach der Auswahl folgt ein Neustart; der Frühstart-Zähler der
  Absturzsicherung wird vorher zurückgesetzt.

Offen: In der Stufe Groß brauchen die Karten mehr Platz - ob alle Seiten
dann noch vollständig auf den Bildschirm passen, zeigt erst das Gerät.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 18:02:23 +02:00
thomas b73fc798c3 Merge pull request 'feat(P4): Cold Extraction und Wartung mehrspaltig (1.7.1)' (#57) from feat/coldex-und-wartung-mehrspaltig into main 2026-09-02 17:52:32 +02:00
raw-designsandClaude Opus 5 5defbe362c feat(P4): Cold Extraction und Wartung mehrspaltig (1.7.1)
Zweiter Teil der Layoutumstellung für das 7-Zoll-Panel.

Cold Extraction, drei Spalten:
- Oben die Karten mit Erklärungstext (Modus, Vorbenetzung, Bezugsende),
  darunter die knappen (Grundeinstellung, Hauptbezug, Pumpenschutz).
- Drei statt zwei Spalten, weil die Erklärungstexte keine volle
  Zeilenbreite brauchen und so keine halbleere Reihe stehen bleibt.
- Der Warnhinweis stand als eigene Zeile am Seitenende, weit entfernt vom
  zugehörigen Schalter - er sitzt jetzt in der Modus-Karte.
- Speichern frei unter allen Karten.

Reinigung & Wartung, zwei Spalten, vier Karten:
- Wartungszähler, Flush-Zeiten, Display-Helligkeit, Reinigung & Entkalkung.
- Bisher lose Zeilen unter bloßen Zwischenüberschriften mit freistehenden
  Knöpfen dazwischen.
- Hier sitzt Speichern IN der Karte: Jede Karte speichert etwas anderes.

Die JC-Panels behalten ihr einspaltiges Layout; beide Varianten mit
arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:52:15 +02:00
thomas 749f50097f Merge pull request 'feat(P4): Brew-Control zweispaltig auf dem 7-Zoll-Panel (1.7.0)' (#56) from feat/brew-control-zweispaltig into main 2026-09-02 17:26:06 +02:00
raw-designsandClaude Opus 5 95373dc619 feat(P4): Brew-Control zweispaltig auf dem 7-Zoll-Panel (1.7.0)
Die Seiten sind als einspaltige Stapel für 800x480 entstanden. Auf 1280x720
bleibt daneben rund 40 % der Fläche leer, während unten Karten aus dem Bild
fallen - und Scrollen ist auf diesem Panel teuer, weil ohne Doppelpufferung
jedes Bild komplett neu entsteht.

- Neue Helfer page_grid() und grid_at() legen eine Seite als LVGL-Raster an.
- build_brew() ordnet die vier Karten in zwei Spalten: links was den Bezug
  startet (Pre-Infusion, Brew-by-Time), rechts was ihn beendet
  (Brew-by-Weight, Dampf-Timer).
- Der Speichern-Knopf steht frei unter allen Karten statt in einer davon -
  er speichert die ganze Seite.
- Nur für WS_PANEL_7H; die JC-Panels bleiben einspaltig.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:24:42 +02:00
thomas a1c047e1ef Merge pull request 'perf(P4): Scrollen ohne Nachlauf am Waveshare-Panel (1.6.27)' (#55) from perf/scrollen-ohne-nachlauf into main 2026-09-02 17:03:04 +02:00
raw-designsandClaude Opus 5 52391c46c9 perf(P4): Scrollen ohne Nachlauf am Waveshare-Panel (1.6.27)
Ohne Doppelpufferung entsteht beim Scrollen jedes Bild komplett neu
(1280x676 in 24 Bit, per Software). Am stärksten fällt das beim Ausrollen
nach dem Loslassen auf: Dort bewegt sich das Bild weiter, ohne dass ein
Finger es führt, und jedes Stocken ist unmittelbar zu sehen.

LV_OBJ_FLAG_SCROLL_MOMENTUM ist für die scrollbaren Container deshalb
abgeschaltet - die Liste folgt nur noch dem Finger. Nur für WS_PANEL_7H;
die JC-Panels haben das Tempoproblem nicht und bleiben unverändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 17:01:40 +02:00
thomas 5d47228eed Merge pull request 'fix(P4): Blaue Zwischenbilder beim Bildlauf beseitigt (1.6.26)' (#54) from fix/blaue-zwischenbilder into main 2026-09-02 03:49:23 +02:00
raw-designsandClaude Opus 5 a3461fe960 fix(P4): Blaue Zwischenbilder beim Bildlauf beseitigt (1.6.26)
Ursache war der zweite Zeichenpuffer aus 1.6.25. esp_lcd_dpi_panel_draw_bitmap
lehnt eine Übertragung ab, solange die vorige noch läuft:

  ESP_RETURN_ON_FALSE(xSemaphoreTake(dpi_panel->draw_sem, 0) == pdTRUE,
                      ESP_ERR_INVALID_STATE, ...)

Mit zwei Puffern schickt LVGL die nächste Übertragung sofort los, statt auf
lv_disp_flush_ready zu warten. Die abgelehnte Übertragung fällt ersatzlos
aus, und der betroffene Bildbereich behält seinen alten oder noch nicht
geschriebenen Inhalt - sichtbar als blaue Zwischenbilder.

Der zweite Zeichenpuffer ist entfernt, lvgl_port_v9.c ist damit wieder
unverändert gegenüber der Hersteller-Portierung. Der eigentliche Gewinn
bleibt: Der Zwischenpuffer hat weiterhin volle Bildhöhe, ein Bildlauf
braucht einen Durchgang statt sechs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 03:47:59 +02:00
thomas 1a05f8e1b3 Merge pull request 'perf(P4): Zeichenpuffer für flüssigen Bildlauf (1.6.25)' (#53) from perf/zeichenpuffer-bildlauf into main 2026-09-02 03:38:44 +02:00
raw-designsandClaude Opus 5 dd8e641aa7 perf(P4): Zeichenpuffer für flüssigen Bildlauf (1.6.25)
Nach der Umstellung auf einen Bildspeicher war der Bildlauf sehr zäh. Grund
war der Zwischenpuffer: Mit 120 Zeilen brauchte ein Bildlauf über die ganze
Seite sechs Durchgänge, jeder mit eigener Übertragung in den Bildspeicher.

- CONFIG_EXAMPLE_LVGL_PORT_BUF_HEIGHT auf LCD_V_RES: ein Durchgang statt
  sechs.
- Zweiter Zeichenpuffer (LVGL_PORT_SECOND_DRAW_BUFFER), damit LVGL den
  nächsten Bereich zeichnen kann, während der vorige übertragen wird. Dafür
  eine kleine, markierte Änderung in lvgl_port_v9.c - der Hersteller-Port
  legt dort nur einen Puffer an.

Kosten 2 x 2,8 MB PSRAM. Das Startverhalten bleibt unberührt: Es handelt
sich um Zeichenpuffer, nicht um Bildspeicher der Anzeige - es bleibt bei
einem Bildspeicher.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 03:37:19 +02:00
thomas 6201c1bc36 Merge pull request 'fix(P4): Nur ein Bildspeicher - Ursache des Startfehlers behoben (1.6.24)' (#52) from fix/ein-bildspeicher into main 2026-09-02 03:20:25 +02:00
raw-designsandClaude Opus 5 6d710f0129 fix(P4): Nur ein Bildspeicher - Ursache des Startfehlers behoben (1.6.24)
Mit dem Prüfbild bei num_fbs = 1 startet das Panel zuverlässig, mit zwei
Bildspeichern nicht. Das war der letzte verbliebene Unterschied zum
funktionierenden Werks-Testsketch - und die Ursache des ganzen
Startproblems: Der Hochlauf meldete durchgehend Erfolg, das Panel blieb
dunkel, erst ein Reset half.

- WS7_SINGLE_FB schaltet die Doppelpufferung für das Waveshare-Panel ab
  (EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE = 0). LVGL zeichnet dann in einen
  Zwischenpuffer von 120 Zeilen im PSRAM, der per DMA in den einen
  Bildspeicher übertragen wird.
- num_fbs folgt wieder LVGL_PORT_LCD_BUFFER_NUMS, das in diesem Modus 1
  ist - die Sonderbehandlung im Prüfbild-Modus entfällt.
- WS7_PARTIAL_REFRESH ist damit wirkungslos und als solches vermerkt.
- PSRAM-Bedarf sinkt von 5,4 MB auf 2,8 MB.

Preis ist mögliches Tearing bei schnellen Bildwechseln.

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 03:19:00 +02:00
thomas 5cc71ffa92 Merge pull request 'diag(P4): Prüfbild mit genau einem Bildspeicher, wie im Testsketch (1.6.23)' (#51) from diag/pruefbild-ein-bildspeicher into main 2026-09-02 03:04:57 +02:00
raw-designsandClaude Opus 5 6bef94b8e1 diag(P4): Prüfbild mit genau einem Bildspeicher, wie im Testsketch (1.6.23)
Der Selbst-Neustart aus 1.6.22 reicht nicht - ein Software-Neustart setzt
offenbar nicht dasselbe zurück wie die Reset-Taste.

Damit zurück zu dem einen Unterschied zum funktionierenden Werks-Testsketch,
der bisher nie überprüft wurde: dsi-test.ino setzt num_fbs = 1, die Firmware
nutzt LVGL_PORT_LCD_BUFFER_NUMS = 2. Der Prüfbild-Test lief bislang ebenfalls
mit zwei Speichern und war damit nie ein echter Nachbau - er wich an genau
der offenen Stelle ab.

Mit WS7_TEST_PATTERN = 1 wird num_fbs jetzt fest auf 1 gesetzt. Erscheinen
die Farbbalken damit zuverlässig, liegt es an der Anzahl der Bildspeicher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
2026-09-02 03:03:33 +02:00
thomas 95e97e5937 Merge pull request 'fix(P4): Nach Kaltstart einmal selbst neu starten (1.6.22)' (#50) from fix/kaltstart-neustart into main 2026-09-02 02:45:31 +02:00
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
raw-designsandClaude Opus 5 9d9591c26e fix(P4): UART auf GPIO20/21, Touch oben/unten spiegeln (1.6.3)
Nach dem ersten Betrieb am Waveshare ESP32-P4-Pico:

- UART zur Hauptplatine auf TX = GPIO20 / RX = GPIO21 (vorher GPIO17/18).
  Die JC-Panels bleiben bei GPIO33/GPIO31.
- Das Bild steht in der tatsächlichen Einbaulage richtig herum, deshalb ist
  WS7_ROTATE_180 wieder 0. Der Schalter bleibt für den Fall einer anderen
  Montage erhalten.
- Der Touch war oben/unten spiegelverkehrt: Der Digitizer sitzt gegenüber dem
  Bild um die Hochachse gedreht. WS7_TOUCH_MIRROR_RAW_X steht daher auf 1.
  Bild- und Touch-Ausrichtung sind zwei getrennte Schalter, weil sie an diesem
  Panel nicht zusammenfallen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 12:34:27 +02:00
thomas 288aacf1b4 Merge pull request 'fix(P4): Panel-Bringup meldet Fehler im Klartext statt abzustürzen (1.6.2)' (#30) from fix/panel-bringup-diagnose into main 2026-09-01 08:08:39 +02:00
raw-designsandClaude Opus 5 d75708993a fix(P4): Panel-Bringup meldet Fehler im Klartext statt abzustürzen (1.6.2)
Ein fehlgeschlagener Panel-Aufbau endete bisher in einem ESP_ERROR_CHECK
tief im LVGL-Port und damit in einem Speicherauszug ohne erkennbare Ursache.

- Die Schritte des Waveshare-Bringups melden ihren Fehler über esp_rom_printf,
  also auch bei Core-Debug-Level "none". Zusätzlich werden PSRAM-Größe,
  freier PSRAM, größter freier Block und der Framebuffer-Bedarf ausgegeben.
- jc_board_bringup() liefert jetzt einen Status. Kommt das Panel nicht hoch,
  bleibt die Firmware ohne Anzeige lauffähig (UART + OTA), statt beim ersten
  LVGL-Zugriff erneut abzustürzen.
- ui.cpp: alle öffentlichen Funktionen prüfen, ob ui_init gelaufen ist.
- Fehlerbehebung: esp_lcd_touch_new_i2c_gt911 setzt im Fehlerfall einen bereits
  freigegebenen Zeiger. Das Handle wird vorbelegt und der Rückgabewert geprüft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 08:07:10 +02:00
thomas 146420a0bc Merge pull request 'refactor: Display-Firmware neutral als P4_Display_Firmware führen' (#29) from refactor/p4-display-firmware-umbenennen into main 2026-09-01 03:36:57 +02:00
raw-designsandClaude Opus 5 4f9f686d81 refactor: Display-Firmware neutral als P4_Display_Firmware führen
Der Ordner hieß JC_Display_Firmware, bedient seit 1.6.0 aber auch das
Waveshare 7inch DSI LCD (H) am ESP32-P4-Pico. Der Name führte in die Irre.

- JC_Display_Firmware/ -> P4_Display_Firmware/ (samt Sketch, den Arduino
  gleichnamig zum Ordner verlangt)
- Doku/JC-Display_UART-Protokoll.md -> Doku/P4-Display_UART-Protokoll.md,
  Verweise und Titel angepasst
- Verweise in CLAUDE.md, README und Quelltextköpfen nachgezogen

Nur Namen und Pfade, keine Logikänderung. Der Vendor-Ordner
JC_Display_Firmware_7zoll/ behält seinen Namen, ebenso die Bezeichner
JC_PANEL_TYPE/jc_board_bringup im Quelltext.

Enthält außerdem die bereits im Arbeitsverzeichnis liegende, noch nicht
committete Ergänzung des Git-Workflows in CLAUDE.md (PRs über die Gitea-API
statt der hängenden tea-CLI).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 03:30:42 +02:00
thomas e0c6784ecf Merge pull request 'fix(P4): Waveshare-Panel um 180 Grad gedreht ausgeben (1.6.1)' (#28) from fix/waveshare-einbaulage-180-grad into main 2026-09-01 03:12:31 +02:00
raw-designsandClaude Opus 5 72b47f6dd6 fix(P4): Waveshare-Panel um 180 Grad gedreht ausgeben (1.6.1)
Das Panel ist auf dem Kopf eingebaut. WS7_ROTATE_180 in pins_config.h dreht
Bild und Touch gemeinsam; Vorgabe ist gedreht.

Die Drehung übernimmt der PPA-Grafikbeschleuniger (EXAMPLE_LVGL_PORT_PPA_
ROTATION_ENABLE), weil die Software-Rotation des LVGL-Ports nur RGB565
verarbeitet und für dieses RGB888-Panel nicht in Frage kommt. Der Touch wird
im process_coordinates-Hook mitgedreht, sonst läge die Bedienung
spiegelbildlich zum Bild.

MADCTL ist als WS7_MADCTL herausgezogen, falls die Bridge es doch auswertet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 03:11:10 +02:00
thomas 69d35e9a28 Merge pull request 'feat(P4): Waveshare ESP32-P4-Pico mit 7inch DSI LCD (H) unterstützen (1.6.0)' (#27) from feat/waveshare-p4-pico-7zoll-dsi into main 2026-09-01 03:05:49 +02:00
raw-designsandClaude Opus 5 a3034f7c42 feat(P4): UART zum S3 beim Waveshare-Board auf GPIO17/GPIO18 legen
Der 40-polige Header des ESP32-P4-Pico hat eine andere Belegung als die
Expansion-IO der JC-Panels. Für WS_PANEL_7H gilt jetzt TX = GPIO17,
RX = GPIO18; die JC-Panels bleiben bei GPIO33/GPIO31.

README und Changelog entsprechend ergänzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 03:04:28 +02:00
raw-designsandClaude Opus 5 93bb0deba1 feat(P4): Waveshare ESP32-P4-Pico mit 7inch DSI LCD (H) unterstützen (1.6.0)
Dieselbe P4-Firmware läuft jetzt zusätzlich auf einem Waveshare ESP32-P4-Pico
mit dem Waveshare 7inch DSI LCD (H) (1280x720). Auswahl über JC_PANEL_TYPE in
config.h (neu: WS_PANEL_7H, ab jetzt Vorgabe); JC_PANEL_43 und JC_PANEL_70
bleiben unverändert.

- board_bringup.c: DSI-Bus, DBI-Kommandokanal und DPI-Panel werden ohne
  Hersteller-Panel-Treiber direkt angelegt (PLL_F20M, 2 Lanes zu 1250 Mbit/s,
  80 MHz DPI, Austastlücken 64/64/64, RGB888). Panel-Freigabe und Helligkeit
  über die Bridge auf I2C 0x45; Licht erst nach dem Start der DSI-Ausgabe.
- lv_conf.h leitet LV_COLOR_DEPTH aus JC_PANEL_TYPE ab (24 Bit für das
  Waveshare-Panel, 16 Bit für die JC-Panels) und liest dafür config.h.
  display_hal.cpp sichert das mit static_assert ab.
- Touch GT9271 (I2C 0x14) über den GT911-Treiber; Achsentausch und Begrenzung
  auf die Bildfläche im process_coordinates-Hook.
- pins_config.h: Auflösung, DSI-Zeitbasis und Touch-Umrechnung als WS7_*-Werte.
- README und Changelog ergänzt.

Die UART-Pins zum S3 stehen für das Waveshare-Board noch auf den JC-Werten
(GPIO33/31) und müssen nach dem Header-Aufdruck gesetzt werden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DxAvqVnrY6YSoGFrNGeRTi
2026-09-01 02:56:23 +02:00
thomas 27a3ee840e Merge pull request 'feat(P4): 7-Zoll-Display JC1060P470C-I-W unterstützen (1.5.0)' (#26) from feat/7zoll-display-JC1060P470C into main 2026-08-22 02:36:52 +02:00
raw-designsandClaude Opus 5 92c1446824 feat(P4): 7-Zoll-Display JC1060P470C-I-W unterstützen (1.5.0)
Dieselbe Display-Firmware bedient jetzt wahlweise das bisherige 4,3-Zoll-Panel
JC4880P443C-I-W (ST7701, 480x800) oder das neue 7-Zoll-Panel JC1060P470C-I-W
(JD9165, 1024x600). Umgeschaltet wird über JC_PANEL_TYPE in config.h
(JC_PANEL_43 / JC_PANEL_70) oder per Compiler-Flag -DJC_PANEL_TYPE=70. Vorgabe
bleibt das 4,3-Zoll-Panel; dessen Verhalten ändert sich nicht.

- src/lcd/esp_lcd_jd9165.c/.h aus dem Hersteller-Paket übernommen;
  board_bringup.c legt je nach Panel DSI-Bus, DPI-Zeitbasis und Herstellertreiber an.
- pins_config.h liefert Auflösung, LCD-Reset (4,3": GPIO5, 7,0": GPIO27) und
  Ausrichtung panelabhängig. Das 7-Zoll-Panel ist nativ Querformat, deshalb
  entfallen dort die 270-Grad-Rotation und die PPA-Beschleunigung.
- lvgl_port_v9.h bezieht LVGL_PORT_H/V_RES aus pins_config.h statt fest 480/800.
- Der GT911 des 7-Zöllers meldet Rohkoordinaten im Raster 800x480 statt 1024x600.
  Die Umrechnung hängt am process_coordinates-Hook von esp_lcd_touch, damit die
  Herstellerdatei esp_lcd_touch_gt911.c unverändert bleibt.
- ui.cpp rechnet nicht mehr mit fest verdrahteten 480 Pixeln Höhe, sondern mit
  DISP_VER_RES. Layout und Schriften bleiben sonst unverändert.
- Hersteller-Paket des 7-Zöllers unter JC_Display_Firmware_7zoll/ abgelegt;
  Werkzeuge, Archive und mitgelieferte Fremdbibliotheken per .gitignore
  ausgeschlossen (587 MB im Original, 21 MB im Repo).

Kompiliertest ESP32-P4, beide Varianten:
  4,3": 1.173.768 B Flash (37 %), 322.880 B RAM (98 %)
  7,0": 1.138.752 B Flash (36 %), 322.600 B RAM (98 %)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 02:35:40 +02:00
thomas 544c6ae3b8 Merge pull request 'fix: Auswahl beim Aufwecken über Touch entfiel manchmal (S3 5.6.3 / P4 1.4.2)' (#25) from fix/wake-abfrage-touch-pfad into main 2026-08-19 01:03:24 +02:00
raw-designsandClaude Opus 5 743c38e994 fix: Auswahl beim Aufwecken über Touch entfiel manchmal (S3 5.6.3 / P4 1.4.2)
Über den Schalter kam die Abfrage "Espresso oder Cold Extraction" zuverlässig,
über Antippen am Display nur manchmal. Ursache waren zwei unterschiedliche
Bedingungen für dieselbe Sache:

- askMode (zeigt die Auswahl im Dialog) verlangte zusätzlich cxArmable
- choiceRelevant (sendet "wakeEspresso" und schließt damit das Auswahlfenster
  der S3) verlangte es nicht

In der Lücke dazwischen zeigte der Dialog nur "Aufwecken", unterdrückte aber
trotzdem die Rückfrage der S3 - die Frage war auf beiden Seiten weg. Der
Schalterweg hatte diese Lücke nie, weil er allein an der S3 hängt.

P4: beide Stellen benutzen jetzt wake_choice_offered(). Bot der Dialog die
Auswahl nicht an, sendet er "deactivateStandby"; dann öffnet die S3 ihr
eigenes Auswahlfenster und die Frage kommt trotzdem.

S3: Die Fensterentscheidung ist als Bedingungskette geschrieben und hält
Quelle und Ergebnis des letzten Aufweckens fest. /Cold-Extraction zeigt das
an - die bisherige Live-Liste zeigt nur den jetzigen Stand, der beim
Nachsehen längst wieder ein anderer sein kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 01:02:30 +02:00
thomas 535f640bbd Merge pull request 'fix: Abfrage beim Aufwecken geht in beide Richtungen (S3 5.6.2 / P4 1.4.1)' (#24) from fix/cold-extraction-abfrage-beide-richtungen into main 2026-08-12 07:20:11 +02:00
raw-designsandClaude Opus 5 f8d51a57b7 fix: Abfrage beim Aufwecken geht in beide Richtungen (S3 5.6.2 / P4 1.4.1)
Die Abfrage erschien nicht, sobald der Cold-Extraction-Modus noch aktiv war — und
da er seit 5.3.1 persistent ist, war er das nach dem ersten kalten Bezug dauerhaft.
Damit erklärt sich, warum weder der Schalter- noch der Touch-Weg etwas zeigte.

Die Bedingung "Modus ist aus" war falsch gedacht ("sonst gibt es nichts zu fragen").
Die Auswahl muss in beide Richtungen gehen, also auch von kalt zurück auf Espresso.
Sonst hängt man nach dem ersten kalten Bezug im kalten Modus fest, ohne beim
Aufwecken je wieder gefragt zu werden.

- Bedingung aus beiden Öffnungspfaden entfernt (Standby-Übergang und Kaltstart).
- Das Fenster bleibt bei aktivem Modus offen; bisher schloss es sofort wieder.
- "wakeEspresso" beendet jetzt einen laufenden Modus, statt ihn nur nicht
  einzuschalten — sonst wäre die Auswahl eine Einbahnstraße.
- Diagnose auf /Cold-Extraction listet den Modus nicht mehr als Voraussetzung,
  sondern zeigt ihn als Ist-Zustand.
- P4: Der Cold-Knopf zeigt den Ist-Zustand ("aktiv lassen" und hervorgehoben, wenn
  der Modus läuft).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 07:19:50 +02:00
thomas 8432bd3f15 Merge pull request 'fix(S3): Auswahl beim Aufwecken auch nach Kaltstart + Diagnose der Bedingungen (5.6.1)' (#23) from fix/cold-extraction-auswahl-diagnose into main 2026-08-12 02:33:54 +02:00
raw-designsandClaude Opus 5 49bdc686c4 fix(S3): Auswahl beim Aufwecken auch nach Kaltstart + Diagnose der Bedingungen (5.6.1)
Die Abfrage hängt an fünf Bedingungen gleichzeitig (freigeschaltet, Abfrage
eingeschaltet, Display verbunden, Modus gerade aus, kein Wartungsmodus/Reinigung/
Tuning). Fehlt eine, passiert schlicht nichts, und von außen war nicht erkennbar
welche. Die Seite /Cold-Extraction zeigt sie jetzt live mit Haken bzw. Kreuz an,
dazu ob das Auswahlfenster gerade offen ist.

Zusätzlich eine echte Lücke geschlossen: Die Abfrage kam nur beim Standby-Übergang.
Wer die Maschine am Netzschalter einschaltet, durchläuft nie einen solchen Übergang
— dieser Weg war nicht abgedeckt. Jetzt öffnet das Fenster einmal pro Laufzeit auch
nach einem Kaltstart, sobald sich das Display gemeldet hat. Startet die Maschine in
den Standby hinein, bleibt es beim Standby-Übergang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 02:33:35 +02:00
thomas 8dcebaa22f Merge pull request 'feat: Espresso/Cold-Auswahl bei jedem Aufwecken, auch per Schalter (S3 5.6.0 / P4 1.4.0)' (#22) from feat/cold-extraction-auswahl-nach-aufwecken into main 2026-08-12 01:47:09 +02:00
raw-designsandClaude Opus 5 7c1e485722 feat: Espresso/Cold-Auswahl bei jedem Aufwecken, auch per Schalter (S3 5.6.0 / P4 1.4.0)
Die Abfrage war eine reine Vorab-Abfrage des Displays und erschien nur beim
Antippen des Standby-Bildschirms. Beim Aufwecken über den Schalter ist der Standby
aber schon beendet, bevor das Display überhaupt fragen könnte — dieser Weg war nie
abgedeckt.

Die Steuerung hält jetzt nach dem Aufwecken ein Auswahlfenster offen (20 s): Der
Wasserkreis heizt in dieser Zeit nicht, das Display blendet die Auswahl von sich aus
ein und zeigt einen Countdown. Ohne Entscheidung wird danach normal geheizt, es geht
also nichts verloren, wenn niemand hinsieht.

Das Fenster öffnet nur mit angeschlossenem Display (sonst könnte niemand wählen),
bei freigeschalteter Funktion und aktiver Einstellung "Abfrage beim Aufwecken", und
nur wenn der Modus nicht ohnehin schon läuft. Es schließt bei Entscheidung,
Bezug/Spülen oder erneutem Standby.

Neue Aktion wakeEspresso (beendet Standby falls aktiv und schließt das Fenster) —
das Display sendet sie statt deactivateStandby, sobald die Auswahl relevant ist,
sonst würde direkt nach dem Aufwecken nochmal gefragt. Neue State-Felder
cxWakeChoice und cxWakeChoiceSec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 01:46:47 +02:00
thomas 7d14f94a72 Merge pull request 'feat: Abfrage Espresso oder Cold Extraction beim Aufwecken (S3 5.5.0 / P4 1.3.0)' (#21) from feat/cold-extraction-abfrage-beim-aufwecken into main 2026-08-12 01:02:58 +02:00
raw-designsandClaude Opus 5 11307d255b feat: Abfrage Espresso oder Cold Extraction beim Aufwecken (S3 5.5.0 / P4 1.3.0)
Der Aufweck-Dialog am Touch-Display wird zur Auswahl: "Womit aufwecken?" mit
"Espresso" (heizt normal auf) und "Cold Extraction (Wasser bleibt kalt)". Damit
lässt sich bei jedem Aufwecken neu entscheiden, ohne vorher die Web-UI zu bedienen.
Bei der kalten Variante wird der Modus scharf geschaltet, bevor der Standby endet —
der Wasserkreis heizt also gar nicht erst an.

Abschaltbar über die neue Einstellung "Abfrage beim Aufwecken" (EEPROM 982), zu
finden auf /Cold-Extraction und auf der Cold-Extraction-Seite des Displays. Ist sie
aus, erscheint wieder der bisherige Dialog. Ohne angeschlossenes Display oder bei
nicht freigeschalteter Funktion hat sie keine Wirkung. Neues Feld cxAskOnWake im
State und in saveBrew.

Dazu: Die Meldungstexte der kalten Extraktion sind jetzt mit echten Umlauten
geschrieben ("nicht möglich" statt "nicht moeglich"). Für das OLED übersetzt die
neue Hilfsfunktion oledUmlauts() sie beim Rendern nach CP437 — dort hätte UTF-8
sonst zwei Fehlzeichen je Umlaut ergeben, weshalb die Texte ursprünglich
transliteriert waren. Web-UI und Touch-Display stellen UTF-8 direkt dar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 01:02:37 +02:00
thomas f1a8d8c175 Merge pull request 'fix: Zielgewicht bei Cold Extraction ueberall korrekt anzeigen (S3 5.4.1 / P4 1.2.1)' (#20) from fix/cold-extraction-zielgewicht-anzeige into main 2026-08-12 00:52:07 +02:00
raw-designsandClaude Opus 5 334896b9cf fix: Zielgewicht bei Cold Extraction überall korrekt anzeigen (S3 5.4.1 / P4 1.2.1)
Während einer kalten Extraktion stand als Zielgewicht überall noch der
Brew-by-Weight-Wert des normalen Espresso-Bezugs. OLED, Web-Dashboard und
Touch-Display griffen alle direkt auf brewByWeightTargetGrams zu — die kalte
Extraktion ersetzt Brew-by-Weight aber und stoppt nach coldExtractionTargetGrams.

Neue Hilfsfunktion activeTargetWeightGrams() liefert das Ziel des aktuellen bzw.
nächsten Bezugs. Das UART-Feld targetWeight führt jetzt diesen Wert; bbwTarget
bleibt unverändert die Roh-Einstellung, damit die Brew-Control-Seite weiter das
Richtige anzeigt.

Zusätzlich hing die Gewichtsanzeige selbst an "Brew-by-Weight aktiv" — das ist bei
kalter Extraktion typischerweise aus, obwohl ein Ziel existiert. OLED, Waage-Kachel
und Live-Bezugsschirm zeigen Gewicht und Fortschritt jetzt auch beim kalten Bezug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:51:48 +02:00
thomas e8b1832fc1 Merge pull request 'feat: eigene Cold-Extraction-Seite in Web-UI und Display (S3 5.4.0 / P4 1.2.0)' (#19) from feat/cold-extraction-eigene-seite into main 2026-08-12 00:09:49 +02:00
raw-designsandClaude Opus 5 2d8f70ae37 feat: eigene Cold-Extraction-Seite in Web-UI und Display (S3 5.4.0 / P4 1.2.0)
Cold Extraction bekommt in beiden Oberflächen einen eigenen Menüpunkt statt eines
angehängten Blocks auf der Brew-Control-Seite. Die Parameter sind damit erstmals
auch am Touch-Display änderbar.

S3: neue Seite /Cold-Extraction, gegliedert in Grundeinstellung, Vorbenetzung,
Hauptbezug, Bezugsende und Pumpenschutz, jeder Abschnitt mit kurzer Begründung.
/Brew-Control verweist nur noch darauf. Das UART-Kommando saveBrew akzeptiert jetzt
die cx-Felder, und das State-JSON liefert die restlichen Parameter mit, damit das
Display seine Felder vorbelegen kann.

Wichtig: Der Speicher-Handler von /Brew-Control wertet die cx-Felder nicht mehr aus.
Er tat es bisher, und nach dem Verschieben der Checkbox hätte ein Speichern dort die
Freischaltung abgeschaltet, weil das Feld im Formular fehlt.

P4: neue Seite "Cold Extraction" mit allen Parametern; der Modus-Schalter ist von der
Temperatur-Seite dorthin umgezogen, damit es nur eine Stelle gibt. Die Brew-Control-
Seite ist neu gegliedert: jede Funktion in einer eigenen Karte mit Titel und einer
Zeile, die erklärt was sie tut, statt einer durchlaufenden Liste unter
Zwischenüberschriften. Neues Hilfsmakro group_card() für beide Seiten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:09:23 +02:00
thomas 537f1d4c20 Merge pull request 'fix(S3): Cold Extraction benetzt sanft statt mit voller Leistung (5.3.6)' (#18) from fix/cold-extraction-sanfte-vorbenetzung into main 2026-08-11 23:41:43 +02:00
raw-designsandClaude Opus 5 2cf375a2b4 fix(S3): Cold Extraction benetzt jetzt sanft statt mit voller Leistung (5.3.6)
Messung an der Maschine: Nach 7 s Vorbenetzung kommen heiß 1-2 sirupartige Tropfen,
kalt bei voller Pumpenleistung rund 10 g dünne Brühe ohne Crema — etwa zehnfacher
Durchfluss trotz dreifacher Viskosität.

Die Ursache ist nicht die Viskosität, sondern das Kaffeebett: Kaltes Mehl quillt
nicht, ist schlecht benetzbar, es wandert kein Feinanteil und es lösen sich kaum
Feststoffe, die das Bett verdichten. Volle Leistung auf den trockenen kalten Puck
legt deshalb sofort Kanäle an, die sich — anders als bei heißem Bezug — nie wieder
schließen; der restliche Bezug läuft daran vorbei statt zu extrahieren.

Vorbenetzung und Hauptbezug pulsen jetzt beide, nur mit unterschiedlicher Leistung.
Neue Einstellung "Pumpenleistung Vorbenetzung" (Standard 10 %), Standard im
Hauptbezug von 30 % auf 20 % gesenkt. Bestehende Installationen behalten ihre Werte;
das neue Feld greift beim Update automatisch auf den Default zurück.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:41:23 +02:00
thomas e33de56edc Merge pull request 'feat: Cold Extraction im Standby scharf schaltbar (S3 5.3.5 / P4 1.1.3)' (#17) from feat/cold-extraction-im-standby into main 2026-08-11 03:01:10 +02:00
raw-designsandClaude Opus 5 9b91543435 feat: Cold Extraction im Standby scharf schaltbar (S3 5.3.5 / P4 1.1.3)
"Standby aktiv" und "Kessel zu warm" galten bisher auch fürs Aktivieren. Das war
falsch herum gedacht: Der Modus schaltet die Heizung AB, man kam aber erst an den
Schalter, nachdem die Maschine aufgeweckt war und schon zu heizen begonnen hatte —
genau der Ablauf, den die Funktion vermeiden soll.

Scharfschalten und Beziehen sind jetzt getrennt:
- Scharfschalten (neues State-Feld cxArmable): blockiert nur durch fehlende
  Freischaltung, Wartungsmodus, Reinigungsassistent, PID-Tuning oder einen
  laufenden Bezug/Spülvorgang.
- Bezug (unverändert cxAllowed): zusätzlich kein Standby, Kessel unter der
  Freigabeschwelle, gültiger Sensorwert.

Ist der Modus scharf, der Bezug aber gesperrt, sagen Web-UI und Display das jetzt
ausdrücklich. Bei Aktivierung mit warmem Kessel geht die Heizung sofort aus, die
Rückmeldung nennt die Sperre bis zum Unterschreiten der Schwelle.

P4: Der Aufweck-Dialog bekommt "Aufwecken mit Cold Extraction" — sendet
startColdExtraction vor deactivateStandby, damit der Wasserkreis gar nicht erst
anheizt. Der Schalter hängt jetzt an cxArmable; bei älterer S3-Firmware fällt das
Feld auf cxEnabled zurück und bleibt bedienbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 02:59:22 +02:00
thomas 6dd3f1eaf2 Merge pull request 'feat: Cold Extraction warnt bei fehlender Waage (S3 5.3.4 / P4 1.1.2)' (#16) from feat/cold-extraction-waagen-hinweis into main 2026-08-11 02:07:35 +02:00
raw-designsandClaude Opus 5 b6155acafe feat: Cold Extraction warnt bei fehlender Waage (S3 5.3.4 / P4 1.1.2)
Ohne aktive und verbundene Waage greifen weder das Zielgewicht noch die
Stillstands-Erkennung — der Bezug endet dann erst nach der maximalen Bezugsdauer,
die damit faktisch zur Dosierung wird. Bisher passierte diese Degradierung
stillschweigend.

Die Funktion bleibt ohne Waage bewusst nutzbar (Zeitsteuerung als Rückfallebene),
damit ein kurzzeitig abgemeldeter HX711 die kalte Extraktion nicht komplett
blockiert. Angezeigt wird der Zustand jetzt an vier Stellen: dauerhafter
Warnhinweis auf /Brew-Control (Zielgewicht-Feld zusätzlich als "nur mit Waage
wirksam" beschriftet), Hinweiszeile unter dem Dashboard-Schalter, Statustext
"Cold Extraction bereit (ohne Waage: Zeitsteuerung)" und eine einmalige Meldung
beim Aktivieren.

P4: Statuszeile zeigt "ohne Waage, Ende nach Zeit" in Warnfarbe statt des blauen
Normalhinweises; Erklärzeile am Schalter und Parameter-Anzeige auf der Brew-Seite
ergänzen den Hinweis und nennen bei aktivem Modus ohne Waage die Zeit statt des
Zielgewichts. Neuer cxNotice-Code 4 (additiv, Protokoll bleibt v2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 02:05:50 +02:00
thomas 4cc9ceea7c Merge pull request 'fix(S3): Cold Extraction brach waehrend der Vorbenetzung ab (5.3.3)' (#15) from fix/cold-extraction-vorbenetzung into main 2026-08-11 01:55:31 +02:00
raw-designsandClaude Opus 5 881acec3df fix(S3): Cold Extraction brach während der Vorbenetzung ab (5.3.3)
Die Stillstands-Erkennung lief bereits während der Vorbenetzung mit. In dieser
Phase sättigt der Puck aber erst, in der Tasse kommt naturgemäß nichts an — bei
einer Vorbenetzung länger als die 15 s Stillstands-Zeit wurde deshalb jeder kalte
Bezug mit "kein Zulauf" abgebrochen. Mit dem bisherigen Standard von 30 s wäre das
ausnahmslos passiert; die Funktion war mit aktiver Waage unbenutzbar. Die
Erkennung startet jetzt erst mit dem Hauptbezug.

Dazu der Standard der Vorbenetzung von 30 s auf 10 s: Die Pumpe läuft in dieser
Phase bewusst ungepulst auf voller Leistung. Heiß kommt der erste Tropfen nach
etwa 7 s, kalt (rund dreifache Viskosität) entsprechend später — 30 s wären längst
voller Bezug bei vollem Druck gewesen, und die Hälfte des Pumpen-Laufzeitbudgets
wäre vor dem eigentlichen Bezug verbraucht. Bestehende Installationen behalten
ihren gespeicherten Wert.

Die Beschriftung auf /Brew-Control sagt jetzt ausdrücklich, dass die Pumpe während
der Vorbenetzung durchläuft und erst im Hauptbezug gepulst wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 01:53:48 +02:00
thomas bc78a10505 Merge pull request 'fix(P4): Compiler-Warnungen des Display-Builds beseitigt (1.1.1)' (#14) from fix/p4-compiler-warnungen into main 2026-08-11 01:23:02 +02:00
raw-designsandClaude Opus 5 1f8cce48aa fix(P4): Compiler-Warnungen des Display-Builds beseitigt (1.1.1)
Von 68 Warnungen (arduino-cli --warnings all) auf eine einzige, die im
ESP32-Core-SDK steckt und nicht im Projekt behebbar ist.

- LV_FS_DEFAULT_DRIVE_LETTER heißt ab LVGL 9.3 LV_FS_DEFAULT_DRIVER_LETTER. Der
  alte Name funktionierte weiter, löste aber in lv_api_map_v9_1.h ein #warning in
  JEDER Übersetzungseinheit aus, die lvgl.h einbindet — allein rund 60 Warnungen
  pro Build, die alles andere zugedeckt haben. In lv_conf.h umbenannt.
- LV_PART_* und LV_STATE_* sind in LVGL 9 verschiedene Enum-Typen; die direkte
  |-Verknüpfung ist in C++20 abgekündigt. Neues Makro TH_SEL(part, state) in
  theme.h castet beide auf lv_style_selector_t (ohnehin uint32_t), 5 Stellen.
- Bug-Fix: Der Puffer der Cold-Extraction-Infozeile auf der Brew-Seite war mit 192
  Bytes zu klein für den ~205 Zeichen langen Text (das "°" zählt doppelt), die
  letzte Zeile wurde abgeschnitten. Jetzt 256 Bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 01:20:49 +02:00
thomas 1933a41e64 Merge pull request 'feat(P4): Cold Extraction am Touch-Display + RX-Zeilenlimit-Fix (1.1.0 / S3 5.3.2)' (#13) from feat/cold-extraction-p4 into main 2026-08-10 23:58:06 +02:00
raw-designsandClaude Opus 5 e8edc8eb30 feat(P4): Cold Extraction am Touch-Display + RX-Zeilenlimit-Fix (1.1.0 / S3 5.3.2)
Display-Seite der kalten Extraktion: Schalter unter "Modi" auf der Temperatur-Seite
mit Erklärzeile, eigener Slot in der Statuszeile direkt hinter der
Sicherheitsmeldung, "Cold Extraction - Heizen aus" in der Wasser-Kachel statt eines
unerreichbaren Sollwerts, unterdrückter Aufheiz-Countdown, "Vorbenetzung" als erste
Phase des kalten Bezugs, Parameter-Anzeige auf der Brew-Seite und "(kalt)"-Markierung
in der Statistik (kalte Bezüge zählen nicht in die mittlere Dauer).

Bug-Fix P4: PROTO_RX_LINE_MAX lag bei 2048 Bytes, während die State-Zeile der S3
bereits rund 2,1 KB erreicht. Mit langem Status-, Profil- oder SSID-Text lag sie
darüber — dann wurde die ganze Zeile verworfen und das Display fror auf dem letzten
Stand ein, ohne dass die Verbindung als tot erkannt wurde. Limit jetzt 4096 Bytes;
der Puffer bleibt bei 2 KB reserviert, weil der interne RAM knapp ist und eine
Arduino-String nicht ins PSRAM alloziert werden kann.

S3 5.3.2: Sperrgrund und transiente Meldung gehen als Codes (cxBlock/cxNotice) statt
als Klartext ans Display, dazu cxPreInf für die Phasenleiste. Der bis zu 110 Byte
lange Freitext hätte die State-Zeile über das Zeilenlimit gedrückt; die Texte rendert
jetzt das Display, analog zur Trennung statusKey/statusText. Auf dem OLED und in den
ack-Antworten bleibt der Klartext unverändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 23:56:17 +02:00
thomas 57b7969f8b Merge pull request 'feat(S3): Cold Extraction ueberlebt Standby und Neustart (5.3.1)' (#12) from feat/cold-extraction-persistent into main 2026-08-10 21:33:40 +02:00
raw-designsandClaude Opus 5 c697e65a01 feat(S3): Cold Extraction überlebt Standby und Neustart (5.3.1)
Der Modus wird im EEPROM (Adresse 977) gespeichert und beim Start wieder
übernommen, sofern die Funktion auf /Brew-Control freigeschaltet ist. Standby
beendet ihn nicht mehr, sondern verwirft nur die Laufzeitdaten.

Grund: Wer nur kalte Bezüge machen will, musste den Modus nach jedem Standby
neu einschalten. Bis dahin heizte die Maschine bereits auf und war für die
nächste kalte Extraktion zu warm — man musste auf das Abkühlen warten, also
genau die Situation, die der Modus vermeiden soll.

Der Modus endet nur noch durch explizites Ausschalten, durch Entzug der
Freischaltung oder durch einen Werksreset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:31:59 +02:00
thomas d7a1a8dee2 Merge pull request 'feat(S3): Cold Extraction (kalte Extraktion) mit Temperatur-Freigabe (5.3.0)' (#11) from feat/cold-extraction into main 2026-08-10 21:04:17 +02:00
4873 changed files with 56266 additions and 1190 deletions
+10
View File
@@ -0,0 +1,10 @@
# -------------------------------------------------------------------------------------
# Hersteller-Paket des 7-Zoll-Displays (JC1060P470C-I-W)
# Der Ordner ist im Original ~587 MB gross. Ins Repo kommen nur Schaltplaene, Specs,
# Datenblaetter, Handbuch und die Arduino-Demos; ausgeschlossen bleiben Werkzeuge,
# Archive und die mitgelieferten Fremdbibliotheken - die sind anderweitig beziehbar.
# -------------------------------------------------------------------------------------
JC_Display_Firmware_7zoll/8-Burn operation/flash_download_tool_3.9.7/
JC_Display_Firmware_7zoll/1-Demo/Demo_Arduino/*/lvgl/
JC_Display_Firmware_7zoll/1-Demo/Demo_IDF/
JC_Display_Firmware_7zoll/7-Character&Picture_Molding_Tool/
+6 -6
View File
@@ -11,17 +11,17 @@ für zwei Mikrocontroller, die per UART (230400 8N1, JSON-Zeilen) verbunden sind
Änderungshistorie: `Changelog.txt` (Root), Version steht in `String version = "…"`. Änderungshistorie: `Changelog.txt` (Root), Version steht in `String version = "…"`.
- **ESP32-P4 UART-Touch-Display (spiegelt den S3-Zustand).** - **ESP32-P4 UART-Touch-Display (spiegelt den S3-Zustand).**
Ordner: `JC_Display_Firmware/` (Hauptsketch `JC_Display_Firmware.ino`, Ordner: `P4_Display_Firmware/` (Hauptsketch `P4_Display_Firmware.ino`,
UI in `ui.cpp`/`ui.h`, UART-Protokoll in `protocol_client.*`, LVGL 9.x). UI in `ui.cpp`/`ui.h`, UART-Protokoll in `protocol_client.*`, LVGL 9.x).
Hat **kein eigenes WLAN** (der ESP32-C6-Co-Prozessor ist ungenutzt); alle Daten Hat **kein eigenes WLAN** (der ESP32-C6-Co-Prozessor ist ungenutzt); alle Daten
kommen über UART vom S3. Eigene Version `DISPLAY_FW_VERSION` + kommen über UART vom S3. Eigene Version `DISPLAY_FW_VERSION` +
`JC_Display_Firmware/Changelog.txt`. `P4_Display_Firmware/Changelog.txt`.
Wenn ich „S3/Controller/Maschine" sage, ist `Dual_PID_FastHeatUp.ino` gemeint; Wenn ich „S3/Controller/Maschine" sage, ist `Dual_PID_FastHeatUp.ino` gemeint;
„P4/Display" ist der Ordner `JC_Display_Firmware/`. „P4/Display" ist der Ordner `P4_Display_Firmware/`.
**Nicht anfassen:** Der Ordner `Sicherungen/` enthält nur datierte Versions-Backups **Nicht anfassen:** Der Ordner `Sicherungen/` enthält nur datierte Versions-Backups
(`.ino`) — niemals dort editieren. Unter `JC_Display_Firmware/JC4880P443C_I_W/` liegen (`.ino`) — niemals dort editieren. Unter `P4_Display_Firmware/JC4880P443C_I_W/` liegen
reine Hersteller-Demos/Beispiele (Board-Support), ebenfalls nicht als Projektcode behandeln. reine Hersteller-Demos/Beispiele (Board-Support), ebenfalls nicht als Projektcode behandeln.
# Agent Instructions # Agent Instructions
@@ -33,8 +33,8 @@ reine Hersteller-Demos/Beispiele (Board-Support), ebenfalls nicht als Projektcod
## Git-Workflow ## Git-Workflow
- Host: Gitea `git.mueller.black`, Repo `thomas/Dual-PID`, User `thomas`, CLI `tea`. - Host: Gitea `git.mueller.black`, Repo `thomas/Dual-PID`, User `thomas`, CLI `tea`.
- Aenderungen immer ueber Feature-Branch + Merge-Request (`tea pr create`), nie direkt auf `main` committen oder pushen. - Aenderungen immer ueber Feature-Branch + Merge-Request, nie direkt auf `main` committen oder pushen.
- Pull/Merge-Requests dürfen immer selbstständig gemergt werden (Auto-Merge, kein grünes CI erforderlich, via `tea pr merge` oder Gitea-API); danach den Merge kurz melden. - PR anlegen **und** mergen bevorzugt über die **Gitea-REST-API** (PowerShell `Invoke-RestMethod` auf `https://git.mueller.black/api/v1/repos/thomas/Dual-PID/pulls[/<nr>/merge]`, Token aus `C:\Users\Thomas\AppData\Local\tea\config.yml`, nur in Variable, nie ausgeben), da die `tea`-CLI gegen `git.mueller.black` oft hängt und nie zurückkehrt. `tea pr create`/`tea pr merge` nur als Ausweichweg. PRs dürfen selbstständig gemergt werden (Auto-Merge, kein grünes CI erforderlich); danach den Merge kurz melden.
- Kein Force-Push, kein History-Rewrite auf geteilten Branches. - Kein Force-Push, kein History-Rewrite auf geteilten Branches.
- Commit-Identitaet bleibt `raw-designs` / `Thomas@raw-designs.de` (globale Git-Config, nicht aendern). - Commit-Identitaet bleibt `raw-designs` / `Thomas@raw-designs.de` (globale Git-Config, nicht aendern).
- Commit-Messages im Conventional-Commits-Format (feat/fix/docs/refactor/...). - Commit-Messages im Conventional-Commits-Format (feat/fix/docs/refactor/...).
+172
View File
@@ -1,3 +1,175 @@
Version 5.6.3:
- Bug-Fix: Beim Aufwecken über das Touch-Display blieb die Auswahl „Espresso oder Cold Extraction"
manchmal aus, während sie über den Schalter kam. Ursache waren zwei unterschiedliche Bedingungen für
ein und dieselbe Sache: Das Display prüfte zum ANZEIGEN der Auswahl zusätzlich, ob der Modus gerade
scharfschaltbar ist (cxArmable) - zum UNTERDRÜCKEN der Rückfrage der S3 („wakeEspresso") aber nicht.
Fiel diese Bedingung weg, zeigte der Dialog nur noch „Aufwecken", schloss aber trotzdem das
Auswahlfenster der S3: Die Frage war auf beiden Seiten weg. Der Schalterweg hatte diese Lücke nie,
weil er allein an der S3 hängt. Behoben in der Display-Firmware 1.4.2.
- Diagnose: /Cold-Extraction zeigt jetzt zusätzlich zum Live-Stand der Voraussetzungen auch das
LETZTE Aufwecken - über welchen Weg (Schalter/Timer/Web oder Display, dort mit getroffener Wahl)
und mit welchem Ergebnis (Fenster geöffnet, am Display bereits entschieden, Abfrage aus, kein
Display verbunden, Wartung/Reinigung/Tuning aktiv, nicht freigeschaltet). Hintergrund: Die
Live-Liste zeigt nur den jetzigen Stand - blieb die Frage aus, hatte sich die Lage bis zum
Nachsehen längst wieder geändert, und der Grund war nicht mehr zu ermitteln.
- Die Entscheidung über das Auswahlfenster ist als Bedingungskette geschrieben, damit jeder Zweig
seinen Grund hinterlässt (vorher eine einzige verknüpfte Bedingung ohne Rückmeldung).
Version 5.6.2:
- Bug-Fix: Die Abfrage beim Aufwecken erschien nicht, sobald der Cold-Extraction-Modus noch aktiv war -
und da er persistent ist, war er das nach dem ersten kalten Bezug dauerhaft. Die Bedingung „Modus ist
aus" war falsch gedacht („sonst gibt es nichts zu fragen"): Die Auswahl muss in BEIDE Richtungen gehen,
also auch von kalt zurück auf Espresso. Ohne das hing man nach dem ersten kalten Bezug im kalten Modus
fest, ohne beim Aufwecken je wieder gefragt zu werden.
- „Espresso" beendet jetzt einen laufenden Cold-Extraction-Modus, statt ihn nur nicht einzuschalten.
Sonst wäre die Auswahl eine Einbahnstraße gewesen.
- Das Auswahlfenster bleibt bei aktivem Modus offen (bisher wurde es sofort geschlossen); die Frage
lautet dann „weiter kalt oder zurück auf Espresso?".
- Die Diagnose auf /Cold-Extraction listet den Modus nicht mehr als Voraussetzung, sondern zeigt ihn
als Ist-Zustand an.
Version 5.6.1:
- Diagnose: Die Seite /Cold-Extraction zeigt jetzt live, welche Voraussetzungen der Abfrage beim
Aufwecken erfüllt sind (freigeschaltet, Abfrage eingeschaltet, Touch-Display verbunden, Modus
gerade aus, kein Wartungsmodus/Reinigung/Tuning) und ob das Auswahlfenster gerade offen ist.
Hintergrund: Die Abfrage hängt an fünf Bedingungen gleichzeitig - fehlt eine, passiert einfach
nichts, und es war von außen nicht erkennbar welche.
- Die Abfrage kommt jetzt auch nach einem Kaltstart, nicht nur nach dem Standby. Wer die Maschine
am Netzschalter einschaltet, durchläuft nie einen Standby-Übergang; dieser Weg war deshalb bisher
nicht abgedeckt. Startet die Maschine in den Standby hinein, bleibt es beim Standby-Übergang.
Version 5.6.0:
- Die Abfrage „Espresso oder Cold Extraction" kommt jetzt bei JEDEM Weg aus dem Standby, also auch beim
Aufwecken über den Schalter. Bisher war sie eine reine Vorab-Abfrage des Touch-Displays und erschien nur
beim Antippen des Standby-Bildschirms - beim Schalter ist der Standby aber schon beendet, bevor das
Display überhaupt fragen könnte. Dieser Weg war damit nie abgedeckt.
- Dafür hält die Steuerung nach dem Aufwecken ein Auswahlfenster offen (20 s): Der Wasserkreis heizt in
dieser Zeit nicht, das Display zeigt die Auswahl mit Countdown. Ohne Entscheidung wird anschließend
normal geheizt - es geht also nichts verloren, wenn niemand hinsieht.
- Das Fenster öffnet nur mit angeschlossenem Touch-Display (ohne Display könnte niemand wählen), bei
freigeschalteter Funktion und aktivierter Einstellung „Abfrage beim Aufwecken", und nur wenn der
Cold-Extraction-Modus nicht ohnehin schon aktiv ist. Es schließt sofort bei einer Entscheidung, bei
Bezug/Spülen oder erneutem Standby.
- Neue Aktion „wakeEspresso": beendet den Standby (falls noch aktiv) und schließt das Auswahlfenster.
Das Display sendet sie statt „deactivateStandby", sobald die Auswahl relevant ist - sonst würde nach
dem Aufwecken über den Dialog gleich nochmal gefragt.
- Neue State-Felder „cxWakeChoice" (Fenster offen) und „cxWakeChoiceSec" (Restsekunden).
Version 5.5.0:
- Neu: Beim Aufwecken aus dem Standby fragt das Touch-Display, ob normal geheizt (Espresso) oder kalt
extrahiert werden soll. Damit lässt sich das bei jedem Aufwecken neu entscheiden, ohne vorher die
Web-UI zu bedienen. Bei „Cold Extraction" wird der Modus scharf geschaltet, BEVOR der Standby endet -
der Wasserkreis heizt also gar nicht erst an.
- Abschaltbar über die neue Einstellung „Abfrage beim Aufwecken" auf /Cold-Extraction bzw. auf der
Cold-Extraction-Seite des Touch-Displays. Ohne angeschlossenes Display oder bei nicht freigeschalteter
Funktion hat sie keine Wirkung; ist sie aus, bleibt der bisherige Dialog („Aufwecken" / „Abbrechen").
- Neues UART-Feld „cxAskOnWake" im State und als Feld in saveBrew.
- Die Meldungstexte der kalten Extraktion sind jetzt mit echten Umlauten geschrieben („nicht möglich"
statt „nicht moeglich"). Für das OLED werden sie beim Rendern nach CP437 übersetzt (neue Hilfsfunktion
oledUmlauts) - dort hätte UTF-8 sonst zwei Fehlzeichen je Umlaut ergeben. Web-UI und Touch-Display
stellen UTF-8 ohnehin direkt dar.
Version 5.4.1:
- Bug-Fix: Während einer kalten Extraktion stand als Zielgewicht überall noch der Brew-by-Weight-Wert
des normalen Espresso-Bezugs. Betroffen waren OLED, Web-Dashboard und Touch-Display, weil alle drei
direkt brewByWeightTargetGrams anzeigten - die kalte Extraktion ersetzt Brew-by-Weight aber und stoppt
nach coldExtractionTargetGrams. Neue Hilfsfunktion activeTargetWeightGrams() liefert jetzt das Ziel
des aktuellen bzw. nächsten Bezugs; das UART-Feld "targetWeight" führt diesen Wert (bbwTarget bleibt
unverändert die Roh-Einstellung).
- Auf dem OLED wird während eines kalten Bezugs jetzt überhaupt Gewicht angezeigt: Die Anzeige hing an
„Brew-by-Weight aktiv", das bei kalter Extraktion typischerweise aus ist - trotz vorhandenem Ziel.
Version 5.4.0:
- Cold Extraction hat eine eigene Seite: /Cold-Extraction, eigener Eintrag im Menü. Auf /Brew-Control
steht nur noch ein Verweis darauf - die Seite war mit dem angehängten Block unübersichtlich geworden.
- Die neue Seite ist in Abschnitte gegliedert (Grundeinstellung, Vorbenetzung, Hauptbezug, Bezugsende,
Pumpenschutz), jeder mit einer kurzen Erklärung, warum der Wert so eingestellt gehört.
- Wichtig für bestehende Installationen: Ein Speichern auf /Brew-Control fasst die Cold-Extraction-
Einstellungen nicht mehr an. Vorher wertete dieser Handler die cx-Felder mit aus; nach dem Verschieben
hätte ein Speichern dort die Freischaltung abgeschaltet, weil die Checkbox im Formular fehlt.
- Das UART-Kommando "saveBrew" akzeptiert jetzt zusätzlich die Cold-Extraction-Felder (cxEnabled,
cxMaxTemp, cxPreInf, cxPreInfDuty, cxPulseMs, cxDuty, cxTarget, cxMaxSecs, cxPumpMaxRun, cxPumpRest) -
damit lässt sich die Funktion auch am Touch-Display vollständig einstellen.
- Das State-JSON liefert die restlichen Parameter mit (cxPreInfDuty, cxDuty, cxPulseMs, cxPumpMaxRun,
cxPumpRest), damit das Display seine Eingabefelder vorbelegen kann.
Version 5.3.6:
- Cold Extraction: Die Vorbenetzung läuft nicht mehr mit voller Pumpenleistung, sondern gepulst mit
eigener, niedriger Leistung (neue Einstellung „Pumpenleistung Vorbenetzung", Standard 10 %).
Messung an der Maschine: Nach 7 s Vorbenetzung kommen heiß 1-2 Tropfen, kalt bei voller Leistung
rund 10 g dünne Brühe ohne Crema - etwa zehnfacher Durchfluss trotz dreifacher Viskosität.
Ursache ist nicht die Viskosität, sondern das Kaffeebett: Kaltes Mehl quillt nicht, ist schlecht
benetzbar, es wandert kein Feinanteil und es lösen sich kaum Feststoffe, die das Bett verdichten.
Volle Leistung auf den trockenen kalten Puck legt deshalb sofort Kanäle an, die sich - anders als
bei heißem Bezug - nie wieder schließen; der restliche Bezug läuft daran vorbei statt zu extrahieren.
- Standard der Pumpenleistung im Hauptbezug von 30 % auf 20 % gesenkt (gleicher Grund: das kalte Bett
ist erheblich durchlässiger als ein heißes). Bestehende Installationen behalten ihren Wert.
- Der Erklärtext auf /Brew-Control benennt den Zusammenhang jetzt und weist darauf hin, dass die
Vorbenetzung deutlich schwächer eingestellt sein sollte als der Hauptbezug.
Version 5.3.5:
- Cold Extraction lässt sich jetzt auch im Standby und bei noch warmem Kessel scharf schalten.
Bisher galt „Standby aktiv" und „Kessel zu warm" auch fürs Aktivieren - das war falsch herum
gedacht: Der Modus schaltet die Heizung AB, man kam aber erst an den Schalter, nachdem die
Maschine aufgeweckt war und bereits zu heizen begonnen hatte. Genau der Ablauf, den die
Funktion vermeiden soll.
- Scharfschalten und Beziehen sind jetzt zwei getrennte Bedingungen:
- Scharfschalten (neues State-Feld „cxArmable"): blockiert nur durch fehlende Freischaltung,
Wartungsmodus, Reinigungsassistent, PID-Tuning oder einen laufenden Bezug/Spülvorgang.
- Bezug (unverändert „cxAllowed"): zusätzlich kein Standby, Kessel unter der Freigabeschwelle,
gültiger Sensorwert.
Ist der Modus scharf, der Bezug aber noch gesperrt, sagen Web-UI und Display das jetzt
ausdrücklich („Modus aktiv, Bezug noch gesperrt: …").
- Wird bei warmem Kessel aktiviert, geht die Wasserheizung sofort aus und der Kessel kühlt ab;
die Rückmeldung weist darauf hin, dass Bezüge bis zum Unterschreiten der Schwelle gesperrt sind.
Version 5.3.4:
- Cold Extraction weist jetzt deutlich darauf hin, wenn keine Waage aktiv/verbunden ist. Ohne Waage
greifen weder das Zielgewicht noch die Stillstands-Erkennung - der Bezug endet dann erst nach der
maximalen Bezugsdauer, die damit faktisch zur Dosierung wird. Bisher passierte das stillschweigend.
- Die Funktion bleibt ohne Waage bewusst nutzbar (Zeitsteuerung als Rückfallebene): Ein kurzzeitig
abgemeldeter HX711 soll die kalte Extraktion nicht komplett blockieren.
- Angezeigt wird das an vier Stellen: dauerhafter Warnhinweis auf /Brew-Control (dort ist das
Zielgewicht-Feld zusätzlich als „nur mit Waage wirksam" beschriftet), Hinweiszeile unter dem
Dashboard-Schalter, Statustext „Cold Extraction bereit (ohne Waage: Zeitsteuerung)" sowie eine
einmalige Meldung beim Aktivieren.
- Neuer Code im UART-Feld cxNotice: 4 = ohne Waage aktiviert (additiv, Protokoll bleibt v2).
Version 5.3.3:
- Bug-Fix (Cold Extraction, hätte die Funktion mit Waage komplett unbrauchbar gemacht): Die
Stillstands-Erkennung lief bereits während der Vorbenetzung mit. In dieser Phase sättigt der Puck
aber erst, es kommt naturgemäß nichts in der Tasse an - bei einer Vorbenetzung länger als die
15 s Stillstands-Zeit wurde deshalb JEDER kalte Bezug mit „kein Zulauf" abgebrochen. Mit dem
bisherigen Standard von 30 s Vorbenetzung wäre das immer passiert. Die Erkennung startet jetzt
erst mit dem Hauptbezug.
- Standard der Vorbenetzung von 30 s auf 10 s gesenkt. In dieser Phase läuft die Pumpe bewusst
ungepulst auf voller Leistung; bei heißem Bezug kommt der erste Tropfen nach etwa 7 s, kalt
(rund dreifache Viskosität) entsprechend später. 30 s wären also längst voller Bezug bei vollem
Druck gewesen, und die Hälfte des Pumpen-Laufzeitbudgets wäre vor dem eigentlichen Bezug
verbraucht. Bestehende Installationen behalten ihren gespeicherten Wert - dort ggf. auf
/Brew-Control nachziehen.
- Die Beschriftung auf /Brew-Control sagt jetzt ausdrücklich, dass die Pumpe während der
Vorbenetzung durchläuft und erst im Hauptbezug gepulst wird.
Version 5.3.2:
- Cold Extraction sendet den Sperrgrund jetzt als Code („cxBlock") statt als Klartext („cxMessage"),
dazu eine transiente Meldung als Code („cxNotice") und die Vorbenetzungsdauer („cxPreInf").
Hintergrund: Die State-Zeile ans Touch-Display liegt real bereits bei rund 2,1 KB, und der P4
verwirft eingehende Zeilen ab einer Obergrenze komplett - ein bis zu 110 Byte langer Klartext
hätte die Zeile über diese Grenze gedrückt und damit die ganze Telemetrie unbrauchbar gemacht.
Die Texte rendert jetzt das Display, analog zur bestehenden Trennung statusKey/statusText.
- Der Klartext bleibt unverändert auf dem OLED und in den Web-/UART-Antworten (ack-message).
Version 5.3.1:
- Cold Extraction ist jetzt persistent: Der Modus wird im EEPROM gespeichert und überlebt sowohl den
Standby als auch einen Neustart. Zuvor wurde er beim Standby beendet und war nach dem Aufwachen aus -
die Maschine heizte dann erst auf, obwohl der nächste kalte Bezug einen kalten Kessel braucht, und man
musste auf das Abkühlen warten. Wer nur kalte Bezüge machen will, schaltet den Modus damit einmal ein
und wieder aus, wenn er fertig ist.
- Der Modus fällt nur weg, wenn auf /Brew-Control die Freischaltung entzogen wird oder ein Werksreset
läuft. Solange er aktiv ist, heizt der Wasserkreis nicht - Dashboard, OLED („(COLD)" in der Wasserzeile)
und Touch-Display zeigen das durchgehend an.
Version 5.3.0: Version 5.3.0:
- Neu: Cold Extraction (kalte Extraktion). Die Maschine hat keinen Bypass um den Wasserkessel, kaltes Wasser - Neu: Cold Extraction (kalte Extraktion). Die Maschine hat keinen Bypass um den Wasserkessel, kaltes Wasser
kommt also nur bei kaltem Kessel am Puck an. Die Funktion wird deshalb über eine Temperaturschwelle kommt also nur bei kaltem Kessel am Puck an. Die Funktion wird deshalb über eine Temperaturschwelle
@@ -1,4 +1,4 @@
# JC4880P443C-I-W Touch-Display — UART-Protokoll & Architektur # P4-Touch-Display — UART-Protokoll & Architektur
Status: Entwurf v1.0 der Doku · Firmware-Protokollversion: **1** (vorhanden, abgeschaltet) · Status: Entwurf v1.0 der Doku · Firmware-Protokollversion: **1** (vorhanden, abgeschaltet) ·
Ziel-Protokollversion: **2** (geplante Erweiterungen) Ziel-Protokollversion: **2** (geplante Erweiterungen)
@@ -16,11 +16,11 @@ Bewusste Designentscheidung (siehe Projektbesprechung):
- Das **Webinterface bleibt auf der Hauptplatine** (ESP32-S3). Es bleibt unverändert - Das **Webinterface bleibt auf der Hauptplatine** (ESP32-S3). Es bleibt unverändert
die zentrale Steuerungs- und Konnektivitätsinstanz. die zentrale Steuerungs- und Konnektivitätsinstanz.
- Das **JC-Display ist ein vollwertiges Touch-Bediengerät**, das über UART angebunden - Das **P4-Display ist ein vollwertiges Touch-Bediengerät**, das über UART angebunden
ist und denselben Funktionsumfang bietet wie das Webinterface — aber **kein** eigenes ist und denselben Funktionsumfang bietet wie das Webinterface — aber **kein** eigenes
WLAN/Webserver betreibt. WLAN/Webserver betreibt.
- Das **OLED (SH1106G) bleibt optional** und wird wie bisher über ein **Compile-Flag** - Das **OLED (SH1106G) bleibt optional** und wird wie bisher über ein **Compile-Flag**
gewählt (`ENABLE_DISPLAY`). Es ist unabhängig vom JC-Display. gewählt (`ENABLE_DISPLAY`). Es ist unabhängig vom P4-Display.
``` ```
+----------------------------+ UART (GPIO43/44) +---------------------------+ +----------------------------+ UART (GPIO43/44) +---------------------------+
@@ -34,13 +34,13 @@ Bewusste Designentscheidung (siehe Projektbesprechung):
| - OLED (optional) | | ST7701 / GT911 / ES8311 | | - OLED (optional) | | ST7701 / GT911 / ES8311 |
+----------------------------+ +---------------------------+ +----------------------------+ +---------------------------+
* Der ESP32-C6 auf dem JC-Display (WLAN/BT) wird in diesem Projekt NICHT für die * Der ESP32-C6 auf dem P4-Display (WLAN/BT) wird in diesem Projekt NICHT für die
Maschinensteuerung benötigt. WLAN/Webinterface laufen weiterhin auf der S3. Maschinensteuerung benötigt. WLAN/Webinterface laufen weiterhin auf der S3.
``` ```
### Rollenverteilung ### Rollenverteilung
| Aufgabe | S3 (Hauptplatine) | P4 (JC-Display) | | Aufgabe | S3 (Hauptplatine) | P4 (P4-Display) |
|---|---|---| |---|---|---|
| Echtzeit-Regelung (PID, SSR, Sicherheit) | **ja** | nein | | Echtzeit-Regelung (PID, SSR, Sicherheit) | **ja** | nein |
| Sensoren (NTC, MAX6675, Waage) | **ja** | nein | | Sensoren (NTC, MAX6675, Waage) | **ja** | nein |
@@ -76,7 +76,7 @@ schickt Bedienwünsche als Kommandos. So bleiben Web-UI, OLED und Touch-Display
``` ```
> **Hinweis (bewusst akzeptiert):** GPIO43/44 sind beim ESP32-S3 zugleich die Standard- > **Hinweis (bewusst akzeptiert):** GPIO43/44 sind beim ESP32-S3 zugleich die Standard-
> Pins der seriellen Debug-Konsole (UART0). Solange das JC-Display dort angeschlossen ist, > Pins der seriellen Debug-Konsole (UART0). Solange das P4-Display dort angeschlossen ist,
> steht die klassische serielle Debug-Ausgabe an diesen Pins nicht parallel zur Verfügung. > steht die klassische serielle Debug-Ausgabe an diesen Pins nicht parallel zur Verfügung.
> Debug/Logging erfolgt dann über USB-CDC bzw. das Webinterface. Diese Pins wurden > Debug/Logging erfolgt dann über USB-CDC bzw. das Webinterface. Diese Pins wurden
> ausdrücklich beibehalten, weil sie auf der Platine herausgeführt sind. > ausdrücklich beibehalten, weil sie auf der Platine herausgeführt sind.
@@ -322,15 +322,31 @@ Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Ze
| `startColdExtraction` | — | Modus aktivieren (Wasserkreis heizt nicht, Dampf bleibt möglich) | | `startColdExtraction` | — | Modus aktivieren (Wasserkreis heizt nicht, Dampf bleibt möglich) |
| `stopColdExtraction` | — | Modus beenden | | `stopColdExtraction` | — | Modus beenden |
| `toggleColdExtraction` | — | Modus umschalten | | `toggleColdExtraction` | — | Modus umschalten |
| `wakeEspresso` | — | Standby beenden (falls aktiv) und das Auswahlfenster schließen, also „normal heizen" wählen (ab S3 5.6.0) |
Alle drei Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht Für das Bedienelement zählt `cxArmable`, **nicht** `cxAllowed`: Der Modus schaltet die
erfüllt sind — Hauptfall ist ein zu warmer Wasserkessel (`cxAllowed:false`). Der Modus wird Wasserheizung ab und muss deshalb auch im Standby und bei noch warmem Kessel scharf zu
**nicht** persistiert und ist nach jedem Neustart aus; Standby beendet ihn. Ein laufender stellen sein — sonst heizt die Maschine nach dem Aufwecken an, bevor man an den Schalter
Bezug wird nie abgebrochen, `stopColdExtraction` schlägt dann fehl. kommt. `cxAllowed` gilt nur für den einzelnen Bezug; ist der Modus scharf und `cxAllowed`
false, sollte die UI „Modus aktiv, Bezug noch gesperrt" zeigen.
Die Parameter der kalten Extraktion (Schwelle, Vorbenetzung, Pulsperiode, Pumpenleistung, Ein Aufwecken direkt in die kalte Extraktion sendet `startColdExtraction` **vor**
Zielgewicht, Timeout, Pumpenschutz) sind bewusst **nicht** über `saveBrew` erreichbar, sondern `deactivateStandby` — in dieser Reihenfolge, damit der Wasserkreis gar nicht erst anheizt.
nur über die Web-Seite `/Brew-Control`. Das Display schaltet den Modus, konfiguriert ihn nicht.
Die Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht
erfüllt sind. Der Modus ist
**persistent** (EEPROM) und überlebt Standby und Neustart; er endet nur durch explizites
Ausschalten, durch Entzug der Freischaltung auf `/Brew-Control` oder durch einen Werksreset.
Das P4-UI sollte `cxActive` deshalb dauerhaft sichtbar spiegeln — sonst wundert sich der
Benutzer, warum die Maschine nicht heizt. Ein laufender Bezug wird nie abgebrochen,
`stopColdExtraction` schlägt dann fehl.
Die Parameter der kalten Extraktion sind ab S3 5.4.0 über `saveBrew` einstellbar (zusätzlich
zur Web-Seite `/Cold-Extraction`). Zulässige Felder: `cxEnabled` (bool), `cxMaxTemp`,
`cxPreInf`, `cxPreInfDuty`, `cxPulseMs`, `cxDuty`, `cxTarget`, `cxMaxSecs`, `cxPumpMaxRun`,
`cxPumpRest`, `cxAskOnWake` (bool). Es werden nur die tatsächlich gesendeten Felder übernommen; Grenzwerte prüft
die S3. Die aktuellen Werte stehen alle im `state`, damit das Display seine Eingabefelder
vorbelegen kann.
**Persistente Toggles** **Persistente Toggles**
@@ -380,12 +396,39 @@ Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 v
| `cxEnabled` | bool | Funktion in der Web-UI freigeschaltet (sonst Bedienelement ausblenden) | | `cxEnabled` | bool | Funktion in der Web-UI freigeschaltet (sonst Bedienelement ausblenden) |
| `cxActive` | bool | Modus scharf: Wasserkreis heizt nicht | | `cxActive` | bool | Modus scharf: Wasserkreis heizt nicht |
| `cxShot` | bool | Der laufende Bezug ist eine kalte Extraktion | | `cxShot` | bool | Der laufende Bezug ist eine kalte Extraktion |
| `cxAllowed` | bool | Freigabe **jetzt** (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) | | `cxAllowed` | bool | **Bezug** jetzt möglich (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) |
| `cxArmable` | bool | **Modus umschaltbar** — auch im Standby und bei warmem Kessel (ab S3 5.3.5) |
| `cxResting` | bool | Zwangspause des Pumpenschutzes läuft (Bezug läuft weiter) | | `cxResting` | bool | Zwangspause des Pumpenschutzes läuft (Bezug läuft weiter) |
| `cxMaxTemp` | float | Freigabeschwelle in °C (für eigene Hinweistexte am Display) | | `cxMaxTemp` | float | Freigabeschwelle in °C (für eigene Hinweistexte am Display) |
| `cxTarget` | float | Zielgewicht in g | | `cxTarget` | float | Zielgewicht in g |
| `cxPreInf` | float | Vorbenetzung in s (auch für die Phasenleiste des kalten Bezugs) |
| `cxPreInfDuty` | float | Pumpenleistung Vorbenetzung in % (ab S3 5.4.0) |
| `cxDuty` | float | Pumpenleistung Hauptbezug in % (ab S3 5.4.0) |
| `cxPulseMs` | uint16 | Pulsperiode in ms (ab S3 5.4.0) |
| `cxPumpMaxRun` | float | Pumpenschutz: max. kumulierte Laufzeit in s (ab S3 5.4.0) |
| `cxPumpRest` | float | Pumpenschutz: Zwangspause in s (ab S3 5.4.0) |
| `cxAskOnWake` | bool | Beim Aufwecken aus dem Standby nach Espresso/Cold Extraction fragen (ab S3 5.5.0) |
| `cxWakeChoice` | bool | Auswahlfenster nach dem Aufwecken offen — Wasserkreis heizt solange nicht (ab S3 5.6.0) |
| `cxWakeChoiceSec` | long | Restsekunden der Bedenkzeit |
| `cxMaxSecs` | float | Sicherheits-Timeout des Bezugs in s | | `cxMaxSecs` | float | Sicherheits-Timeout des Bezugs in s |
| `cxMessage` | string | Sperr-/Abbruchmeldung, leer = keine (läuft nach ~6 s selbst ab) | | `cxBlock` | uint8 | Sperrgrund (siehe unten), `0` = frei |
| `cxNotice` | uint8 | transiente Meldung, `0` = keine (läuft nach ~6 s selbst ab) |
`cxBlock`: `0` frei, `1` nicht freigeschaltet, `2` Standby, `3` Wartungsmodus,
`4` Reinigungsassistent, `5` PID-Tuning, `6` Sensorfehler, `7` Wasserkessel zu warm.
`cxNotice`: `0` keine, `1` Start abgewiesen (Grund steht in `cxBlock`), `2` Bezug wegen
fehlenden Zulaufs beendet, `3` Umschalten während eines Bezugs abgelehnt, `4` ohne Waage
aktiviert (ab S3 5.3.4).
**Ohne Waage** (`scaleEnabled`/`scaleConnected` false) läuft die kalte Extraktion rein
zeitgesteuert: `cxTarget` ist wirkungslos und die Stillstands-Erkennung ist inaktiv, es
bleibt `cxMaxSecs` als Ende. Das ist Absicht (ein kurz abgemeldeter HX711 soll die Funktion
nicht blockieren) und **kein** Sperrgrund — das Display sollte es aber dauerhaft anzeigen.
Bewusst **Codes statt Klartext**: Die State-Zeile muss unter dem Zeilenlimit des P4 bleiben
(`PROTO_RX_LINE_MAX`; sie liegt real bereits bei ~2,1 KB), und die Texte gehören ins Display —
dieselbe Trennung wie bei `statusKey`/`statusText`.
Während `cxShot` folgt die Phase weiterhin `piState`: `1` = Vorbenetzung, `3` = Hauptbezug Während `cxShot` folgt die Phase weiterhin `piState`: `1` = Vorbenetzung, `3` = Hauptbezug
(gepulst). Eine Pause-Phase (`2`) gibt es bei der kalten Extraktion nicht. (gepulst). Eine Pause-Phase (`2`) gibt es bei der kalten Extraktion nicht.
@@ -476,7 +519,7 @@ Für ein vollwertiges 480×800-Touch-UI fehlen v1 noch einige Daten/Funktionen.
## 9. OLED-Koexistenz (Compile-Flag) ## 9. OLED-Koexistenz (Compile-Flag)
- OLED bleibt über `ENABLE_DISPLAY` (Compile-Flag) wählbar — unverändert. - OLED bleibt über `ENABLE_DISPLAY` (Compile-Flag) wählbar — unverändert.
- Das JC-Display wird über `TOUCH_UART_ENABLED` (Compile-Flag) aktiviert. - Das P4-Display wird über `TOUCH_UART_ENABLED` (Compile-Flag) aktiviert.
- **Geplante Änderung:** beide Flags strikt **unabhängig** halten, damit alle Kombinationen - **Geplante Änderung:** beide Flags strikt **unabhängig** halten, damit alle Kombinationen
baubar sind: baubar sind:
@@ -519,7 +562,7 @@ Puffergrößen/Chunking, optionaler CRC + Handshake, `action`-Stringliste dokume
**Löschen:** Nichts. **Löschen:** Nichts.
**Fully-Kiosk bleibt erhalten** (Entscheidung) — als eigenständige, optionale Funktion **Fully-Kiosk bleibt erhalten** (Entscheidung) — als eigenständige, optionale Funktion
neben dem JC-Display. Lastbewertung der S3: neben dem P4-Display. Lastbewertung der S3:
- Einziger Aufruf `sendFullyKioskLoadUrl()` wird **nur bei Standby-Zustandswechsel** - Einziger Aufruf `sendFullyKioskLoadUrl()` wird **nur bei Standby-Zustandswechsel**
getriggert (`if (standbyModeActive != lastStandbyModeActive)`), **nicht periodisch** getriggert (`if (standbyModeActive != lastStandbyModeActive)`), **nicht periodisch**
(wenige Male pro Tag). (wenige Male pro Tag).
@@ -543,10 +586,10 @@ neben dem JC-Display. Lastbewertung der S3:
6. **[offen, optional]** CRC + ggf. Puffer-Feinjustage (§8.3) — bewusst nicht als toter, 6. **[offen, optional]** CRC + ggf. Puffer-Feinjustage (§8.3) — bewusst nicht als toter,
ungetesteter Code eingebaut; bei Bedarf später. ungetesteter Code eingebaut; bei Bedarf später.
7. **[offen, durch dich]** `TOUCH_UART_ENABLED` auf 1 setzen + Build verifizieren, **sobald** 7. **[offen, durch dich]** `TOUCH_UART_ENABLED` auf 1 setzen + Build verifizieren, **sobald**
das JC-Display physisch an Serial1 (GPIO43/44) hängt. Bewusst noch **0**, damit die das P4-Display physisch an Serial1 (GPIO43/44) hängt. Bewusst noch **0**, damit die
serielle Debug-Konsole (GPIO43/44) im aktuellen Betrieb erhalten bleibt. serielle Debug-Konsole (GPIO43/44) im aktuellen Betrieb erhalten bleibt.
### Phase B — P4-Seite (JC-Display, Projekt `JC_Display_Firmware/`) ### Phase B — P4-Seite (P4-Display, Projekt `P4_Display_Firmware/`)
Grundgerüst angelegt (Arduino-ESP32 P4 + LVGL 9 + ArduinoJson 7). Aufteilung: Grundgerüst angelegt (Arduino-ESP32 P4 + LVGL 9 + ArduinoJson 7). Aufteilung:
`config.h`, `machine_state.h`, `protocol_client.*`, `ui.*`, `display_hal.*`, `.ino`, `README.md`. `config.h`, `machine_state.h`, `protocol_client.*`, `ui.*`, `display_hal.*`, `.ino`, `README.md`.
@@ -560,7 +603,7 @@ Grundgerüst angelegt (Arduino-ESP32 P4 + LVGL 9 + ArduinoJson 7). Aufteilung:
(`display_hal.cpp`, Schalter `JC_USE_REAL_PANEL`): MIPI-DSI/ST7701, GT911 (I²C SDA=7/SCL=8), (`display_hal.cpp`, Schalter `JC_USE_REAL_PANEL`): MIPI-DSI/ST7701, GT911 (I²C SDA=7/SCL=8),
Backlight GPIO23, LCD-Reset GPIO5; LVGL-Port mit eigenem Task + Lock. Stub-Modus bleibt für Backlight GPIO23, LCD-Reset GPIO5; LVGL-Port mit eigenem Task + Lock. Stub-Modus bleibt für
Schreibtisch-Tests. Vendor-BSP-Dateien sind in den Sketch zu kopieren (siehe Projekt-README). Schreibtisch-Tests. Vendor-BSP-Dateien sind in den Sketch zu kopieren (siehe Projekt-README).
5. **[festgelegt]** UART-Pins am JC-Display: **P4 GPIO50 (TX) / GPIO51 (RX)** vom Expand-IO-Header 5. **[festgelegt]** UART-Pins am P4-Display: **P4 GPIO50 (TX) / GPIO51 (RX)** vom Expand-IO-Header
JP1 (Schaltplan `4_USB&IO.png`). Gegenseite S3 unverändert GPIO43/44. JP1 (Schaltplan `4_USB&IO.png`). Gegenseite S3 unverändert GPIO43/44.
6. **[offen]** Weitere Screens: Einstellungen (Brew/Service/Sensor), Profil-Auswahl/-Editor, 6. **[offen]** Weitere Screens: Einstellungen (Brew/Service/Sensor), Profil-Auswahl/-Editor,
PID/AutoTune-Bedienung; Ausrichtung Hochformat (Rotation 0 in `pins_config.h`). PID/AutoTune-Bedienung; Ausrichtung Hochformat (Rotation 0 in `pins_config.h`).
+718 -120
View File
File diff suppressed because it is too large Load Diff
-121
View File
@@ -1,121 +0,0 @@
Version 1.0.13:
- Profil-Schnellwahl + Anzeige des aktiven Profils (braucht S3-Firmware ab 5.1.1):
- Neuer Profil-Chip im Header (auf allen Seiten sichtbar): zeigt das zuletzt geladene bzw.
gespeicherte Profil; ein „*" hinter dem Namen bedeutet, dass seitdem profil-relevante
Einstellungen geändert wurden. Lange Namen werden gekürzt; ohne bekanntes Profil
(oder mit älterer S3-Firmware) bleibt der Chip unsichtbar.
- Antippen des Chips öffnet die Profil-Schnellwahl: ein Overlay mit allen gespeicherten
Profilen, das aktive mit Häkchen und Akzentfarbe hervorgehoben. Ein Tap lädt das Profil
direkt (Toast bestätigt, Chip aktualisiert sich mit dem nächsten State) — zwei Berührungen
vom Dashboard zum Profilwechsel statt Umweg über die Profile-Seite.
- Profile-Seite: das aktive Profil wird in der Liste mit Häkchen-Symbol und Akzentfarbe
markiert.
Version 1.0.12:
- Verlauf-Chart: Flackernde Heizleistungs-Kurven in den großen Zeitfenstern behoben. Die
Dezimierung pickte pro Chart-Punkt ein Einzelsample, dessen Abtast-Raster mit jedem
Sekundentick um eine Position wanderte — die schnell schaltende Heizleistung (PWM)
sprang dadurch sichtbar zwischen zwei Kurvenbildern hin und her. Jetzt wird über den
gesamten Zeitabschnitt eines Chart-Punkts gemittelt: das Bild steht ruhig, und die
Leistungs-Kurve zeigt die tatsächliche mittlere Heizleistung statt zufälliger
Momentwerte. Im 2-min-Fenster (1 Sample je Punkt) ändert sich nichts.
Version 1.0.11:
- Verlauf-Seite ausgebaut (nur P4, keine S3-/Protokolländerung nötig):
- Umschaltbares Zeitfenster: Buttons „2 min / 10 min / 30 min / 60 min" in der Kopfzeile.
Intern läuft eine 1-s-Historie als Ringpuffer über volle 60 Minuten; das Chart zeigt
weiterhin 120 Punkte und dezimiert je Fenster. Dadurch ist beim Fensterwechsel der
komplette Verlauf sofort da (kein leeres Chart, kein Neuaufbau). Die Wahl wird P4-lokal
im NVS gespeichert und überlebt Neustarts.
- Gestrichelte Soll-Linien für Wasser und Dampf im Chart (Farbe der jeweiligen Kurve,
gedimmt). Die Y-Skala bezieht die Soll-Werte mit ein, damit die Linien im Bild liegen —
aber nur für Kreise, deren Ist-Kurve im Fenster sichtbar ist (ein kalter Dampfkessel
zieht die Skala nicht auseinander).
- Heizleistung zuschaltbar: Button „Leistung" blendet Duty-Cycle Wasser/Dampf als gedimmte
Zusatzkurven auf einer zweiten Achse (0100 %) ein, inkl. Live-Legende. Einstellung
ebenfalls P4-lokal im NVS.
- Feinere Kurven: Die Temperatur-Historie wird jetzt in Zehntelgrad geführt und gezeichnet
(vorher ganzzahlig gerundet) — PID-Schwingungen um ±1 °C sind damit sauber ablesbar.
- Die ~29 KB Historie liegen im PSRAM (heap_caps_malloc, wie die LVGL-Framebuffer) —
als statische Arrays sprengten sie den internen RAM (Linker: 107 % belegt).
- Shot-Zusammenfassung: Karte auf 560 px verbreitert und Spaltenabstand zwischen
Dauer/Gewicht/Flow deutlich vergrößert (28 px statt 10 px) — die drei Werte standen
zu nahe beisammen und waren schlecht lesbar.
- PID-AutoTune sichtbar gemacht: Während eines Tuning-Laufs erscheint auf der Verlauf-Seite
eine Live-Karte (mit Spinner) — je Kreis Laufzeit, aktuelle Phase (heizt/kühlt ab) und
Schwingungshub samt Mittelpunkt (Min/Max der Ist-Temperatur seit Start). Nach dem Ende
zeigt die Karte ~30 s das Ergebnis (Erfolg/Abbruch samt Grund, farbcodiert). Die
PID-Tuning-Meldung in der Statuszeile ist jetzt antippbar und springt zur Verlauf-Seite,
wo die Schwingung auch als Kurve zu sehen ist.
Version 1.0.10:
- Live-Bezugsschirm (abschaltbar): Beim Start eines echten Bezugs blendet sich ein Vollbild-Overlay
weich ein — große Bezugszeit, Live-Gewicht und Flow samt Flow-Verlaufskurve der letzten 30 Sekunden
(nur bei verbundener Waage), Fortschrittsbalken Richtung Zielgewicht (Brew-by-Weight) bzw. Zielzeit
(Brew-by-Time) und Pre-Infusion-Phasenhinweis mit Restzeit. Über das X oben rechts für den laufenden
Bezug schließbar; beim nächsten Bezug erscheint er wieder. Ein/Aus-Schalter auf der Info-Seite unter
„Darstellung", die Einstellung wird P4-lokal im NVS gespeichert und überlebt Neustarts. Während
Flush und Reinigungsassistent erscheint das Overlay bewusst nicht.
- Shot-Zusammenfassung aufgewertet: Die Karte nach Bezugsende blendet jetzt weich ein/aus (Fade),
wirft einen Schlagschatten und zeigt Dauer, Endgewicht und mittleren Flow als große Werte mit
gedimmter Beschriftung statt reiner Textzeilen (Gewicht/Flow nur bei aktiver Waage).
- Pre-Infusion-Phasenanzeige auf dem Dashboard: Während eines Bezugs mit aktiver Pre-Infusion zeigt
eine Schrittleiste die Phasen „Pre-Infusion → Pause → Extraktion" an — erledigte Schritte mit Haken,
die aktive Phase hervorgehoben (Gold) inkl. Restzeit-Countdown (100-ms-Takt). Die aktive Phase kommt
quelltreu von der S3 (neues State-Feld "piState", ab S3-Firmware 5.0.12); bei älterer S3-Firmware wird
sie zeitbasiert aus den konfigurierten Phasendauern abgeleitet. Ist keine Pause konfiguriert (0 s),
wird der Pause-Schritt ausgeblendet.
- Temperatur-Seite: Solltemperaturen für Wasser und Dampf werden jetzt über radiale Drehregler (Arc,
270°) mit großem Wert mittig und /+-Feinjustage eingestellt. Gesendet wird entprellt (nach kurzer
Bedienpause bzw. sofort beim Loslassen des Reglers oder Verlassen der Seite), damit Drag-Gesten die
S3 nicht mit setTemp-Kommandos und EEPROM-Schreibzugriffen fluten. Tippen in die Reglermitte löst
bewusst keinen Wertsprung aus (ADV_HITTEST: nur Ring/Griff reagieren).
- Dashboard-Feinschliff (nur P4, keine S3-/Protokolländerung nötig):
- Weiche Temperatur-Zustandsfarben: Der große Ist-Wert färbt sich stufenlos über den Abstand
zum Soll (kontinuierliche Farbinterpolation statt harter Stufen) — Blau beim Aufheizen,
Gold kurz vor dem Ziel, Grün im Zielband (±0,5 °C), Richtung Warngelb bei deutlichem
Überschwinger. Dazu neue „BEREIT"-Pille im Header (auf allen Seiten sichtbar), wenn der
Wasserkessel — und der Dampfkessel, sofern er heizt — im Zielband liegt, kein Fehler/
Standby/Eco aktiv ist und kein Aufheiz-Countdown läuft; mit Hysterese gegen Flackern
an der Bandgrenze, erscheint und verschwindet weich (Fade).
- Umschaltbares Temperatur-Design: Die Dashboard-Temperaturanzeige gibt es jetzt in fünf
Varianten — „Klassisch" (bisherige Karten mit Prozentbalken), „Instrumente" (freistehende
Rundinstrumente im Manometer-Look: Metall-Bezel mit Verlauf und Schlagschatten für eine
leichte 3D-Wirkung, 270°-Bogen füllt sich in der Zustandsfarbe, Soll-Wert in der
Bogen-Lücke), „Minimal" (komplett rahmenlos, nur große Werte mit dünner Ist/Soll-Linie),
„Soft-3D" (Neumorph: Fläche fast im Hintergrundton wirkt herausgedrückt — dunkler
Schatten unten rechts, heller Lichtschein oben links, eingelassener Balken) und
„Thermometer" (vertikale Säulen, die sich von unten in der Zustandsfarbe füllen, mit
Glanzstreifen als Glas-Effekt und rundem Kolben unten). Außer bei „Klassisch" folgt
auch die Füllfarbe von Balken/Bogen/Säule stufenlos der Zustandsfarbe des Ist-Werts.
Umschalter auf der Info-Seite unter „Darstellung"; die Wahl wird P4-lokal im NVS
gespeichert und überlebt Neustarts. Standard: Instrumente.
- Konsolidierte Statuszeile: Die sechs gestapelten Dashboard-Banner (Sicherheit, Dampf,
Reinigung, PID-Tuning, Wartung, Aufheiz-Countdown) teilen sich jetzt EINE feste einzeilige
Meldung — mehrere aktive Hinweise rotieren alle 4 s weich durch (Fade) statt das Layout
zu verschieben, Priorität Rot > Gelb > Blau. Wartungs- und Reinigungsmeldung bleiben
antippbar (Serviceseite bzw. Reinigungs-Sub-Screen), Überlängen laufen als Laufschrift durch.
Version 1.0.9:
- Weiche Übergänge (dezente Fade-Animationen) hinzugefügt:
- Start-Animation: Boot-Splash mit Logo, Untertitel, Lade-Spinner ("Verbinde mit Maschine...")
und Firmware-Stand; blendet sich beim Start weich ein und beim ersten State von der S3
(Verbindung steht) wieder aus.
- Einschlaf-Animation: Die Standby-Uhr wird beim Wechsel in den Standby weich eingeblendet.
- Aufweck-Animation: Beim Aufwecken blendet das Standby-Overlay weich aus und gibt das
Dashboard frei.
- Umsetzung speicher-neutral über das 'opa'-Style (kein opa_layered/Vollbild-Puffer), um den
LVGL-Speicherpool nicht zu belasten. Bei ausgeschalteter Standby-Uhr (Display aus) gibt es
weiterhin keine Einschlaf-Animation (schwarzer Bildschirm bietet keine Anzeigefläche).
Version 1.0.8:
- Statistik-Seite hinzugefügt
Version 1.0.7:
- Brew-by-Weight-Gewichtseingabelimit auf 150g erhöht, um auch Heißwasserbzug für Americano oder ähnliches via BBW zu ermöglichen
Version 1.0.6:
- Boot-Loop-Schutz implementiert:
P4-Boot-Loop-Schutz + „Update-Modus", der den UART-OTA offenhält.
Wenn die P4-Firmware in eine Absturzschleife läuft, erkennt sie das (RTC-Zähler wie beim S3) und
startet in einen abgespeckten Modus, der bewusst das riskanteste überspringt — vor allem die Display-/LVGL-Init (der wahrscheinlichste P4-Absturzgrund, siehe unser LVGL-Mem-Pool-Thema) — und nur den UART-Empfang + OTA-Receiver laufen lässt. Dann kann der S3 die P4-Firmware jederzeit per UART neu flashen, auch wenn die normale P4-Oberfläche kaputt ist. (Optional: eine ganz simple statische „Update-Modus"-Bildschirmmeldung, falls das Panel-Bringup als sicher genug gilt.)
-122
View File
@@ -1,122 +0,0 @@
# JC4880P443C-I-W — Touch-Display-Firmware (ESP32-P4)
Client-Firmware für das Guition-Smart-Display **JC4880P443C-I-W**, die als
UART-Touch-Bedienung für die Dual-PID-Siebträgersteuerung dient.
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/JC-Display_UART-Protokoll.md`](../Doku/JC-Display_UART-Protokoll.md).
## Hardware (laut Vendor-Paket im Ordner `JC4880P443C_I_W/`)
| Komponente | Bauteil / Pin |
|---|---|
| Haupt-MCU (UI) | ESP32-P4 |
| WLAN/BT (hier ungenutzt) | ESP32-C6 |
| Display | ST7701, 480×800 IPS, **MIPI-DSI** |
| Touch | GT911, I²C **SDA=GPIO7 / SCL=GPIO8** |
| Backlight | **GPIO23** (LEDC-PWM) · LCD-Reset **GPIO5** |
| Freie Expansion-IO (JP1) | GPIO 52/51/50/49/35/34/33/32/31/30/29/28, ES_I2C, +3V3/+5V/GND |
## Verkabelung zur Hauptplatine (UART, gekreuzt)
Pins aus dem Expand-IO-Header **JP1** (Schaltplan `JC4880P443C_I_W/5-Schematic/4_USB&IO.png`):
```
S3 GPIO43 (TX) ----> P4 GPIO31 (RX) (DISPLAY_UART_RX_PIN)
S3 GPIO44 (RX) <---- P4 GPIO33 (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 |
|---|---|
| `JC_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 + `lv_conf.h`. Tipp: die `lv_conf.h` aus dem Vendor-Beispiel
(`JC4880P443C_I_W/1-Demo/arduino_examples/lvgl_v9_sw_rotation/lv_conf.h`) übernehmen
und `LV_FONT_MONTSERRAT_40` aktivieren (große Temperaturanzeige).
- **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/ (komplett: esp_lcd_st7701*.c/.h, st7701_lcd.*)
src/touch/ (komplett: esp_lcd_touch*.c/.h, gt911_touch.*)
```
Der Init-Ablauf (MIPI-DSI-PHY-LDO, ST7701, 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.
**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 Vendor-Beispiel nutzt **90° (Querformat 800×480)**. Das hiesige Dashboard ist für
**Hochformat 480×800** ausgelegt → in `pins_config.h`
`EXAMPLE_LVGL_PORT_ROTATION_DEGREE_` auf **0** setzen (oder das UI-Layout in `ui.cpp` auf
Querformat anpassen).
## 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`, `getProfileDetails` vorhanden.
**Board-Bringup:** auf die konkreten JC-Pins/Treiber gesetzt (ST7701/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`/`GPIO51`**
identifizieren, nicht nach Header-Pin-Nummern.
- **S3-Seite:** muss mit `TOUCH_UART_ENABLED 1` geflasht sein und laufen. Da `Serial`
(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: gesendete `hello`,
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!**
-197
View File
@@ -1,197 +0,0 @@
// =====================================================================================
// board_bringup.c - ESP32-P4 / ST7701 / GT911 Hardware-Bringup (JC4880P443C-I-W)
// =====================================================================================
// BEWUSST eine .c-Datei (wird als C kompiliert): die esp_lcd-Init-Makros nutzen
// Compound-Literals und Out-of-Order-Designated-Initializer, die in C++ (Arduino .cpp)
// zu Fehlern fuehren. Der Ablauf ist 1:1 aus dem Hersteller-Beispiel
// arduino_examples/lvgl_v9_sw_rotation/lvgl_sw_rotation.c uebernommen - lediglich der
// Demo-Aufruf (lv_demo_widgets) entfaellt; das UI wird ueber display_hal.cpp aufgebaut.
//
// Nur aktiv bei JC_USE_REAL_PANEL == 1. Erwartet die Vendor-BSP-Dateien im Sketch:
// pins_config.h, lvgl_port_v9.h/.c, src/lcd/*, src/touch/*
// =====================================================================================
#include "config.h" // liefert JC_USE_REAL_PANEL (C-sicher)
#if JC_USE_REAL_PANEL
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_cache.h"
#include "esp_ldo_regulator.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "src/touch/esp_lcd_touch_gt911.h"
#include "src/lcd/esp_lcd_st7701.h"
#include "lvgl_port_v9.h"
#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3)
#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
#define BSP_LCD_H_RES (480)
#define BSP_LCD_V_RES (800)
#define BSP_I2C_NUM (I2C_NUM_1)
#define BSP_I2C_SDA (GPIO_NUM_7)
#define BSP_I2C_SCL (GPIO_NUM_8)
#define BSP_LCD_TOUCH_RST (GPIO_NUM_NC)
#define BSP_LCD_TOUCH_INT (GPIO_NUM_NC)
#define BSP_LCD_RST (GPIO_NUM_5)
#define BSP_LCD_BACKLIGHT GPIO_NUM_23
#define LCD_LEDC_CH LEDC_CHANNEL_0
static i2c_master_bus_handle_t s_i2c_handle = NULL;
void jc_backlight_init(void)
{
const ledc_channel_config_t ch = {
.gpio_num = BSP_LCD_BACKLIGHT,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LCD_LEDC_CH,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = 1,
.duty = 0,
.hpoint = 0
};
const ledc_timer_config_t tm = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = 1,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&tm);
ledc_channel_config(&ch);
}
void jc_backlight_set(int percent)
{
if (percent > 100) percent = 100;
if (percent < 0) percent = 0;
uint32_t duty = (1023 * percent) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH);
}
IRAM_ATTR static bool on_vsync(esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t *edata, void *ctx)
{
return lvgl_port_notify_lcd_vsync();
}
// Bringt Panel + Touch + LVGL-Port hoch (LVGL laeuft danach in eigenem Task).
void 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);
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;
esp_lcd_dsi_bus_config_t bus_config = ST7701_PANEL_BUS_DSI_2CH_CONFIG();
esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
esp_lcd_panel_io_handle_t io = NULL;
esp_lcd_dbi_io_config_t dbi_config = ST7701_PANEL_IO_DBI_CONFIG();
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
esp_lcd_panel_handle_t disp_panel = NULL;
esp_lcd_dpi_panel_config_t dpi_config = ST7701_480_360_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565);
dpi_config.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS;
st7701_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
.flags = {
.use_mipi_interface = 1,
},
};
esp_lcd_panel_dev_config_t lcd_dev_config = {
.bits_per_pixel = 16,
.rgb_ele_order = ESP_LCD_COLOR_SPACE_RGB,
.reset_gpio_num = BSP_LCD_RST,
.vendor_config = &vendor_config,
};
esp_lcd_new_panel_st7701(io, &lcd_dev_config, &disp_panel);
esp_lcd_panel_reset(disp_panel);
esp_lcd_panel_init(disp_panel);
// Alle Framebuffer einmal auf Schwarz loeschen. Im Avoid-Tear-Direct-Mode (mehrere
// Buffer) kopiert LVGL pro Frame nur die geaenderten Bereiche in den naechsten Buffer;
// bis alle Buffer einmal vollstaendig bemalt sind, wuerde sonst uninitialisierter
// Speicher (weisses Rauschen) durchblitzen. Schwarz faellt gegen das dunkle Theme
// nicht auf. Cache-Writeback noetig, da die Buffer im PSRAM liegen und der DMA liest.
{
void *fbs[3] = { NULL, NULL, NULL };
size_t fb_size = (size_t)BSP_LCD_H_RES * BSP_LCD_V_RES * 2; // RGB565 = 2 Byte/Pixel
#if LVGL_PORT_LCD_BUFFER_NUMS >= 3
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 3, &fbs[0], &fbs[1], &fbs[2]);
#elif LVGL_PORT_LCD_BUFFER_NUMS == 2
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 2, &fbs[0], &fbs[1]);
#else
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 1, &fbs[0]);
#endif
for (int i = 0; i < 3; i++) {
if (fbs[i]) {
memset(fbs[i], 0, fb_size);
esp_cache_msync(fbs[i], fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
}
}
}
esp_lcd_dpi_panel_event_callbacks_t cbs = {
#if LVGL_PORT_AVOID_TEAR_MODE
.on_refresh_done = on_vsync,
#else
.on_color_trans_done = on_vsync,
#endif
};
esp_lcd_dpi_panel_register_event_callbacks(disp_panel, &cbs, NULL);
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
esp_lcd_touch_handle_t tp_handle;
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
tp_io_config.scl_speed_hz = 100000;
esp_lcd_new_panel_io_i2c(s_i2c_handle, &tp_io_config, &tp_io_handle);
const esp_lcd_touch_config_t tp_cfg = {
.x_max = BSP_LCD_H_RES,
.y_max = BSP_LCD_V_RES,
.rst_gpio_num = BSP_LCD_TOUCH_RST,
.int_gpio_num = BSP_LCD_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
};
esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp_handle);
lvgl_port_interface_t interface =
(dpi_config.flags.use_dma2d) ? LVGL_PORT_INTERFACE_MIPI_DSI_DMA
: LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA;
lvgl_port_init(disp_panel, tp_handle, interface);
}
#endif // JC_USE_REAL_PANEL
-89
View File
@@ -1,89 +0,0 @@
// =====================================================================================
// JC4880P443C-I-W - Touch-Display-Firmware fuer die Dual-PID-Siebtraegersteuerung
// config.h - zentrale Konfiguration (Pins, UART, Protokoll)
// =====================================================================================
//
// Ziel-Hardware: Guition JC4880P443C-I-W
// - MCU: ESP32-P4 (UI / LVGL)
// - WLAN: ESP32-C6 (hier NICHT genutzt - WLAN/Webinterface laufen auf der Hauptplatine)
// - Display: ST7701 (480 x 800 IPS, MIPI-DSI)
// - Touch: GT911 (kapazitiv, I2C)
// - Audio: ES8311 (optional)
//
// Diese Firmware ist ein reiner UART-Client. Quelle der Wahrheit ist die Hauptplatine
// (ESP32-S3, Dual_PID_FastHeatUp.ino). Siehe Doku/JC-Display_UART-Protokoll.md.
// =====================================================================================
#pragma once
// -------------------------------------------------------------------------------------
// Panel-Modus (C-sicher hier definiert, da auch board_bringup.c ihn liest)
// 0 = Stub-Modus (kein Display-Treiber; Protokoll/Logik-Test)
// 1 = echte Hardware (Hersteller-BSP; siehe README)
// -------------------------------------------------------------------------------------
#ifndef JC_USE_REAL_PANEL
#define JC_USE_REAL_PANEL 1
#endif
// -------------------------------------------------------------------------------------
// DIAGNOSE: interner UART-Loopback-Selbsttest
// 1 = TX wird chip-intern auf RX gelegt (kein Draht, keine Pin-Unsicherheit).
// -> Laeuft "RX byte" im Debug-Panel hoch, sind Firmware+UART ok und das
// Problem liegt definitiv an Pins/Verkabelung. Bleibt es 0, ist es die
// Firmware/UART-Seite. NACH dem Test wieder auf 0 setzen!
// -------------------------------------------------------------------------------------
#ifndef UART_SELFTEST_INTERNAL_LOOPBACK
#define UART_SELFTEST_INTERNAL_LOOPBACK 0
#endif
// -------------------------------------------------------------------------------------
// DIAGNOSE: gruenes On-Screen-Debug-Panel (UART-Zaehler/letzte Rohzeile)
// 0 = aus (normale UI). 1 = Panel unten einblenden (fuer UART-Fehlersuche).
// -------------------------------------------------------------------------------------
#ifndef SHOW_DEBUG_OVERLAY
#define SHOW_DEBUG_OVERLAY 0
#endif
// -------------------------------------------------------------------------------------
// UART zur Hauptplatine
// -------------------------------------------------------------------------------------
// Gegenseite (S3): Serial1, 230400 8N1, TX=GPIO43 / RX=GPIO44.
// Verkabelung gekreuzt: S3-TX(43) -> P4-RX, S3-RX(44) -> P4-TX, GND <-> GND.
//
// Pins laut Schaltplan "4_USB&IO.png" (Expand-IO-Header JP1). Frei herausgefuehrte
// P4-GPIOs dort: 52/51/50/49/35/34/33/32/31/30/29/28 (+ ES_I2C, +3V3/+5V/GND).
// Die rechte Header-Spalte (C6_U01TXD/C6_I10RXD/...) gehoert zum ESP32-C6 -> NICHT nutzen.
// Gewaehlt: P4-TX = GPIO33, P4-RX = GPIO31.
// JP1 GPIO33 (P4 TX) ----> S3 GPIO44 (RX)
// JP1 GPIO31 (P4 RX) <---- S3 GPIO43 (TX)
// JP1 GND ---- S3 GND
#define DISPLAY_UART_PORT Serial1
#define DISPLAY_UART_BAUDRATE 230400
#define DISPLAY_UART_TX_PIN 33
#define DISPLAY_UART_RX_PIN 31
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.0.13" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// -------------------------------------------------------------------------------------
// Protokoll
// -------------------------------------------------------------------------------------
#define PROTO_EXPECTED_VERSION 2 // muss zur S3-Firmware passen (TOUCH_UART_PROTOCOL_VERSION)
#define PROTO_RX_LINE_MAX 2048 // max. eingehende Zeilenlaenge (Bytes)
#define PROTO_HEARTBEAT_MS 5000 // Intervall fuer 'ping' (S3-Timeout = 15 s)
#define PROTO_HELLO_RETRY_MS 2000 // erneuter 'hello'-Versuch, solange nicht verbunden
#define PROTO_LINK_TIMEOUT_MS 8000 // ohne empfangene Nachricht -> Verbindung gilt als tot
// -------------------------------------------------------------------------------------
// Display / LVGL
// -------------------------------------------------------------------------------------
// Querformat: Panel ist physisch 480x800, per Rotation 90 (pins_config.h) -> 800x480.
#define DISP_HOR_RES 800
#define DISP_VER_RES 480
#define LVGL_TICK_MS 5
// Debug-Ausgaben ueber USB-CDC (nicht ueber den Display-UART!)
#define DBG_SERIAL Serial
#define DBG_BAUD 115200
-35
View File
@@ -1,35 +0,0 @@
#pragma once
#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
#define EXAMPLE_LVGL_PORT_TASK_STACK_SIZE_KB 32 //KB (erhoeht: komplexe UI, Listen-Rendering, JSON in Callbacks)
#define EXAMPLE_LVGL_PORT_TASK_CORE -1 //range -1 to 1
#define EXAMPLE_LVGL_PORT_TICK 2 //ragne 1 to 100
#define EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE 1
#ifdef EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE
// 2 = Triple-Buffer + LVGL Full-Refresh: zeichnet bei jeder Aenderung das komplette
// 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.
#define EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE 2 //range 1 to 3
#define EXAMPLE_LVGL_PORT_ROTATION_DEGREE_ 270 // 0,90,180,270 (270 = Querformat 800x480, um 180 Grad gedreht vs. 90)
#define EXAMPLE_LVGL_PORT_PPA_ROTATION_ENABLE 1
#endif
#define LCD_H_RES 480
#define LCD_V_RES 800
#define LCD_RST -1
#define LCD_LED -1
#define TP_I2C_SDA 7
#define TP_I2C_SCL 8
#define TP_RST -1
#define TP_INT -1
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,17 @@
# 请使用arduino_esp32_v3.1版本
# lv_conf.h文件必要设置(lvgl_v8)
```c
#define LV_COLOR_DEPTH 16
#define LV_COLOR_16_SWAP 0
#define LV_MEM_CUSTOM 1
#define LV_TICK_CUSTOM 1
#define LV_MEMCPY_MEMSET_STD 1
#define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR
```
@@ -0,0 +1,115 @@
#pragma GCC push_options
#pragma GCC optimize("O3")
#include <Arduino.h>
#include "lvgl.h"
#include "demos/lv_demos.h"
#include "pins_config.h"
#include "src/lcd/jd9165_lcd.h"
#include "src/touch/gt911_touch.h"123
jd9165_lcd lcd = jd9165_lcd(LCD_RST);
gt911_touch touch = gt911_touch(TP_I2C_SDA, TP_I2C_SCL, TP_RST, TP_INT);
static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf;
static lv_color_t *buf1;
// 显示刷新
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lcd.lcd_draw_bitmap(offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, &color_p->full);
lv_disp_flush_ready(disp); // 告诉lvgl刷新完成
}
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
bool touched;
uint16_t touchX, touchY;
touched = touch.getTouch(&touchX, &touchY);
if (!touched)
{
data->state = LV_INDEV_STATE_REL;
}
else
{
data->state = LV_INDEV_STATE_PR;
// 设置坐标
data->point.x = touchX;
data->point.y = touchY;
Serial.printf("x=%d,y=%d \r\n",touchX,touchY);
}
}
static void lvgl_port_update_callback(lv_disp_drv_t *drv)
{
switch (drv->rotated) {
case LV_DISP_ROT_NONE:
touch.set_rotation(0);
break;
case LV_DISP_ROT_90:
touch.set_rotation(1);
break;
case LV_DISP_ROT_180:
touch.set_rotation(2);
break;
case LV_DISP_ROT_270:
touch.set_rotation(3);
break;
}
}
void setup()
{
Serial.begin(115200);
Serial.println("ESP32P4 MIPI DSI LVGL");
lcd.begin();
touch.begin();
lv_init();
size_t buffer_size = sizeof(int32_t) * LCD_H_RES * LCD_V_RES;
// buf = (int32_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
// buf1 = (int32_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
buf = (lv_color_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
buf1 = (lv_color_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
assert(buf);
assert(buf1);
lv_disp_draw_buf_init(&draw_buf, buf, buf1, LCD_H_RES * LCD_V_RES);
static lv_disp_drv_t disp_drv;
/*Initialize the display*/
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LCD_H_RES;
disp_drv.ver_res = LCD_V_RES;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
disp_drv.full_refresh = true;
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
// lv_disp_set_rotation(NULL, 0);
lv_demo_widgets(); /* 小部件示例 */
// lv_demo_music(); /* 类似智能手机的现代音乐播放器演示 */
// lv_demo_stress(); /* LVGL 压力测试 */
// lv_demo_benchmark(); /* 用于测量 LVGL 性能或比较不同设置的演示 */
}
void loop()
{
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,12 @@
#pragma once
#define LCD_H_RES 1024
#define LCD_V_RES 600
#define LCD_RST 27
#define LCD_LED 23
#define TP_I2C_SDA 7
#define TP_I2C_SCL 8
#define TP_RST 22
#define TP_INT 21
@@ -0,0 +1,339 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_check.h"
#include "esp_log.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_vendor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_lcd_jd9165.h"
#define JD9165_CMD_GS_BIT (1 << 0)
#define JD9165_CMD_SS_BIT (1 << 1)
typedef struct {
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register
const jd9165_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int reset_level: 1;
} flags;
// To save the original functions of MIPI DPI panel
esp_err_t (*del)(esp_lcd_panel_t *panel);
esp_err_t (*init)(esp_lcd_panel_t *panel);
} jd9165_panel_t;
static const char *TAG = "jd9165";
static esp_err_t panel_jd9165_del(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_init(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_jd9165_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_jd9165_disp_on_off(esp_lcd_panel_t *panel, bool on_off);
esp_err_t esp_lcd_new_panel_jd9165(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
esp_lcd_panel_handle_t *ret_panel)
{
ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
jd9165_vendor_config_t *vendor_config = (jd9165_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
"invalid vendor config");
esp_err_t ret = ESP_OK;
jd9165_panel_t *jd9165 = (jd9165_panel_t *)calloc(1, sizeof(jd9165_panel_t));
ESP_RETURN_ON_FALSE(jd9165, ESP_ERR_NO_MEM, TAG, "no mem for jd9165 panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
switch (panel_dev_config->color_space) {
case LCD_RGB_ELEMENT_ORDER_RGB:
jd9165->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
jd9165->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
break;
}
jd9165->io = io;
jd9165->init_cmds = vendor_config->init_cmds;
jd9165->init_cmds_size = vendor_config->init_cmds_size;
jd9165->reset_gpio_num = panel_dev_config->reset_gpio_num;
jd9165->flags.reset_level = panel_dev_config->flags.reset_active_high;
// Create MIPI DPI panel
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, &panel_handle), err, TAG,
"create MIPI DPI panel failed");
ESP_LOGD(TAG, "new MIPI DPI panel @%p", panel_handle);
// Save the original functions of MIPI DPI panel
jd9165->del = panel_handle->del;
jd9165->init = panel_handle->init;
// Overwrite the functions of MIPI DPI panel
panel_handle->del = panel_jd9165_del;
panel_handle->init = panel_jd9165_init;
panel_handle->reset = panel_jd9165_reset;
panel_handle->mirror = panel_jd9165_mirror;
panel_handle->invert_color = panel_jd9165_invert_color;
panel_handle->disp_on_off = panel_jd9165_disp_on_off;
panel_handle->user_data = jd9165;
*ret_panel = panel_handle;
ESP_LOGD(TAG, "new jd9165 panel @%p", jd9165);
return ESP_OK;
err:
if (jd9165) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(jd9165);
}
return ret;
}
static const jd9165_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
//{0x11, (uint8_t []){0x00}, 1, 120},
//{0x29, (uint8_t []){0x00}, 1, 20},
{0x30, (uint8_t[]){0x00}, 1, 0},
{0xF7, (uint8_t[]){0x49,0x61,0x02,0x00}, 4, 0},
{0x30, (uint8_t[]){0x01}, 1, 0},
{0x04, (uint8_t[]){0x0C}, 1, 0},
{0x05, (uint8_t[]){0x00}, 1, 0},
{0x06, (uint8_t[]){0x00}, 1, 0},
{0x0B, (uint8_t[]){0x11}, 1, 0},
{0x17, (uint8_t[]){0x00}, 1, 0},
{0x20, (uint8_t[]){0x04}, 1, 0},
{0x1F, (uint8_t[]){0x05}, 1, 0},
{0x23, (uint8_t[]){0x00}, 1, 0},
{0x25, (uint8_t[]){0x19}, 1, 0},
{0x28, (uint8_t[]){0x18}, 1, 0},
{0x29, (uint8_t[]){0x04}, 1, 0},
{0x2A, (uint8_t[]){0x01}, 1, 0},
{0x2B, (uint8_t[]){0x04}, 1, 0},
{0x2C, (uint8_t[]){0x01}, 1, 0},
{0x30, (uint8_t[]){0x02}, 1, 0},
{0x01, (uint8_t[]){0x22}, 1, 0},
{0x03, (uint8_t[]){0x12}, 1, 0},
{0x04, (uint8_t[]){0x00}, 1, 0},
{0x05, (uint8_t[]){0x64}, 1, 0},
{0x0A, (uint8_t[]){0x08}, 1, 0},
{0x0B, (uint8_t[]){0x0A,0x1A,0x0B,0x0D,0x0D,0x11,0x10,0x06,0x08,0x1F,0x1D}, 11, 0},
{0x0C, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0D, (uint8_t[]){0x16,0x1B,0x0B,0x0D,0x0D,0x11,0x10,0x07,0x09,0x1E,0x1C}, 11, 0},
{0x0E, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0F, (uint8_t[]){0x16,0x1B,0x0D,0x0B,0x0D,0x11,0x10,0x1C,0x1E,0x09,0x07}, 11, 0},
{0x10, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x11, (uint8_t[]){0x0A,0x1A,0x0D,0x0B,0x0D,0x11,0x10,0x1D,0x1F,0x08,0x06}, 11, 0},
{0x12, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x14, (uint8_t[]){0x00,0x00,0x11,0x11}, 4, 0},
{0x18, (uint8_t[]){0x99}, 1, 0},
{0x30, (uint8_t[]){0x06}, 1, 0},
{0x12, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
{0x13, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
// {0x30, (uint8_t[]){0x08}, 1, 0},
// {0x05, (uint8_t[]){0x01}, 1, 0},
// {0x0C, (uint8_t[]){0x1A}, 1, 0},
// {0x0D, (uint8_t[]){0x0E}, 1, 0},
// {0x30, (uint8_t[]){0x07}, 1, 0},
// {0x01, (uint8_t[]){0x04}, 1, 0},
{0x30, (uint8_t[]){0x0A}, 1, 0},
{0x02, (uint8_t[]){0x4F}, 1, 0},
{0x0B, (uint8_t[]){0x40}, 1, 0},
{0x12, (uint8_t[]){0x3E}, 1, 0},
{0x13, (uint8_t[]){0x78}, 1, 0},
{0x30, (uint8_t[]){0x0D}, 1, 0},
{0x0D, (uint8_t[]){0x04}, 1, 0},
{0x10, (uint8_t[]){0x0C}, 1, 0},
{0x11, (uint8_t[]){0x0C}, 1, 0},
{0x12, (uint8_t[]){0x0C}, 1, 0},
{0x13, (uint8_t[]){0x0C}, 1, 0},
{0x30, (uint8_t[]){0x00}, 1, 0},
// {0X3A, (uint8_t[]){0x55}, 1, 0},
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 50},
};
static esp_err_t panel_jd9165_del(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
if (jd9165->reset_gpio_num >= 0) {
gpio_reset_pin(jd9165->reset_gpio_num);
}
// Delete MIPI DPI panel
jd9165->del(panel);
ESP_LOGD(TAG, "del jd9165 panel @%p", jd9165);
free(jd9165);
return ESP_OK;
}
static esp_err_t panel_jd9165_init(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
const jd9165_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_cmd_overwritten = false;
uint8_t ID[3];
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io, 0x04, ID, 3), TAG, "read ID failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
jd9165->madctl_val,
}, 1), TAG, "send command failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (jd9165->init_cmds) {
init_cmds = jd9165->init_cmds;
init_cmds_size = jd9165->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9165_lcd_init_cmd_t);
}
for (int i = 0; i < init_cmds_size; i++) {
// Check if the command has been used or conflicts with the internal
if (init_cmds[i].data_bytes > 0) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
jd9165->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
default:
is_cmd_overwritten = false;
break;
}
if (is_cmd_overwritten) {
is_cmd_overwritten = false;
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
init_cmds[i].cmd);
}
}
// Send command
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
}
ESP_LOGD(TAG, "send init commands success");
ESP_RETURN_ON_ERROR(jd9165->init(panel), TAG, "init MIPI DPI panel failed");
return ESP_OK;
}
static esp_err_t panel_jd9165_reset(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
// Perform hardware reset
if (jd9165->reset_gpio_num >= 0) {
gpio_set_level(jd9165->reset_gpio_num, !jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(5));
gpio_set_level(jd9165->reset_gpio_num, jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(jd9165->reset_gpio_num, !jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(120));
} else if (io) { // Perform software reset
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(120));
}
return ESP_OK;
}
static esp_err_t panel_jd9165_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
uint8_t command = 0;
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
if (invert_color_data) {
command = LCD_CMD_INVON;
} else {
command = LCD_CMD_INVOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_jd9165_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
uint8_t madctl_val = jd9165->madctl_val;
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
// Control mirror through LCD command
if (mirror_x) {
madctl_val |= JD9165_CMD_GS_BIT;
} else {
madctl_val &= ~JD9165_CMD_GS_BIT;
}
if (mirror_y) {
madctl_val |= JD9165_CMD_SS_BIT;
} else {
madctl_val &= ~JD9165_CMD_SS_BIT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
madctl_val
}, 1), TAG, "send command failed");
jd9165->madctl_val = madctl_val;
return ESP_OK;
}
static esp_err_t panel_jd9165_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
int command = 0;
if (on_off) {
command = LCD_CMD_DISPON;
} else {
command = LCD_CMD_DISPOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
#endif
@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_mipi_dsi.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LCD panel initialization commands.
*
*/
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} jd9165_lcd_init_cmd_t;
/**
* @brief LCD panel vendor configuration.
*
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
*
*/
typedef struct {
const jd9165_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
* The array should be declared as `static const` and positioned outside the function.
* Please refer to `vendor_specific_init_default` in source file.
*/
uint16_t init_cmds_size; /*<! Number of commands in above array */
struct {
esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
} mipi_config;
} jd9165_vendor_config_t;
/**
* @brief Create LCD panel for model JD9165
*
* @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config General panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
* - Otherwise on fail
*/
esp_err_t esp_lcd_new_panel_jd9165(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
esp_lcd_panel_handle_t *ret_panel);
/**
* @brief MIPI-DSI bus configuration structure
*
* @param[in] lane_num Number of data lanes
* @param[in] lane_mbps Lane bit rate in Mbps
*
*/
#define JD9165_PANEL_BUS_DSI_2CH_CONFIG() \
{ \
.bus_id = 0, \
.num_data_lanes = 2, \
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \
.lane_bit_rate_mbps = 750, \
}
/**
* @brief MIPI-DBI panel IO configuration structure
*
*/
#define JD9165_PANEL_IO_DBI_CONFIG() \
{ \
.virtual_channel = 0, \
.lcd_cmd_bits = 8, \
.lcd_param_bits = 8, \
}
/**
* @brief MIPI DPI configuration structure
*
* @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
*
* @param[in] px_format Pixel format of the panel
*
*/
#define JD9165_1024_600_PANEL_60HZ_DPI_CONFIG(px_format) \
{ \
.virtual_channel = 0, \
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
.dpi_clock_freq_mhz = 56, \
.pixel_format = px_format, \
.num_fbs = 1, \
.video_timing = { \
.h_size = 1024, \
.v_size = 600, \
.hsync_pulse_width = 40, \
.hsync_back_porch = 160, \
.hsync_front_porch = 160, \
.vsync_pulse_width = 10, \
.vsync_back_porch = 23, \
.vsync_front_porch = 12, \
}, \
.flags= { \
.use_dma2d = true, \
}, \
}
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,153 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_io.h"
#include "esp_ldo_regulator.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "Arduino.h"
#include "esp_lcd_jd9165.h"
#include "jd9165_lcd.h"
#define LCD_H_RES 1024
#define LCD_V_RES 600
#define MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB565)
#define LCD_BIT_PER_PIXEL (16)
// “VDD_MIPI_DPHY”应供电 2.5V,可从内部 LDO 稳压器或外部 LDO 芯片获取电源
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 连接至 VDD_MIPI_DPHY
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_BK_LIGHT GPIO_NUM_23
static const char *TAG = "example";
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
jd9165_lcd::jd9165_lcd(int8_t lcd_rst)
{
_lcd_rst = lcd_rst;
}
void jd9165_lcd::example_bsp_enable_dsi_phy_power()
{
// 打开 MIPI DSI PHY 的电源,使其从“无电”状态进入“关机”状态
esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
#ifdef EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN
esp_ldo_channel_config_t ldo_mipi_phy_config = {
.chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
#endif
}
void jd9165_lcd::example_bsp_init_lcd_backlight()
{
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
gpio_config_t bk_gpio_config = {
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT,
.mode = GPIO_MODE_OUTPUT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
#endif
}
void jd9165_lcd::example_bsp_set_lcd_backlight(uint32_t level)
{
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, level);
#endif
}
void jd9165_lcd::begin()
{
example_bsp_enable_dsi_phy_power();
example_bsp_init_lcd_backlight();
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
// 首先创建 MIPI DSI 总线,它还将初始化 DSI PHY
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
esp_lcd_dsi_bus_config_t bus_config = JD9165_PANEL_BUS_DSI_2CH_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
// 我们使用DBI接口发送LCD命令和参数
esp_lcd_dbi_io_config_t dbi_config = JD9165_PANEL_IO_DBI_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io_handle));
// 创建JD9165控制面板
esp_lcd_dpi_panel_config_t dpi_config = JD9165_1024_600_PANEL_60HZ_DPI_CONFIG(MIPI_DPI_PX_FORMAT);
jd9165_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = _lcd_rst,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_jd9165(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// 打开背光
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
}
void jd9165_lcd::lcd_draw_bitmap(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t *color_data)
{
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, color_data);
}
void jd9165_lcd::draw16bitbergbbitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *color_data)
{
uint16_t x_start = x;
uint16_t y_start = y;
uint16_t x_end = w + x;
uint16_t y_end = h + y;
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, color_data);
}
void jd9165_lcd::fillScreen(uint16_t color)
{
uint16_t *color_data = (uint16_t *)heap_caps_malloc(480 * 272 * 2, MALLOC_CAP_INTERNAL);
memset(color_data, color, 480 * 272 * 2);
draw16bitbergbbitmap(0, 0, 480, 272, color_data);
free(color_data);
}
void jd9165_lcd::te_on()
{
esp_lcd_panel_io_tx_param(io_handle, 0x35,new (uint8_t[]){0x00}, 1);
}
void jd9165_lcd::te_off()
{
esp_lcd_panel_io_tx_param(io_handle, 0x34,new (uint8_t[]){0x00}, 0);
}
uint16_t jd9165_lcd::width()
{
return LCD_H_RES;
}
uint16_t jd9165_lcd::height()
{
return LCD_V_RES;
}
@@ -0,0 +1,26 @@
#ifndef _JD9165_LCD_H
#define _JD9165_LCD_H
#include <stdio.h>
class jd9165_lcd
{
public:
jd9165_lcd(int8_t lcd_rst);
void begin();
void example_bsp_enable_dsi_phy_power();
void example_bsp_init_lcd_backlight();
void example_bsp_set_lcd_backlight(uint32_t level);
void lcd_draw_bitmap(uint16_t x_start, uint16_t y_start,
uint16_t x_end, uint16_t y_end, uint16_t *color_data);
void draw16bitbergbbitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *color_data);
void fillScreen(uint16_t color);
void te_on();
void te_off();
uint16_t width();
uint16_t height();
private:
int8_t _lcd_rst;
};
#endif
@@ -0,0 +1,284 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
static const char *TAG = "GT911";
/* GT911 registers */
#define ESP_LCD_TOUCH_GT911_READ_XY_REG (0x814E)
#define ESP_LCD_TOUCH_GT911_CONFIG_REG (0x8047)
#define ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG (0x8140)
/*******************************************************************************
* Function definitions
*******************************************************************************/
static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp);
static bool esp_lcd_touch_gt911_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);
static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp);
/* I2C read/write */
static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data);
/* GT911 reset */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp);
/* Read status and config register */
static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp);
static void touch_scale(uint16_t *x_coordinate,uint16_t *y_coordinate);
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch)
{
esp_err_t ret = ESP_OK;
assert(io != NULL);
assert(config != NULL);
assert(out_touch != NULL);
/* Prepare main structure */
esp_lcd_touch_handle_t esp_lcd_touch_gt911 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
ESP_GOTO_ON_FALSE(esp_lcd_touch_gt911, ESP_ERR_NO_MEM, err, TAG, "no mem for GT911 controller");
/* Communication interface */
esp_lcd_touch_gt911->io = io;
/* Only supported callbacks are set */
esp_lcd_touch_gt911->read_data = esp_lcd_touch_gt911_read_data;
esp_lcd_touch_gt911->get_xy = esp_lcd_touch_gt911_get_xy;
esp_lcd_touch_gt911->del = esp_lcd_touch_gt911_del;
/* Mutex */
esp_lcd_touch_gt911->data.lock.owner = portMUX_FREE_VAL;
/* Save config */
memcpy(&esp_lcd_touch_gt911->config, config, sizeof(esp_lcd_touch_config_t));
/* Prepare pin for touch interrupt */
if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) {
const gpio_config_t int_gpio_config = {
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_NEGEDGE,
.pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num)
};
ret = gpio_config(&int_gpio_config);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
/* Register interrupt callback */
if (esp_lcd_touch_gt911->config.interrupt_callback) {
esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_gt911, esp_lcd_touch_gt911->config.interrupt_callback);
}
}
/* Prepare pin for touch controller reset */
if (esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) {
const gpio_config_t rst_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.rst_gpio_num)
};
ret = gpio_config(&rst_gpio_config);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
}
/* Reset controller */
ret = touch_gt911_reset(esp_lcd_touch_gt911);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 reset failed");
/* Read status and config info */
ret = touch_gt911_read_cfg(esp_lcd_touch_gt911);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 init failed");
err:
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (0x%x)! Touch controller GT911 initialization failed!", ret);
if (esp_lcd_touch_gt911) {
esp_lcd_touch_gt911_del(esp_lcd_touch_gt911);
}
}
*out_touch = esp_lcd_touch_gt911;
return ret;
}
static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp)
{
esp_err_t err;
uint8_t buf[41];
uint8_t touch_cnt = 0;
uint8_t clear = 0;
size_t i = 0;
assert(tp != NULL);
err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, buf, 1);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
/* Any touch data? */
if ((buf[0] & 0x80) == 0x00) {
touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
} else {
/* Count of touched points */
touch_cnt = buf[0] & 0x0f;
if (touch_cnt > 5 || touch_cnt == 0) {
touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
return ESP_OK;
}
/* Read all points */
err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG + 1, &buf[1], touch_cnt * 8);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
/* Clear all */
err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
portENTER_CRITICAL(&tp->data.lock);
/* Number of touched points */
touch_cnt = (touch_cnt > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : touch_cnt);
tp->data.points = touch_cnt;
/* Fill all coordinates */
for (i = 0; i < touch_cnt; i++) {
tp->data.coords[i].x = ((uint16_t)buf[(i * 8) + 3] << 8) + buf[(i * 8) + 2];
tp->data.coords[i].y = (((uint16_t)buf[(i * 8) + 5] << 8) + buf[(i * 8) + 4]);
tp->data.coords[i].strength = (((uint16_t)buf[(i * 8) + 7] << 8) + buf[(i * 8) + 6]);
}
portEXIT_CRITICAL(&tp->data.lock);
}
return ESP_OK;
}
static bool esp_lcd_touch_gt911_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)
{
assert(tp != NULL);
assert(x != NULL);
assert(y != NULL);
assert(point_num != NULL);
assert(max_point_num > 0);
portENTER_CRITICAL(&tp->data.lock);
/* Count of points */
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
for (size_t i = 0; i < *point_num; i++) {
x[i] = tp->data.coords[i].x;
y[i] = tp->data.coords[i].y;
touch_scale(&x[i],&y[i]);
if (strength) {
strength[i] = tp->data.coords[i].strength;
}
}
/* Invalidate */
tp->data.points = 0;
portEXIT_CRITICAL(&tp->data.lock);
return (*point_num > 0);
}
static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
/* Reset GPIO pin settings */
if (tp->config.int_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.int_gpio_num);
}
/* Reset GPIO pin settings */
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.rst_gpio_num);
}
free(tp);
return ESP_OK;
}
/*******************************************************************************
* Private API function
*******************************************************************************/
/* Reset controller */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
vTaskDelay(pdMS_TO_TICKS(10));
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK;
}
static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp)
{
uint8_t buf[4];
assert(tp != NULL);
ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG, (uint8_t *)&buf[0], 3), TAG, "GT911 read error!");
ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&buf[3], 1), TAG, "GT911 read error!");
ESP_LOGI(TAG, "TouchPad_ID:0x%02x,0x%02x,0x%02x", buf[0], buf[1], buf[2]);
ESP_LOGI(TAG, "TouchPad_Config_Version:%d", buf[3]);
return ESP_OK;
}
static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
assert(tp != NULL);
assert(data != NULL);
/* Read data */
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}
static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data)
{
assert(tp != NULL);
// *INDENT-OFF*
/* Write data */
return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1);
// *INDENT-ON*
}
static void touch_scale(uint16_t *x_coordinate,uint16_t *y_coordinate)
{
uint32_t temp_x;
uint32_t temp_y;
temp_x = (*x_coordinate) * 1024 *10 / 800 / 10;
temp_y = (*y_coordinate) * 600 * 10 / 480 / 10;
*x_coordinate = (uint16_t)temp_x;
*y_coordinate = (uint16_t)temp_y;
}
@@ -0,0 +1,99 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c.h"
#include "esp_lcd_touch_gt911.h"
#include "gt911_touch.h"
#define CONFIG_LCD_HRES 600
#define CONFIG_LCD_VRES 1024
static const char *TAG = "example";
esp_lcd_touch_handle_t tp;
esp_lcd_panel_io_handle_t tp_io_handle;
uint16_t touch_strength[1];
uint8_t touch_cnt = 0;
gt911_touch::gt911_touch(int8_t sda_pin, int8_t scl_pin, int8_t rst_pin, int8_t int_pin)
{
_sda = sda_pin;
_scl = scl_pin;
_rst = rst_pin;
_int = int_pin;
}
void gt911_touch::begin()
{
i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = (gpio_num_t)_sda,
.scl_io_num = (gpio_num_t)_scl,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
};
i2c_conf.master.clk_speed = 400000; // 400kHz
ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, i2c_conf.mode, 0, 0, 0));
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
ESP_LOGI(TAG, "Initialize touch IO (I2C)");
esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_NUM_0, &tp_io_config, &tp_io_handle);
esp_lcd_touch_config_t tp_cfg = {
.x_max = CONFIG_LCD_HRES,
.y_max = CONFIG_LCD_VRES,
.rst_gpio_num = (gpio_num_t)_rst,
.int_gpio_num = (gpio_num_t)_int,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
};
ESP_LOGI(TAG, "Initialize touch controller gt911");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
}
bool gt911_touch::getTouch(uint16_t *x, uint16_t *y)
{
esp_lcd_touch_read_data(tp);
bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, x, y, touch_strength, &touch_cnt, 1);
return touchpad_pressed;
}
void gt911_touch::set_rotation(uint8_t r){
switch(r){
case 0:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
esp_lcd_touch_set_mirror_y(tp, false);
break;
case 1:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, true);
esp_lcd_touch_set_mirror_y(tp, true);
break;
case 2:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
esp_lcd_touch_set_mirror_y(tp, false);
break;
case 3:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, true);
esp_lcd_touch_set_mirror_y(tp, true);
break;
}
}
@@ -0,0 +1,703 @@
/**
* @file lv_conf.h
* Configuration file for v8.3.0-dev
*/
/*
* Copy this file as `lv_conf.h`
* 1. simply next to the `lvgl` folder
* 2. or any other places and
* - define `LV_CONF_INCLUDE_SIMPLE`
* - add the path as include path
*/
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/*====================
COLOR SETTINGS
*====================*/
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0
/*Enable more complex drawing routines to manage screens transparency.
*Can be used if the UI is above another layer, e.g. an OSD menu or video player.
*Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
#define LV_COLOR_SCREEN_TRANSP 0
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128)
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
/*=========================
MEMORY SETTINGS
*=========================*/
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#define LV_MEM_SIZE (40U * 1024U) /*[bytes]*/
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#define LV_MEM_ADR 0 /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
//#define LV_MEM_POOL_INCLUDE your_alloc_library /* Uncomment if using an external allocator*/
//#define LV_MEM_POOL_ALLOC your_alloc /* Uncomment if using an external allocator*/
#endif
#else /*LV_MEM_CUSTOM*/
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
#define LV_MEM_CUSTOM_ALLOC malloc
#define LV_MEM_CUSTOM_FREE free
#define LV_MEM_CUSTOM_REALLOC realloc
#endif /*LV_MEM_CUSTOM*/
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
*You will see an error log message if there wasn't enough buffers. */
#define LV_MEM_BUF_MAX_NUM 16
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 1
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
/*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)*/
#define LV_DPI_DEF 130 /*[px/inch]*/
/*=======================
* FEATURE CONFIGURATION
*=======================*/
/*-------------
* Drawing
*-----------*/
/*Enable complex draw engine.
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0
/*Allow buffering some shadow calculation.
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0
/* Set number of maximally cached circle data.
* The circumference of 1/4 circle are saved for anti-aliasing
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/
/*Default image cache size. Image caching keeps the images opened.
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
*However the opened images might consume additional RAM.
*0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 0
/*Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
/*Default gradient buffer size.
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
*If the cache is too small the map will be allocated only while it's required for the drawing.
*0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
/*Maximum buffer size to allocate for rotation.
*Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)
/*-------------
* GPU
*-----------*/
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif
/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
*/
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0
/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif
/*-------------
* Logging
*-----------*/
/*Enable the log module*/
#define LV_USE_LOG 0
#if LV_USE_LOG
/*How important log should be added:
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
*LV_LOG_LEVEL_INFO Log important events
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
*LV_LOG_LEVEL_USER Only logs added by the user
*LV_LOG_LEVEL_NONE Do not log anything*/
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
/*1: Print the log with 'printf';
*0: User need to register a callback with `lv_log_register_print_cb()`*/
#define LV_LOG_PRINTF 0
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#define LV_LOG_TRACE_MEM 1
#define LV_LOG_TRACE_TIMER 1
#define LV_LOG_TRACE_INDEV 1
#define LV_LOG_TRACE_DISP_REFR 1
#define LV_LOG_TRACE_EVENT 1
#define LV_LOG_TRACE_OBJ_CREATE 1
#define LV_LOG_TRACE_LAYOUT 1
#define LV_LOG_TRACE_ANIM 1
#endif /*LV_USE_LOG*/
/*-------------
* Asserts
*-----------*/
/*Enable asserts if an operation is failed or an invalid data is found.
*If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
/*-------------
* Others
*-----------*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0
/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#define LV_SPRINTF_INCLUDE <stdio.h>
#define lv_snprintf snprintf
#define lv_vsnprintf vsnprintf
#else /*LV_SPRINTF_CUSTOM*/
#define LV_SPRINTF_USE_FLOAT 0
#endif /*LV_SPRINTF_CUSTOM*/
#define LV_USE_USER_DATA 1
/*Garbage Collector settings
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/
/*=====================
* COMPILER SETTINGS
*====================*/
/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0
/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC
/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER
/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY
/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN
/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST
/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
*should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0
/*==================
* FONT USAGE
*===================*/
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 1
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 0
/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally.
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_CUSTOM_DECLARE
/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14
/*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp.
*Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 0
/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif
/*=================
* TEXT SETTINGS
*=================*/
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"
/*If a word is at least this long, will break wherever "prettiest"
*To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0
/*Minimum number of characters in a long word to put on a line before a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
/*Minimum number of characters in a long word to put on a line after a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
*The direction will be processed according to the Unicode Bidirectional Algorithm:
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif
/*Enable Arabic/Persian processing
*In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/*==================
* WIDGET USAGE
*================*/
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
#define LV_USE_ARC 1
#define LV_USE_ANIMIMG 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
#define LV_USE_IMG 1 /*Requires: lv_label*/
#define LV_USE_LABEL 1
#if LV_USE_LABEL
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
#endif
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
#if LV_USE_ROLLER
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
#endif
#define LV_USE_TABLE 1
/*==================
* EXTRA COMPONENTS
*==================*/
/*-----------
* Widgets
*----------*/
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#if LV_CALENDAR_WEEK_STARTS_MONDAY
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
#else
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
#endif
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
#define LV_USE_CALENDAR_HEADER_ARROW 1
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif /*LV_USE_CALENDAR*/
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
#define LV_USE_SPAN 1
#if LV_USE_SPAN
/*A line text can contain maximum num of span descriptor */
#define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif
/*-----------
* Themes
*----------*/
/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0
/*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1
/*Default transition time in [ms]*/
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/
/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1
/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1
/*-----------
* Layouts
*----------*/
/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1
/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1
/*---------------------
* 3rd party libraries
*--------------------*/
/*File system interfaces for common APIs */
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#define LV_USE_FS_FATFS 0
#if LV_USE_FS_FATFS
#define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*PNG decoder library*/
#define LV_USE_PNG 0
/*BMP decoder library*/
#define LV_USE_BMP 0
/* JPG + split JPG decoder library.
* Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 0
/*GIF decoder library*/
#define LV_USE_GIF 0
/*QR code library*/
#define LV_USE_QRCODE 0
/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 0
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif
/*Rlottie library*/
#define LV_USE_RLOTTIE 0
/*FFmpeg library for image decoding and playing videos
*Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
/*Dump input information to stderr*/
#define LV_FFMPEG_AV_DUMP_FORMAT 0
#endif
/*-----------
* Others
*----------*/
/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0
/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0
/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0
/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0
/*==================
* EXAMPLES
*==================*/
/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 1
/*===================
* DEMO USAGE
====================*/
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 1
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 1
#endif
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 1
/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 1
/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 1
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 1
#if LV_USE_DEMO_MUSIC
# define LV_DEMO_MUSIC_SQUARE 0
# define LV_DEMO_MUSIC_LANDSCAPE 0
# define LV_DEMO_MUSIC_ROUND 0
# define LV_DEMO_MUSIC_LARGE 0
# define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif
/*--END OF LV_CONF_H--*/
#endif /*LV_CONF_H*/
#endif /*End of "Content enable"*/
@@ -0,0 +1 @@
ESP32 version V3.1.0 LVGL version V8.4
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,17 @@
# 请使用arduino_esp32_v3.1版本
# lv_conf.h文件必要设置(lvgl_v8)
```c
#define LV_COLOR_DEPTH 16
#define LV_COLOR_16_SWAP 0
#define LV_MEM_CUSTOM 1
#define LV_TICK_CUSTOM 1
#define LV_MEMCPY_MEMSET_STD 1
#define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR
```
@@ -0,0 +1,87 @@
#pragma GCC push_options
#pragma GCC optimize("O3")
#include <Arduino.h>
#include "lvgl.h"
#include "demos/lv_demos.h"
#include "pins_config.h"
#include "src/lcd/jd9165_lcd.h"
#include "src/touch/gt911_touch.h"
jd9165_lcd lcd = jd9165_lcd(LCD_RST);
gt911_touch touch = gt911_touch(TP_I2C_SDA, TP_I2C_SCL, TP_RST, TP_INT);
// static lv_disp_draw_buf_t draw_buf;
lv_display_t * disp_drv;
static uint32_t *buf;
static uint32_t *buf1;
// 显示刷新
void my_disp_flush( lv_display_t *disp, const lv_area_t *area, uint8_t * color_map)
{
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lcd.lcd_draw_bitmap(offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
lv_display_flush_ready(disp); // 告诉lvgl刷新完成
}
void my_touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data)
{
bool touched;
uint16_t touchX, touchY;
touched = touch.getTouch(&touchX, &touchY);
if (!touched)
{
data->state = LV_INDEV_STATE_REL;
}
else
{
data->state = LV_INDEV_STATE_PR;
// 设置坐标
data->point.x = touchX;
data->point.y = touchY;
Serial.printf("x=%d,y=%d \r\n",touchX,touchY);
}
}
void setup()
{
Serial.begin(115200);
Serial.println("ESP32P4 MIPI DSI LVGL");
lcd.begin();
touch.begin();
lv_init();
uint32_t buffer_size = LCD_H_RES * LCD_V_RES;
buf = (uint32_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
buf1 =(uint32_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
assert(buf);
assert(buf1);
disp_drv = lv_display_create(LCD_H_RES, LCD_V_RES);
lv_display_set_flush_cb(disp_drv, my_disp_flush);
lv_display_set_buffers(disp_drv, buf, buf1, buffer_size * sizeof(uint32_t), LV_DISPLAY_RENDER_MODE_FULL);
/*Initialize the display*/
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, my_touchpad_read);
lv_demo_widgets(); /* 小部件示例 */
// lv_demo_music(); /* 类似智能手机的现代音乐播放器演示 */
// lv_demo_stress(); /* LVGL 压力测试 */
// lv_demo_benchmark(); /* 用于测量 LVGL 性能或比较不同设置的演示 */
Serial.println("setup ");
}
void loop()
{
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,12 @@
#pragma once
#define LCD_H_RES 1024
#define LCD_V_RES 600
#define LCD_RST 27
#define LCD_LED 23
#define TP_I2C_SDA 7
#define TP_I2C_SCL 8
#define TP_RST -1
#define TP_INT -1
@@ -0,0 +1,341 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_check.h"
#include "esp_log.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_vendor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_lcd_jd9165.h"
#define JD9165_CMD_GS_BIT (1 << 0)
#define JD9165_CMD_SS_BIT (1 << 1)
typedef struct {
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register
const jd9165_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int reset_level: 1;
} flags;
// To save the original functions of MIPI DPI panel
esp_err_t (*del)(esp_lcd_panel_t *panel);
esp_err_t (*init)(esp_lcd_panel_t *panel);
} jd9165_panel_t;
static const char *TAG = "jd9165";
static esp_err_t panel_jd9165_del(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_init(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9165_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_jd9165_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_jd9165_disp_on_off(esp_lcd_panel_t *panel, bool on_off);
esp_err_t esp_lcd_new_panel_jd9165(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
esp_lcd_panel_handle_t *ret_panel)
{
ESP_LOGI(TAG,"jd9165:1.0.1");
ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
jd9165_vendor_config_t *vendor_config = (jd9165_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
"invalid vendor config");
esp_err_t ret = ESP_OK;
jd9165_panel_t *jd9165 = (jd9165_panel_t *)calloc(1, sizeof(jd9165_panel_t));
ESP_RETURN_ON_FALSE(jd9165, ESP_ERR_NO_MEM, TAG, "no mem for jd9165 panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
switch (panel_dev_config->color_space) {
case LCD_RGB_ELEMENT_ORDER_RGB:
jd9165->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
jd9165->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
break;
}
jd9165->io = io;
jd9165->init_cmds = vendor_config->init_cmds;
jd9165->init_cmds_size = vendor_config->init_cmds_size;
jd9165->reset_gpio_num = panel_dev_config->reset_gpio_num;
jd9165->flags.reset_level = panel_dev_config->flags.reset_active_high;
// Create MIPI DPI panel
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, &panel_handle), err, TAG,
"create MIPI DPI panel failed");
ESP_LOGD(TAG, "new MIPI DPI panel @%p", panel_handle);
// Save the original functions of MIPI DPI panel
jd9165->del = panel_handle->del;
jd9165->init = panel_handle->init;
// Overwrite the functions of MIPI DPI panel
panel_handle->del = panel_jd9165_del;
panel_handle->init = panel_jd9165_init;
panel_handle->reset = panel_jd9165_reset;
panel_handle->mirror = panel_jd9165_mirror;
panel_handle->invert_color = panel_jd9165_invert_color;
panel_handle->disp_on_off = panel_jd9165_disp_on_off;
panel_handle->user_data = jd9165;
*ret_panel = panel_handle;
ESP_LOGD(TAG, "new jd9165 panel @%p", jd9165);
return ESP_OK;
err:
if (jd9165) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(jd9165);
}
return ret;
}
static const jd9165_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
//{0x11, (uint8_t []){0x00}, 1, 120},
//{0x29, (uint8_t []){0x00}, 1, 20},
{0x30, (uint8_t[]){0x00}, 1, 0},
{0xF7, (uint8_t[]){0x49,0x61,0x02,0x00}, 4, 0},
{0x30, (uint8_t[]){0x01}, 1, 0},
{0x04, (uint8_t[]){0x0C}, 1, 0},
{0x05, (uint8_t[]){0x00}, 1, 0},
{0x06, (uint8_t[]){0x00}, 1, 0},
{0x0B, (uint8_t[]){0x11}, 1, 0},
{0x17, (uint8_t[]){0x00}, 1, 0},
{0x20, (uint8_t[]){0x04}, 1, 0},
{0x1F, (uint8_t[]){0x05}, 1, 0},
{0x23, (uint8_t[]){0x00}, 1, 0},
{0x25, (uint8_t[]){0x19}, 1, 0},
{0x28, (uint8_t[]){0x18}, 1, 0},
{0x29, (uint8_t[]){0x04}, 1, 0},
{0x2A, (uint8_t[]){0x01}, 1, 0},
{0x2B, (uint8_t[]){0x04}, 1, 0},
{0x2C, (uint8_t[]){0x01}, 1, 0},
{0x30, (uint8_t[]){0x02}, 1, 0},
{0x01, (uint8_t[]){0x22}, 1, 0},
{0x03, (uint8_t[]){0x12}, 1, 0},
{0x04, (uint8_t[]){0x00}, 1, 0},
{0x05, (uint8_t[]){0x64}, 1, 0},
{0x0A, (uint8_t[]){0x08}, 1, 0},
{0x0B, (uint8_t[]){0x0A,0x1A,0x0B,0x0D,0x0D,0x11,0x10,0x06,0x08,0x1F,0x1D}, 11, 0},
{0x0C, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0D, (uint8_t[]){0x16,0x1B,0x0B,0x0D,0x0D,0x11,0x10,0x07,0x09,0x1E,0x1C}, 11, 0},
{0x0E, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0F, (uint8_t[]){0x16,0x1B,0x0D,0x0B,0x0D,0x11,0x10,0x1C,0x1E,0x09,0x07}, 11, 0},
{0x10, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x11, (uint8_t[]){0x0A,0x1A,0x0D,0x0B,0x0D,0x11,0x10,0x1D,0x1F,0x08,0x06}, 11, 0},
{0x12, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x14, (uint8_t[]){0x00,0x00,0x11,0x11}, 4, 0},
{0x18, (uint8_t[]){0x99}, 1, 0},
{0x30, (uint8_t[]){0x06}, 1, 0},
{0x12, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
{0x13, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
// {0x30, (uint8_t[]){0x08}, 1, 0},
// {0x05, (uint8_t[]){0x01}, 1, 0},
// {0x0C, (uint8_t[]){0x1A}, 1, 0},
// {0x0D, (uint8_t[]){0x0E}, 1, 0},
// {0x30, (uint8_t[]){0x07}, 1, 0},
// {0x01, (uint8_t[]){0x04}, 1, 0},
{0x30, (uint8_t[]){0x0A}, 1, 0},
{0x02, (uint8_t[]){0x4F}, 1, 0},
{0x0B, (uint8_t[]){0x40}, 1, 0},
{0x12, (uint8_t[]){0x3E}, 1, 0},
{0x13, (uint8_t[]){0x78}, 1, 0},
{0x30, (uint8_t[]){0x0D}, 1, 0},
{0x0D, (uint8_t[]){0x04}, 1, 0},
{0x10, (uint8_t[]){0x0C}, 1, 0},
{0x11, (uint8_t[]){0x0C}, 1, 0},
{0x12, (uint8_t[]){0x0C}, 1, 0},
{0x13, (uint8_t[]){0x0C}, 1, 0},
{0x30, (uint8_t[]){0x00}, 1, 0},
// {0X3A, (uint8_t[]){0x55}, 1, 0},
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 50},
};
static esp_err_t panel_jd9165_del(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
if (jd9165->reset_gpio_num >= 0) {
gpio_reset_pin(jd9165->reset_gpio_num);
}
// Delete MIPI DPI panel
jd9165->del(panel);
ESP_LOGD(TAG, "del jd9165 panel @%p", jd9165);
free(jd9165);
return ESP_OK;
}
static esp_err_t panel_jd9165_init(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
const jd9165_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_cmd_overwritten = false;
uint8_t ID[3];
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io, 0x04, ID, 3), TAG, "read ID failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
jd9165->madctl_val,
}, 1), TAG, "send command failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (jd9165->init_cmds) {
init_cmds = jd9165->init_cmds;
init_cmds_size = jd9165->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9165_lcd_init_cmd_t);
}
for (int i = 0; i < init_cmds_size; i++) {
// Check if the command has been used or conflicts with the internal
if (init_cmds[i].data_bytes > 0) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
jd9165->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
default:
is_cmd_overwritten = false;
break;
}
if (is_cmd_overwritten) {
is_cmd_overwritten = false;
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
init_cmds[i].cmd);
}
}
// Send command
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
}
ESP_LOGD(TAG, "send init commands success");
ESP_RETURN_ON_ERROR(jd9165->init(panel), TAG, "init MIPI DPI panel failed");
return ESP_OK;
}
static esp_err_t panel_jd9165_reset(esp_lcd_panel_t *panel)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
// Perform hardware reset
if (jd9165->reset_gpio_num >= 0) {
gpio_set_level(jd9165->reset_gpio_num, !jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(5));
gpio_set_level(jd9165->reset_gpio_num, jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(jd9165->reset_gpio_num, !jd9165->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(120));
} else if (io) { // Perform software reset
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(120));
}
return ESP_OK;
}
static esp_err_t panel_jd9165_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
uint8_t command = 0;
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
if (invert_color_data) {
command = LCD_CMD_INVON;
} else {
command = LCD_CMD_INVOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_jd9165_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
uint8_t madctl_val = jd9165->madctl_val;
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
// Control mirror through LCD command
if (mirror_x) {
madctl_val |= JD9165_CMD_GS_BIT;
} else {
madctl_val &= ~JD9165_CMD_GS_BIT;
}
if (mirror_y) {
madctl_val |= JD9165_CMD_SS_BIT;
} else {
madctl_val &= ~JD9165_CMD_SS_BIT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
madctl_val
}, 1), TAG, "send command failed");
jd9165->madctl_val = madctl_val;
return ESP_OK;
}
static esp_err_t panel_jd9165_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
jd9165_panel_t *jd9165 = (jd9165_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = jd9165->io;
int command = 0;
if (on_off) {
command = LCD_CMD_DISPON;
} else {
command = LCD_CMD_DISPOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
#endif
@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_mipi_dsi.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LCD panel initialization commands.
*
*/
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} jd9165_lcd_init_cmd_t;
/**
* @brief LCD panel vendor configuration.
*
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
*
*/
typedef struct {
const jd9165_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
* The array should be declared as `static const` and positioned outside the function.
* Please refer to `vendor_specific_init_default` in source file.
*/
uint16_t init_cmds_size; /*<! Number of commands in above array */
struct {
esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
} mipi_config;
} jd9165_vendor_config_t;
/**
* @brief Create LCD panel for model JD9165
*
* @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config General panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
* - Otherwise on fail
*/
esp_err_t esp_lcd_new_panel_jd9165(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
esp_lcd_panel_handle_t *ret_panel);
/**
* @brief MIPI-DSI bus configuration structure
*
* @param[in] lane_num Number of data lanes
* @param[in] lane_mbps Lane bit rate in Mbps
*
*/
#define JD9165_PANEL_BUS_DSI_2CH_CONFIG() \
{ \
.bus_id = 0, \
.num_data_lanes = 2, \
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, \
.lane_bit_rate_mbps = 550, \
}
/**
* @brief MIPI-DBI panel IO configuration structure
*
*/
#define JD9165_PANEL_IO_DBI_CONFIG() \
{ \
.virtual_channel = 0, \
.lcd_cmd_bits = 8, \
.lcd_param_bits = 8, \
}
/**
* @brief MIPI DPI configuration structure
*
* @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
*
* @param[in] px_format Pixel format of the panel
*
*/
#define JD9165_1024_600_PANEL_60HZ_DPI_CONFIG(px_format) \
{ \
.virtual_channel = 0, \
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
.dpi_clock_freq_mhz = 56, \
.pixel_format = px_format, \
.num_fbs = 1, \
.video_timing = { \
.h_size = 1024, \
.v_size = 600, \
.hsync_pulse_width = 40, \
.hsync_back_porch = 160, \
.hsync_front_porch = 160, \
.vsync_pulse_width = 10, \
.vsync_back_porch = 23, \
.vsync_front_porch = 12, \
}, \
.flags= { \
.use_dma2d = true, \
}, \
}
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,153 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_io.h"
#include "esp_ldo_regulator.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "Arduino.h"
#include "esp_lcd_jd9165.h"
#include "jd9165_lcd.h"
#define LCD_H_RES 1024
#define LCD_V_RES 600
#define MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB565)
#define LCD_BIT_PER_PIXEL (16)
// “VDD_MIPI_DPHY”应供电 2.5V,可从内部 LDO 稳压器或外部 LDO 芯片获取电源
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 连接至 VDD_MIPI_DPHY
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_BK_LIGHT GPIO_NUM_23
static const char *TAG = "example";
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
jd9165_lcd::jd9165_lcd(int8_t lcd_rst)
{
_lcd_rst = lcd_rst;
}
void jd9165_lcd::example_bsp_enable_dsi_phy_power()
{
// 打开 MIPI DSI PHY 的电源,使其从“无电”状态进入“关机”状态
esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
#ifdef EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN
esp_ldo_channel_config_t ldo_mipi_phy_config = {
.chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
#endif
}
void jd9165_lcd::example_bsp_init_lcd_backlight()
{
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
gpio_config_t bk_gpio_config = {
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT,
.mode = GPIO_MODE_OUTPUT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
#endif
}
void jd9165_lcd::example_bsp_set_lcd_backlight(uint32_t level)
{
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, level);
#endif
}
void jd9165_lcd::begin()
{
example_bsp_enable_dsi_phy_power();
example_bsp_init_lcd_backlight();
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
// 首先创建 MIPI DSI 总线,它还将初始化 DSI PHY
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
esp_lcd_dsi_bus_config_t bus_config = JD9165_PANEL_BUS_DSI_2CH_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
// 我们使用DBI接口发送LCD命令和参数
esp_lcd_dbi_io_config_t dbi_config = JD9165_PANEL_IO_DBI_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io_handle));
// 创建JD9165控制面板
esp_lcd_dpi_panel_config_t dpi_config = JD9165_1024_600_PANEL_60HZ_DPI_CONFIG(MIPI_DPI_PX_FORMAT);
jd9165_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = _lcd_rst,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_jd9165(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// 打开背光
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
}
void jd9165_lcd::lcd_draw_bitmap(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint8_t *color_data)
{
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, color_data);
}
void jd9165_lcd::draw16bitbergbbitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *color_data)
{
uint16_t x_start = x;
uint16_t y_start = y;
uint16_t x_end = w + x;
uint16_t y_end = h + y;
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end, y_end, color_data);
}
void jd9165_lcd::fillScreen(uint16_t color)
{
uint16_t *color_data = (uint16_t *)heap_caps_malloc(480 * 272 * 2, MALLOC_CAP_INTERNAL);
memset(color_data, color, 480 * 272 * 2);
draw16bitbergbbitmap(0, 0, 480, 272, color_data);
free(color_data);
}
void jd9165_lcd::te_on()
{
esp_lcd_panel_io_tx_param(io_handle, 0x35,new (uint8_t[]){0x00}, 1);
}
void jd9165_lcd::te_off()
{
esp_lcd_panel_io_tx_param(io_handle, 0x34,new (uint8_t[]){0x00}, 0);
}
uint16_t jd9165_lcd::width()
{
return LCD_H_RES;
}
uint16_t jd9165_lcd::height()
{
return LCD_V_RES;
}
@@ -0,0 +1,26 @@
#ifndef _JD9165_LCD_H
#define _JD9165_LCD_H
#include <stdio.h>
class jd9165_lcd
{
public:
jd9165_lcd(int8_t lcd_rst);
void begin();
void example_bsp_enable_dsi_phy_power();
void example_bsp_init_lcd_backlight();
void example_bsp_set_lcd_backlight(uint32_t level);
void lcd_draw_bitmap(uint16_t x_start, uint16_t y_start,
uint16_t x_end, uint16_t y_end, uint8_t *color_data);
void draw16bitbergbbitmap(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *color_data);
void fillScreen(uint16_t color);
void te_on();
void te_off();
uint16_t width();
uint16_t height();
private:
int8_t _lcd_rst;
};
#endif
@@ -0,0 +1,284 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
static const char *TAG = "GT911";
/* GT911 registers */
#define ESP_LCD_TOUCH_GT911_READ_XY_REG (0x814E)
#define ESP_LCD_TOUCH_GT911_CONFIG_REG (0x8047)
#define ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG (0x8140)
/*******************************************************************************
* Function definitions
*******************************************************************************/
static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp);
static bool esp_lcd_touch_gt911_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);
static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp);
/* I2C read/write */
static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data);
/* GT911 reset */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp);
/* Read status and config register */
static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp);
static void touch_scale(uint16_t *x_coordinate,uint16_t *y_coordinate);
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch)
{
esp_err_t ret = ESP_OK;
assert(io != NULL);
assert(config != NULL);
assert(out_touch != NULL);
/* Prepare main structure */
esp_lcd_touch_handle_t esp_lcd_touch_gt911 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
ESP_GOTO_ON_FALSE(esp_lcd_touch_gt911, ESP_ERR_NO_MEM, err, TAG, "no mem for GT911 controller");
/* Communication interface */
esp_lcd_touch_gt911->io = io;
/* Only supported callbacks are set */
esp_lcd_touch_gt911->read_data = esp_lcd_touch_gt911_read_data;
esp_lcd_touch_gt911->get_xy = esp_lcd_touch_gt911_get_xy;
esp_lcd_touch_gt911->del = esp_lcd_touch_gt911_del;
/* Mutex */
esp_lcd_touch_gt911->data.lock.owner = portMUX_FREE_VAL;
/* Save config */
memcpy(&esp_lcd_touch_gt911->config, config, sizeof(esp_lcd_touch_config_t));
/* Prepare pin for touch interrupt */
if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) {
const gpio_config_t int_gpio_config = {
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_NEGEDGE,
.pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num)
};
ret = gpio_config(&int_gpio_config);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
/* Register interrupt callback */
if (esp_lcd_touch_gt911->config.interrupt_callback) {
esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_gt911, esp_lcd_touch_gt911->config.interrupt_callback);
}
}
/* Prepare pin for touch controller reset */
if (esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) {
const gpio_config_t rst_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.rst_gpio_num)
};
ret = gpio_config(&rst_gpio_config);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
}
/* Reset controller */
ret = touch_gt911_reset(esp_lcd_touch_gt911);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 reset failed");
/* Read status and config info */
ret = touch_gt911_read_cfg(esp_lcd_touch_gt911);
ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 init failed");
err:
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error (0x%x)! Touch controller GT911 initialization failed!", ret);
if (esp_lcd_touch_gt911) {
esp_lcd_touch_gt911_del(esp_lcd_touch_gt911);
}
}
*out_touch = esp_lcd_touch_gt911;
return ret;
}
static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp)
{
esp_err_t err;
uint8_t buf[41];
uint8_t touch_cnt = 0;
uint8_t clear = 0;
size_t i = 0;
assert(tp != NULL);
err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, buf, 1);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
/* Any touch data? */
if ((buf[0] & 0x80) == 0x00) {
touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
} else {
/* Count of touched points */
touch_cnt = buf[0] & 0x0f;
if (touch_cnt > 5 || touch_cnt == 0) {
touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
return ESP_OK;
}
/* Read all points */
err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG + 1, &buf[1], touch_cnt * 8);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
/* Clear all */
err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
portENTER_CRITICAL(&tp->data.lock);
/* Number of touched points */
touch_cnt = (touch_cnt > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : touch_cnt);
tp->data.points = touch_cnt;
/* Fill all coordinates */
for (i = 0; i < touch_cnt; i++) {
tp->data.coords[i].x = ((uint16_t)buf[(i * 8) + 3] << 8) + buf[(i * 8) + 2];
tp->data.coords[i].y = (((uint16_t)buf[(i * 8) + 5] << 8) + buf[(i * 8) + 4]);
tp->data.coords[i].strength = (((uint16_t)buf[(i * 8) + 7] << 8) + buf[(i * 8) + 6]);
}
portEXIT_CRITICAL(&tp->data.lock);
}
return ESP_OK;
}
static bool esp_lcd_touch_gt911_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)
{
assert(tp != NULL);
assert(x != NULL);
assert(y != NULL);
assert(point_num != NULL);
assert(max_point_num > 0);
portENTER_CRITICAL(&tp->data.lock);
/* Count of points */
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
for (size_t i = 0; i < *point_num; i++) {
x[i] = tp->data.coords[i].x;
y[i] = tp->data.coords[i].y;
touch_scale(&x[i],&y[i]);
if (strength) {
strength[i] = tp->data.coords[i].strength;
}
}
/* Invalidate */
tp->data.points = 0;
portEXIT_CRITICAL(&tp->data.lock);
return (*point_num > 0);
}
static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
/* Reset GPIO pin settings */
if (tp->config.int_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.int_gpio_num);
}
/* Reset GPIO pin settings */
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
gpio_reset_pin(tp->config.rst_gpio_num);
}
free(tp);
return ESP_OK;
}
/*******************************************************************************
* Private API function
*******************************************************************************/
/* Reset controller */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
vTaskDelay(pdMS_TO_TICKS(10));
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK;
}
static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp)
{
uint8_t buf[4];
assert(tp != NULL);
ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG, (uint8_t *)&buf[0], 3), TAG, "GT911 read error!");
ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&buf[3], 1), TAG, "GT911 read error!");
ESP_LOGI(TAG, "TouchPad_ID:0x%02x,0x%02x,0x%02x", buf[0], buf[1], buf[2]);
ESP_LOGI(TAG, "TouchPad_Config_Version:%d", buf[3]);
return ESP_OK;
}
static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
assert(tp != NULL);
assert(data != NULL);
/* Read data */
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}
static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data)
{
assert(tp != NULL);
// *INDENT-OFF*
/* Write data */
return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1);
// *INDENT-ON*
}
static void touch_scale(uint16_t *x_coordinate,uint16_t *y_coordinate)
{
uint32_t temp_x;
uint32_t temp_y;
temp_x = (*x_coordinate) * 1024 *10 / 800 / 10;
temp_y = (*y_coordinate) * 600 * 10 / 480 / 10;
*x_coordinate = (uint16_t)temp_x;
*y_coordinate = (uint16_t)temp_y;
}
@@ -0,0 +1,99 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c.h"
#include "esp_lcd_touch_gt911.h"
#include "gt911_touch.h"
#define CONFIG_LCD_HRES 600
#define CONFIG_LCD_VRES 1024
static const char *TAG = "example";
esp_lcd_touch_handle_t tp;
esp_lcd_panel_io_handle_t tp_io_handle;
uint16_t touch_strength[1];
uint8_t touch_cnt = 0;
gt911_touch::gt911_touch(int8_t sda_pin, int8_t scl_pin, int8_t rst_pin, int8_t int_pin)
{
_sda = sda_pin;
_scl = scl_pin;
_rst = rst_pin;
_int = int_pin;
}
void gt911_touch::begin()
{
i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = (gpio_num_t)_sda,
.scl_io_num = (gpio_num_t)_scl,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
};
i2c_conf.master.clk_speed = 400000; // 400kHz
ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, i2c_conf.mode, 0, 0, 0));
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
ESP_LOGI(TAG, "Initialize touch IO (I2C)");
esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_NUM_0, &tp_io_config, &tp_io_handle);
esp_lcd_touch_config_t tp_cfg = {
.x_max = CONFIG_LCD_HRES,
.y_max = CONFIG_LCD_VRES,
.rst_gpio_num = (gpio_num_t)_rst,
.int_gpio_num = (gpio_num_t)_int,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
};
ESP_LOGI(TAG, "Initialize touch controller gt911");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
}
bool gt911_touch::getTouch(uint16_t *x, uint16_t *y)
{
esp_lcd_touch_read_data(tp);
bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, x, y, touch_strength, &touch_cnt, 1);
return touchpad_pressed;
}
void gt911_touch::set_rotation(uint8_t r){
switch(r){
case 0:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
esp_lcd_touch_set_mirror_y(tp, false);
break;
case 1:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, true);
esp_lcd_touch_set_mirror_y(tp, true);
break;
case 2:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
esp_lcd_touch_set_mirror_y(tp, false);
break;
case 3:
esp_lcd_touch_set_swap_xy(tp, false);
esp_lcd_touch_set_mirror_x(tp, true);
esp_lcd_touch_set_mirror_y(tp, true);
break;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

@@ -0,0 +1,78 @@
JDEVB_RSOX(1024);
JDEVB_RSOY(600);
JDEVB_VS(2);
JDEVB_VBP(21);
JDEVB_VFP(12); //12->18(HKC),CKV_OFF(41->4B)_20230228
JDEVB_HS(24);
JDEVB_HBP(136);
JDEVB_HFP(160);
JDEVB_DOTCLK(51.2);
//------------------------------------------------//
//VCC=1.8V/3.3V, AVDD=11V, VGH=20V, VGL=-7, VCOM=3.6(~3.8)V
//1+2Dot inversion
//------------------------------------------------//
{0x30, 1,{0x00}},
{0xF7, 4,{0x49,0x61,0x02,0x00}},
{0x30, 1,{0x01}},
{0x04, 1,{0x0C}},
{0x05, 1,{0x00}}, //05=06(xhs)
{0x06, 1,{0x00}}, //06=80(xhs)
{0x0B, 1,{0x11}}, //0x13=4lanes0x12=3lanes0x11=2lanes0x10=1 lanes
{0x17, 1,{0x00}},
{0x20, 1,{0x04}}, //add //r_lansel_sel_reg=1, software charge lane must open
{0x1F, 1,{0x05}}, //add hs_settle time
{0x23, 1,{0x00}}, //add //close gas
{0x25, 1,{0x19}},
{0x28, 1,{0x18}},
{0x29, 1,{0x04}}, //revcom
{0x2A, 1,{0x01}}, //revcom
{0x2B, 1,{0x04}}, //vcom
{0x2C, 1,{0x01}}, //vcom
{0x30, 1,{0x02}},
{0x01, 1,{0x22}},
{0x03, 1,{0x12}},
{0x04, 1,{0x00}},
{0x05, 1,{0x64}},
{0x0A, 1,{0x08}},
{0x0B, 11,{0x0A,0x1A,0x0B,0x0D,0x0D,0x11,0x10,0x06,0x08,0x1F,0x1D}},
{0x0C, 11,{0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}},
{0x0D, 11,{0x16,0x1B,0x0B,0x0D,0x0D,0x11,0x10,0x07,0x09,0x1E,0x1C}},
{0x0E, 11,{0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}},
{0x0F, 11,{0x16,0x1B,0x0D,0x0B,0x0D,0x11,0x10,0x1C,0x1E,0x09,0x07}},
{0x10, 11,{0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}},
{0x11, 11,{0x0A,0x1A,0x0D,0x0B,0x0D,0x11,0x10,0x1D,0x1F,0x08,0x06}},
{0x12, 11,{0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}},
{0x14, 4,{0x00,0x00,0x11,0x11}}, //CKV_OFF
{0x18, 1,{0x99}},
{0x30, 1,{0x06}},
{0x12, 14,{0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}},
{0x13, 14,{0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}},
//{0x30, 1,{0x08}}, //RGB&LVDS add
//{0x05, 1,{0x01}}, //RGB&LVDS add
//{0x0C, 1,{0x1A}}, //RGB&LVDS add
//{0x0D, 1,{0x0E}}, //RGB&LVDS add
//{0x30, 1,{0x07}},
//{0x01, 1,{0x04}}, //P7_r01=0x04(0x06)_r_hster_delay
{0x30, 1,{0x0A}},
{0x02, 1,{0x4F}},
{0x0B, 1,{0x40}},
{0x12, 1,{0x3E}},
{0x13, 1,{0x78}},
{0x30, 1,{0x0D}},
{0x0D, 1,{0x04}}, //0x0C, 0x04
{0x10, 1,{0x0C}},
{0x11, 1,{0x0C}},
{0x12, 1,{0x0C}},
{0x13, 1,{0x0C}},
{0x30, 1,{0x00}},
{0x11,0,{}},
{REGFLAG_DELAY, 120, {}},
{0x29,0,{}},
{REGFLAG_DELAY, 20, {}},
{REGFLAG_END_OF_TABLE, 0x00, {}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,3 @@
1、电脑需要安装CH340驱动程序:The computer needs to install the CH340 driver program
2、保证供电充足,开发板电流600ma以上:Ensure sufficient power supply, with a development board current of over 600mA
3、烧录选择USB1口:Choose USB1 port for burning
+887
View File
@@ -0,0 +1,887 @@
Version 1.9.5:
- Die festen Breiten aus 1.9.4 haben nicht gereicht: Auch die HÖHE muss festliegen. LVGL
prüft beim Setzen eines Textes, ob sich die Eigengröße der Beschriftung geändert haben
könnte - ist eines der beiden Maße frei, gilt das Layout des Elternteils als neu zu
berechnen, und das macht die ganze Seite ungültig. Temperaturwerte, Sollwerte, die Werte
der unteren Zeile und die Uhrzeit in der Kopfleiste haben jetzt feste Maße in beide
Richtungen.
- Die Uhrzeit hatte denselben Effekt in der Kopfleiste: Beim Wechsel der Minute wurde die
Leiste samt Menü-Knopf neu berechnet. Das erklärt den Bereich „x 6..57, y 2..41", der in
fast jedem Durchgang auftauchte.
- Zusätzlich meldet die Firmware jetzt, wenn der Inhaltsbereich oder die Dashboard-Seite als
Ganzes ungültig gemacht wird. Damit ist zu unterscheiden, ob eine einzelne Beschriftung
den Bildaufbau auslöst oder gleich der komplette Bereich - zwei verschiedene Ursachen mit
verschiedenen Gegenmitteln.
Version 1.9.4:
- Die Messung mit Koordinaten hat den Verursacher benannt: Der ungültige Bereich war
„x 0..1279, y 44..719" - also der gesamte Inhalt unterhalb der Kopfleiste, bei jeder
Zustandsmeldung.
- Grund: Die Anzeigewerte hatten keine feste Breite. Wächst oder schrumpft eine Beschriftung
mit ihrem Inhalt - etwa von „92,4 °C" auf „100,1 °C" -, rechnet LVGL das Layout der
gesamten Seite neu und macht sie dabei komplett ungültig. Es genügt also nicht, den Text
nur bei echter Änderung zu setzen; er darf beim Ändern auch die Breite nicht verändern.
- Temperaturwerte, Sollwerte und die Werte der unteren Zeile haben jetzt eine feste Breite
mit zentriertem Text. Sichtbar ändert sich dadurch nichts - die Werte standen ohnehin
mittig.
Version 1.9.3:
- Fehlerbehebung: Bei geöffnetem Menü war der Seiteninhalt dahinter verschwunden. Der
Vorhang des Menüs war in 1.8.7 deckend gemacht worden, um Zeichenlast zu sparen - dabei
verdeckt er aber genau das, was sichtbar bleiben soll. Er ist wieder durchscheinend.
- Weitere Stellen umgestellt, die bei jeder Zustandsmeldung neu zeichnen ließen, obwohl sich
nichts geändert hatte: das Zustandsband der Darstellung „Band" (es spannt sich über die
volle Breite und machte damit allein schon einen Streifen über den ganzen Bildschirm
ungültig), die Heizleistungsbalken und das WLAN-Symbol.
- Die Messung nennt jetzt zusätzlich die Lage der neu gezeichneten Bereiche. Die Größe
allein sagt nicht, welcher Teil der Oberfläche das Neuzeichnen auslöst - die Koordinaten
schon.
Version 1.9.2:
- Ursache der Trägheit endlich gefunden. Die erweiterte Messung zeigte: „1 Bereich, 100 %
der Fläche" - LVGL zeichnete bei jeder Zustandsmeldung den kompletten Bildschirm neu, also
einmal pro Sekunde 240 bis 300 Millisekunden lang. Es war also kein Tempoproblem beim
Zeichnen, sondern es wurde viel zu viel als „muss neu gezeichnet werden" gemeldet.
- Grund: LVGL macht den Bereich einer Beschriftung ungültig, sobald ihr Text gesetzt wird -
auch dann, wenn er sich gar nicht geändert hat. Dasselbe gilt für Farbwechsel. Auf dem
Dashboard wurden im Sekundentakt Werte quer über den ganzen Bildschirm gesetzt: Uhrzeit
oben, Temperaturen in der Mitte, Bezugswerte unten. LVGL fasst benachbarte ungültige
Bereiche zu einem zusammen - und der umfasste dann die gesamte Fläche.
- Alle regelmäßig gesetzten Werte werden jetzt vorher verglichen und nur bei echter Änderung
neu geschrieben; für Farben gilt dasselbe. Da sich Temperaturen selten um ein Zehntelgrad
ändern und der Sollwert praktisch nie, entfallen die meisten dieser Neuzeichnungen.
- Das betrifft alle Bildschirmgrößen; auf den JC-Panels war es nur nie störend, weil dort ein
Bildaufbau ein Bruchteil davon kostet.
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).
Bisher erbten sie die kleine Grundschrift und behielten eine feste Breite - neben dem
größeren Text wirkten sie dadurch verloren.
- Die große Standby-Uhr bleibt bewusst bei ihrer festen Größe: Sie ist bereits auf die
Bildschirmhöhe abgestimmt, eine Umschaltung würde sie nur schlechter passend machen.
- Neu: eine Zeitmessung für langsame Bildaufbauten. Überschreitet ein Zeichenzyklus
200 Millisekunden, meldet die serielle Konsole seine Dauer. Damit lässt sich feststellen,
welcher Vorgang tatsächlich Zeit kostet - die bisherigen Erklärungen für die Verzögerung
beim Aufweckdialog haben sich als falsch erwiesen, und weiteres Raten hilft nicht.
Im Normalbetrieb bleibt die Ausgabe still.
Version 1.8.7:
- Vom Fingertipp auf die Standby-Uhr bis zum Aufweckdialog vergingen rund fünf Sekunden mit
schwarzem Bildschirm. Ursache war der halbdurchsichtige Vorhang, den der Dialog über den
ganzen Bildschirm legt: Dafür muss LVGL einen Vollbild-Zwischenpuffer anlegen und die
gesamte Fläche mischen - bei 1280x720 in 24 Bit die teuerste Operation überhaupt.
- Der Vorhang ist auf dem Waveshare-Panel jetzt deckend. Verloren geht dabei nichts: Im
Standby liegt dahinter ohnehin nur Schwarz.
- Aus demselben Grund ist auch der Vorhang des Menüs deckend, in einem dunklen Ton. Auf
dunklem Grund sieht das praktisch genauso aus wie halbdurchsichtig, spart aber dieselbe
Mischarbeit. Die JC-Panels behalten in beiden Fällen die Transparenz.
- Der Aufweckdialog wird mit größerer Schrift auch breiter (420 / 640 / 900 Pixel), damit
der Text nicht an unglücklichen Stellen umbricht.
Version 1.8.6:
- Die Uhrzeit verschwindet nicht mehr, wenn die Hauptplatine ihre Zeit gerade neu abgleicht.
Der P4 merkt sich die zuletzt empfangene Uhrzeit und zählt zwischen den Meldungen selbst
weiter; sobald die Hauptplatine wieder eine Zeit schickt, gilt deren Wert.
- Der P4 stellt dabei nichts richtig und ersetzt keine Zeitquelle - er überbrückt nur die
Lücke. Hat er noch nie eine Uhrzeit bekommen, bleibt die Anzeige wie bisher leer.
- Gilt für die Uhr in der Kopfleiste und für die große Standby-Uhr.
Version 1.8.5:
- Die Verzögerung von rund zwei Sekunden rund um den Standby ist behoben. Sie lag nicht an
der Helligkeitsumschaltung, sondern daran, dass die Standby-Uhr bei JEDER Zustandsmeldung
der Hauptplatine nach vorn geholt wurde. Das ordnet die Ebenen neu und lässt LVGL den
gesamten Bildschirm neu zeichnen - einmal pro Sekunde, jedes Mal 1280x720 in 24 Bit.
Dadurch hing die Oberfläche dauerhaft ein bis zwei Sekunden hinterher. Nach vorn geholt
wird die Uhr jetzt beim Einblenden, danach bleibt sie dort.
- Der Aufweckdialog schließt sich nach 30 Sekunden ohne Auswahl von selbst und wird wie
„Abbrechen" gewertet. Vorher blieb das Display hell stehen - der Dialog schaltet auf
normale Helligkeit, und ein versehentlicher Tipp auf die Uhr hätte die Maschine die ganze
Nacht leuchten lassen.
- Die Verlaufskurve hat keinen eigenen Hintergrund mehr; sie liegt jetzt direkt auf der
Karte. Die abgesetzte Fläche war nur nötig, solange die Kurve vor dem ersten Messwert
leer blieb - seit sie beim aktuellen Wert startet, ist immer eine Linie da.
- Auf der Statistikseite erschien ein leeres Rechteck, wenn ohne Waage bezogen wurde. Dort
stand ein typografischer Gedankenstrich, den der Zeichensatz der Schrift nicht enthält.
Jetzt ein einfacher Bindestrich.
Version 1.8.4:
- Standby-Uhr und Aufweckdialog erscheinen jetzt sofort statt mit spürbarer Verzögerung.
Ursache waren die weichen Ein- und Ausblendungen: Eine Deckkraft-Animation ist bei einem
Bildspeicher in RGB888 das Teuerste, was die Oberfläche tun kann, weil für jedes Einzelbild
die gesamte Fläche unter der Ebene neu gezeichnet und mit ihr gemischt werden muss. Auf
1280x720 dauerte eine solche Blende deutlich länger als die 450 ms, die vorgesehen waren.
Auf dem Waveshare-Panel laufen die Übergänge deshalb ohne Animation; die JC-Panels
behalten sie.
- Beim Wechsel der Schriftgröße blitzte kurz ein blaues Bild auf. Das kommt nicht von der
Firmware: Das Panel zeigt von sich aus Blau, sobald kein Videosignal anliegt - und beim
nötigen Neustart reißt das Signal kurz ab. Die Beleuchtung wird jetzt vorher abgeschaltet;
dunkel ist unauffälliger als blau.
- Neue Einstellung unter Darstellung: „Temperaturzahl in der Zustandsfarbe". Aus bleibt die
Zahl weiß, die Zustandsfarbe steckt dann allein im Balken darunter - ruhiger, und die Zahl
bleibt eine Zahl. Ein bedeutet, sie färbt sich mit, wie bisher. Vorgabe ist aus.
Warnungen bleiben in jedem Fall rot; das ist keine Gestaltung, sondern eine Meldung.
Version 1.8.3:
- Fehlerbehebung: Bei allen Darstellungen stand hinter der Temperatur ein leeres Rechteck
statt des C. Die beiden großen Schriften waren auf Ziffern, Komma, Punkt, Minus und
Gradzeichen beschränkt - das C fehlte darin schlicht. Es ist jetzt enthalten, ebenso g
und s für die Gewichts- und Zeitanzeige während eines Bezugs.
- Fehlerbehebung: Bei der Darstellung „Verlauf" war keine Kurve zu sehen. Ein frisch
angelegtes Diagramm enthält lauter „kein Wert"-Punkte und zeichnet deshalb nichts; ohne
Hintergrund war der Bereich vollständig unsichtbar, bis nach einer Minute genug Messwerte
zusammengekommen wären. Die Kurve startet jetzt beim aktuellen Messwert, und ihre Fläche
ist leicht abgesetzt.
- Die großen Zahlen des Dashboards stehen jetzt im fetten Schnitt und etwas enger, wie im
Entwurf. Erzeugt aus der variablen Maven-Pro-Datei mit Gewicht 700 - dieselbe Schriftart
wie bisher, nur kräftiger. Die übrige Oberfläche bleibt unverändert.
Version 1.8.2:
- Die Liste der letzten Bezüge auf der Statistikseite ist jetzt eine Tabelle mit festen
Spalten: Zeitpunkt, Dauer, Gewicht und gegebenenfalls die Kennzeichnung „kalt".
- Vorher war es ein einziges mehrzeiliges Textfeld, in dem die Werte durch Leerzeichen
getrennt aneinandergereiht standen. Weil Zahlen unterschiedlich breit sind, stand nichts
untereinander - zwei Bezüge ließen sich nicht vergleichen, ohne jede Zeile einzeln zu
lesen. Genau das sah unruhig aus.
- Ziffern haben jetzt gleiche Breite, jede zweite Zeile ist leicht abgesetzt, und eine
Kopfzeile benennt die Spalten. Wurde ohne Waage bezogen, bleibt die Gewichtsspalte leer,
statt eine Null vorzutäuschen.
- Die Zeilen werden einmal angelegt und danach nur gefüllt oder ausgeblendet, statt bei
jeder Aktualisierung neu zu entstehen.
Version 1.8.1:
- Die Bezugsanzeige ist da, und zwar für alle vier Darstellungen gemeinsam: Sobald ein
Bezug oder Dampfbezug läuft, schrumpft der Temperaturbereich und die untere Zeile wächst.
Shot-Timer und Gewicht werden dabei auf 96 Pixel vergrößert - während eines Bezugs zählen
Zeit und Gewicht, die Kesseltemperaturen sind dann Nebensache.
- Es gibt keine zweite Ansicht: Die Seite verschiebt nur das Gewicht zwischen ihren beiden
Bereichen. Damit bleibt alles an seinem Platz, und es gibt nichts doppelt zu pflegen.
- Der Wechsel hält nach dem Bezug noch vier Sekunden an. Ohne dieses Nachhalten würde die
Seite bei jeder kurzen Unterbrechung hin- und herspringen - das wäre unruhiger als gar
kein Wechsel.
Version 1.8.0:
- Das Dashboard hat vier neue Darstellungen, die die bisherigen fünf ersetzen. Sie sind
keine Varianten desselben Bildes, sondern vier Antworten auf die Frage, was die große
Fläche des 7-Zöllers leisten soll:
* Gewichtet - Wasser bekommt zwei Drittel der Fläche, Dampf ein Drittel. Beim Espresso
zählt die Wassertemperatur; die Asymmetrie ist die Aussage.
* Verlauf - unter jeder Temperatur eine flache Kurve der letzten Minute. Die eigentliche
Frage an eine PID-Regelung ist nicht „wie warm", sondern „hält sie" - und das kann eine
Zahl grundsätzlich nicht zeigen. Der Ausschnitt hängt am Sollwert, damit die Abweichung
sichtbar wird statt in der Skala unterzugehen.
* Flächen - keine Kartenkanten, zwei Felder mit einem Hauch Helligkeitsunterschied.
* Band - ein farbiges Band über die volle Breite sagt den Zustand, bevor eine Zahl
gelesen wird: bereit, heizt, Bezug, Cold Extraction oder Standby. Neue Vorgabe.
- Dafür gibt es zwei neue Schriftgrade (96 und 140 Pixel). Sie enthalten nur Ziffern,
Komma, Punkt, Minus und Gradzeichen - mit vollem Zeichensatz wären sie um ein Vielfaches
größer, ohne etwas beizutragen. Die größte Textschrift hatte bisher 48 Pixel, was für die
großen Zahlen nicht reichte.
- Ältere Stände haben in den Einstellungen noch die Nummer einer der früheren Darstellungen
gespeichert; liegt sie außerhalb, fällt sie auf die Vorgabe zurück.
- Der Aufbau der Bezugsanzeige - untere Zeile wechselt während eines Bezugs auf große
Timer- und Gewichtsanzeige - folgt als nächster Schritt.
Version 1.7.3:
- Die Schriftgröße steht jetzt auf der Info-Seite im Abschnitt „Darstellung", zusammen mit
der Wahl der Temperaturanzeige und dem Live-Bezugsschirm. Dort gehört sie hin: Es ist
eine Einstellung dieses Displays, keine Wartungsaufgabe.
- Die Seite Reinigung & Wartung hat damit wieder vier Karten in zwei Spalten.
- Die Auswahl selbst ist unverändert: drei Stufen, Neustart nach der Auswahl.
- Sie steht auf allen Panels zur Verfügung, nicht nur auf dem 7-Zöller.
Version 1.7.2:
- Die Schriftgröße lässt sich jetzt einstellen: Klein, Mittel oder Groß, zu finden auf der
Seite Reinigung & Wartung in der neuen Karte „Darstellung".
- LVGL-Schriften sind fest einkompilierte Bilddaten; es gibt sie nur in den Größen, die im
Projekt liegen (14, 18, 28, 40 und 48 Pixel). Stufenlos skalieren lässt sich also nichts.
Stattdessen gibt es drei Rollen - klein, normal, hervorgehoben -, die je nach Einstellung
eine Stufe höher greifen.
- Die großen Anzeigen für Temperatur und Uhrzeit bleiben unverändert: Sie sind bereits auf
die Bildschirmgröße abgestimmt und sollen nicht mitwachsen.
- Die Einstellung liegt lokal im Display, nicht auf der Hauptplatine - sie betrifft nur
dieses Gerät. Nach der Auswahl startet der P4 neu, weil die Schriften in bereits
angelegten Bedienelementen stecken und sich nicht nachträglich umhängen lassen.
- Zu erwarten: In der Stufe Groß brauchen die Karten mehr Platz. Ob alle Seiten dann noch
vollständig auf den Bildschirm passen, zeigt erst das Gerät.
Version 1.7.1:
- Cold Extraction ist auf dem 7-Zoll-Panel dreispaltig: oben die drei Karten mit
Erklaerungstext (Modus, Vorbenetzung, Bezugsende), darunter die knappen (Grundeinstellung,
Hauptbezug, Pumpenschutz), der Speichern-Knopf frei unter allen sechs. Drei statt zwei
Spalten, weil die Erklaerungstexte keine volle Zeilenbreite brauchen und so am Ende keine
halbleere Reihe stehen bleibt.
- Der Warnhinweis stand bisher als eigene Zeile am Seitenende, weit entfernt von dem
Schalter, auf den er sich bezieht. Er sitzt jetzt in der Modus-Karte.
- Reinigung & Wartung ist zweispaltig und besteht aus vier Karten: Wartungszaehler,
Flush-Zeiten, Display-Helligkeit sowie Reinigung & Entkalkung. Bisher standen dort lose
Zeilen unter blossen Zwischenueberschriften, dazwischen freistehende Knoepfe.
- Anders als bei Bruehen und Cold Extraction sitzt der Speichern-Knopf hier IN der Karte:
Jede Karte speichert etwas anderes, deshalb gehoert der Knopf zur Karte statt unter die
Seite.
- Alle drei umgestellten Seiten passen jetzt vollstaendig auf den Bildschirm. Die JC-Panels
behalten ihr einspaltiges Layout.
Version 1.7.0:
- Brew-Control ist auf dem 7-Zoll-Panel zweispaltig: Pre-Infusion und Brew-by-Weight oben,
Brew-by-Time und Dampf-Timer darunter, der Speichern-Knopf frei unter allen Karten.
Damit passt die Seite vollstaendig auf den Bildschirm und muss nicht mehr gescrollt
werden - der teuerste Vorgang der Oberflaeche entfaellt dort ersatzlos.
- Die Anordnung folgt der Bedeutung: links steht, was den Bezug startet, rechts, was ihn
beendet.
- Der Speichern-Knopf sitzt bewusst nicht in einer der Karten. Er speichert die ganze Seite
und wuerde in einer einzelnen Karte so wirken, als gaelte er nur fuer diese.
- Die JC-Panels bleiben einspaltig - dort ist der Platz knapp, nicht ueppig.
- Cold Extraction und Reinigung/Wartung folgen als naechstes.
Version 1.6.27:
- Beim Waveshare-Panel rollen scrollbare Listen nach dem Loslassen nicht mehr aus, sondern
folgen nur dem Finger. Ohne Doppelpufferung entsteht beim Scrollen jedes Bild komplett
neu; am staerksten faellt das beim Ausrollen auf, wo sich das Bild ohne fuehrenden Finger
weiterbewegt und jedes Stocken unmittelbar sichtbar ist.
- Die JC-Panels sind ausgenommen, ihr Bediengefuehl bleibt unveraendert.
Version 1.6.26:
- Fehlerbehebung: Beim Bildlauf blitzten blaue Zwischenbilder auf. Ursache war der zweite
Zeichenpuffer aus 1.6.25. Der Panel-Treiber lehnt eine Uebertragung ab, solange die
vorige noch laeuft; mit zwei Puffern schickt LVGL die naechste aber sofort los. Die
abgelehnte Uebertragung faellt ersatzlos aus, und der betroffene Bildbereich behaelt
seinen alten Inhalt.
- Der zweite Zeichenpuffer ist deshalb wieder entfernt, die Herstellerdatei lvgl_port_v9.c
damit wieder unveraendert. Der eigentliche Gewinn beim Bildlauf bleibt erhalten: Der
Zwischenpuffer hat weiterhin volle Bildhoehe, ein Bildlauf braucht also einen Durchgang
statt sechs.
Version 1.6.25:
- Der Bildlauf war nach der Umstellung auf einen Bildspeicher sehr zaeh. Grund war der
Zwischenpuffer, in den LVGL zeichnet: Mit 120 Zeilen brauchte ein Bildlauf ueber die
ganze Seite sechs Durchgaenge, jeder mit eigener Uebertragung in den Bildspeicher.
- Der Zwischenpuffer hat jetzt volle Bildhoehe - ein Durchgang statt sechs. Dazu kommt ein
zweiter Puffer, damit LVGL bereits den naechsten Bereich zeichnen kann, waehrend der
vorige noch uebertragen wird.
- Kosten: 2 x 2,8 MB PSRAM zusaetzlich, bei 32 MB unkritisch. Am Startverhalten aendert
sich nichts: Diese Puffer sind Zeichenpuffer, keine Bildspeicher der Anzeige - es bleibt
bei EINEM Bildspeicher, und nur der war fuer den Startfehler verantwortlich.
Version 1.6.24:
- Ursache des unzuverlaessigen Starts gefunden und behoben: die Anzahl der Bildspeicher.
Mit zwei Bildspeichern kam nach dem Einschalten kein Bild - der Hochlauf meldete
durchgehend Erfolg, das Panel blieb dunkel, und erst ein Reset half. Mit genau einem
startet es zuverlaessig. Belegt wurde das mit den Prueffarbbalken: Sie erscheinen mit
einem Speicher sofort, mit zweien nicht. Der Werks-Testsketch benutzt ebenfalls einen -
das war der letzte verbliebene Unterschied zu ihm.
- Die Oberflaeche laeuft deshalb ohne Doppelpufferung: LVGL zeichnet in einen
Zwischenpuffer von 120 Zeilen im PSRAM, der dann in den einen Bildspeicher uebertragen
wird. Bei schnellen Bildwechseln kann kurz eine Kante sichtbar werden; ein zuverlaessig
startendes Display wiegt das auf.
- Schalter WS7_SINGLE_FB in pins_config.h. WS7_PARTIAL_REFRESH ist damit wirkungslos, weil
es ohne Doppelpufferung nichts umzuschalten gibt.
- Der PSRAM-Bedarf sinkt von 5,4 MB auf 2,8 MB.
- Die Umgehungen aus den Vorversionen bleiben erhalten: Anlaufzeit, wiederholte
Bridge-Grundeinstellung, wiederholte Weckbefehle und der Neustart nach Kaltstart. Ob
jede davon noch noetig ist, laesst sich jetzt einzeln pruefen - der Startfehler selbst
ist behoben.
Version 1.6.23:
- Der Selbst-Neustart aus 1.6.22 reicht nicht: Ein Software-Neustart setzt offenbar nicht
dasselbe zurueck wie die Reset-Taste. Der Schalter bleibt eingeschaltet, er schadet nicht,
loest das Problem aber nicht allein.
- Zurueck zu dem einen Unterschied zum funktionierenden Werks-Testsketch, der bisher nie
ueberprueft wurde: Dieser legt EINEN Bildspeicher an, die Firmware zwei. Der Pruefbild-Test
lief bislang ebenfalls mit zwei und war damit nie ein echter Nachbau des Testsketches - er
wich an genau der Stelle ab, die noch offen ist.
- Mit WS7_TEST_PATTERN = 1 wird jetzt genau ein Bildspeicher angelegt, wie im Testsketch.
Erscheinen die Farbbalken damit zuverlaessig, liegt es an der Anzahl der Bildspeicher, und
die Oberflaeche laesst sich darauf umstellen.
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.
- Das Bild steht in der tatsächlichen Einbaulage richtig herum, die 180-Grad-Drehung aus
1.6.1 ist deshalb wieder abgeschaltet (WS7_ROTATE_180 = 0). Sie bleibt als Schalter
erhalten, falls das Panel später anders montiert wird.
- Der Touch war dagegen oben/unten spiegelverkehrt: Der Digitizer sitzt gegenüber dem Bild
um die Hochachse gedreht. WS7_TOUCH_MIRROR_RAW_X steht dafür jetzt auf 1. Bild- und
Touch-Ausrichtung sind damit zwei getrennte Schalter, weil sie an diesem Panel eben
nicht zusammenfallen.
Version 1.6.2:
- Kommt das Panel nicht hoch, stürzt der P4 nicht mehr wortlos ab. Die Bringup-Schritte
melden ihren Fehler jetzt im Klartext auf der seriellen Konsole (auch bei abgeschaltetem
Core-Debug-Level), zusammen mit PSRAM-Größe, freiem PSRAM und dem tatsächlich benötigten
Framebuffer-Bedarf. Vorher endete ein fehlgeschlagener Panel-Aufbau in einem
Speicherauszug, aus dem die Ursache nicht hervorging.
- Schlägt der Panel-Aufbau fehl, läuft die Firmware ohne Anzeige weiter, statt ein zweites
Mal abzustürzen: UART-Verbindung und OTA bleiben offen, die S3 kann also neu flashen.
Alle Oberflächenfunktionen prüfen dafür, ob das Dashboard überhaupt aufgebaut wurde.
- Fehlerbehebung: Findet der Touch-Controller nicht statt, lieferte der Hersteller-Treiber
einen bereits freigegebenen Zeiger zurück, der ungeprüft weiterverwendet wurde. Das
Display läuft in diesem Fall jetzt einfach ohne Bedienung weiter.
Version 1.6.1:
- Waveshare 7" (H): Bild und Touch lassen sich um 180 Grad drehen, wenn das Panel auf dem
Kopf eingebaut ist. Schalter WS7_ROTATE_180 in pins_config.h, Vorgabe ist gedreht.
Die Drehung übernimmt der PPA-Grafikbeschleuniger des ESP32-P4 - die Software-Rotation
des LVGL-Ports rechnet nur in RGB565 und wäre für dieses Panel unbrauchbar.
- Der Touch dreht mit, sonst läge die Bedienung spiegelbildlich zum Bild.
Version 1.6.0:
- Neu: Dieselbe Firmware läuft jetzt auch auf einem Waveshare ESP32-P4-Pico mit dem
Waveshare 7inch DSI LCD (H) (1280x720). Umgeschaltet wird über „JC_PANEL_TYPE" in
config.h; WS_PANEL_7H ist ab dieser Version die Vorgabe, JC_PANEL_43 und JC_PANEL_70
bleiben unverändert verfügbar.
- Das Waveshare-Panel hängt hinter einer Bridge, die ausschließlich RGB888 annimmt. LVGL
rechnet für dieses Panel deshalb mit 24 statt 16 Bit Farbtiefe. lv_conf.h liest dazu
JC_PANEL_TYPE aus config.h, damit Sketch und LVGL-Bibliothek garantiert dieselbe
Farbtiefe benutzen; display_hal.cpp bricht den Build ab, falls sie auseinanderlaufen.
Der Panelwechsel muss aus diesem Grund in config.h erfolgen und nicht per Compiler-Flag.
- Bringup ohne Hersteller-Panel-Treiber: DSI-Bus, Kommandokanal und DPI-Panel werden direkt
angelegt. Entscheidend ist die Legacy-Referenztaktquelle PLL_F20M - mit der Standardquelle
bleibt der Bildschirm auf ESP32-P4 Rev. 1.3 / ECO2 schwarz. Die Zeitbasis (2 Lanes zu
1250 Mbit/s, 80 MHz DPI-Takt, Austastlücken 64/64/64, rund 60 Hz) steht als WS7_*-Werte
in pins_config.h und ist dort anpassbar, falls das Bild reißt.
- Panel-Freigabe und Helligkeit laufen beim Waveshare nicht über einen PWM-Pin, sondern über
Register der Bridge auf I2C-Adresse 0x45. Die Hintergrundbeleuchtung wird bewusst erst
eingeschaltet, wenn die DSI-Ausgabe steht - sonst blitzt beim Start Bildrauschen auf.
Standby-Abdunklung und „Uhrzeit im Standby" funktionieren wie bei den JC-Panels.
- Touch: Der GT9271 des Waveshare-Panels benutzt dieselbe Registerbelegung wie der GT911,
liegt aber auf I2C-Adresse 0x14 und meldet im Hochformat. X und Y werden deshalb getauscht
und auf die Bildfläche begrenzt; Ausreißer des Controllers landen nicht mehr als Klick am
Bildrand. Sollte die Bedienung spiegelverkehrt reagieren, lässt sich das mit
WS7_TOUCH_MIRROR_RAW_X / _Y in pins_config.h umdrehen.
- Der Framebuffer wächst auf 3 x 2,8 MB (rund 8,3 MB PSRAM). Auf dem P4-Pico mit 32 MB
PSRAM ist das unkritisch.
- Die UART-Pins zur Hauptplatine sind für das Waveshare-Board getrennt eingestellt, weil
dessen 40-poliger Header eine andere Belegung hat als die JC-Panels: TX = GPIO17,
RX = GPIO18 (JC-Panels unverändert TX = GPIO33, RX = GPIO31).
Version 1.5.0:
- Neu: Dieselbe Firmware unterstützt jetzt auch das 7-Zoll-Display Guition JC1060P470C-I-W
(JD9165, 1024x600) zusätzlich zum bisherigen 4,3-Zöller JC4880P443C-I-W (ST7701, 480x800).
Umgeschaltet wird über „JC_PANEL_TYPE" in config.h (JC_PANEL_43 / JC_PANEL_70) oder per
Compiler-Flag -DJC_PANEL_TYPE=70. Vorgabe bleibt das 4,3-Zoll-Panel, dessen Verhalten sich
nicht ändert.
- Panel-Treiber esp_lcd_jd9165.c/.h aus dem Hersteller-Paket übernommen; board_bringup.c legt
je nach Panel den passenden DSI-Bus, die DPI-Zeitbasis und den Herstellertreiber an.
- pins_config.h liefert Auflösung, LCD-Reset-Pin (4,3": GPIO5, 7,0": GPIO27) und Ausrichtung
panelabhängig. Das 7-Zoll-Panel ist nativ Querformat, deshalb entfällt dort die 270-Grad-
Rotation samt PPA-Beschleunigung.
- Der GT911 des 7-Zöllers meldet Rohkoordinaten im Raster 800x480 statt 1024x600. Die
Umrechnung hängt am process_coordinates-Hook von esp_lcd_touch, damit die Herstellerdatei
esp_lcd_touch_gt911.c unverändert bleibt. Ohne sie wäre nur der linke obere Bildteil
bedienbar.
- Die Oberfläche rechnet nicht mehr mit fest verdrahteten 480 Pixeln Höhe, sondern mit
DISP_VER_RES. Layout und Schriften bleiben sonst unverändert: Das Dashboard arbeitet fast
durchgehend mit Prozentmaßen, und die feineren Pixelabstände des 7-Zöllers (170 statt 213 dpi)
lassen dieselben Schriftgrößen physisch größer erscheinen.
- Die UART-Verkabelung ist bei beiden Panels gleich (P4 GPIO33 = TX, GPIO31 = RX). Beim
7-Zöller liegen diese Pins allerdings auf der FPC-Buchse FPC4 (1,0 mm, 16-polig) statt auf
einer Stiftleiste - dort wird ein Adapterkabel gebraucht.
Version 1.4.2:
- Bug-Fix: Beim Aufwecken über Antippen erschien die Auswahl „Espresso oder Cold Extraction"
manchmal nicht. Der Dialog entschied nach einer anderen Bedingung, ob er die Auswahl ANZEIGT
(verlangte zusätzlich cxArmable), als er beim Absenden benutzte, um die Rückfrage der S3 zu
UNTERDRÜCKEN. In der Lücke dazwischen zeigte er nur „Aufwecken", sendete aber „wakeEspresso" -
und damit war die Frage auch auf der S3 erledigt. Beide Stellen benutzen jetzt dieselbe Funktion
wake_choice_offered().
- Hat der Dialog die Auswahl nicht angeboten, sendet er wieder „deactivateStandby". Dann öffnet die
S3 ihr eigenes Auswahlfenster, und die Frage kommt trotzdem - derselbe Weg wie beim Aufwecken über
den Schalter. Die Auswahl kann damit nicht mehr ersatzlos entfallen.
Version 1.4.1:
- Passend zu S3 5.6.2: Die Auswahl beim Aufwecken erscheint auch dann, wenn der Cold-Extraction-Modus
bereits aktiv ist - sie geht in beide Richtungen. „Espresso" schaltet den Modus dann ab.
- Der Cold-Extraction-Knopf zeigt den Ist-Zustand: Bei aktivem Modus ist er hervorgehoben und heißt
„Cold Extraction (aktiv lassen)", sonst „Cold Extraction (Wasser bleibt kalt)".
Version 1.4.0:
- Die Auswahl „Espresso oder Cold Extraction" erscheint jetzt auch beim Aufwecken über den Schalter
(braucht S3-Firmware ab 5.6.0). Meldet die S3 ein offenes Auswahlfenster (cxWakeChoice), blendet das
Display den Dialog von sich aus ein - ohne Antippen - und zeigt die verbleibende Bedenkzeit als
Countdown. „Abbrechen" entfällt in diesem Fall, weil es nichts abzubrechen gibt: Läuft die Zeit ab,
heizt die Maschine normal auf.
- Der Espresso-Knopf sendet „wakeEspresso" statt „deactivateStandby", sobald die Auswahl relevant ist.
Damit wird die Entscheidung auf der S3 vermerkt und nicht direkt danach erneut gefragt.
- Der Cold-Extraction-Knopf beendet den Standby nur noch, wenn er überhaupt aktiv ist - im Auswahlfenster
nach dem Aufwecken schaltet er nur den Modus.
Version 1.3.0:
- Der Aufweck-Dialog wird zur Auswahl: „Womit aufwecken?" mit den Knöpfen „Espresso" (heizt normal auf)
und „Cold Extraction (Wasser bleibt kalt)". Braucht S3-Firmware ab 5.5.0.
Bei „Cold Extraction" wird erst der Modus scharf geschaltet und dann der Standby beendet, damit der
Wasserkreis nicht zwischendurch anheizt.
- Steuerbar über die neue Einstellung „Beim Aufwecken fragen" (Cold-Extraction-Seite, Abschnitt
Grundeinstellung; identisch zur Web-UI). Ist sie aus, erscheint wieder der bisherige Dialog mit
„Aufwecken" und „Abbrechen" - der Zwischenschritt entfällt dann komplett.
Version 1.2.1:
- Bug-Fix: Während einer kalten Extraktion zeigten Waage-Kachel und Live-Bezugsschirm das Zielgewicht
des normalen Espresso-Bezugs (bbwTarget) statt des Ziels der kalten Extraktion (cxTarget). Außerdem
erschienen Gewichtsanzeige und Fortschrittsbalken nur, wenn Brew-by-Weight aktiv war - bei kalter
Extraktion ist das typischerweise aus, obwohl ein Ziel existiert. Beide Anzeigen nutzen jetzt
cxTarget, sobald cxShot gesetzt ist.
Version 1.2.0:
- Neue Seite „Cold Extraction" mit eigenem Menüeintrag (braucht S3-Firmware ab 5.4.0). Dort stehen jetzt
alle Parameter der kalten Extraktion und lassen sich direkt am Display ändern: Freigabetemperatur,
Vorbenetzung (Dauer + Pumpenleistung), Hauptbezug (Pulsperiode + Pumpenleistung), Zielgewicht,
maximale Bezugsdauer sowie der Pumpenschutz. Gespeichert wird über das erweiterte saveBrew-Kommando.
- Der Modus-Schalter ist von der Temperatur-Seite auf die neue Seite umgezogen (eine Stelle statt zwei).
Der Knopf „Aufwecken mit Cold Extraction" im Standby-Dialog bleibt unverändert.
- Brew-Control-Seite neu gegliedert: Statt einer durchlaufenden Liste unter Zwischenüberschriften liegt
jede Funktion (Pre-Infusion, Brew-by-Time, Brew-by-Weight, Dampf-Timer) jetzt in einer eigenen Karte
mit Titel und einer Zeile, die erklärt, was sie tut. Die neue Cold-Extraction-Seite nutzt dieselbe
Gliederung.
- Eingabefelder werden nur beim Öffnen der Seite aus dem Zustand gefüllt, damit laufende State-Pushes
eine begonnene Eingabe nicht überschreiben (gleiches Verhalten wie auf der Brew-Control-Seite).
Version 1.1.3:
- Aufweck-Dialog hat einen dritten Knopf: „Aufwecken mit Cold Extraction" (braucht S3 ab 5.3.5).
Er stellt den Modus scharf und beendet erst danach den Standby - in dieser Reihenfolge, damit
der Wasserkreis gar nicht erst anheizt. Ohne diesen Weg musste man normal aufwecken (Maschine
heizt an) und den Modus danach umschalten. Der Knopf erscheint nur, wenn die Funktion
freigeschaltet und umschaltbar ist und der Modus nicht ohnehin schon läuft.
- Der Schalter „Cold Extraction" auf der Temperatur-Seite hängt jetzt am neuen State-Feld
cxArmable statt an cxAllowed: umschaltbar auch im Standby und bei warmem Kessel, gesperrt nur
während eines laufenden kalten Bezugs. Bei älterer S3-Firmware fällt das Feld auf cxEnabled
zurück, der Schalter bleibt also bedienbar.
- Erklärzeile im Standby: „Nach dem Aufwecken bleibt der Wasserkreis aus."
Version 1.1.2:
- Cold Extraction: Fehlt eine nutzbare Waage, weist das Display jetzt dauerhaft darauf hin.
Ohne Waage greifen weder Zielgewicht noch Stillstands-Erkennung, der Bezug endet erst nach der
maximalen Bezugsdauer (braucht S3-Firmware ab 5.3.4).
- Statuszeile: „Cold Extraction aktiv - ohne Waage, Ende nach Zeit" (gelb) statt des blauen
Normalhinweises; beim Aktivieren zusätzlich kurz die Meldung zum neuen cxNotice-Code 4.
- Erklärzeile unter dem Schalter und die Parameter-Anzeige auf der Brew-Seite ergänzen den
Hinweis; bei aktivem Modus ohne Waage nennt die Zeile die Zeit statt des Zielgewichts.
Version 1.1.1:
- Compiler-Warnungen des P4-Builds aufgeräumt (rein technisch, keine Funktionsänderung):
- „LV_FS_DEFAULT_DRIVE_LETTER is deprecated": Die Option heißt ab LVGL 9.3
LV_FS_DEFAULT_DRIVER_LETTER. Der alte Name funktionierte zwar noch, löste aber in
lv_api_map_v9_1.h ein #warning aus - und zwar in JEDER Übersetzungseinheit, die lvgl.h
einbindet. Das allein waren rund 60 Warnungen pro Build, die alles andere zugedeckt haben.
In lv_conf.h umbenannt.
- „bitwise operation between different enumeration types is deprecated": LV_PART_* und
LV_STATE_* sind in LVGL 9 zwei verschiedene Enum-Typen; sie direkt mit | zu verknüpfen ist
in C++20 abgekündigt. Neues Makro TH_SEL(part, state) in theme.h castet beide auf
lv_style_selector_t (ohnehin uint32_t). Betraf 5 Stellen in ui.cpp und theme.h.
- Bug-Fix: Der Puffer für die Cold-Extraction-Infozeile auf der Brew-Seite war mit 192 Bytes
zu klein - der Text ist rund 205 Zeichen lang (das „°" zählt als zwei Bytes), die letzte
Zeile wurde also abgeschnitten. Jetzt 256 Bytes.
Version 1.1.0:
- Cold Extraction am Display bedienbar (braucht S3-Firmware ab 5.3.2):
- Neuer Schalter „Cold Extraction" auf der Seite „Temperaturen" unter „Modi", darunter eine
Erklärzeile mit dem aktuellen Zustand bzw. dem Sperrgrund. Schalter und Zeile bleiben
unsichtbar, solange die S3 die Funktion nicht freigeschaltet meldet (auch bei älterer
S3-Firmware, die die Felder gar nicht sendet).
- Einschalten ist nur bei erteilter Freigabe möglich (Wasserkessel kalt genug, kein Standby /
Wartung / Tuning); ausschalten immer, außer während eines laufenden kalten Bezugs. Gesendet
wird gezielt startColdExtraction/stopColdExtraction statt eines Toggles, damit ein
abgewiesener Befehl den Schalter nicht in einen nie bestätigten Zustand bringt.
- Statuszeile: eigener Slot direkt hinter der Sicherheitsmeldung. Zeigt dauerhaft
„Cold Extraction aktiv - Wasserkreis heizt nicht", bei fehlender Freigabe den Grund,
während der Pumpen-Zwangspause den Pumpenschutz und transient einen abgewiesenen Start
bzw. einen Abbruch wegen fehlenden Zulaufs.
- Wasser-Kachel im Dashboard zeigt bei aktivem Modus „Cold Extraction - Heizen aus" statt
eines Sollwerts, der nicht erreicht wird; der Aufheiz-Countdown ist dann unterdrückt.
- Phasenleiste und Live-Bezugsschirm benennen die erste Phase eines kalten Bezugs
„Vorbenetzung" (keine Pause-Phase) und zeigen die Pumpenpause an.
- Brew-Seite: Cold-Extraction-Parameter als Anzeige (Freigabeschwelle, Vorbenetzung,
Zielgewicht, Timeout) mit Hinweis, dass sie in der Web-UI eingestellt werden.
- Statistik-Seite: kalte Bezüge sind mit „(kalt)" markiert und gehen nicht in die mittlere
Bezugsdauer ein - gleiche Regel wie in der S3-Statistik.
- Bug-Fix: Das Limit für eingehende UART-Zeilen (PROTO_RX_LINE_MAX) lag bei 2048 Bytes, während
die State-Zeile der S3 bereits rund 2,1 KB erreicht - mit langem Status-, Profil- oder
SSID-Text konnte sie darüber liegen. In dem Fall wurde die GANZE Zeile verworfen und das
Display fror auf dem letzten Stand ein, ohne dass die Verbindung als tot erkannt wurde.
Das Limit ist jetzt 4096 Bytes; der RX-Puffer selbst bleibt bei 2 KB reserviert, weil der
interne RAM knapp ist und eine Arduino-String nicht ins PSRAM alloziert werden kann.
Version 1.0.13:
- Profil-Schnellwahl + Anzeige des aktiven Profils (braucht S3-Firmware ab 5.1.1):
- Neuer Profil-Chip im Header (auf allen Seiten sichtbar): zeigt das zuletzt geladene bzw.
gespeicherte Profil; ein „*" hinter dem Namen bedeutet, dass seitdem profil-relevante
Einstellungen geändert wurden. Lange Namen werden gekürzt; ohne bekanntes Profil
(oder mit älterer S3-Firmware) bleibt der Chip unsichtbar.
- Antippen des Chips öffnet die Profil-Schnellwahl: ein Overlay mit allen gespeicherten
Profilen, das aktive mit Häkchen und Akzentfarbe hervorgehoben. Ein Tap lädt das Profil
direkt (Toast bestätigt, Chip aktualisiert sich mit dem nächsten State) — zwei Berührungen
vom Dashboard zum Profilwechsel statt Umweg über die Profile-Seite.
- Profile-Seite: das aktive Profil wird in der Liste mit Häkchen-Symbol und Akzentfarbe
markiert.
Version 1.0.12:
- Verlauf-Chart: Flackernde Heizleistungs-Kurven in den großen Zeitfenstern behoben. Die
Dezimierung pickte pro Chart-Punkt ein Einzelsample, dessen Abtast-Raster mit jedem
Sekundentick um eine Position wanderte — die schnell schaltende Heizleistung (PWM)
sprang dadurch sichtbar zwischen zwei Kurvenbildern hin und her. Jetzt wird über den
gesamten Zeitabschnitt eines Chart-Punkts gemittelt: das Bild steht ruhig, und die
Leistungs-Kurve zeigt die tatsächliche mittlere Heizleistung statt zufälliger
Momentwerte. Im 2-min-Fenster (1 Sample je Punkt) ändert sich nichts.
Version 1.0.11:
- Verlauf-Seite ausgebaut (nur P4, keine S3-/Protokolländerung nötig):
- Umschaltbares Zeitfenster: Buttons „2 min / 10 min / 30 min / 60 min" in der Kopfzeile.
Intern läuft eine 1-s-Historie als Ringpuffer über volle 60 Minuten; das Chart zeigt
weiterhin 120 Punkte und dezimiert je Fenster. Dadurch ist beim Fensterwechsel der
komplette Verlauf sofort da (kein leeres Chart, kein Neuaufbau). Die Wahl wird P4-lokal
im NVS gespeichert und überlebt Neustarts.
- Gestrichelte Soll-Linien für Wasser und Dampf im Chart (Farbe der jeweiligen Kurve,
gedimmt). Die Y-Skala bezieht die Soll-Werte mit ein, damit die Linien im Bild liegen —
aber nur für Kreise, deren Ist-Kurve im Fenster sichtbar ist (ein kalter Dampfkessel
zieht die Skala nicht auseinander).
- Heizleistung zuschaltbar: Button „Leistung" blendet Duty-Cycle Wasser/Dampf als gedimmte
Zusatzkurven auf einer zweiten Achse (0100 %) ein, inkl. Live-Legende. Einstellung
ebenfalls P4-lokal im NVS.
- Feinere Kurven: Die Temperatur-Historie wird jetzt in Zehntelgrad geführt und gezeichnet
(vorher ganzzahlig gerundet) — PID-Schwingungen um ±1 °C sind damit sauber ablesbar.
- Die ~29 KB Historie liegen im PSRAM (heap_caps_malloc, wie die LVGL-Framebuffer) —
als statische Arrays sprengten sie den internen RAM (Linker: 107 % belegt).
- Shot-Zusammenfassung: Karte auf 560 px verbreitert und Spaltenabstand zwischen
Dauer/Gewicht/Flow deutlich vergrößert (28 px statt 10 px) — die drei Werte standen
zu nahe beisammen und waren schlecht lesbar.
- PID-AutoTune sichtbar gemacht: Während eines Tuning-Laufs erscheint auf der Verlauf-Seite
eine Live-Karte (mit Spinner) — je Kreis Laufzeit, aktuelle Phase (heizt/kühlt ab) und
Schwingungshub samt Mittelpunkt (Min/Max der Ist-Temperatur seit Start). Nach dem Ende
zeigt die Karte ~30 s das Ergebnis (Erfolg/Abbruch samt Grund, farbcodiert). Die
PID-Tuning-Meldung in der Statuszeile ist jetzt antippbar und springt zur Verlauf-Seite,
wo die Schwingung auch als Kurve zu sehen ist.
Version 1.0.10:
- Live-Bezugsschirm (abschaltbar): Beim Start eines echten Bezugs blendet sich ein Vollbild-Overlay
weich ein — große Bezugszeit, Live-Gewicht und Flow samt Flow-Verlaufskurve der letzten 30 Sekunden
(nur bei verbundener Waage), Fortschrittsbalken Richtung Zielgewicht (Brew-by-Weight) bzw. Zielzeit
(Brew-by-Time) und Pre-Infusion-Phasenhinweis mit Restzeit. Über das X oben rechts für den laufenden
Bezug schließbar; beim nächsten Bezug erscheint er wieder. Ein/Aus-Schalter auf der Info-Seite unter
„Darstellung", die Einstellung wird P4-lokal im NVS gespeichert und überlebt Neustarts. Während
Flush und Reinigungsassistent erscheint das Overlay bewusst nicht.
- Shot-Zusammenfassung aufgewertet: Die Karte nach Bezugsende blendet jetzt weich ein/aus (Fade),
wirft einen Schlagschatten und zeigt Dauer, Endgewicht und mittleren Flow als große Werte mit
gedimmter Beschriftung statt reiner Textzeilen (Gewicht/Flow nur bei aktiver Waage).
- Pre-Infusion-Phasenanzeige auf dem Dashboard: Während eines Bezugs mit aktiver Pre-Infusion zeigt
eine Schrittleiste die Phasen „Pre-Infusion → Pause → Extraktion" an — erledigte Schritte mit Haken,
die aktive Phase hervorgehoben (Gold) inkl. Restzeit-Countdown (100-ms-Takt). Die aktive Phase kommt
quelltreu von der S3 (neues State-Feld "piState", ab S3-Firmware 5.0.12); bei älterer S3-Firmware wird
sie zeitbasiert aus den konfigurierten Phasendauern abgeleitet. Ist keine Pause konfiguriert (0 s),
wird der Pause-Schritt ausgeblendet.
- Temperatur-Seite: Solltemperaturen für Wasser und Dampf werden jetzt über radiale Drehregler (Arc,
270°) mit großem Wert mittig und /+-Feinjustage eingestellt. Gesendet wird entprellt (nach kurzer
Bedienpause bzw. sofort beim Loslassen des Reglers oder Verlassen der Seite), damit Drag-Gesten die
S3 nicht mit setTemp-Kommandos und EEPROM-Schreibzugriffen fluten. Tippen in die Reglermitte löst
bewusst keinen Wertsprung aus (ADV_HITTEST: nur Ring/Griff reagieren).
- Dashboard-Feinschliff (nur P4, keine S3-/Protokolländerung nötig):
- Weiche Temperatur-Zustandsfarben: Der große Ist-Wert färbt sich stufenlos über den Abstand
zum Soll (kontinuierliche Farbinterpolation statt harter Stufen) — Blau beim Aufheizen,
Gold kurz vor dem Ziel, Grün im Zielband (±0,5 °C), Richtung Warngelb bei deutlichem
Überschwinger. Dazu neue „BEREIT"-Pille im Header (auf allen Seiten sichtbar), wenn der
Wasserkessel — und der Dampfkessel, sofern er heizt — im Zielband liegt, kein Fehler/
Standby/Eco aktiv ist und kein Aufheiz-Countdown läuft; mit Hysterese gegen Flackern
an der Bandgrenze, erscheint und verschwindet weich (Fade).
- Umschaltbares Temperatur-Design: Die Dashboard-Temperaturanzeige gibt es jetzt in fünf
Varianten — „Klassisch" (bisherige Karten mit Prozentbalken), „Instrumente" (freistehende
Rundinstrumente im Manometer-Look: Metall-Bezel mit Verlauf und Schlagschatten für eine
leichte 3D-Wirkung, 270°-Bogen füllt sich in der Zustandsfarbe, Soll-Wert in der
Bogen-Lücke), „Minimal" (komplett rahmenlos, nur große Werte mit dünner Ist/Soll-Linie),
„Soft-3D" (Neumorph: Fläche fast im Hintergrundton wirkt herausgedrückt — dunkler
Schatten unten rechts, heller Lichtschein oben links, eingelassener Balken) und
„Thermometer" (vertikale Säulen, die sich von unten in der Zustandsfarbe füllen, mit
Glanzstreifen als Glas-Effekt und rundem Kolben unten). Außer bei „Klassisch" folgt
auch die Füllfarbe von Balken/Bogen/Säule stufenlos der Zustandsfarbe des Ist-Werts.
Umschalter auf der Info-Seite unter „Darstellung"; die Wahl wird P4-lokal im NVS
gespeichert und überlebt Neustarts. Standard: Instrumente.
- Konsolidierte Statuszeile: Die sechs gestapelten Dashboard-Banner (Sicherheit, Dampf,
Reinigung, PID-Tuning, Wartung, Aufheiz-Countdown) teilen sich jetzt EINE feste einzeilige
Meldung — mehrere aktive Hinweise rotieren alle 4 s weich durch (Fade) statt das Layout
zu verschieben, Priorität Rot > Gelb > Blau. Wartungs- und Reinigungsmeldung bleiben
antippbar (Serviceseite bzw. Reinigungs-Sub-Screen), Überlängen laufen als Laufschrift durch.
Version 1.0.9:
- Weiche Übergänge (dezente Fade-Animationen) hinzugefügt:
- Start-Animation: Boot-Splash mit Logo, Untertitel, Lade-Spinner ("Verbinde mit Maschine...")
und Firmware-Stand; blendet sich beim Start weich ein und beim ersten State von der S3
(Verbindung steht) wieder aus.
- Einschlaf-Animation: Die Standby-Uhr wird beim Wechsel in den Standby weich eingeblendet.
- Aufweck-Animation: Beim Aufwecken blendet das Standby-Overlay weich aus und gibt das
Dashboard frei.
- Umsetzung speicher-neutral über das 'opa'-Style (kein opa_layered/Vollbild-Puffer), um den
LVGL-Speicherpool nicht zu belasten. Bei ausgeschalteter Standby-Uhr (Display aus) gibt es
weiterhin keine Einschlaf-Animation (schwarzer Bildschirm bietet keine Anzeigefläche).
Version 1.0.8:
- Statistik-Seite hinzugefügt
Version 1.0.7:
- Brew-by-Weight-Gewichtseingabelimit auf 150g erhöht, um auch Heißwasserbzug für Americano oder ähnliches via BBW zu ermöglichen
Version 1.0.6:
- Boot-Loop-Schutz implementiert:
P4-Boot-Loop-Schutz + „Update-Modus", der den UART-OTA offenhält.
Wenn die P4-Firmware in eine Absturzschleife läuft, erkennt sie das (RTC-Zähler wie beim S3) und
startet in einen abgespeckten Modus, der bewusst das riskanteste überspringt — vor allem die Display-/LVGL-Init (der wahrscheinlichste P4-Absturzgrund, siehe unser LVGL-Mem-Pool-Thema) — und nur den UART-Empfang + OTA-Receiver laufen lässt. Dann kann der S3 die P4-Firmware jederzeit per UART neu flashen, auch wenn die normale P4-Oberfläche kaputt ist. (Optional: eine ganz simple statische „Update-Modus"-Bildschirmmeldung, falls das Panel-Bringup als sicher genug gilt.)

Some files were not shown because too many files have changed in this diff Show More