Initiale Bereitstellung

Initiale Bereitstellung der aktuellen Version auf Gitea
This commit is contained in:
raw-designs
2026-07-10 18:13:43 +02:00
commit 453644816f
4952 changed files with 2613188 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# Projekt-Orientierung
Dieses Repository ist die Steuerung einer Espresso-/Siebträgermaschine (Dual-PID,
Wasser- und Dampfkessel getrennt geregelt). Sie besteht aus zwei getrennten Firmwares
für zwei Mikrocontroller, die per UART (230400 8N1, JSON-Zeilen) verbunden sind:
- **ESP32-S3 Hauptsteuerung in der Maschine (Source of Truth).**
Datei: `Dual_PID_FastHeatUp.ino` (im Projekt-Root).
Regelt PID/Heizungen/SSRs, Pumpe/Ventile, Waage (HX711/Brew-by-Weight), Standby/Eco,
Profile, Nutzungsstatistik und stellt die **Web-UI** + WLAN + OTA bereit.
Änderungshistorie: `Changelog.txt` (Root), Version steht in `String version = "…"`.
- **ESP32-P4 UART-Touch-Display (spiegelt den S3-Zustand).**
Ordner: `JC_Display_Firmware/` (Hauptsketch `JC_Display_Firmware.ino`,
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
kommen über UART vom S3. Eigene Version `DISPLAY_FW_VERSION` +
`JC_Display_Firmware/Changelog.txt`.
Wenn ich „S3/Controller/Maschine" sage, ist `Dual_PID_FastHeatUp.ino` gemeint;
„P4/Display" ist der Ordner `JC_Display_Firmware/`.
**Nicht anfassen:** Der Ordner `Sicherungen/` enthält nur datierte Versions-Backups
(`.ino`) — niemals dort editieren. Unter `JC_Display_Firmware/JC4880P443C_I_W/` liegen
reine Hersteller-Demos/Beispiele (Board-Support), ebenfalls nicht als Projektcode behandeln.
# Agent Instructions
- UI-Texte muessen immer in allen verfuegbaren Anwendungssprachen hinterlegt oder geaendert werden. Wenn neue UI-Strings entstehen, sind die entsprechenden Sprachdateien vollstaendig zu ergaenzen.
- Dateien duerfen generell nur ohne BOM geschrieben werden. Textdateien sind als UTF-8 ohne BOM anzulegen oder zu aktualisieren.
- Sprachstrings/Texte sind immer mit Umlauten zu schreiben
+169
View File
@@ -0,0 +1,169 @@
Version 5.0.11:
- Bug-Fix: Dauerhafter Neustart (Panic/Exception) der Steuerung, sobald das UART-Touch-Display (P4) angeschlossen wurde.
Ursache: Stack-Überlauf im loopTask der 3000-Byte-Puffer für das State-JSON (touchUartSendState) sowie der Puffer
für Profildetails (getProfileDetails) lagen auf dem Task-Stack (~8 KB) und liefen zusammen mit der Aufrufkette des
hello-Handshakes über. Beide Puffer sind jetzt static (analog zu touchUartSendUsageStats). Der Pfad wird nur bei
verbundenem Display ausgeführt, daher lief die Maschine ohne Display stabil.
Version 5.0.10:
- Sicherheits-Fix: Während eines Display-Firmware-Updates (P4) werden die Heizungen nun garantiert abgeschaltet.
Zuvor konnte die zeitproportionale SSR-Steuerung während der Übertragung ausgebremst werden, sodass ein gerade
eingeschaltetes Heizungs-SSR eingeschaltet blieb (Überhitzungsgefahr, z. B. zu hohe Wassertemperatur nach einem
P4-Update). Zusätzlich wurde eine Ursache für einen verzögerten Neustart (Panic) nach P4-Updates entschärft
(delay(1) statt yield(), damit die Regel-/Sicherheitsschleife während der Übertragung weiterläuft).
Version 5.0.9:
- Statistik-Seite für das UART-Display hinzugefügt
Version 5.0.8:
- Brew-by-Weight-Gewichtseingabelimit auf 150g erhöht, um auch Heißwasserbzug für Americano oder ähnliches via BBW zu ermöglichen
Version 5.0.7:
- ECO-Option „Nach Neustart/Power-On in Standby gehen" hinzugefügt
Version 5.0.6:
- UART-Touch-Display implementiert
- Rettungsmodus (für Fehlerhafte Firmware-Updates) hinzugefügt
Version 4.8.1:
- Glättungs-Funktion im Waage-Modus kann nun über Sensoren im Webinterface aktiviert/deaktiviert werden
Version 4.8.0:
- Waage-Modus nochmals verbessert
- Kalibrierungsassistent für die Wage zur Bestimmung des Kalibrierungsfaktors implementiert
Version 4.7.0:
- Unterstützung für Fully Kiosk-Endgeräte als Display hinzugefügt
- Fragmente der alten Ultraschallsensor-Komponente entfernt
- Nun nutzt auch die Desktop-Ansicht der Web-UI ein Hamburger-/Sandwich-Menü
Version 4.6.0:
- Waage weiter verbessert:
- Median-von-3 gegen einzelne Ausreißer
- Spike-Erkennung:
- außerhalb eines Bezugs: Sprünge > 3.0 g werden zunächst verworfen
- während Bezug: Sprünge > 10.0 g werden zunächst verworfen
- kommt der große Sprung wiederholt, wird er akzeptiert, damit echte Änderungen nicht hängen bleiben
- Reset der Filter bei Tara, Disconnect, Reconnect und Initialisierung
- currentWeightReading wird jetzt nur noch zentral über die Filterfunktionen gesetzt
Brew-by-Weight bleibt schnell, bekommt aber Schutz gegen einzelne Störimpulse. Performance-Kosten sind minimal: drei
Float-Samples, ein Median-Vergleich und ein paar fabs()-Checks pro Waagenmessung.
Version 4.5.2:
- Waage-Anpassung: Im Waage-Modus wird jetzt ein geglätteter und etwas trägerer Wert angezeigt, während beim Brühen der schnelle Roh-Wert der Waage verwendet wird (dieser "schwankt" daher auch manchmal leicht)
Version 4.5.1:
- Bug-Fix: Beim X-Switch war noch ein Teil der alten Schalter-Logik enthalten,
wodurch der X-Switch-Status nicht über die Web-UI geändert werden konnte
- Zeit für Longpress ist nun über die Web-UI einstellbar
Version 4.5.0:
- Neues Feature in Brew Control eingeführt: FlowGuard
Ermöglicht es, auch bei zu grober Mahlung durch Pulsen der Pumpe eine voreingestellte Ziel-Brühzeit zu erreichen
- Style-Anpassungen für mobile Ansicht
Version 4.4.2:
- Neustart-Grund wird nun in der Web-UI unter Info erweitert angezeigt, um bei Fehlern eine bessere Diagnose durchführen zu können.
Version 4.4.1:
- Einstellbares Temperatur-Offset für den Zusatz-Temperatursensor (Gehäuse/Tassenablage)
Version 4.4.0:
- Neue Funktion "Offset-Kompensation" in den PID-Einstellungen:
Bei negativem Temperatur-Offset wird in Display und Web-UI bis zur Referenzschwelle die Temperatur ohne Offset angezeigt;
funktioniert auch nach dem Aufwachen aus dem Standby
Version 4.3.5:
- Dampf- und Wasserflush ist nun auch zeitgleich möglich, sowie Dampf-Flush auch während eines Espresso-Bezugs
Version 4.3.4:
- Wechsel in Eco-Modus passiert nun nicht mehr während eines Bezugs, der knapp an der Zeit für das Eco-Umschalten liegt
- Button-Longpress von 2s auf 1s geändert
Version 4.3.3:
- Bug beim Tarieren des ersten Bezugs nach Maschinenstart / Aufwachen aus Standby behoben.
Version 4.3.2:
- Reinigungsassistent um weiteren Parameter ergänzt - Nun lässt sich auch die Pausenzeit zwischen den Zyklen einstellen.
Zudem wurde die Erfolgsmeldung/Abschlussmeldung in der Web-UI wieder entfernt.
Version 4.3.1:
- Neue Funktion für "Dampf-Nicht heizen"-Setting bei Neustart und Aufwachen aus dem Standby in den Eco-Einstellungen
Version 4.3.0:
- Home-Assistant-Integration verbessert
- Anzeige des Zusatz-Temperatursensors (Gehäuse/Tassenablage) nun im Display möglich
- Dampf-Timer in Brew-Control-Settings einstellbar für automatisches Abschalten des Dampfs nach X Sekunden
Version 4.2.8:
- HX711 verbessert: bei aktivierter Waage + Brew-by-Weight wird vor Bezugsstart zuerst 1 Sekunde tariert; im Display erscheint dabei "Waage wird tariert..."
- HX711 verbessert: beim Eintritt in den Waagenmodus wird nun ebenfalls automatisch tariert
Version 4.2.7:
- X-Switch erweitert: Short-Press und Long-Press können jetzt getrennt mit Funktionen belegt werden
Version 4.2.6:
- Hardware-Eingänge auf Tasterlogik umgestellt: Standby, Eco, X-Funktion, Bezug und Dampf arbeiten jetzt per Tastendruck statt per dauerhaftem Schalterzustand
- Bezug- und Dampftaster unterstützen jetzt Long-Press: 2 Sekunden halten startet Wasser- bzw. Dampf-Flush
- Alte Abhängigkeiten vom "Schalter zurückstellen" nach Auto-Stopp entfernt; Bezug/Dampf/Flush laufen jetzt konsistent über die Software-Logik
- OLED-Anzeige ergänzt: bei aktivem "Dampf nicht heizen" wird statt der Dampf-Temperatur "Nicht heizen aktiv" angezeigt
Version 4.2.5:
- Home-Assistant-API ergänzt: eigene HTTP-Endpunkte für Status, Standby an/aus und "Jetzt aufheizen"
- Standard-Kalibrierungsfaktor für HX711 geändert
Version 4.2.4:
- Heizung wird nach langem Standby beim Aufwecken wieder korrekt gestartet; PID-Zeitfenster werden beim Verlassen von Standby sauber resynchronisiert
Version 4.2.3:
- Standby auf reinen Software-Standby umgestellt; Hardware-Standby-Schalter arbeitet jetzt als Taster/Toggle
- Display-Hinweis ergänzt, falls der Standby-Taster versehentlich gedrückt gehalten wird
- Neue Timer-Seite in der Web-UI: beliebig viele Timer mit Wochentagen, aktiv/inaktiv sowie Standby an/aus
- Timer werden auf FATFS gespeichert und nur bei synchronisierter NTP-Zeit ausgeführt
Version 4.2.2:
- Reinigungsassistent: Heizt Wasser nun vor Start der Zyklen auf 93 °C auf, Dampfheizung bleibt dabei aus
- Reinigungsassistent: Status/Phase wird nun auch im Display angezeigt
- Standby/Web-Override: Dampf-Startverzögerung startet nach Verlassen von Standby wieder korrekt neu
- Standby: temporäre Eco- und Dampfverzögerungs-Overrides werden beim Standby-Wechsel zurückgesetzt
Version 4.2.1:
- Dampf-Timer wird nun auch (analog zum Shot-Timer) 3 Sekunden lang nach Timer-Ende im Display angezeigt
- Temperatur-Profile können nun mit Leerzeichen und Umlauten gespeichert/angezeigt werden
Version 4.2.0:
- Reinigungsassistent im Bereich Service hinzugefügt
Version 4.1.5:
- Standby-Funktion kann über Web-UI temporär deaktiviert werden
Version 4.1.4:
- Umstellung von LittleFS zu FATFS aufgrund des 16MB-MC-Flash
Version 4.1.3:
- Dampf-Heizverzögerung zählt nun erst, wenn das Gerät im "Normalbetrieb" läuft und nicht Standby-Zeit
- Standby-Zeit wird nicht mehr als Betriebszeit getrackt (verringert auch die Schreibzyklen im Speicher)
- Flush-Funktion nun auch für den Dampf-Kreislauf
Version 4.1.2:
- Dashboard: Abstände verringert / kompakter gestaltet
Version 4.1.1:
- Einstellung für Licht-Aus/An im Standby
- Versehentlich entfernte Funktion wiederhergestellt: Eco-Info im Display anzeigen
- Eco-Option "Aufheizverzögerung per Bezugsschalter überspringen" wird jetzt korrekt persistent gespeichert
- Display: Timeranzeige bei aktivem Dampf-Bezug und aktivem Flush ergänzt (Priorität: Shot > Dampf > Flush)
- Service-Seite: Flush-Zeit ist jetzt einstellbar und persistent speicherbar (Standard: 3 Sekunden)
- Eco-Timer wird nun auch durch Dampfbezug und Flush zurückgesetzt (nicht nur durch Shot)
- UART-Touch-UI (ESP32-S3 7") angebunden: neues UART-Frame-Protokoll (HELLO/STATE/CHART) mit Live-Daten für Dashboard und Chart (M1)
- UART-Touch-UI M2: ACTION_REQ/ACTION_ACK umgesetzt (Touch-Aktionen steuern jetzt direkt die Dashboard-Logik der Steuerung)
Version 4.0:
- Weitere Komfort-Funktionen (Licht, Dampf-Steuerung, Standby, Flush via Web-UI,...)
- ESP8266-Support entfällt
Version 3.7.1 - 2026-01-01
- Dashboard: Status zeigt verbleibende Minuten bis Eco-Start (z.B. "Eco-Modus in 43min")
- Web-UI /ECO: neuer Toggle "Eco-Info auf dem Display anzeigen"
- Display (Idle): optionales Layout mit Eco-Countdown bzw. "Eco-Modus aktiv"
+1
View File
@@ -0,0 +1 @@
claude --permission-mode acceptEdits
+1
View File
@@ -0,0 +1 @@
codex -a never -s danger-full-access
+537
View File
@@ -0,0 +1,537 @@
# JC4880P443C-I-W Touch-Display — UART-Protokoll & Architektur
Status: Entwurf v1.0 der Doku · Firmware-Protokollversion: **1** (vorhanden, abgeschaltet) ·
Ziel-Protokollversion: **2** (geplante Erweiterungen)
Diese Datei beschreibt die Anbindung des Touch-Displays **JC4880P443C-I-W** an die
Siebträger-Steuerung (`Dual_PID_FastHeatUp.ino`). Sie dokumentiert den **bereits
vorhandenen** UART-Code, bewertet ihn und legt die geplanten Erweiterungen fest.
Es wird **noch kein Code geändert** — diese Doku ist die Grundlage dafür.
---
## 1. Architektur-Überblick
Bewusste Designentscheidung (siehe Projektbesprechung):
- Das **Webinterface bleibt auf der Hauptplatine** (ESP32-S3). Es bleibt unverändert
die zentrale Steuerungs- und Konnektivitätsinstanz.
- Das **JC-Display ist ein vollwertiges Touch-Bediengerät**, das über UART angebunden
ist und denselben Funktionsumfang bietet wie das Webinterface — aber **kein** eigenes
WLAN/Webserver betreibt.
- Das **OLED (SH1106G) bleibt optional** und wird wie bisher über ein **Compile-Flag**
gewählt (`ENABLE_DISPLAY`). Es ist unabhängig vom JC-Display.
```
+----------------------------+ UART (GPIO43/44) +---------------------------+
| Hauptplatine ESP32-S3 | <----------------------------------> | JC4880P443C-I-W |
| Dual_PID_FastHeatUp.ino | JSON-Zeilen, 230400 Baud, 8N1 | ESP32-P4 (UI/LVGL) |
| | | + ESP32-C6 (WLAN/BT)* |
| - PID-Regelung (Wasser/ | S3 -> P4: state / ack / error | |
| Dampf), SSRs, Sensoren | P4 -> S3: op-Kommandos | - Touch-Bedienung |
| - Waage, Profile, EEPROM | | - Statusanzeige |
| - WLAN + Webinterface | | - Einstellungen |
| - OLED (optional) | | ST7701 / GT911 / ES8311 |
+----------------------------+ +---------------------------+
* Der ESP32-C6 auf dem JC-Display (WLAN/BT) wird in diesem Projekt NICHT für die
Maschinensteuerung benötigt. WLAN/Webinterface laufen weiterhin auf der S3.
```
### Rollenverteilung
| Aufgabe | S3 (Hauptplatine) | P4 (JC-Display) |
|---|---|---|
| Echtzeit-Regelung (PID, SSR, Sicherheit) | **ja** | nein |
| Sensoren (NTC, MAX6675, Waage) | **ja** | nein |
| Persistenz (EEPROM, FFat-Profile) | **ja** | nein |
| WLAN + Webinterface | **ja** | nein |
| Touch-UI / Bedienoberfläche | nein | **ja** |
| OLED-Statusanzeige (optional) | **ja** (Compile-Flag) | — |
| "Quelle der Wahrheit" für den Zustand | **ja** | spiegelt nur |
**Wichtig:** Die S3 bleibt die alleinige Quelle der Wahrheit. Das P4-Display hält keinen
eigenen, abweichenden Zustand, sondern stellt den von der S3 gepushten `state` dar und
schickt Bedienwünsche als Kommandos. So bleiben Web-UI, OLED und Touch-Display konsistent.
---
## 2. Hardware-Anbindung
| Parameter | Wert | Quelle im Code |
|---|---|---|
| UART-Instanz | `Serial1` | `HardwareSerial& touchUart = Serial1;` |
| TX (S3 -> P4) | **GPIO43** | `#define TOUCH_UART_TX_PIN 43` |
| RX (S3 <- P4) | **GPIO44** | `#define TOUCH_UART_RX_PIN 44` |
| Baudrate | 230400 | `#define TOUCH_UART_BAUDRATE 230400` |
| Format | 8N1 | `touchUart.begin(..., SERIAL_8N1, RX, TX)` |
| Logikpegel | 3,3 V | ESP32-S3 / ESP32-P4 beide 3,3 V — **keine Pegelwandlung nötig** |
### Verkabelung (gekreuzt)
```
S3 GPIO43 (TX) -----> P4 RX
S3 GPIO44 (RX) <----- P4 TX
S3 GND ------ P4 GND (gemeinsame Masse zwingend)
```
> **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,
> 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
> ausdrücklich beibehalten, weil sie auf der Platine herausgeführt sind.
---
## 3. Transport & Framing
- **Zeilenbasiert:** Eine Nachricht = eine kompakte JSON-Zeile, abgeschlossen mit `\n`.
`\r` wird empfangsseitig ignoriert.
- **Max. Zeilenlänge (RX):** `TOUCH_UART_MAX_LINE_LEN` = 1024 Byte. Längere Zeilen werden
verworfen und mit einem `error` (`"Zeile zu lang"`) quittiert (Overflow-Schutz).
- **State-Puffer (TX):** `TOUCH_UART_JSON_BUFFER_SIZE` = 2400 Byte (`snprintf`-basiert).
- **Encoding:** ASCII/UTF-8. Strings werden escaped (`jsonEscapeForUart`,
`escapedStatusText` etc.): `\`, `"`, `\n`, `\r`, `\t`.
- **Kein Binär-Frame, kein CRC in v1.** (CRC/Handshake siehe v2-Erweiterungen.)
### Nachrichtenrichtungen
| Richtung | `type`/`op` | Bedeutung |
|---|---|---|
| **S3 -> P4** | `type:"state"` | Vollständiger Zustands-Push (Telemetrie) |
| **S3 -> P4** | `type:"ack"` | Bestätigung eines Kommandos (`ok:true/false`, `message`) |
| **S3 -> P4** | `type:"error"` | Fehler (Parsing, unbekannter op, Overflow) |
| **S3 -> P4** | `type:"profiles"` | Liste der Profilnamen (Antwort auf `listProfiles`) |
| **S3 -> P4** | `type:"usageStats"` | Nutzungsstatistik (Antwort auf `getUsageStats`) |
| **P4 -> S3** | `op:"..."` | Kommando vom Display (siehe §6) |
Jedes Kommando trägt eine optionale **`id`** (uint32), die in `ack`/`error` zurückgespiegelt
wird (Request/Response-Korrelation).
---
## 4. Push-Verhalten (S3 -> P4)
Implementiert in `touchUartTick()`:
- **Client-Aktivierung:** Erst nachdem das Display ein Kommando gesendet hat, gilt es als
aktiv (`touchUartClientActive`). Ohne aktiven Client wird **nicht** gepusht.
- **Client-Timeout:** `TOUCH_UART_CLIENT_TIMEOUT_MS` = 15000 ms ohne Kommando -> inaktiv.
-> Das Display muss regelmäßig `ping` (oder ein beliebiges Kommando) senden (Heartbeat).
- **Adaptives Intervall:**
- Leerlauf: `TOUCH_UART_INTERVAL_IDLE_MS` = 1000 ms
- Aktiv (Bezug/Flush/Burst): `TOUCH_UART_INTERVAL_ACTIVE_MS` = 250 ms
- **Force-Push** bei Zustandswechseln: Bezug, Flush, Dampfkreis (= Dampfbezug/Dampf-Flush),
Tarier-Phase, Sensorfehler/Sicherheitsabschaltung, Reinigungsassistent, Standby (Details in §5).
- **Burst (250 ms)** zusätzlich während: Bezug, Flush, Dampfkreis, Tarier-Phase, Reinigungsassistent.
- **Burst** nach Bezugsende: `TOUCH_UART_SHOT_END_GRACE_MS` = 500 ms Nachlauf.
**Empfehlung P4-Seite:** alle ~5 s `ping` senden (Heartbeat), nach jedem Bedienvorgang
zusätzlich `getState` ist nicht nötig (die S3 pusht nach jedem Kommando automatisch `state`).
---
## 5. Telemetrie — `state`-Objekt (Protokoll v1, IST-Zustand)
Erzeugt von `buildTouchUartStateJson()`. Alle Felder in einem flachen JSON-Objekt.
| Feld | Typ | Bedeutung |
|---|---|---|
| `type` | string | immer `"state"` |
| `protocolVersion` | int | aktuell `1` |
| `firmwareVersion` | string | z. B. `"4.8.1"` |
| `tempW` | float | Ist-Temperatur Wasser (angezeigt) |
| `setW` | float | Soll-Temperatur Wasser |
| `tempD` | float | Ist-Temperatur Dampf (angezeigt) |
| `setD` | float | Soll-Temperatur Dampf |
| `statusText` | string | Klartext-Status (DE), z. B. `"Bezug aktiv..."` |
| `statusKey` | string | maschinenlesbarer Statusschlüssel (siehe §7) |
| `shotActive` | bool | Bezug läuft |
| `shotElapsedMs` | uint | vergangene Bezugszeit in ms |
| `steamCircuitActive` | bool | Dampf-/Wasserkreis aktiv |
| `steamFlushActive` | bool | Steam-Flush aktiv |
| `flushActive` | bool | Flush aktiv |
| `ecoActive` | bool | Eco-Modus aktiv |
| `standbyActive` | bool | Standby aktiv |
| `maintenanceActive` | bool | Wartungsmodus aktiv |
| `steamHeatDisabled` | bool | Dampfheizung per Nutzer deaktiviert |
| `lightOn` | bool | Beleuchtung an |
| `piezoEnabled` | bool | Piezo-Summer aktiv |
| `weight` | float | aktuelles Waagengewicht (UI) |
| `targetWeight` | float | Ziel-Gewicht (Brew-by-Weight) |
| `scaleEnabled` | bool | Waage aktiviert |
| `scaleType` | uint | Waagentyp |
| `scaleConnected` | bool | Waage verbunden |
| `hx711CalFactor` | float | HX711-Kalibrierfaktor |
| `hx711DisplaySmoothing` | bool | HX711-Anzeigeglättung |
| `piEnabled` | bool | Pre-Infusion aktiv |
| `bbtEnabled` | bool | Brew-by-Time aktiv |
| `bbwEnabled` | bool | Brew-by-Weight aktiv |
| `sbtEnabled` | bool | Steam-by-Time aktiv |
| `bbtSecs` | float | Brew-by-Time Zielzeit (s) |
| `sbtSecs` | float | Steam-by-Time Zielzeit (s) |
| `piDurSecs` | float | Pre-Infusion Dauer (s) |
| `piPauseSecs` | float | Pre-Infusion Pause (s) |
| `bbwTarget` | float | Brew-by-Weight Zielgewicht (g) |
| `bbwOffset` | float | Brew-by-Weight Offset (g) |
| `caseSensorEnabled` | bool | Gehäusesensor aktiv |
| `caseTempDashboard` | bool | Gehäusetemp. im Dashboard sichtbar (effektiv) |
| `caseTempDashboardSetting` | bool | Einstellung Dashboard-Anzeige |
| `caseTempDisplaySetting` | bool | Einstellung Display-Anzeige |
| `caseSensorType` | uint | Typ des Gehäusesensors |
| `caseOffset` | float | Offset Gehäusesensor |
| `caseTemp` | float\|null | Gehäusetemperatur (`null` bei Fehler/inaktiv) |
| `maintenanceInterval` | int | Wartungsintervall |
| `maintenanceIntervalCounter` | uint | Wartungszähler |
| `flushDurationSeconds` | uint | Flush-Dauer (s) |
| `steamFlushDurationSeconds` | uint | Steam-Flush-Dauer (s) |
| `xSwitchAction` | uint | X-Switch Kurz-Aktion |
| `xSwitchLongAction` | uint | X-Switch Lang-Aktion |
**v2-Felder (seit Phase A.1 implementiert):**
| Feld | Typ | Bedeutung |
|---|---|---|
| `dutyW` | float | Heizleistung Wasser in % (`OutputWasser / windowSizeWasser * 100`) |
| `dutyD` | float | Heizleistung Dampf in % |
| `kpW` / `kiW` / `kdW` | float | PID-Parameter Wasser (read-only) |
| `kpD` / `kiD` / `kdD` | float | PID-Parameter Dampf (read-only) |
| `autoTuneW` | bool | AutoTune Wasser läuft |
| `autoTuneD` | bool | AutoTune Dampf läuft |
| `autoTuneWStatus` | uint8 | Ergebnis/Grund letzter Wasser-Lauf: 0=Idle, 1=läuft, 2=Erfolg, 3=Failsafe (>9 Peaks, evtl. ungenau), 4=degeneriert (keine Schwingung, Werte verworfen), 5=Sensorfehler, 6=Sicherheitsabschaltung, 7=manuell abgebrochen |
| `autoTuneDStatus` | uint8 | Ergebnis/Grund letzter Dampf-Lauf (Codes wie `autoTuneWStatus`) |
| `wifiConnected` | bool | STA verbunden **oder** AP-Modus aktiv |
| `apMode` | bool | Access-Point-Modus aktiv |
| `ip` | string | aktuelle IP (STA-IP, sonst softAP-IP) |
| `ssid` | string | verbundene SSID bzw. AP-SSID |
| `rssi` | int | WLAN-Signalstärke (dBm), `0` wenn nicht STA-verbunden |
> `protocolVersion` ist mit diesen Feldern auf **2** angehoben.
**v2.1-Felder (Timer-Kopplung, Sicherheit, Reinigungsassistent):**
| Feld | Typ | Bedeutung |
|---|---|---|
| `steamElapsedMs` | uint | vergangene Dampfbezugszeit in ms (rückwirkende Timer-Kopplung, analog `shotElapsedMs`) |
| `flushElapsedMs` | uint | vergangene Wasser-Flush-Zeit in ms (rückwirkende Timer-Kopplung) |
| `scaleTaring` | bool | Tarier-Phase vor einem Brew-by-Weight-Bezug läuft (HX711); Display zeigt „Tariere…" |
| `wasserSensorError` | bool | Wassersensor liefert ungültigen Wert |
| `wasserSafetyShutdown` | bool | Wassertemperatur über Sicherheitslimit → Heizung aus |
| `dampfSensorError` | bool | Dampfsensor liefert ungültigen Wert |
| `dampfSafetyShutdown` | bool | Dampftemperatur über Sicherheitslimit → Heizung aus |
| `cleaningActive` | bool | Reinigungsassistent läuft |
| `cleaningWaiting` | bool | Reinigungsassistent heizt noch auf 93 °C vor |
| `cleaningBrewPhase` | bool | aktuelle Phase: `true` = Bezug, `false` = Pause |
| `cleaningCycle` | uint | aktueller Zyklus |
| `cleaningCycles` | uint | Gesamtzahl der Zyklen |
| `cleaningBrewSeconds` | uint | Bezugsdauer je Zyklus (s) |
| `cleaningPauseSeconds` | uint | Pausendauer je Zyklus (s) |
| `cleaningPhaseRemainSec` | int | verbleibende Sekunden der aktuellen Phase |
> **Force-Push** zusätzlich bei: Dampfkreis start/stop (deckt Dampfbezug **und** Dampf-Flush ab),
> Tarier-Phase start/stop, Sensorfehler/Sicherheitsabschaltung (Flanke), Reinigungsassistent start/stop,
> Standby-Wechsel. **Burst (250 ms)** zusätzlich während: Dampfkreis, Tarier-Phase, Reinigungsassistent.
---
## 6. Kommando-Referenz (Protokoll v1, IST-Zustand)
Verarbeitet in `touchUartHandleCommandLine()`. Alle Kommandos: `{"op":"...","id":<n>,...}`.
Antwort jeweils `ack`/`error`; bei zustandsändernden Ops folgt automatisch ein `state`-Push.
| `op` | Zusatzfelder | Wirkung | Antwort |
|---|---|---|---|
| `ping` | — | Heartbeat | `ack` `"pong"` |
| `getState` | — | erzwingt sofortigen State-Push | `state` + `ack` |
| `listProfiles` | — | Profilnamen anfordern | `profiles` + `ack` |
| `getUsageStats` | — | Nutzungsstatistik anfordern | `usageStats` + `ack` |
| `action` | `action` (string), `value` (opt.) | ruft `executeDashboardAction()` auf — **dieselbe** Aktions-Engine wie das Web-Dashboard | `ack` + `state` |
| `loadProfile` | `profile` (string) | Profil laden | `ack` + `state` |
| `saveProfile` | `profile` (string) | aktuelle Einstellungen als Profil speichern | `ack` (+ `state`) |
| `saveBrew` | `bbtEnabled`,`bbtSecs`,`bbwEnabled`,`bbwTarget`,`bbwOffset`,`piEnabled`,`piDurSecs`,`piPauseSecs`,`sbtEnabled`,`sbtSecs` (alle optional) | `applyBrewControlUpdate()` | `ack` + `state` |
| `saveService` | `piezoEnabled`,`maintenanceInterval`,`flushDurationSeconds`,`steamFlushDurationSeconds`,`resetMaintenanceCounter` | `applyServiceSettingsUpdate()` | `ack` + `state` |
| `saveSensor` | `section` (`case`/`scale`/`xswitch`) + jeweilige Felder (`caseSensorEnabled`, `scaleEnabled`, `scaleType`, `hx711CalFactor`, `xSwitchAction`, …) | `applySensorSettingsUpdate()` | `ack` + `state` |
| `startCleaning` | `cycles`,`brewSeconds`,`pauseSeconds` (alle optional, sonst aktuelle Werte) | setzt die Parameter und startet den Reinigungsassistenten (Prüfung via `canStartCleaningAssistant()`; bei Ablehnung `ok:false` mit Begründung) | `ack` + `state` |
| `stopCleaning` | — | bricht den laufenden Reinigungsassistenten ab | `ack` + `state` |
### Antwort-Envelopes
```jsonc
// ack
{"type":"ack","id":42,"ok":true,"message":"Brew gespeichert"}
// error
{"type":"error","id":42,"message":"op fehlt"}
// profiles
{"type":"profiles","id":7,"profiles":["Espresso","Lungo","Reinigung"]}
```
### Die wichtigste Erweiterungsstelle: `action`
`executeDashboardAction(action, value, success, message, settingsChanged, pidNeedsUpdate)`
ist **die gemeinsame Aktions-Engine** von Web-Dashboard und Touch-Display. Jede Aktion, die
das Webinterface auslöst (Standby, Eco, Licht, Flush, Bezug-Steuerung, Setpoints,
Reinigungsassistent, …), ist damit ohne Mehraufwand auch übers Display verfügbar.
### 6.1 Vollständige `action`-Referenz
Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Zeilen
~1339913863). Aufruf vom Display als `{"op":"action","id":<n>,"action":"<name>","value":"<v>"}`.
`value` ist immer ein **String**; die Engine konvertiert (`toFloat`/`toInt`, bzw. Vergleich
`== "true"`).
**Temperatur-Sollwerte**
| `action` | `value` | Wirkung |
|---|---|---|
| `setTempW` | float `0..135` | Wasser-Sollwert (°C), persistiert in EEPROM |
| `setTempD` | float `0..200` | Dampf-Sollwert (°C), persistiert in EEPROM |
| `setTempDisplayMode` | int (`CLASSIC..RAILS`) | Temperatur-Anzeigemodus im Dashboard |
**Betriebsmodi**
| `action` | `value` | Wirkung |
|---|---|---|
| `startEco` | — | Eco-Modus aktivieren |
| `stopEco` | — | Eco-Modus für diesen Zyklus beenden |
| `activateStandby` | — | Standby aktivieren (Software) |
| `deactivateStandby` | — | Standby aufheben (Software) |
| `activateStandbyLegacy` | — | Standby-Override (schalterbasiert) reaktivieren |
| `deactivateStandbyLegacy` | — | Standby per WebUI aufheben (nur bei aktivem Schalter) |
| `toggleMaintenance` | `"true"`/`"false"` | Wartungsmodus ein/aus (temporär) |
**Bezug / Dampf / Spülen**
| `action` | `value` | Wirkung |
|---|---|---|
| `startShot` | — | Bezug starten (Software) |
| `stopShot` | — | Bezug beenden |
| `startSteam` | — | Dampfbezug starten |
| `stopSteam` | — | Dampfbezug / Dampf-Flush beenden |
| `flush` | — | Spülen starten (Dauer = `flushDurationSeconds`) |
| `steamFlush` | — | Dampf-Flush starten (Dauer = `steamFlushDurationSeconds`) |
| `overrideSteamDelay` | — | Dampfverzögerung temporär überspringen |
| `disableSteamHeat` | — | Dampf-Heizung abschalten |
| `enableSteamHeat` | — | Dampf-Heizung wieder aktivieren |
**Persistente Toggles**
| `action` | `value` | Wirkung |
|---|---|---|
| `toggleBoostW` | `"true"`/`"false"` | Boost Wasser (EEPROM) |
| `toggleBoostD` | `"true"`/`"false"` | Boost Dampf (EEPROM) |
| `togglePI` | `"true"`/`"false"` | Pre-Infusion (EEPROM) |
| `toggleBBT` | `"true"`/`"false"` | Brew-by-Time (EEPROM) |
| `toggleBBW` | `"true"`/`"false"` | Brew-by-Weight (EEPROM) |
| `toggleLight` | `"true"`/`"false"` | Beleuchtung (SSR + EEPROM) |
**Waage**
| `action` | `value` | Wirkung |
|---|---|---|
| `tareScale` | — | Tarieren (nur I2C/HX711; ESP-NOW nicht über UI) |
| `toggleScaleMode` | `"true"`/`"false"` | Waage-Anzeige-/Wiegemodus (temporär) |
**Profil**
| `action` | `value` | Wirkung |
|---|---|---|
| `loadProfile` | Profilname | Profil laden (identisch zu `op:"loadProfile"`) |
> **Sperre bei Reinigungsassistent:** Solange `cleaningAssistantActive`, weisen
> `startEco`, `toggleMaintenance`, `startShot`, `stopShot`, `startSteam`, `stopSteam`,
> `flush`, `steamFlush` mit `success:false` und entsprechender Meldung ab. Das Display
> sollte diese Aktionen dann ausgegraut darstellen (Status `maintenance`).
> **Unbekannte Aktion:** liefert `message:"Unbekannte Aktion."` und `success:false`.
---
## 7. Status-Schlüssel (`statusKey`)
Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 verwenden
(statt `statusText` zu parsen):
`ready`, `error`, `standby`, `maintenance`, `tuning`, `brewing`, `eco`, `fastheatup`,
`heating`, `ready_eco_pending`.
---
## 8. Geplante Erweiterungen — Protokoll v2
Für ein vollwertiges 480×800-Touch-UI fehlen v1 noch einige Daten/Funktionen. Geplant:
### 8.1 Neue Telemetrie-Felder im `state`
- **[erledigt A.1] Netzwerk:** `wifiConnected`, `apMode`, `ssid`, `ip`, `rssi`.
- **[erledigt A.1] PID:** aktuelle Kp/Ki/Kd Wasser & Dampf (read-only).
- **[erledigt A.1] AutoTune:** `autoTuneW` / `autoTuneD` (laufend ja/nein).
- **[erledigt] AutoTune-Ergebnis:** `autoTuneWStatus` / `autoTuneDStatus` (Erfolg/Abbruch + Grund; siehe §8.1-Tabelle). P4-Dashboard blendet das Ergebnis nach Lauf-Ende ~30 s als Banner ein; S3-Web `/PID-Tuning` zeigt es persistent.
- **[erledigt A.1] Heizleistung:** Duty Wasser & Dampf (`dutyW`/`dutyD`, für Balkenanzeige).
- **[offen] Flow-Rate:** `flowRate` (g/s) als **Live-Zahl** (kein Chart) — bei Bedarf später.
- **[offen] AutoTune-Fortschritt:** numerischer Fortschritt (Peak-Count) — derzeit nur Lauf-Flag + Ergebnis-/Abbruchstatus.
> **Kein Chart am Display** (Entscheidung): Es wird **keine** Verlaufs-/Historiendaten-
> Übertragung benötigt. Damit entfällt der aufwändigste Teil (großer Ringpuffer, Chunking).
### 8.2 Neue Kommandos (`op`)
| `op` | Status | Zweck |
|---|---|---|
| `hello` | **erledigt** | Handshake: P4 meldet sich, S3 antwortet mit `type:"hello"` + `firmwareVersion`, `protocolVersion`, `capabilities[]`, danach `state` + `ack`. |
| `getProfileDetails` | **erledigt** | Profil-Inhalt (`profile`) → Antwort `type:"profileDetails"` mit allen Feldern (Setpoints, Offsets, Kp/Ki/Kd, Boost, Window, Eco, Brew, Piezo, FastHeatUp). |
| `setPid` | **erledigt** | Felder `kpW/kiW/kdW/kpD/kiD/kdD` (alle optional) → EEPROM + Live-`SetTunings`. |
| `startAutotune` | **erledigt** | `target`:`water`\|`steam` (auch `wasser`/`dampf`). Spiegelt `handleAutoTune*` (Guard: nur wenn keins läuft, Wartungsmodus wird zurückgesetzt). |
| `stopAutotune` | **erledigt** | Bricht laufendes AutoTune Wasser **und** Dampf ab. |
| `scanWifi` | **erledigt** | Startet einen **asynchronen** WLAN-Scan. Sofort `ack`; wenn fertig, pusht die S3 `type:"wifiNetworks"` (Array `{ssid,rssi,enc}`, max. 20). Timeout 15 s. |
| `setWifi` | **erledigt** | Felder `ssid` (Pflicht) + `password` (optional, offenes Netz = leer). Speichert in `WiFiConfig` (EEPROM) und ruft `WiFi.begin(...)` **nicht-blockierend** auf; vorhandene IP-Einstellungen bleiben erhalten. Fortschritt über die `wifi*`-Felder im `state`. |
| `getUsageStats` | **erledigt** | Nutzungsstatistik anfordern → Antwort `type:"usageStats"`. Aggregierte Kennzahlen aus `calculateShotStatistics()` (`today/week/month`, `yesterday/lastWeek/lastMonth`, `total`, `avgDur`, `avgPerDay`) plus `shots[]` = **letzte ~12** Einzelbezüge (`dt`, `d`=Dauer s, `w`=Gewicht g bzw. `-1`). Es wird nur das **Dateiende** von `/Nutzungsstatistik.csv` gelesen, nie die ganze Datei (P4-Heap schonen). |
| `getNetworkInfo` | entfällt | WLAN-Details sind bereits im `state` enthalten — eigener Op unnötig. |
> Antwort auf `scanWifi` (Beispiel, eigene Push-Nachricht sobald der Scan fertig ist):
> ```jsonc
> {"type":"wifiNetworks","networks":[
> {"ssid":"MeinWLAN","rssi":-52,"enc":true},
> {"ssid":"Gast","rssi":-71,"enc":false}
> ]}
> ```
> Antwort auf `getUsageStats` (Beispiel):
> ```jsonc
> {"type":"usageStats","id":7,"available":true,"total":340,
> "today":3,"week":18,"month":72,"yesterday":5,"lastWeek":21,"lastMonth":80,
> "avgDur":27.4,"avgPerDay":4.9,
> "shots":[
> {"dt":"02.07. 08:14","d":27.5,"w":36.5},
> {"dt":"02.07. 07:41","d":25.0,"w":-1}
> ]}
> ```
> Antwort auf `hello` (Beispiel):
> ```jsonc
> {"type":"hello","protocolVersion":2,"firmwareVersion":"4.8.1",
> "capabilities":["state","action","listProfiles","loadProfile","saveProfile","saveBrew","saveService","saveSensor","setPid","startAutotune","stopAutotune","getProfileDetails","scanWifi","setWifi","getUsageStats"]}
> ```
>
> Anzeige am P4: eigener Menüpunkt **„Statistik"** (`ui.cpp`, `PG_STATS`). Beim Öffnen der
> Seite (und über einen „Aktualisieren"-Button) sendet der P4 `getUsageStats` und stellt die
> Aggregate (Heute/Woche/Monat inkl. Vorperiode, Gesamt, mittlere Dauer, mittl. Bezüge/Tag)
> sowie die letzten ~12 Einzelbezüge dar.
### 8.3 Robustheit / Transport
- **Handshake + Capabilities** statt impliziter Aktivierung -> P4 kann sich an die
tatsächliche Firmware-Version anpassen (Vorwärts-/Rückwärtskompatibilität).
- **Optionale Prüfsumme** (z. B. CRC16 als Feld `crc`) gegen SSR-/EMV-Störungen auf der
Maschine. Empfänger verwirft Zeilen mit falscher Prüfsumme.
- **Puffergrößen:** Ohne Chart bleiben die Nutzdaten klein. `getProfileDetails` (ein
`TemperatureProfile`) passt voraussichtlich in die bestehenden Limits (Zeile 1024 B,
State 2400 B). Chunking ist damit **nicht** erforderlich; Limit nur bei Bedarf leicht anheben.
### 8.4 Versionierung
- `protocolVersion` wird auf **2** angehoben, **sobald** ein nicht abwärtskompatibles Feld
Pflicht wird. Reine *Ergänzungen* (neue optionale Felder/Ops) bleiben v1-kompatibel —
der P4 ignoriert Unbekanntes.
- Der P4 prüft beim `hello` die `protocolVersion` der S3 und schaltet Features entsprechend
frei/aus.
---
## 9. OLED-Koexistenz (Compile-Flag)
- OLED bleibt über `ENABLE_DISPLAY` (Compile-Flag) wählbar — unverändert.
- Das JC-Display wird über `TOUCH_UART_ENABLED` (Compile-Flag) aktiviert.
- **Geplante Änderung:** beide Flags strikt **unabhängig** halten, damit alle Kombinationen
baubar sind:
| `ENABLE_DISPLAY` | `TOUCH_UART_ENABLED` | Ergebnis |
|---|---|---|
| 0 | 0 | weder OLED noch Touch (Headless, nur Web) |
| 1 | 0 | nur OLED (heutiger Standard) |
| 0 | 1 | nur JC-Touch-Display |
| 1 | 1 | OLED **und** JC-Touch parallel |
> Aktuell ist sicherzustellen, dass kein UART-Code versehentlich von `ENABLE_DISPLAY`
> abhängt und keine I2C-/Pin-Ressource doppelt belegt wird (OLED nutzt I2C GPIO17/18,
> Touch nutzt UART GPIO43/44 — **kein Konflikt**).
---
## 10. Sprachen / UI-Texte
- **Die App ist aktuell reines Deutsch** — es existiert keine i18n-/Sprachumschalt-
Infrastruktur (geprüft: S3-Firmware und Web-Skripte haben nur fest verdrahtetes Deutsch,
HTML durchgängig `lang="de"`). Damit ist **kein** `getStrings`/`setLanguage` nötig.
- Der P4 hält seine **deutschen** UI-Texte lokal (statische Strings im Display-Projekt).
- `statusKey` (sprachneutral, §7) bleibt trotzdem die saubere Grundlage für die UI-Logik
(Icons/Zustände), unabhängig vom angezeigten `statusText`.
- Projekt-Hinweis: Sollte später eine zweite Anwendungssprache eingeführt werden, ist sie
laut Projektregel überall zu ergänzen — dann auch in den P4-Stringtabellen.
---
## 11. Bewertung des Bestands (Keep / Change / Delete)
**Behalten (1:1):** Protokoll-Framing, Push-Engine (`touchUartTick`), RX-Parser
(`touchUartRxTick`), Kommando-Dispatch (`touchUartHandleCommandLine`), JSON-Helfer
(`jsonGetStringValue/UInt/Bool/Float/RawValue`, `jsonDecodeString`, `jsonEscapeForUart`),
ACK/Error-Envelopes, Reset-Checkpoints (`RESET_CP_TOUCH_UART_RX/TX`).
**Ändern/Erweitern:** Flag-Entkopplung (§9), Telemetrie-/Op-Erweiterungen (§8),
Puffergrößen/Chunking, optionaler CRC + Handshake, `action`-Stringliste dokumentieren.
**Löschen:** Nichts.
**Fully-Kiosk bleibt erhalten** (Entscheidung) — als eigenständige, optionale Funktion
neben dem JC-Display. Lastbewertung der S3:
- Einziger Aufruf `sendFullyKioskLoadUrl()` wird **nur bei Standby-Zustandswechsel**
getriggert (`if (standbyModeActive != lastStandbyModeActive)`), **nicht periodisch**
(wenige Male pro Tag).
- Blockierender HTTP-GET, aber mit **kurzem Timeout** (`timeoutMs`, Default 700 ms);
bei `enabled=0` sofortiger Rückkehr ohne Netzwerkzugriff.
- Übergänge passieren im Standby, also **außerhalb eines Bezugs** -> kein Einfluss auf die
Regelung. **Keine relevante Dauerlast, keine Leistungsgrenze.** Koexistiert problemlos
mit dem UART-Display.
---
## 12. Implementierungsplan
### Phase A — S3-Seite (Hauptplatine, `Dual_PID_FastHeatUp.ino`)
1. **[erledigt]** `action`-Stringliste aus `executeDashboardAction()` dokumentiert (§6.1).
2. **[erledigt A.1]** Flag-Entkopplung von `ENABLE_DISPLAY` dokumentiert/abgesichert (§9);
Defines kommentiert, `TOUCH_UART_PROTOCOL_VERSION` -> 2, Puffer -> 3000.
3. **[erledigt A.1]** v2-Telemetriefelder ergänzt: Netzwerk, PID, Duty, AutoTune-Status (§8.1).
4. **[erledigt A.1]** Op `hello` implementiert (§8.2).
5. **[erledigt]** Weitere Ops: `getProfileDetails`, `setPid`, `startAutotune/stopAutotune` (§8.2).
6. **[offen, optional]** CRC + ggf. Puffer-Feinjustage (§8.3) — bewusst nicht als toter,
ungetesteter Code eingebaut; bei Bedarf später.
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
serielle Debug-Konsole (GPIO43/44) im aktuellen Betrieb erhalten bleibt.
### Phase B — P4-Seite (JC-Display, Projekt `JC_Display_Firmware/`)
Grundgerüst angelegt (Arduino-ESP32 P4 + LVGL 9 + ArduinoJson 7). Aufteilung:
`config.h`, `machine_state.h`, `protocol_client.*`, `ui.*`, `display_hal.*`, `.ino`, `README.md`.
1. **[erledigt]** UART-Client: Zeilen-RX/TX, JSON-Parsing, `state` -> UI-Modell,
`hello`-Handshake, Heartbeat, Link-Timeout (`protocol_client.*`).
2. **[erledigt]** Dashboard: Temps/Soll/Duty, Status-Banner (nach `statusKey`),
Verbindungs-/WLAN-Anzeige, Bezugs-Timer, Bedien-Buttons -> Kommandos (`ui.*`).
3. **[erledigt]** Senderoutinen für `setPid`, `startAutotune/stopAutotune`,
`getProfileDetails` im Client vorhanden.
4. **[erledigt]** Board-Bringup auf Basis der **Hersteller-Portierung** verdrahtet
(`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
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
JP1 (Schaltplan `4_USB&IO.png`). Gegenseite S3 unverändert GPIO43/44.
6. **[offen]** Weitere Screens: Einstellungen (Brew/Service/Sensor), Profil-Auswahl/-Editor,
PID/AutoTune-Bedienung; Ausrichtung Hochformat (Rotation 0 in `pins_config.h`).
### Entschieden
- **Sprachen:** App ist Deutsch-only, keine i18n -> P4 hält deutsche Strings lokal (§10).
- **Chart am Display:** nicht benötigt -> kein `getChartData`/Chunking (§8).
- **Fully-Kiosk:** bleibt als optionale Funktion erhalten; keine relevante S3-Last (§11).
---
*Letzter Abgleich mit Code: `Dual_PID_FastHeatUp.ino` v4.8.1 — Abschnitt „Dashboard Code
Block" (`buildTouchUartStateJson`, `touchUartHandleCommandLine`, `touchUartTick`,
`touchUartRxTick`) sowie Defines `TOUCH_UART_*` (Zeilen ~155184).*
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
Home Assistant Anleitung fuer die Dual PID Custom Integration
1. Dateien kopieren
Kopiere den Ordner
custom_components\dual_pid
nach Home Assistant in:
/config/custom_components/dual_pid
Am Ende sollte es dort z.B. so aussehen:
- /config/custom_components/dual_pid/manifest.json
- /config/custom_components/dual_pid/__init__.py
- /config/custom_components/dual_pid/config_flow.py
2. Home Assistant neu starten
Danach Home Assistant komplett neu starten.
Ein reines "Automationen neu laden" reicht nicht, weil custom_components nur beim Start geladen werden.
3. Integration hinzufuegen
Dann in Home Assistant:
1. Einstellungen
2. Geraete & Dienste
3. Integration hinzufuegen
4. nach "Dual PID Espresso" suchen
Falls die Integration dort nicht auftaucht:
- pruefen, ob der Ordnername exakt dual_pid heisst
- pruefen, ob wirklich unter /config/custom_components/dual_pid kopiert wurde
- Home Assistant Protokoll oeffnen und nach dual_pid suchen
4. Konfiguration in der UI
Im Dialog eintragen:
- Name: frei waehlbar, z.B. "Espressomaschine"
- Host / IP-Adresse: z.B. 192.168.178.50
- Port: normalerweise 80
- Aktualisierungsintervall: z.B. 10 Sekunden
Wichtig:
Die Maschine muss im Netzwerk erreichbar sein und bereits die neue Firmware mit der API haben, also mindestens:
- GET /api/status
- POST /api/start
- POST /api/standby/on
- POST /api/standby/off
5. Nach erfolgreicher Einrichtung
Dann sollte in Home Assistant ein neues Geraet erscheinen mit Entitaeten wie:
- Standby
- Jetzt aufheizen
- Status
- Wasser Temperatur
- Wasser Sollwert
- Dampf Temperatur
- Dampf Sollwert
- diverse Binaersensoren wie Eco aktiv, Dampfverzoegerung aktiv, Zeit synchronisiert
6. Ins Dashboard aufnehmen
Am einfachsten:
1. Geraet oeffnen
2. "Zum Dashboard hinzufuegen"
3. gewuenschte Entitaeten auswaehlen
Oder manuell in einer Entities-Karte.
7. Wenn etwas nicht funktioniert
Zuerst diese Punkte pruefen:
- Browser-Test: http://DEINE-IP/api/status
- Es muss JSON zurueckkommen
- Wenn dort nichts kommt, liegt es an der Maschine/API, nicht an Home Assistant
Zusaetzlich in Home Assistant:
- Einstellungen > System > Protokolle
- nach dual_pid suchen
8. Typische Fehlerursachen
- falscher Zielpfad beim Kopieren
- falscher Ordnername
- Home Assistant nicht neu gestartet
- Maschine noch mit aelterer Firmware
- IP-Adresse geaendert
- Firewall oder Netzwerk blockiert den Zugriff
@@ -0,0 +1,78 @@
type: vertical-stack
cards:
- type: entities
title: Espressomaschine - Steuerung
show_header_toggle: false
entities:
- entity: sensor.espressomaschine_status
name: Status
- entity: switch.espressomaschine_standby
name: Standby
- entity: button.espressomaschine_jetzt_aufheizen
name: Jetzt aufheizen
- type: entities
title: Temperaturen
show_header_toggle: false
entities:
- entity: sensor.espressomaschine_wasser_temperatur
name: Wasser Temperatur
- entity: sensor.espressomaschine_wasser_sollwert
name: Wasser Sollwert
- entity: sensor.espressomaschine_dampf_temperatur
name: Dampf Temperatur
- entity: sensor.espressomaschine_dampf_sollwert
name: Dampf Sollwert
- type: glance
title: Betriebszustaende
show_state: true
entities:
- entity: binary_sensor.espressomaschine_zeit_synchronisiert
name: Zeit
- entity: binary_sensor.espressomaschine_eco_aktiv
name: Eco
- entity: binary_sensor.espressomaschine_wartung_aktiv
name: Wartung
- entity: binary_sensor.espressomaschine_dampfverzoegerung_aktiv
name: Dampf Delay
- entity: binary_sensor.espressomaschine_dampf_heizung_deaktiviert
name: Dampf AUS
- type: glance
title: Aktive Funktionen
show_state: true
entities:
- entity: binary_sensor.espressomaschine_bezug_aktiv
name: Bezug
- entity: binary_sensor.espressomaschine_dampf_aktiv
name: Dampf
- entity: binary_sensor.espressomaschine_wasser_flush_aktiv
name: Wasser Flush
- entity: binary_sensor.espressomaschine_dampf_flush_aktiv
name: Dampf Flush
Hinweis:
Die Entitaetsnamen koennen je nach gewaehltem Geraetename in Home Assistant abweichen.
Wenn deine Integration z.B. nicht "Espressomaschine", sondern "Dual PID" heisst,
dann koennen die IDs eher so aussehen:
- sensor.dual_pid_status
- switch.dual_pid_standby
- button.dual_pid_jetzt_aufheizen
Falls eine Entitaet nicht gefunden wird:
1. In Home Assistant unter Einstellungen > Geraete & Dienste > Entitaeten nachsehen
2. Die echten Entitaets-IDs in die Karte uebernehmen
So fuegst du die Karte ein:
1. Dashboard oeffnen
2. Bearbeiten
3. Karte hinzufuegen
4. Manuell
5. diesen YAML-Inhalt einfuegen
@@ -0,0 +1,163 @@
type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: custom:mushroom-template-card
primary: Espressomaschine
secondary: "{{ states('sensor.espressomaschine_status') }}"
icon: mdi:coffee-maker
icon_color: >-
{% if is_state('switch.espressomaschine_standby', 'on') %}
grey
{% elif is_state('binary_sensor.espressomaschine_bezug_aktiv', 'on') %}
amber
{% elif is_state('binary_sensor.espressomaschine_dampf_aktiv', 'on') %}
deep-orange
{% elif is_state('binary_sensor.espressomaschine_eco_aktiv', 'on') %}
green
{% else %}
teal
{% endif %}
layout: vertical
multiline_secondary: true
- type: custom:mushroom-entity-card
entity: switch.espressomaschine_standby
name: Standby
icon_color: amber
layout: vertical
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: button.espressomaschine_jetzt_aufheizen
name: Jetzt aufheizen
icon: mdi:fire
icon_color: red
tap_action:
action: toggle
- type: custom:mushroom-template-card
primary: Dampfverzögerung
secondary: >-
{% if is_state('binary_sensor.espressomaschine_dampfverzoegerung_aktiv', 'on') %}
aktiv
{% else %}
aus
{% endif %}
icon: mdi:timer-sand
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_dampfverzoegerung_aktiv', 'on') %}
orange
{% else %}
grey
{% endif %}
layout: vertical
- type: horizontal-stack
cards:
- type: custom:mushroom-template-card
primary: Wasser
secondary: >-
{{ states('sensor.espressomaschine_wasser_temperatur') }} °C
/ Soll {{ states('sensor.espressomaschine_wasser_sollwert') }} °C
icon: mdi:thermometer-water
icon_color: blue
multiline_secondary: true
- type: custom:mushroom-template-card
primary: Dampf
secondary: >-
{{ states('sensor.espressomaschine_dampf_temperatur') }} °C
/ Soll {{ states('sensor.espressomaschine_dampf_sollwert') }} °C
icon: mdi:steam
icon_color: deep-orange
multiline_secondary: true
- type: grid
columns: 3
square: false
cards:
- type: custom:mushroom-template-card
primary: Zeit
secondary: >-
{% if is_state('binary_sensor.espressomaschine_zeit_synchronisiert', 'on') %}
synchronisiert
{% else %}
keine NTP-Zeit
{% endif %}
icon: mdi:clock-check-outline
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_zeit_synchronisiert', 'on') %}
green
{% else %}
red
{% endif %}
- type: custom:mushroom-template-card
primary: Eco
secondary: "{{ 'aktiv' if is_state('binary_sensor.espressomaschine_eco_aktiv', 'on') else 'aus' }}"
icon: mdi:leaf
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_eco_aktiv', 'on') %}
green
{% else %}
grey
{% endif %}
- type: custom:mushroom-template-card
primary: Wartung
secondary: "{{ 'aktiv' if is_state('binary_sensor.espressomaschine_wartung_aktiv', 'on') else 'aus' }}"
icon: mdi:tools
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_wartung_aktiv', 'on') %}
orange
{% else %}
grey
{% endif %}
- type: custom:mushroom-template-card
primary: Bezug
secondary: "{{ 'aktiv' if is_state('binary_sensor.espressomaschine_bezug_aktiv', 'on') else 'aus' }}"
icon: mdi:coffee
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_bezug_aktiv', 'on') %}
amber
{% else %}
grey
{% endif %}
- type: custom:mushroom-template-card
primary: Dampf
secondary: "{{ 'aktiv' if is_state('binary_sensor.espressomaschine_dampf_aktiv', 'on') else 'aus' }}"
icon: mdi:steam
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_dampf_aktiv', 'on') %}
deep-orange
{% else %}
grey
{% endif %}
- type: custom:mushroom-template-card
primary: Dampf AUS
secondary: "{{ 'ja' if is_state('binary_sensor.espressomaschine_dampf_heizung_deaktiviert', 'on') else 'nein' }}"
icon: mdi:radiator-disabled
icon_color: >-
{% if is_state('binary_sensor.espressomaschine_dampf_heizung_deaktiviert', 'on') %}
red
{% else %}
grey
{% endif %}
Hinweis:
Diese Vorlage nutzt Mushroom-Karten und setzt voraus, dass "Mushroom" in Home Assistant installiert ist,
z.B. ueber HACS.
Falls deine Entitaeten nicht mit "espressomaschine_" beginnen, musst du die Entity-IDs anpassen.
Typische Alternativen waeren z.B.:
- sensor.dual_pid_status
- switch.dual_pid_standby
- button.dual_pid_jetzt_aufheizen
So fuegst du die Karte ein:
1. Dashboard oeffnen
2. Bearbeiten
3. Karte hinzufuegen
4. Manuell
5. diesen YAML-Inhalt einfuegen
@@ -0,0 +1,65 @@
type: entities
title: Espressomaschine
show_header_toggle: false
entities:
- entity: sensor.espressomaschine_status
name: Status
- entity: switch.espressomaschine_standby
name: Standby
- entity: button.espressomaschine_jetzt_aufheizen
name: Jetzt aufheizen
- type: section
label: Temperaturen
- entity: sensor.espressomaschine_wasser_temperatur
name: Wasser Temperatur
- entity: sensor.espressomaschine_wasser_sollwert
name: Wasser Sollwert
- entity: sensor.espressomaschine_dampf_temperatur
name: Dampf Temperatur
- entity: sensor.espressomaschine_dampf_sollwert
name: Dampf Sollwert
- type: section
label: Zustaende
- entity: binary_sensor.espressomaschine_zeit_synchronisiert
name: Zeit synchronisiert
- entity: binary_sensor.espressomaschine_eco_aktiv
name: Eco aktiv
- entity: binary_sensor.espressomaschine_wartung_aktiv
name: Wartung aktiv
- entity: binary_sensor.espressomaschine_bezug_aktiv
name: Bezug aktiv
- entity: binary_sensor.espressomaschine_dampf_aktiv
name: Dampf aktiv
- entity: binary_sensor.espressomaschine_wasser_flush_aktiv
name: Wasser-Flush aktiv
- entity: binary_sensor.espressomaschine_dampf_flush_aktiv
name: Dampf-Flush aktiv
- entity: binary_sensor.espressomaschine_dampfverzoegerung_aktiv
name: Dampfverzoegerung aktiv
- entity: binary_sensor.espressomaschine_dampf_heizung_deaktiviert
name: Dampf-Heizung deaktiviert
Hinweis:
Die Entitaetsnamen koennen je nach gewaehhltem Geraetename in Home Assistant leicht abweichen.
Wenn du die Integration z.B. nicht als "Espressomaschine", sondern als "Dual PID" angelegt hast,
dann sehen die Entitaeten eher so aus:
- sensor.dual_pid_status
- switch.dual_pid_standby
- button.dual_pid_jetzt_aufheizen
Falls eine Entitaet nicht gefunden wird:
1. In Home Assistant unter Einstellungen > Geraete & Dienste > Entitaeten nachsehen
2. Die echten Entitaets-IDs in die Karte uebernehmen
So fuegst du die Karte ein:
1. Dashboard oeffnen
2. Bearbeiten
3. Karte hinzufuegen
4. Manuell
5. diesen YAML-Inhalt einfuegen
@@ -0,0 +1,46 @@
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .api import DualPidApiClient
from .const import DATA_API, DATA_COORDINATOR, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS
from .coordinator import DualPidDataUpdateCoordinator
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN]["logger"] = logging.getLogger(__package__)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
session = async_get_clientsession(hass)
api = DualPidApiClient(
session=session,
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
timeout=DEFAULT_TIMEOUT,
)
coordinator = DualPidDataUpdateCoordinator(hass, api, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_API: api,
DATA_COORDINATOR: coordinator,
}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id, None)
return unload_ok
@@ -0,0 +1,64 @@
from __future__ import annotations
from typing import Any
from aiohttp import ClientError, ClientSession, ClientTimeout
from .const import DEFAULT_TIMEOUT
class DualPidApiError(Exception):
"""Base API error."""
class DualPidApiConnectionError(DualPidApiError):
"""Raised when the API cannot be reached."""
class DualPidApiClient:
"""Simple client for the Dual PID HTTP API."""
def __init__(
self,
session: ClientSession,
host: str,
port: int,
timeout: int = DEFAULT_TIMEOUT,
) -> None:
self._session = session
self._host = host.strip().rstrip("/")
self._port = port
self._timeout = timeout
@property
def base_url(self) -> str:
return f"http://{self._host}:{self._port}"
async def _async_request(self, method: str, path: str) -> dict[str, Any]:
url = f"{self.base_url}{path}"
try:
response = await self._session.request(
method,
url,
timeout=ClientTimeout(total=self._timeout),
)
response.raise_for_status()
payload = await response.json()
except (ClientError, TimeoutError, ValueError) as err:
raise DualPidApiConnectionError(f"Request to {url} failed: {err}") from err
if not isinstance(payload, dict):
raise DualPidApiError("API response is not a JSON object")
return payload
async def async_get_status(self) -> dict[str, Any]:
return await self._async_request("GET", "/api/status")
async def async_start(self) -> dict[str, Any]:
return await self._async_request("POST", "/api/start")
async def async_standby_on(self) -> dict[str, Any]:
return await self._async_request("POST", "/api/standby/on")
async def async_standby_off(self) -> dict[str, Any]:
return await self._async_request("POST", "/api/standby/off")
@@ -0,0 +1,123 @@
from __future__ import annotations
from dataclasses import dataclass
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
DATA_COORDINATOR,
DOMAIN,
ECO_ACTIVE_KEY,
FLUSH_ACTIVE_KEY,
MAINTENANCE_ACTIVE_KEY,
SHOT_ACTIVE_KEY,
STEAM_CIRCUIT_ACTIVE_KEY,
STEAM_DELAY_ACTIVE_KEY,
STEAM_FLUSH_ACTIVE_KEY,
STEAM_HEAT_DISABLED_KEY,
TIME_SYNCED_KEY,
)
from .coordinator import DualPidDataUpdateCoordinator
from .entity import DualPidEntity
@dataclass(frozen=True, kw_only=True)
class DualPidBinarySensorDescription(BinarySensorEntityDescription):
value_key: str
BINARY_SENSORS: tuple[DualPidBinarySensorDescription, ...] = (
DualPidBinarySensorDescription(
key="time_synced",
name="Zeit synchronisiert",
icon="mdi:clock-check-outline",
value_key=TIME_SYNCED_KEY,
),
DualPidBinarySensorDescription(
key="eco_active",
name="Eco aktiv",
icon="mdi:leaf",
value_key=ECO_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="maintenance_active",
name="Wartung aktiv",
icon="mdi:tools",
value_key=MAINTENANCE_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="shot_active",
name="Bezug aktiv",
icon="mdi:coffee",
value_key=SHOT_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="steam_active",
name="Dampf aktiv",
icon="mdi:steam",
value_key=STEAM_CIRCUIT_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="flush_active",
name="Wasser-Flush aktiv",
icon="mdi:water-sync",
value_key=FLUSH_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="steam_flush_active",
name="Dampf-Flush aktiv",
icon="mdi:steam",
value_key=STEAM_FLUSH_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="steam_delay_active",
name="Dampfverzögerung aktiv",
icon="mdi:timer-sand",
value_key=STEAM_DELAY_ACTIVE_KEY,
),
DualPidBinarySensorDescription(
key="steam_heat_disabled",
name="Dampf-Heizung deaktiviert",
icon="mdi:radiator-disabled",
value_key=STEAM_HEAT_DISABLED_KEY,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator: DualPidDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
async_add_entities(
DualPidBinarySensor(coordinator, entry, description)
for description in BINARY_SENSORS
)
class DualPidBinarySensor(DualPidEntity, BinarySensorEntity):
"""Representation of a Dual PID binary sensor."""
entity_description: DualPidBinarySensorDescription
def __init__(
self,
coordinator: DualPidDataUpdateCoordinator,
entry: ConfigEntry,
description: DualPidBinarySensorDescription,
) -> None:
super().__init__(coordinator, entry, description.key)
self.entity_description = description
self._attr_name = description.name
self._attr_icon = description.icon
@property
def is_on(self) -> bool:
return bool(self.coordinator.data.get(self.entity_description.value_key))
@@ -0,0 +1,36 @@
from __future__ import annotations
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_COORDINATOR, DOMAIN
from .coordinator import DualPidDataUpdateCoordinator
from .entity import DualPidEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator: DualPidDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
async_add_entities([DualPidStartButton(coordinator, entry)])
class DualPidStartButton(DualPidEntity, ButtonEntity):
"""Button entity to wake the machine and start heating."""
_attr_name = "Jetzt aufheizen"
_attr_icon = "mdi:coffee-maker-check"
def __init__(
self,
coordinator: DualPidDataUpdateCoordinator,
entry: ConfigEntry,
) -> None:
super().__init__(coordinator, entry, "start_button")
async def async_press(self) -> None:
await self.coordinator.async_execute_action("start")
@@ -0,0 +1,213 @@
from __future__ import annotations
from typing import Any
from urllib.parse import urlparse
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import OptionsFlowWithReload
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .api import DualPidApiClient, DualPidApiConnectionError
from .const import (
CONF_SCAN_INTERVAL,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TIMEOUT,
DOMAIN,
)
class CannotConnect(Exception):
"""Error to indicate we cannot connect."""
def _normalize_host(host: str) -> str:
host = host.strip()
if "://" in host:
parsed = urlparse(host)
if parsed.hostname:
return parsed.hostname
return host.strip("/ ")
async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
client = DualPidApiClient(
session=async_get_clientsession(hass),
host=data[CONF_HOST],
port=data[CONF_PORT],
timeout=DEFAULT_TIMEOUT,
)
try:
status = await client.async_get_status()
except DualPidApiConnectionError as err:
raise CannotConnect from err
return status
def _build_schema(
*,
name: str = DEFAULT_NAME,
host: str = "",
port: int = DEFAULT_PORT,
scan_interval: int = DEFAULT_SCAN_INTERVAL,
) -> vol.Schema:
return vol.Schema(
{
vol.Required(CONF_NAME, default=name): str,
vol.Required(CONF_HOST, default=host): str,
vol.Required(CONF_PORT, default=port): vol.All(
vol.Coerce(int), vol.Range(min=1, max=65535)
),
vol.Required(CONF_SCAN_INTERVAL, default=scan_interval): vol.All(
vol.Coerce(int), vol.Range(min=2, max=300)
),
}
)
def _user_schema(
*,
name: str = DEFAULT_NAME,
host: str = "",
port: int = DEFAULT_PORT,
scan_interval: int = DEFAULT_SCAN_INTERVAL,
) -> vol.Schema:
return _build_schema(
name=name,
host=host,
port=port,
scan_interval=scan_interval,
)
class DualPidConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Dual PID."""
VERSION = 1
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult:
errors: dict[str, str] = {}
if user_input is not None:
user_input[CONF_HOST] = _normalize_host(user_input[CONF_HOST])
for entry in self._async_current_entries():
if (
entry.data.get(CONF_HOST) == user_input[CONF_HOST]
and entry.data.get(CONF_PORT) == user_input[CONF_PORT]
):
return self.async_abort(reason="already_configured")
try:
await _validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception:
errors["base"] = "unknown"
else:
return self.async_create_entry(
title=user_input[CONF_NAME],
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=_user_schema(),
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
return DualPidOptionsFlow()
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
errors: dict[str, str] = {}
entry = self._get_reconfigure_entry()
if user_input is not None:
user_input[CONF_HOST] = _normalize_host(user_input[CONF_HOST])
try:
await _validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception:
errors["base"] = "unknown"
else:
current_entry = self._get_reconfigure_entry()
new_data = {
**current_entry.data,
CONF_NAME: user_input[CONF_NAME],
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
CONF_SCAN_INTERVAL: user_input[CONF_SCAN_INTERVAL],
}
self.hass.config_entries.async_update_entry(
current_entry,
title=user_input[CONF_NAME],
options={
**current_entry.options,
CONF_SCAN_INTERVAL: user_input[CONF_SCAN_INTERVAL],
},
)
return self.async_update_reload_and_abort(
current_entry,
data_updates=new_data,
)
return self.async_show_form(
step_id="reconfigure",
data_schema=_build_schema(
name=entry.title or entry.data.get(CONF_NAME, DEFAULT_NAME),
host=entry.data.get(CONF_HOST, ""),
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
scan_interval=entry.options.get(
CONF_SCAN_INTERVAL,
entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
),
),
errors=errors,
)
class DualPidOptionsFlow(OptionsFlowWithReload):
"""Handle options for Dual PID."""
async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult:
if user_input is not None:
return self.async_create_entry(data=user_input)
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_SCAN_INTERVAL): vol.All(
vol.Coerce(int), vol.Range(min=2, max=300)
)
}
),
{
CONF_SCAN_INTERVAL: self.config_entry.options.get(
CONF_SCAN_INTERVAL,
self.config_entry.data.get(
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
),
)
},
),
)
@@ -0,0 +1,45 @@
from __future__ import annotations
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform
DOMAIN = "dual_pid"
CONF_SCAN_INTERVAL = "scan_interval"
DEFAULT_NAME = "Dual PID"
DEFAULT_PORT = 80
DEFAULT_SCAN_INTERVAL = 10
DEFAULT_TIMEOUT = 10
MANUFACTURER = "Thomas Mueller"
MODEL = "Dual PID Espresso Controller"
PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SWITCH,
Platform.BUTTON,
]
DATA_API = "api"
DATA_COORDINATOR = "coordinator"
STATUS_KEY = "status"
STATUS_TEXT_KEY = "statusText"
TIME_SYNCED_KEY = "timeSynced"
CURRENT_TIME_KEY = "currentTime"
STANDBY_ACTIVE_KEY = "standbyActive"
ECO_ACTIVE_KEY = "ecoActive"
MAINTENANCE_ACTIVE_KEY = "maintenanceActive"
SHOT_ACTIVE_KEY = "shotActive"
STEAM_CIRCUIT_ACTIVE_KEY = "steamCircuitActive"
FLUSH_ACTIVE_KEY = "flushActive"
STEAM_FLUSH_ACTIVE_KEY = "steamFlushActive"
STEAM_DELAY_ACTIVE_KEY = "steamDelayActive"
STEAM_HEAT_DISABLED_KEY = "steamHeatDisabled"
WATER_TEMP_KEY = "waterTemp"
WATER_TARGET_KEY = "waterTarget"
STEAM_TEMP_KEY = "steamTemp"
STEAM_TARGET_KEY = "steamTarget"
CONFIG_FIELDS = (CONF_NAME, CONF_HOST, CONF_PORT, CONF_SCAN_INTERVAL)
@@ -0,0 +1,55 @@
from __future__ import annotations
from datetime import timedelta
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .api import DualPidApiClient, DualPidApiError
from .const import CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, DOMAIN
class DualPidDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Coordinator for Dual PID status polling."""
def __init__(
self,
hass: HomeAssistant,
api: DualPidApiClient,
entry: ConfigEntry,
) -> None:
self.api = api
self.entry = entry
super().__init__(
hass,
logger=hass.data[DOMAIN]["logger"],
name=f"{entry.data.get(CONF_NAME, DOMAIN)} status",
update_interval=timedelta(
seconds=entry.options.get(
CONF_SCAN_INTERVAL,
entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
)
),
)
async def _async_update_data(self) -> dict[str, Any]:
try:
return await self.api.async_get_status()
except DualPidApiError as err:
raise UpdateFailed(str(err)) from err
async def async_execute_action(self, action: str) -> dict[str, Any]:
if action == "start":
data = await self.api.async_start()
elif action == "standby_on":
data = await self.api.async_standby_on()
elif action == "standby_off":
data = await self.api.async_standby_off()
else:
raise ValueError(f"Unsupported action: {action}")
self.async_set_updated_data(data)
return data
@@ -0,0 +1,35 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER, MODEL
from .coordinator import DualPidDataUpdateCoordinator
class DualPidEntity(CoordinatorEntity[DualPidDataUpdateCoordinator]):
"""Base entity for Dual PID entities."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: DualPidDataUpdateCoordinator,
entry: ConfigEntry,
key: str,
) -> None:
super().__init__(coordinator)
self._entry = entry
self._key = key
self._attr_unique_id = f"{entry.entry_id}_{key}"
@property
def device_info(self) -> DeviceInfo:
return DeviceInfo(
identifiers={(DOMAIN, self._entry.entry_id)},
name=self._entry.title,
manufacturer=MANUFACTURER,
model=MODEL,
configuration_url=f"http://{self._entry.data['host']}:{self._entry.data['port']}",
)
@@ -0,0 +1,11 @@
{
"domain": "dual_pid",
"name": "Dual PID Espresso",
"codeowners": [],
"config_flow": true,
"documentation": "https://github.com/local/dual_pid",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": [],
"version": "0.1.0"
}
@@ -0,0 +1,102 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
CURRENT_TIME_KEY,
DATA_COORDINATOR,
DOMAIN,
STATUS_TEXT_KEY,
STEAM_TARGET_KEY,
STEAM_TEMP_KEY,
WATER_TARGET_KEY,
WATER_TEMP_KEY,
)
from .coordinator import DualPidDataUpdateCoordinator
from .entity import DualPidEntity
@dataclass(frozen=True, kw_only=True)
class DualPidSensorDescription(SensorEntityDescription):
value_key: str
SENSORS: tuple[DualPidSensorDescription, ...] = (
DualPidSensorDescription(
key="status",
name="Status",
icon="mdi:coffee-maker",
value_key=STATUS_TEXT_KEY,
),
DualPidSensorDescription(
key="current_time",
name="Controller-Zeit",
icon="mdi:clock-outline",
value_key=CURRENT_TIME_KEY,
),
DualPidSensorDescription(
key="water_temp",
name="Wasser Temperatur",
icon="mdi:thermometer-water",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_key=WATER_TEMP_KEY,
),
DualPidSensorDescription(
key="water_target",
name="Wasser Sollwert",
icon="mdi:thermometer-chevron-up",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_key=WATER_TARGET_KEY,
),
DualPidSensorDescription(
key="steam_temp",
name="Dampf Temperatur",
icon="mdi:thermometer",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_key=STEAM_TEMP_KEY,
),
DualPidSensorDescription(
key="steam_target",
name="Dampf Sollwert",
icon="mdi:thermometer-chevron-up",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_key=STEAM_TARGET_KEY,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator: DualPidDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
async_add_entities(DualPidSensor(coordinator, entry, description) for description in SENSORS)
class DualPidSensor(DualPidEntity, SensorEntity):
"""Representation of a Dual PID sensor."""
entity_description: DualPidSensorDescription
def __init__(
self,
coordinator: DualPidDataUpdateCoordinator,
entry: ConfigEntry,
description: DualPidSensorDescription,
) -> None:
super().__init__(coordinator, entry, description.key)
self.entity_description = description
self._attr_name = description.name
self._attr_icon = description.icon
@property
def native_value(self) -> Any:
return self.coordinator.data.get(self.entity_description.value_key)
@@ -0,0 +1,44 @@
{
"title": "Dual PID Espresso",
"config": {
"step": {
"user": {
"title": "Dual PID Controller verbinden",
"description": "Trage IP-Adresse oder Hostname deiner Maschine ein.",
"data": {
"name": "Name",
"host": "Host / IP-Adresse",
"port": "Port",
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
},
"reconfigure": {
"title": "Dual PID Controller neu konfigurieren",
"description": "Name, Host, Port und Aktualisierungsintervall anpassen.",
"data": {
"name": "Name",
"host": "Host / IP-Adresse",
"port": "Port",
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
}
},
"error": {
"cannot_connect": "Verbindung zur Maschine fehlgeschlagen.",
"unknown": "Unbekannter Fehler."
},
"abort": {
"already_configured": "Diese Maschine ist bereits eingerichtet."
}
},
"options": {
"step": {
"init": {
"title": "Dual PID Optionen",
"data": {
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
}
}
}
}
@@ -0,0 +1,43 @@
from __future__ import annotations
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_COORDINATOR, DOMAIN, STANDBY_ACTIVE_KEY
from .coordinator import DualPidDataUpdateCoordinator
from .entity import DualPidEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator: DualPidDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
async_add_entities([DualPidStandbySwitch(coordinator, entry)])
class DualPidStandbySwitch(DualPidEntity, SwitchEntity):
"""Switch entity to control machine power semantics via standby."""
_attr_name = "Maschine"
_attr_icon = "mdi:coffee-maker"
def __init__(
self,
coordinator: DualPidDataUpdateCoordinator,
entry: ConfigEntry,
) -> None:
super().__init__(coordinator, entry, "standby_switch")
@property
def is_on(self) -> bool:
return not bool(self.coordinator.data.get(STANDBY_ACTIVE_KEY))
async def async_turn_on(self, **kwargs) -> None:
await self.coordinator.async_execute_action("standby_off")
async def async_turn_off(self, **kwargs) -> None:
await self.coordinator.async_execute_action("standby_on")
@@ -0,0 +1,44 @@
{
"title": "Dual PID Espresso",
"config": {
"step": {
"user": {
"title": "Dual PID Controller verbinden",
"description": "Trage IP-Adresse oder Hostname deiner Maschine ein.",
"data": {
"name": "Name",
"host": "Host / IP-Adresse",
"port": "Port",
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
},
"reconfigure": {
"title": "Dual PID Controller neu konfigurieren",
"description": "Name, Host, Port und Aktualisierungsintervall anpassen.",
"data": {
"name": "Name",
"host": "Host / IP-Adresse",
"port": "Port",
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
}
},
"error": {
"cannot_connect": "Verbindung zur Maschine fehlgeschlagen.",
"unknown": "Unbekannter Fehler."
},
"abort": {
"already_configured": "Diese Maschine ist bereits eingerichtet."
}
},
"options": {
"step": {
"init": {
"title": "Dual PID Optionen",
"data": {
"scan_interval": "Aktualisierungsintervall (Sekunden)"
}
}
}
}
}
@@ -0,0 +1,44 @@
{
"title": "Dual PID Espresso",
"config": {
"step": {
"user": {
"title": "Connect Dual PID controller",
"description": "Enter the IP address or hostname of your machine.",
"data": {
"name": "Name",
"host": "Host / IP address",
"port": "Port",
"scan_interval": "Update interval (seconds)"
}
},
"reconfigure": {
"title": "Reconfigure Dual PID controller",
"description": "Adjust name, host, port and update interval.",
"data": {
"name": "Name",
"host": "Host / IP address",
"port": "Port",
"scan_interval": "Update interval (seconds)"
}
}
},
"error": {
"cannot_connect": "Failed to connect to the machine.",
"unknown": "Unknown error."
},
"abort": {
"already_configured": "This machine is already configured."
}
},
"options": {
"step": {
"init": {
"title": "Dual PID options",
"data": {
"scan_interval": "Update interval (seconds)"
}
}
}
}
}
+236
View File
@@ -0,0 +1,236 @@
Deutsche Anleitung: I²C-Drucksensor an ESP32 (Arduino)
Basierend auf der Herstelleranleitung (siehe mitgeliefertes PDF).
============================================================
1) Überblick
============================================================
Der Sensor liefert kalibrierte digitale Messwerte über I²C:
- Druck als 24Bit ADC-Wert (signed) aus den Registern 0x06, 0x07, 0x08
- Temperatur als 16Bit Wert (signed) aus den Registern 0x09, 0x0A
- Messung wird über Register 0x30 gestartet; Fertig-Status über SCO-Bit (Bit 3) in 0x30
============================================================
2) Verdrahtung an ESP32 (WICHTIG: 3,3 V!)
============================================================
Kabelfarben → ESP32:
- Braun → 3V3 (Versorgung +)
- Blau → GND
- Weiß → SDA
- Schwarz→ SCL
Pull-Ups:
- Im Hersteller-Schaltbild sind 4,7 kΩ Pull-Ups eingezeichnet.
- Laut Text sind Pull-Ups bereits integriert → normalerweise keine zusätzlichen Pull-Ups nötig.
ESP32-Pins (Beispiel):
- SDA = GPIO 21
- SCL = GPIO 22
Du kannst andere I²C-fähige GPIOs nehmen und im Code Wire.begin(SDA, SCL) setzen.
============================================================
3) I²C-Adresse
============================================================
In der Hersteller-Bit-Tabelle ist A7..A1 = 1, danach W/R.
Das ergibt eine 7BitAdresse von 0x7F.
Hinweis:
0x7F ist im I²C-Standard „speziell“/reserviert. Manche I²CScanner finden ihn nicht.
Im Zweifel den Sensor trotzdem direkt mit 0x7F ansprechen (siehe Beispielcode).
============================================================
4) Messablauf (Register-Protokoll)
============================================================
1) Messung starten:
Schreibe 0x0A in Register 0x30 (Temp + Pressure acquisition)
2) Warten bis fertig:
Lies Register 0x30. Wenn SCO (Bit 3) = 0, ist die Messung fertig.
Alternativ kann man (laut Beispiel) ~50 ms warten.
3) Daten lesen:
Lies 0x06..0x0A in einem Rutsch (5 Bytes):
- 0x06,0x07,0x08: Druck (24-bit)
- 0x09,0x0A: Temperatur (16-bit)
============================================================
5) Umrechnung in Druck und Temperatur
============================================================
5.1 Druck (24Bit signed, Fullscale nötig)
Aus den drei Bytes:
- m = (x << 16) + (y << 8) + z (x=0x06, y=0x07, z=0x08)
Vorzeichenbehandlung:
- Wenn m > 2^23:
pressure = (m - 2^24) / 2^23 * Fullscale
- sonst:
pressure = m / 2^23 * Fullscale
WICHTIG:
Fullscale = Messbereich deines Sensors (z. B. 10 bar, 16 bar, 1.2 MPa, …)
Den musst du im Code passend setzen.
5.2 Temperatur (16Bit signed, Skalierung /256)
- n = (a << 8) + b (a=0x09, b=0x0A)
Vorzeichen & Skalierung:
- Wenn n > 2^15:
tempC = (n - 2^16) / 256
- sonst:
tempC = n / 256
============================================================
6) ESP32 Arduino-Beispielcode (Wire)
============================================================
WICHTIG: Setze FULLSCALE_BAR passend zu deinem Sensor!
```cpp
#include <Wire.h>
// --- I2C-Konfiguration ---
static constexpr uint8_t I2C_ADDR = 0x7F; // laut Hersteller-Tabelle (A7..A1 = 1)
static constexpr uint8_t REG_CMD = 0x30; // Command/Status Register
static constexpr uint8_t CMD_START = 0x0A; // Start: Temp + Pressure acquisition
// Datenregister
static constexpr uint8_t REG_DATA_START = 0x06; // 0x06..0x0A (5 Bytes)
// ESP32 Pins (anpassen falls nötig)
static constexpr int PIN_SDA = 21;
static constexpr int PIN_SCL = 22;
// --- Sensor-Messbereich (WICHTIG anpassen!) ---
static constexpr float FULLSCALE_BAR = 16.0f; // Beispiel: 16 bar Fullscale
// I2C: 1 Byte in Register schreiben
bool i2cWriteReg(uint8_t addr, uint8_t reg, uint8_t value) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(value);
return (Wire.endTransmission() == 0);
}
// I2C: 1 Byte aus Register lesen
bool i2cReadReg(uint8_t addr, uint8_t reg, uint8_t &value) {
Wire.beginTransmission(addr);
Wire.write(reg);
if (Wire.endTransmission(false) != 0) return false; // Repeated start
if (Wire.requestFrom((int)addr, 1) != 1) return false;
value = Wire.read();
return true;
}
// I2C: mehrere Bytes ab Startregister lesen
bool i2cReadBytes(uint8_t addr, uint8_t startReg, uint8_t *buf, size_t len) {
Wire.beginTransmission(addr);
Wire.write(startReg);
if (Wire.endTransmission(false) != 0) return false; // Repeated start
int got = Wire.requestFrom((int)addr, (int)len);
if (got != (int)len) return false;
for (size_t i = 0; i < len; i++) buf[i] = Wire.read();
return true;
}
// Umrechnung nach Herstellerformeln
float convertPressureBar(uint8_t b0, uint8_t b1, uint8_t b2) {
uint32_t m = ((uint32_t)b0 << 16) | ((uint32_t)b1 << 8) | (uint32_t)b2;
// Hersteller: wenn m > 2^23 => negativ, sonst positiv
// 2^23 = 8388608, 2^24 = 16777216
float ratio;
if (m > 8388608UL) {
ratio = ((int32_t)m - 16777216.0f) / 8388608.0f;
} else {
ratio = (float)m / 8388608.0f;
}
return ratio * FULLSCALE_BAR;
}
float convertTempC(uint8_t b0, uint8_t b1) {
uint16_t n = ((uint16_t)b0 << 8) | (uint16_t)b1;
// Hersteller: wenn n > 2^15 => negativ
// 2^15 = 32768, 2^16 = 65536, Skalierung /256
float t;
if (n > 32768U) {
t = ((int32_t)n - 65536.0f) / 256.0f;
} else {
t = (float)n / 256.0f;
}
return t;
}
bool readPressureAndTemp(float &pressureBar, float &tempC) {
// 1) Messung starten (0x0A nach 0x30)
if (!i2cWriteReg(I2C_ADDR, REG_CMD, CMD_START)) return false;
// 2) Fertig abfragen (SCO = Bit3). Alternativ könnte man fix 50ms warten.
// Wir machen: kurze Warte + Polling mit Timeout.
const uint32_t t0 = millis();
while (true) {
uint8_t st = 0;
if (!i2cReadReg(I2C_ADDR, REG_CMD, st)) return false;
bool scoBusy = (st & (1 << 3)) != 0; // SCO bit3
if (!scoBusy) break;
if (millis() - t0 > 100) return false; // Timeout 100ms
delay(5);
}
// 3) 5 Bytes lesen: 0x06..0x0A
uint8_t data[5];
if (!i2cReadBytes(I2C_ADDR, REG_DATA_START, data, sizeof(data))) return false;
pressureBar = convertPressureBar(data[0], data[1], data[2]);
tempC = convertTempC(data[3], data[4]);
return true;
}
void setup() {
Serial.begin(115200);
delay(200);
Wire.begin(PIN_SDA, PIN_SCL);
Wire.setClock(100000); // 100 kHz als solider Start
Serial.println("I2C Drucksensor Start");
}
void loop() {
float pBar, tC;
if (readPressureAndTemp(pBar, tC)) {
Serial.print("Druck [bar]: ");
Serial.print(pBar, 3);
Serial.print(" | Temp [C]: ");
Serial.println(tC, 2);
} else {
Serial.println("Sensor-Lesefehler (I2C/Timeout).");
}
delay(200);
}
```
============================================================
7) Typische Stolpersteine & Debug
============================================================
1) Spannung wirklich 3,3 V?
Der Hersteller zeigt 3,3 V im Wiring-Bild. 5 V können je nach Sensor/Board problematisch sein.
2) I²C-Adresse 0x7F „ungewöhnlich“
Wenn dein I²C-Scanner nichts findet: trotzdem den Sensor direkt mit 0x7F testen.
3) Kabelfarben prüfen
Braun=V+, Blau=GND, Weiß=SDA, Schwarz=SCL.
4) Messbereich (Fullscale) falsch → Druckwerte falsch skaliert
Beispiel: 16 bar statt 10 bar führt zu Faktor 1,6.
5) Lange Leitungen in der Maschine
Bei Aussetzern:
- I²C-Clock reduzieren (z. B. 50 kHz oder 10 kHz)
- SDA/GND und SCL/GND verdrillen
- saubere Masseführung
Ende.
+61
View File
@@ -0,0 +1,61 @@
Version 1.0.10:
- 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.)
@@ -0,0 +1 @@
The sample program has a video playback function, and copying the MJPEG folder to the TF card can play videos. The speaker needs to be connected externally. The TF card capacity should be less than 32GB, formatted as FAT32.
@@ -0,0 +1,2 @@
# 请使用arduino_esp32_v3.2.1版本
#lvgl v8.4.0
@@ -0,0 +1,237 @@
#pragma GCC push_options
#pragma GCC optimize("O3")
#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "WiFi.h"
#include "driver/i2c_master.h"
#include "demos/lv_demos.h"
#include "pins_config.h"
#include "src/lcd/st7701_lcd.h"
#include "src/touch/gt911_touch.h"
bsp_lcd_handles_t lcd_panels;
st7701_lcd lcd = st7701_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;
LV_FONT_DECLARE(weiruanyahei_14);
lv_obj_t *obj = NULL;
lv_obj_t *label[25];
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
// lv_disp_flush_ready(disp_drv);
lv_disp_flush_ready(disp_drv);
// if (disp_ctx->trans_size && disp_ctx->trans_sem) {
// xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake);
// }
return false;
}
// 显示刷新
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);
// touchX = 800 - touchX;
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;
}
}
static void wifiScanTask(void *arg)
{
while(1)
{
printf("Scan start\r\n");
// WiFi.scanNetworks will return the number of networks found.
int n = WiFi.scanNetworks();
printf("Scan done\r\n");
if(n > 25)
n=25;
if (n == 0) {
printf("no networks found\r\n");
} else {
printf("%d",n);
printf(" networks found\r\n");
printf("Nr | SSID | RSSI | CH | Encryption\r\n");
for (int i = 0; i < n; ++i) {
// Print SSID and RSSI for each network found
printf("%2d", i + 1);
printf(" | ");
printf("%-32.32s", WiFi.SSID(i).c_str());
printf(" | ");
printf("%4ld", WiFi.RSSI(i));
printf(" | ");
printf("%2ld", WiFi.channel(i));
printf(" | ");
switch (WiFi.encryptionType(i)) {
case WIFI_AUTH_OPEN: printf("open"); break;
case WIFI_AUTH_WEP: printf("WEP"); break;
case WIFI_AUTH_WPA_PSK: printf("WPA"); break;
case WIFI_AUTH_WPA2_PSK: printf("WPA2"); break;
case WIFI_AUTH_WPA_WPA2_PSK: printf("WPA+WPA2"); break;
case WIFI_AUTH_WPA2_ENTERPRISE: printf("WPA2-EAP"); break;
case WIFI_AUTH_WPA3_PSK: printf("WPA3"); break;
case WIFI_AUTH_WPA2_WPA3_PSK: printf("WPA2+WPA3"); break;
case WIFI_AUTH_WAPI_PSK: printf("WAPI"); break;
default: printf("unknown");
}
printf("\r\n");
lv_label_set_text_fmt(label[i],"%-32s %4ld",WiFi.SSID(i).c_str(),WiFi.RSSI(i));
delay(10);
}
}
Serial.println("");
// Delete the scan result to free memory for code below.
WiFi.scanDelete();
// Wait a bit before scanning again.
delay(5000);
}
}
void setup() {
Serial.begin(115200);
// Set WiFi to station mode and disconnect from an AP if it was previously connected.
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
i2c_master_bus_handle_t i2c_handle = NULL;
i2c_master_bus_config_t i2c_bus_conf = {
.i2c_port = I2C_NUM_1,
.sda_io_num = (gpio_num_t)TP_I2C_SDA,
.scl_io_num = (gpio_num_t)TP_I2C_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
i2c_new_master_bus(&i2c_bus_conf, &i2c_handle);
lcd.begin();
touch.begin();
lcd.get_handle(&lcd_panels);
lv_init();
size_t buffer_size = sizeof(int16_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 = false;
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);
esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(lcd_panels.panel, &cbs, &disp_drv);
// lv_disp_set_rotation(NULL, 0);
Serial.println("start demo");
obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj,480,800);
label[0] = lv_label_create(obj);
lv_obj_align(label[0],LV_ALIGN_TOP_LEFT,0,0);
lv_obj_set_style_text_font(label[0], &weiruanyahei_14, 0);
for(int i =1;i<25;i++)
{
label[i] = lv_label_create(obj);
lv_obj_set_style_text_font(label[i], &weiruanyahei_14, 0);
lv_obj_align_to(label[i],label[i-1],LV_ALIGN_OUT_BOTTOM_LEFT,0,0);
}
xTaskCreatePinnedToCore(wifiScanTask, "WiFi Scan", 4096, NULL, 4, NULL,1);
Serial.println("Setup done");
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -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 (48U * 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 0
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 15 /*[ms]*/ //15
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30 /*[ms]*///30
/*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
/*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 0
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 0
#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 1
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 1
/*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 0
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 0
#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,12 @@
#pragma once
#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
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_types.h"
#include "esp_lcd_st7701_interface.h"
#include "esp_lcd_st7701.h"
static const char *TAG = "st7701";
esp_err_t esp_lcd_new_panel_st7701(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, "version: %d.%d.%d", ESP_LCD_ST7701_VER_MAJOR, ESP_LCD_ST7701_VER_MINOR, ESP_LCD_ST7701_VER_PATCH);
ESP_RETURN_ON_FALSE(panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config, ESP_ERR_INVALID_ARG, TAG, "`vendor_config` is necessary");
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
#if SOC_LCD_RGB_SUPPORTED
if (!vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_rgb(io, panel_dev_config, ret_panel);
}
#endif
#if SOC_MIPI_DSI_SUPPORTED
if (vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_mipi(io, panel_dev_config, ret_panel);
}
#endif
return ret;
}
@@ -0,0 +1,199 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "hal/lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#if SOC_LCD_RGB_SUPPORTED
#include "esp_lcd_panel_rgb.h"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_mipi_dsi.h"
#endif
#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 */
} st7701_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 st7701_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 */
union {
#if SOC_LCD_RGB_SUPPORTED
const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
#endif
#if SOC_MIPI_DSI_SUPPORTED
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;
#endif
};
struct {
unsigned int use_mipi_interface: 1; /*<! Set to 1 if using MIPI interface, default is RGB interface */
unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
* Otherwise, the function will be implemented by software.
* This flag is only valid for the RGB interface.
*/
union {
unsigned int auto_del_panel_io: 1;
unsigned int enable_io_multiplex: 1;
}; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
* If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
* Please set it to 1 to release the panel IO and its pins (except CS signal).
* This flag is only valid for the RGB interface.
*/
} flags;
} st7701_vendor_config_t;
/**
* @brief Create LCD panel for model ST7701
*
* @note When `enable_io_multiplex` is set to 1, this function will first initialize the ST7701 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
* @note When `enable_io_multiplex` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the ST7701 and RGB.
* @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 (`vendor_config` and `rgb_config` are necessary)
* @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_st7701(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 3-wire SPI panel IO configuration structure
*
* @param[in] line_cfg SPI line configuration
* @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
*
*/
#define ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
{ \
.line_config = line_cfg, \
.expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
.spi_mode = scl_active_edge ? 1 : 0, \
.lcd_cmd_bytes = 1, \
.lcd_param_bytes = 1, \
.flags = { \
.use_dc_bit = 1, \
.dc_zero_on_data = 0, \
.lsb_first = 0, \
.cs_high_active = 0, \
.del_keep_cs_inactive = 1, \
}, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Default Configuration Macros for RGB Interface /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief RGB timing structure
*
* @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
* / bits_per_pixel
*
*/
#define ST7701_480_480_PANEL_60HZ_RGB_TIMING() \
{ \
.pclk_hz = 16 * 1000 * 1000, \
.h_res = 480, \
.v_res = 480, \
.hsync_pulse_width = 10, \
.hsync_back_porch = 10, \
.hsync_front_porch = 20, \
.vsync_pulse_width = 10, \
.vsync_back_porch = 10, \
.vsync_front_porch = 10, \
.flags.pclk_active_neg = false, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Default Configuration Macros for MIPI-DSI Interface //////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief MIPI-DSI bus configuration structure
*/
#define ST7701_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 = 500, \
}
/**
* @brief MIPI-DBI panel IO configuration structure
*
*/
#define ST7701_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 ST7701_480_360_PANEL_60HZ_DPI_CONFIG(px_format) \
{ \
.virtual_channel = 0, \
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
.dpi_clock_freq_mhz = 34, \
.pixel_format = px_format, \
.num_fbs = 1, \
.video_timing = { \
.h_size = 480, \
.v_size = 800, \
.hsync_pulse_width = 12, \
.hsync_back_porch = 42, \
.hsync_front_porch = 42, \
.vsync_pulse_width = 2, \
.vsync_back_porch = 8, \
.vsync_front_porch = 166, \
}, \
.flags = {.use_dma2d = true,} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ST7701_CMD_SDIR (0xC7)
#define ST7701_CMD_SS_BIT (1 << 2)
#define ST7701_CMD_CND2BKxSEL (0xFF)
#define ST7701_CMD_BKxSEL_BYTE0 (0x77)
#define ST7701_CMD_BKxSEL_BYTE1 (0x01)
#define ST7701_CMD_BKxSEL_BYTE2 (0x00)
#define ST7701_CMD_BKxSEL_BYTE3 (0x00)
#define ST7701_CMD_CN2_BIT (1 << 4)
#define ST7701_CMD_BKxSEL_BK0 (0x00)
#if SOC_LCD_RGB_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with RGB interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_rgb(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);
#endif
#if SOC_MIPI_DSI_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with MIPI interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_mipi(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);
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,478 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
static const char *TAG = "st7701_mipi";
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep);
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 st7701_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);
} st7701_panel_t;
esp_err_t esp_lcd_new_panel_st7701_mipi(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 argument");
st7701_vendor_config_t *vendor_config = (st7701_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;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order");
break;
}
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x55;
break;
case 18: // RGB666
st7701->colmod_val = 0x66;
break;
case 24: // RGB888
st7701->colmod_val = 0x77;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->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
st7701->del = panel_handle->del;
st7701->init = panel_handle->init;
// Overwrite the functions of MIPI DPI panel
panel_handle->del = panel_st7701_del;
panel_handle->init = panel_st7701_init;
panel_handle->reset = panel_st7701_reset;
panel_handle->mirror = panel_st7701_mirror;
panel_handle->invert_color = panel_st7701_invert_color;
panel_handle->disp_on_off = panel_st7701_disp_on_off;
panel_handle->disp_sleep = panel_st7701_sleep;
panel_handle->user_data = st7701;
*ret_panel = panel_handle;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
}
return ret;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete MIPI DPI panel
st7701->del(panel);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
free(st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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(20)); // spec, wait at least 5ms before sending new command
}
return ESP_OK;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
// {0xC0, (uint8_t []){0x2c, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x10, 0x0C}, 2, 0},
// {0xC2, (uint8_t []){0x21, 0x0A}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x00, 0x0B, 0x12, 0x0D, 0x10, 0x06, 0x02, 0x08, 0x07, 0x1F, 0x04, 0x11, 0x0F, 0x29, 0x31, 0x1E}, 16, 0},
// {0xB1, (uint8_t []){0x00, 0x0B, 0x13, 0x0D, 0x11, 0x06, 0x03, 0x08, 0x07, 0x20, 0x04, 0x12, 0x11, 0x29, 0x31, 0x1E}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x72}, 1, 0},
// {0xB2, (uint8_t []){0x84}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4D}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x20}, 1, 0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x80, 0x00, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x33, 0x33}, 11, 0},
// {0xE2, (uint8_t []){0x00, 0x00, 0x30, 0x30, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 12, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x0C, 0x78, 0x00, 0xE0, 0x0E, 0x7A, 0x00, 0xE0, 0x08, 0x74, 0x00, 0xE0, 0x0A, 0x76, 0x00, 0xE0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x0D, 0x79, 0x00, 0xE0, 0x0F, 0x7B, 0x00, 0xE0, 0x09, 0x75, 0x00, 0xE0, 0x0B, 0x77, 0x00, 0xE0}, 16, 0},
// {0xE9, (uint8_t []){0x36, 0x00}, 2, 0},
// {0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x40}, 7, 0},
// {0xED, (uint8_t []){0xA1, 0xC2, 0xFB, 0x0F, 0x67, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x76, 0xF0, 0xBF, 0x2C, 0x1A}, 16, 0},
// {0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xE8, (uint8_t []){0x00, 0x0E}, 2, 0},
// {0xE8, (uint8_t []){0x00, 0x0C}, 2, 20},
// {0xE8, (uint8_t []){0x00, 0x00}, 2, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x11, (uint8_t []){0x00}, 0, 120},
// {0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
/*
*/
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
{0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x10, 0x08}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x80, 0x09, 0x53, 0x0C, 0xD0, 0x07, 0x0C, 0x09, 0x09, 0x28, 0x06, 0xD4, 0x13, 0x69, 0x2B, 0x71}, 16, 0},
{0xB1, (uint8_t []){0x80, 0x94, 0x5A, 0x10, 0xD3, 0x06, 0x0A, 0x08, 0x08, 0x25, 0x03, 0xD3, 0x12, 0x66, 0x6A, 0x0D}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x5D}, 1, 0},
{0xB1, (uint8_t []){0x58}, 1, 0},
{0xB2, (uint8_t []){0x87}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x4E}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x21}, 1, 0},
{0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
{0xBB, (uint8_t []){0x03}, 1,0},
{0xBC, (uint8_t []){0x00}, 1,0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 0},
{0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
{0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
{0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
{0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
{0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x3A, (uint8_t []){0x66}, 1, 0},
{0x11, (uint8_t []){0x00}, 1, 120},
{0x29, (uint8_t []){0x00}, 1, 20},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
// {0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
// {0xC2, (uint8_t []){0x17, 0x08}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x40, 0xC9, 0x94, 0x0E, 0x10, 0x05, 0x0B, 0x09, 0x08, 0x26, 0x04, 0x52, 0x10, 0x69, 0x6B, 0x69}, 16, 0},
// {0xB1, (uint8_t []){0x40, 0xD2, 0x98, 0x0C, 0x92, 0x07, 0x09, 0x08, 0x07, 0x25, 0x02, 0x0E, 0x1C, 0x6E, 0x78, 0x55}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x4E}, 1, 0},
// {0xB2, (uint8_t []){0x87}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4E}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x21}, 1, 0},
// {0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
// {0xBB, (uint8_t []){0x03}, 1,0},
// {0xBC, (uint8_t []){0x00}, 1,0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
// {0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
// {0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
// {0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
// {0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
// {0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// // {0x3A, (uint8_t []){0x66}, 1, 0},
// {0x11, (uint8_t []){0x00}, 1, 120},
// {0x29, (uint8_t []){0x00}, 1, 20},
};
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
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_LOGI(TAG, "LCD ID: %02X %02X %02X", ID[0], ID[1], ID[2]);
// back to CMD_Page 0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
st7701->colmod_val,
}, 1), TAG, "send command failed");
ESP_LOGI(TAG, " st7701->madctl_val: 0x%x, st7701->colmod_val: 0x%x", st7701->madctl_val, st7701->colmod_val);
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init MIPI DPI panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
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_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->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;
}
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (sleep) {
command = LCD_CMD_SLPIN;
} else {
command = LCD_CMD_SLPOUT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
"io tx param failed");
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
#endif
@@ -0,0 +1,386 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_LCD_RGB_SUPPORTED
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_log.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
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 current value of LCD_CMD_COLMOD register
const st7701_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int mirror_by_cmd: 1;
unsigned int enable_io_multiplex: 1;
unsigned int display_on_off_use_cmd: 1;
unsigned int reset_level: 1;
} flags;
// To save the original functions of RGB panel
esp_err_t (*init)(esp_lcd_panel_t *panel);
esp_err_t (*del)(esp_lcd_panel_t *panel);
esp_err_t (*reset)(esp_lcd_panel_t *panel);
esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
} st7701_panel_t;
static const char *TAG = "st7701_rgb";
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
esp_err_t esp_lcd_new_panel_st7701_rgb(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");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
ESP_RETURN_ON_FALSE(!vendor_config->flags.enable_io_multiplex || !vendor_config->flags.mirror_by_cmd,
ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `enable_io_multiplex` cannot work together");
esp_err_t ret = ESP_OK;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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->rgb_ele_order) {
case LCD_RGB_ELEMENT_ORDER_RGB:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
break;
}
st7701->colmod_val = 0;
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x50;
break;
case 18: // RGB666
st7701->colmod_val = 0x60;
break;
case 24: // RGB888
st7701->colmod_val = 0x70;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
st7701->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
st7701->flags.enable_io_multiplex = vendor_config->flags.enable_io_multiplex;
st7701->flags.reset_level = panel_dev_config->flags.reset_active_high;
if (st7701->flags.enable_io_multiplex) {
if (st7701->reset_gpio_num >= 0) { // Perform hardware reset
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->flags.reset_level);
} else { // Perform software reset
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
}
vTaskDelay(pdMS_TO_TICKS(120));
/**
* In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
* (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
* `esp_lcd_new_rgb_panel()` is called.
*/
ESP_GOTO_ON_ERROR(panel_st7701_send_init_cmds(st7701), err, TAG, "send init commands failed");
// After sending the initialization commands, the 3-wire SPI interface can be deleted
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
st7701->io = NULL;
ESP_LOGD(TAG, "delete panel IO");
}
// Create RGB panel
ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
// Save the original functions of RGB panel
st7701->init = (*ret_panel)->init;
st7701->del = (*ret_panel)->del;
st7701->reset = (*ret_panel)->reset;
st7701->mirror = (*ret_panel)->mirror;
st7701->disp_on_off = (*ret_panel)->disp_on_off;
// Overwrite the functions of RGB panel
(*ret_panel)->init = panel_st7701_init;
(*ret_panel)->del = panel_st7701_del;
(*ret_panel)->reset = panel_st7701_reset;
(*ret_panel)->mirror = panel_st7701_mirror;
(*ret_panel)->disp_on_off = panel_st7701_disp_on_off;
(*ret_panel)->user_data = st7701;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(st7701);
}
return ret;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, (uint8_t []){0x3B, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x10, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x20, 0x06}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xB1, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x8D}, 1, 0},
{0xB1, (uint8_t []){0x48}, 1, 0},
{0xB2, (uint8_t []){0x89}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x49}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x32}, 1, 0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 100},
{0xE0, (uint8_t []){0x00, 0x00, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x05, 0xC0, 0x07, 0xC0, 0x04, 0xC0, 0x06, 0xC0, 0x00, 0x44, 0x44}, 11, 0},
{0xE2, (uint8_t []){0x00, 0x00, 0x33, 0x33, 0x01, 0xC0, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x0D, 0xF1, 0x10, 0x98, 0x0F, 0xF3, 0x10, 0x98, 0x09, 0xED, 0x10, 0x98, 0x0B, 0xEF, 0x10, 0x98}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x0C, 0xF0, 0x10, 0x98, 0x0E, 0xF2, 0x10, 0x98, 0x08, 0xEC, 0x10, 0x98, 0x0A, 0xEE, 0x10, 0x98}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x00}, 7, 0},
{0xED, (uint8_t []){0xFF, 0x04, 0x56, 0x7F, 0xBA, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xAB, 0xF7, 0x65, 0x40, 0xFF}, 16, 0},
{0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
};
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701)
{
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
bool is_cmd_overwritten = false;
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
// Set color format
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
st7701->madctl_val
}, 1), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t []) {
st7701->colmod_val
}, 1), TAG, "Write cmd failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
return ESP_OK;
}
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (!st7701->flags.enable_io_multiplex) {
ESP_RETURN_ON_ERROR(panel_st7701_send_init_cmds(st7701), TAG, "send init commands failed");
}
// Init RGB panel
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete RGB panel
st7701->del(panel);
free(st7701);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// Perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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));
}
// Reset RGB panel
ESP_RETURN_ON_ERROR(st7701->reset(panel), TAG, "reset RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
if (st7701->flags.mirror_by_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
} else {
// Control mirror through RGB panel
ESP_RETURN_ON_ERROR(st7701->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
}
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (st7701->flags.display_on_off_use_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control display on/off through LCD command
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");
} else {
// Control display on/off through display control signal
ESP_RETURN_ON_ERROR(st7701->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
}
return ESP_OK;
}
#endif
@@ -0,0 +1,183 @@
#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 "driver/i2c_master.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "Arduino.h"
#include "esp_lcd_st7701.h"
#include "st7701_lcd.h"
#define LCD_H_RES 480
#define LCD_V_RES 800
#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 (0)
#define EXAMPLE_PIN_NUM_BK_LIGHT GPIO_NUM_23
#define BSP_LCD_BACKLIGHT GPIO_NUM_23
#define LCD_LEDC_CH LEDC_CHANNEL_0
static const char *TAG = "example";
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
st7701_lcd::st7701_lcd(int8_t lcd_rst)
{
_lcd_rst = lcd_rst;
}
void st7701_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 st7701_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 st7701_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 st7701_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 = ST7701_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 = ST7701_PANEL_IO_DBI_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io_handle));
// 创建JD9365控制面板
esp_lcd_dpi_panel_config_t dpi_config = ST7701_480_360_PANEL_60HZ_DPI_CONFIG(MIPI_DPI_PX_FORMAT);
st7701_vendor_config_t vendor_config = {
// .init_cmds = lcd_cmd,
// .init_cmds_size = sizeof(lcd_cmd) / sizeof(st7701_lcd_init_cmd_t),
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
.flags = {
.use_mipi_interface = 1,
}
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_5,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
// if (dsi_cfg->flags.avoid_tearing) {
// cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback;
// } else {
// cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
// }
// /* Register done callback */
// esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv);
// 打开背光
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
}
void st7701_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 st7701_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 st7701_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 st7701_lcd::te_on()
{
esp_lcd_panel_io_tx_param(io_handle, 0x35,new (uint8_t[]){0x00}, 1);
}
void st7701_lcd::te_off()
{
esp_lcd_panel_io_tx_param(io_handle, 0x34,new (uint8_t[]){0x00}, 0);
}
uint16_t st7701_lcd::width()
{
return LCD_H_RES;
}
uint16_t st7701_lcd::height()
{
return LCD_V_RES;
}
void st7701_lcd::get_handle(bsp_lcd_handles_t *ret_handles)
{
ret_handles->io = io_handle;
ret_handles->mipi_dsi_bus = NULL;
ret_handles->panel = panel_handle;
ret_handles->control = NULL;
}
@@ -0,0 +1,36 @@
#ifndef _ST7701_LCD_H
#define _ST7701_LCD_H
#include <stdio.h>
#include "esp_lcd_types.h"
#include "esp_lcd_mipi_dsi.h"
typedef struct {
esp_lcd_dsi_bus_handle_t mipi_dsi_bus; /*!< MIPI DSI bus handle */
esp_lcd_panel_io_handle_t io; /*!< ESP LCD IO handle */
esp_lcd_panel_handle_t panel; /*!< ESP LCD panel (color) handle */
esp_lcd_panel_handle_t control; /*!< ESP LCD panel (control) handle */
} bsp_lcd_handles_t;
class st7701_lcd
{
public:
st7701_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();
void get_handle(bsp_lcd_handles_t *ret_handles);
private:
int8_t _lcd_rst;
};
#endif
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_lcd_touch.h"
static const char *TAG = "TP";
/*******************************************************************************
* Function definitions
*******************************************************************************/
/*******************************************************************************
* Local variables
*******************************************************************************/
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
assert(tp->read_data != NULL);
return tp->read_data(tp);
}
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
bool touched = false;
assert(tp != NULL);
assert(x != NULL);
assert(y != NULL);
assert(tp->get_xy != NULL);
touched = tp->get_xy(tp, x, y, strength, point_num, max_point_num);
if (!touched) {
return false;
}
/* Process coordinates by user */
if (tp->config.process_coordinates != NULL) {
tp->config.process_coordinates(tp, x, y, strength, point_num, max_point_num);
}
/* Software coordinates adjustment needed */
bool sw_adj_needed = ((tp->config.flags.mirror_x && (tp->set_mirror_x == NULL)) ||
(tp->config.flags.mirror_y && (tp->set_mirror_y == NULL)) ||
(tp->config.flags.swap_xy && (tp->set_swap_xy == NULL)));
/* Adjust all coordinates */
for (int i = 0; (sw_adj_needed && i < *point_num); i++) {
/* Mirror X coordinates (if not supported by HW) */
if (tp->config.flags.mirror_x && tp->set_mirror_x == NULL) {
x[i] = tp->config.x_max - x[i];
}
/* Mirror Y coordinates (if not supported by HW) */
if (tp->config.flags.mirror_y && tp->set_mirror_y == NULL) {
y[i] = tp->config.y_max - y[i];
}
/* Swap X and Y coordinates (if not supported by HW) */
if (tp->config.flags.swap_xy && tp->set_swap_xy == NULL) {
uint16_t tmp = x[i];
x[i] = y[i];
y[i] = tmp;
}
}
return touched;
}
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state)
{
assert(tp != NULL);
assert(state != NULL);
*state = 0;
if (tp->get_button_state) {
return tp->get_button_state(tp, n, state);
} else {
return ESP_ERR_NOT_SUPPORTED;
}
return ESP_OK;
}
#endif
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap)
{
assert(tp != NULL);
tp->config.flags.swap_xy = swap;
/* Is swap supported by HW? */
if (tp->set_swap_xy) {
return tp->set_swap_xy(tp, swap);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap)
{
assert(tp != NULL);
assert(swap != NULL);
/* Is swap supported by HW? */
if (tp->get_swap_xy) {
return tp->get_swap_xy(tp, swap);
} else {
*swap = tp->config.flags.swap_xy;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_x = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_x) {
return tp->set_mirror_x(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_x) {
return tp->get_mirror_x(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_x;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_y = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_y) {
return tp->set_mirror_y(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_y) {
return tp->get_mirror_y(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_y;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
if (tp->del != NULL) {
return tp->del(tp);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback)
{
esp_err_t ret = ESP_OK;
assert(tp != NULL);
/* Interrupt pin is not selected */
if (tp->config.int_gpio_num == GPIO_NUM_NC) {
return ESP_ERR_INVALID_ARG;
}
tp->config.interrupt_callback = callback;
if (callback != NULL) {
ret = gpio_install_isr_service(0);
/* ISR service can be installed from user before, then it returns invalid state */
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "GPIO ISR install failed");
return ret;
}
/* Add GPIO ISR handler */
ret = gpio_intr_enable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
ret = gpio_isr_handler_add(tp->config.int_gpio_num, (gpio_isr_t)tp->config.interrupt_callback, tp);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
} else {
/* Remove GPIO ISR handler */
ret = gpio_isr_handler_remove(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR remove handler failed");
ret = gpio_intr_disable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR disable failed");
}
return ESP_OK;
}
@@ -0,0 +1,370 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch
*/
#pragma once
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS (1)
#define CONFIG_ESP_LCD_TOUCH_MAX_POINTS (5)
/**
* @brief Touch controller type
*
*/
typedef struct esp_lcd_touch_s esp_lcd_touch_t;
typedef esp_lcd_touch_t *esp_lcd_touch_handle_t;
/**
* @brief Touch controller interrupt callback type
*
*/
typedef void (*esp_lcd_touch_interrupt_callback_t)(esp_lcd_touch_handle_t tp);
/**
* @brief Touch Configuration Type
*
*/
typedef struct {
uint16_t x_max; /*!< X coordinates max (for mirroring) */
uint16_t y_max; /*!< Y coordinates max (for mirroring) */
gpio_num_t rst_gpio_num; /*!< GPIO number of reset pin */
gpio_num_t int_gpio_num; /*!< GPIO number of interrupt pin */
struct {
unsigned int reset: 1; /*!< Level of reset pin in reset */
unsigned int interrupt: 1;/*!< Active Level of interrupt pin */
} levels;
struct {
unsigned int swap_xy: 1; /*!< Swap X and Y after read coordinates */
unsigned int mirror_x: 1; /*!< Mirror X after read coordinates */
unsigned int mirror_y: 1; /*!< Mirror Y after read coordinates */
} flags;
/*!< User callback called after get coordinates from touch controller for apply user adjusting */
void (*process_coordinates)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
/*!< User callback called after the touch interrupt occured */
esp_lcd_touch_interrupt_callback_t interrupt_callback;
} esp_lcd_touch_config_t;
typedef struct {
uint8_t points; /*!< Count of touch points saved */
struct {
uint16_t x; /*!< X coordinate */
uint16_t y; /*!< Y coordinate */
uint16_t strength; /*!< Strength */
} coords[CONFIG_ESP_LCD_TOUCH_MAX_POINTS];
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
uint8_t buttons; /*!< Count of buttons states saved */
struct {
uint8_t status; /*!< Status of button */
} button[CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS];
#endif
portMUX_TYPE lock; /*!< Lock for read/write */
} esp_lcd_touch_data_t;
/**
* @brief Declare of Touch Type
*
*/
struct esp_lcd_touch_s {
/**
* @brief Read data from touch controller (mandatory)
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*read_data)(esp_lcd_touch_handle_t tp);
/**
* @brief Get coordinates from touch controller (mandatory)
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of strengths
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool (*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);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state (optional)
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates (optional)
* If set, then not used SW swapping.
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_swap_xy)(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped (optional)
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_swap_xy)(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_x)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X (optional)
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_x)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_y)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y (optional)
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_y)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete Touch
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*del)(esp_lcd_touch_handle_t tp);
/**
* @brief Configuration structure
*/
esp_lcd_touch_config_t config;
/**
* @brief Communication interface
*/
esp_lcd_panel_io_handle_t io;
/**
* @brief Data structure
*/
esp_lcd_touch_data_t data;
};
/**
* @brief Read data from touch controller
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG parameter error
* - ESP_FAIL sending command error, slave hasn't ACK the transfer
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode
* - ESP_ERR_TIMEOUT operation timeout because the bus is busy
*/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp);
/**
* @brief Read coordinates from touch controller
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of the strengths (can be NULL)
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by controller
* - ESP_ERR_INVALID_ARG if bad button index
*/
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete touch (free all allocated memory and restart HW)
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp);
/**
* @brief Register user callback called after the touch interrupt occured
*
* @param tp: Touch handler
* @param callback: Interrupt callback
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,270 @@
/*
* 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);
/*******************************************************************************
* 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;
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*
}
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: GT911
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create a new GT911 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD/Touch panel IO handle
* @param config: Touch configuration
* @param out_touch: Touch instance handle
* @return
* - ESP_OK on success
* - ESP_ERR_NO_MEM if there is no memory for allocating main structure
*/
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);
/**
* @brief I2C address of the GT911 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS (0x5D)
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 16, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,103 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_lcd_touch_gt911.h"
#include "gt911_touch.h"
#define CONFIG_LCD_HRES 480
#define CONFIG_LCD_VRES 800
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));
i2c_master_bus_handle_t i2c_handle = NULL;
i2c_master_get_bus_handle(1,&i2c_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_LOGI(TAG, "Initialize touch IO (I2C)");
esp_lcd_new_panel_io_i2c(i2c_handle, &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,18 @@
#ifndef _GT911_TOUCH_H
#define _GT911_TOUCH_H
#include <stdio.h>
class gt911_touch
{
public:
gt911_touch(int8_t sda_pin, int8_t scl_pin, int8_t rst_pin = -1, int8_t int_pin = -1);
void begin();
bool getTouch(uint16_t *x, uint16_t *y);
void set_rotation(uint8_t r);
private:
int8_t _sda, _scl, _rst, _int;
};
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,4 @@
# 请使用arduino_esp32_v3.2.1版本
@@ -0,0 +1,138 @@
#include "esp_log.h"
#include "esp_check.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#define EXAMPLE_ADC2_CHAN0 ADC_CHANNEL_4
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12
#define V_C_MAX (2450) //电池满电时检测的值
#define V_C_MIN (2250) //电池没电时检测的值
adc_oneshot_unit_handle_t adc2_handle;
adc_cali_handle_t adc2_cali_handle = NULL;
bool do_calibration2;
static int adc_raw;
static int adc_raw_;
static int voltage_;
static int voltage;
static int voltage_per;
static int voltage_per_;
static bool example_adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle);
static void example_adc_calibration_deinit(adc_cali_handle_t handle);
void setup() {
Serial.begin(115200); // 初始化串口通信
adc_oneshot_chan_cfg_t config = {
.atten = EXAMPLE_ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
//-------------ADC2 Init---------------//
adc_oneshot_unit_init_cfg_t init_config2 = {
.unit_id = ADC_UNIT_2,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config2, &adc2_handle));
//-------------ADC2 Calibration Init---------------//
do_calibration2 = example_adc_calibration_init(ADC_UNIT_2, EXAMPLE_ADC2_CHAN0, EXAMPLE_ADC_ATTEN, &adc2_cali_handle);
//-------------ADC2 Config---------------//
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_ADC2_CHAN0, &config));
}
void loop() {
for(int i=0;i<500;i++)
{
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_ADC2_CHAN0,&adc_raw));
adc_raw_ += adc_raw;
}
adc_raw_ = adc_raw_ / 500;
Serial.printf("ADC%d Channel[%d] Raw Data: %d \r\n", ADC_UNIT_2 + 1, EXAMPLE_ADC2_CHAN0, adc_raw_);
if (do_calibration2) {
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc2_cali_handle, adc_raw_, &voltage));
Serial.printf("ADC%d Channel[%d] Cali Voltage: %d mV \r\n", ADC_UNIT_2 + 1, EXAMPLE_ADC2_CHAN0, voltage);
}
voltage_ = voltage - V_C_MIN;
if(voltage_ < 0)
voltage_ = 0;
voltage_per_ = voltage_per;
voltage_per = voltage_ * 10000 / (V_C_MAX - V_C_MIN) / 100 ;
voltage_per = (voltage_per_ + voltage_per) / 2;
if(voltage_per > 100)
voltage_per = 100;
Serial.printf("Battery charge: %d %% \r\n",voltage_per);
delay(1000); // 延迟1秒
}
/*---------------------------------------------------------------
ADC Calibration
---------------------------------------------------------------*/
static bool example_adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.chan = channel,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
*out_handle = handle;
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Calibration Success");
} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated;
}
static void example_adc_calibration_deinit(adc_cali_handle_t handle)
{
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
ESP_LOGI(TAG, "deregister %s calibration scheme", "Curve Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_curve_fitting(handle));
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
ESP_LOGI(TAG, "deregister %s calibration scheme", "Line Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(handle));
#endif
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,21 @@
# 请使用arduino_esp32_v3.2版本
#此样例使用的屏幕是微雪的10.1寸屏幕,具体型号位:10.1-DSI-TOUCH-A
# lvgl V8.4.0
# lv_conf.h文件必要设置(lvgl_v8)
# 要将lvgl文件夹中的demos文件夹移动到同目录下的src文件夹中
```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,160 @@
#pragma GCC push_options
#pragma GCC optimize("O3")
#include <Arduino.h>
#include "lvgl.h"
#include "driver/i2c_master.h"
#include "demos/lv_demos.h"
#include "pins_config.h"
#include "src/lcd/st7701_lcd.h"
#include "src/touch/gt911_touch.h"
bsp_lcd_handles_t lcd_panels;
st7701_lcd lcd = st7701_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;
static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
// lv_disp_flush_ready(disp_drv);
lv_disp_flush_ready(disp_drv);
// if (disp_ctx->trans_size && disp_ctx->trans_sem) {
// xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake);
// }
return false;
}
// 显示刷新
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);
// touchX = 800 - touchX;
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");
i2c_master_bus_handle_t i2c_handle = NULL;
i2c_master_bus_config_t i2c_bus_conf = {
.i2c_port = I2C_NUM_1,
.sda_io_num = (gpio_num_t)TP_I2C_SDA,
.scl_io_num = (gpio_num_t)TP_I2C_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
i2c_new_master_bus(&i2c_bus_conf, &i2c_handle);
lcd.begin();
touch.begin();
lcd.get_handle(&lcd_panels);
lv_init();
size_t buffer_size = sizeof(int16_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 = false;
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);
esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(lcd_panels.panel, &cbs, &disp_drv);
// lv_disp_set_rotation(NULL, 0);
Serial.println("start demo");
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 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
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_types.h"
#include "esp_lcd_st7701_interface.h"
#include "esp_lcd_st7701.h"
static const char *TAG = "st7701";
esp_err_t esp_lcd_new_panel_st7701(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, "version: %d.%d.%d", ESP_LCD_ST7701_VER_MAJOR, ESP_LCD_ST7701_VER_MINOR, ESP_LCD_ST7701_VER_PATCH);
ESP_RETURN_ON_FALSE(panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config, ESP_ERR_INVALID_ARG, TAG, "`vendor_config` is necessary");
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
#if SOC_LCD_RGB_SUPPORTED
if (!vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_rgb(io, panel_dev_config, ret_panel);
}
#endif
#if SOC_MIPI_DSI_SUPPORTED
if (vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_mipi(io, panel_dev_config, ret_panel);
}
#endif
return ret;
}
@@ -0,0 +1,199 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "hal/lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#if SOC_LCD_RGB_SUPPORTED
#include "esp_lcd_panel_rgb.h"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_mipi_dsi.h"
#endif
#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 */
} st7701_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 st7701_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 */
union {
#if SOC_LCD_RGB_SUPPORTED
const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
#endif
#if SOC_MIPI_DSI_SUPPORTED
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;
#endif
};
struct {
unsigned int use_mipi_interface: 1; /*<! Set to 1 if using MIPI interface, default is RGB interface */
unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
* Otherwise, the function will be implemented by software.
* This flag is only valid for the RGB interface.
*/
union {
unsigned int auto_del_panel_io: 1;
unsigned int enable_io_multiplex: 1;
}; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
* If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
* Please set it to 1 to release the panel IO and its pins (except CS signal).
* This flag is only valid for the RGB interface.
*/
} flags;
} st7701_vendor_config_t;
/**
* @brief Create LCD panel for model ST7701
*
* @note When `enable_io_multiplex` is set to 1, this function will first initialize the ST7701 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
* @note When `enable_io_multiplex` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the ST7701 and RGB.
* @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 (`vendor_config` and `rgb_config` are necessary)
* @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_st7701(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 3-wire SPI panel IO configuration structure
*
* @param[in] line_cfg SPI line configuration
* @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
*
*/
#define ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
{ \
.line_config = line_cfg, \
.expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
.spi_mode = scl_active_edge ? 1 : 0, \
.lcd_cmd_bytes = 1, \
.lcd_param_bytes = 1, \
.flags = { \
.use_dc_bit = 1, \
.dc_zero_on_data = 0, \
.lsb_first = 0, \
.cs_high_active = 0, \
.del_keep_cs_inactive = 1, \
}, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Default Configuration Macros for RGB Interface /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief RGB timing structure
*
* @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
* / bits_per_pixel
*
*/
#define ST7701_480_480_PANEL_60HZ_RGB_TIMING() \
{ \
.pclk_hz = 16 * 1000 * 1000, \
.h_res = 480, \
.v_res = 480, \
.hsync_pulse_width = 10, \
.hsync_back_porch = 10, \
.hsync_front_porch = 20, \
.vsync_pulse_width = 10, \
.vsync_back_porch = 10, \
.vsync_front_porch = 10, \
.flags.pclk_active_neg = false, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Default Configuration Macros for MIPI-DSI Interface //////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief MIPI-DSI bus configuration structure
*/
#define ST7701_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 = 500, \
}
/**
* @brief MIPI-DBI panel IO configuration structure
*
*/
#define ST7701_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 ST7701_480_360_PANEL_60HZ_DPI_CONFIG(px_format) \
{ \
.virtual_channel = 0, \
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
.dpi_clock_freq_mhz = 34, \
.pixel_format = px_format, \
.num_fbs = 1, \
.video_timing = { \
.h_size = 480, \
.v_size = 800, \
.hsync_pulse_width = 12, \
.hsync_back_porch = 42, \
.hsync_front_porch = 42, \
.vsync_pulse_width = 2, \
.vsync_back_porch = 8, \
.vsync_front_porch = 166, \
}, \
.flags = {.use_dma2d = true,} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ST7701_CMD_SDIR (0xC7)
#define ST7701_CMD_SS_BIT (1 << 2)
#define ST7701_CMD_CND2BKxSEL (0xFF)
#define ST7701_CMD_BKxSEL_BYTE0 (0x77)
#define ST7701_CMD_BKxSEL_BYTE1 (0x01)
#define ST7701_CMD_BKxSEL_BYTE2 (0x00)
#define ST7701_CMD_BKxSEL_BYTE3 (0x00)
#define ST7701_CMD_CN2_BIT (1 << 4)
#define ST7701_CMD_BKxSEL_BK0 (0x00)
#if SOC_LCD_RGB_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with RGB interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_rgb(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);
#endif
#if SOC_MIPI_DSI_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with MIPI interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_mipi(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);
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,478 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
static const char *TAG = "st7701_mipi";
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep);
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 st7701_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);
} st7701_panel_t;
esp_err_t esp_lcd_new_panel_st7701_mipi(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 argument");
st7701_vendor_config_t *vendor_config = (st7701_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;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order");
break;
}
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x55;
break;
case 18: // RGB666
st7701->colmod_val = 0x66;
break;
case 24: // RGB888
st7701->colmod_val = 0x77;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->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
st7701->del = panel_handle->del;
st7701->init = panel_handle->init;
// Overwrite the functions of MIPI DPI panel
panel_handle->del = panel_st7701_del;
panel_handle->init = panel_st7701_init;
panel_handle->reset = panel_st7701_reset;
panel_handle->mirror = panel_st7701_mirror;
panel_handle->invert_color = panel_st7701_invert_color;
panel_handle->disp_on_off = panel_st7701_disp_on_off;
panel_handle->disp_sleep = panel_st7701_sleep;
panel_handle->user_data = st7701;
*ret_panel = panel_handle;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
}
return ret;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete MIPI DPI panel
st7701->del(panel);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
free(st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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(20)); // spec, wait at least 5ms before sending new command
}
return ESP_OK;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
// {0xC0, (uint8_t []){0x2c, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x10, 0x0C}, 2, 0},
// {0xC2, (uint8_t []){0x21, 0x0A}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x00, 0x0B, 0x12, 0x0D, 0x10, 0x06, 0x02, 0x08, 0x07, 0x1F, 0x04, 0x11, 0x0F, 0x29, 0x31, 0x1E}, 16, 0},
// {0xB1, (uint8_t []){0x00, 0x0B, 0x13, 0x0D, 0x11, 0x06, 0x03, 0x08, 0x07, 0x20, 0x04, 0x12, 0x11, 0x29, 0x31, 0x1E}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x72}, 1, 0},
// {0xB2, (uint8_t []){0x84}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4D}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x20}, 1, 0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x80, 0x00, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x33, 0x33}, 11, 0},
// {0xE2, (uint8_t []){0x00, 0x00, 0x30, 0x30, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 12, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x0C, 0x78, 0x00, 0xE0, 0x0E, 0x7A, 0x00, 0xE0, 0x08, 0x74, 0x00, 0xE0, 0x0A, 0x76, 0x00, 0xE0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x0D, 0x79, 0x00, 0xE0, 0x0F, 0x7B, 0x00, 0xE0, 0x09, 0x75, 0x00, 0xE0, 0x0B, 0x77, 0x00, 0xE0}, 16, 0},
// {0xE9, (uint8_t []){0x36, 0x00}, 2, 0},
// {0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x40}, 7, 0},
// {0xED, (uint8_t []){0xA1, 0xC2, 0xFB, 0x0F, 0x67, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x76, 0xF0, 0xBF, 0x2C, 0x1A}, 16, 0},
// {0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xE8, (uint8_t []){0x00, 0x0E}, 2, 0},
// {0xE8, (uint8_t []){0x00, 0x0C}, 2, 20},
// {0xE8, (uint8_t []){0x00, 0x00}, 2, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x11, (uint8_t []){0x00}, 0, 120},
// {0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
/*
*/
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
{0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x10, 0x08}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x80, 0x09, 0x53, 0x0C, 0xD0, 0x07, 0x0C, 0x09, 0x09, 0x28, 0x06, 0xD4, 0x13, 0x69, 0x2B, 0x71}, 16, 0},
{0xB1, (uint8_t []){0x80, 0x94, 0x5A, 0x10, 0xD3, 0x06, 0x0A, 0x08, 0x08, 0x25, 0x03, 0xD3, 0x12, 0x66, 0x6A, 0x0D}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x5D}, 1, 0},
{0xB1, (uint8_t []){0x58}, 1, 0},
{0xB2, (uint8_t []){0x87}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x4E}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x21}, 1, 0},
{0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
{0xBB, (uint8_t []){0x03}, 1,0},
{0xBC, (uint8_t []){0x00}, 1,0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 0},
{0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
{0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
{0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
{0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
{0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x3A, (uint8_t []){0x66}, 1, 0},
{0x11, (uint8_t []){0x00}, 1, 120},
{0x29, (uint8_t []){0x00}, 1, 20},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
// {0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
// {0xC2, (uint8_t []){0x17, 0x08}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x40, 0xC9, 0x94, 0x0E, 0x10, 0x05, 0x0B, 0x09, 0x08, 0x26, 0x04, 0x52, 0x10, 0x69, 0x6B, 0x69}, 16, 0},
// {0xB1, (uint8_t []){0x40, 0xD2, 0x98, 0x0C, 0x92, 0x07, 0x09, 0x08, 0x07, 0x25, 0x02, 0x0E, 0x1C, 0x6E, 0x78, 0x55}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x4E}, 1, 0},
// {0xB2, (uint8_t []){0x87}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4E}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x21}, 1, 0},
// {0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
// {0xBB, (uint8_t []){0x03}, 1,0},
// {0xBC, (uint8_t []){0x00}, 1,0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
// {0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
// {0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
// {0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
// {0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
// {0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// // {0x3A, (uint8_t []){0x66}, 1, 0},
// {0x11, (uint8_t []){0x00}, 1, 120},
// {0x29, (uint8_t []){0x00}, 1, 20},
};
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
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_LOGI(TAG, "LCD ID: %02X %02X %02X", ID[0], ID[1], ID[2]);
// back to CMD_Page 0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
st7701->colmod_val,
}, 1), TAG, "send command failed");
ESP_LOGI(TAG, " st7701->madctl_val: 0x%x, st7701->colmod_val: 0x%x", st7701->madctl_val, st7701->colmod_val);
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init MIPI DPI panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
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_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->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;
}
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (sleep) {
command = LCD_CMD_SLPIN;
} else {
command = LCD_CMD_SLPOUT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
"io tx param failed");
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
#endif
@@ -0,0 +1,386 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_LCD_RGB_SUPPORTED
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_log.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
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 current value of LCD_CMD_COLMOD register
const st7701_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int mirror_by_cmd: 1;
unsigned int enable_io_multiplex: 1;
unsigned int display_on_off_use_cmd: 1;
unsigned int reset_level: 1;
} flags;
// To save the original functions of RGB panel
esp_err_t (*init)(esp_lcd_panel_t *panel);
esp_err_t (*del)(esp_lcd_panel_t *panel);
esp_err_t (*reset)(esp_lcd_panel_t *panel);
esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
} st7701_panel_t;
static const char *TAG = "st7701_rgb";
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
esp_err_t esp_lcd_new_panel_st7701_rgb(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");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
ESP_RETURN_ON_FALSE(!vendor_config->flags.enable_io_multiplex || !vendor_config->flags.mirror_by_cmd,
ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `enable_io_multiplex` cannot work together");
esp_err_t ret = ESP_OK;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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->rgb_ele_order) {
case LCD_RGB_ELEMENT_ORDER_RGB:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
break;
}
st7701->colmod_val = 0;
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x50;
break;
case 18: // RGB666
st7701->colmod_val = 0x60;
break;
case 24: // RGB888
st7701->colmod_val = 0x70;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
st7701->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
st7701->flags.enable_io_multiplex = vendor_config->flags.enable_io_multiplex;
st7701->flags.reset_level = panel_dev_config->flags.reset_active_high;
if (st7701->flags.enable_io_multiplex) {
if (st7701->reset_gpio_num >= 0) { // Perform hardware reset
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->flags.reset_level);
} else { // Perform software reset
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
}
vTaskDelay(pdMS_TO_TICKS(120));
/**
* In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
* (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
* `esp_lcd_new_rgb_panel()` is called.
*/
ESP_GOTO_ON_ERROR(panel_st7701_send_init_cmds(st7701), err, TAG, "send init commands failed");
// After sending the initialization commands, the 3-wire SPI interface can be deleted
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
st7701->io = NULL;
ESP_LOGD(TAG, "delete panel IO");
}
// Create RGB panel
ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
// Save the original functions of RGB panel
st7701->init = (*ret_panel)->init;
st7701->del = (*ret_panel)->del;
st7701->reset = (*ret_panel)->reset;
st7701->mirror = (*ret_panel)->mirror;
st7701->disp_on_off = (*ret_panel)->disp_on_off;
// Overwrite the functions of RGB panel
(*ret_panel)->init = panel_st7701_init;
(*ret_panel)->del = panel_st7701_del;
(*ret_panel)->reset = panel_st7701_reset;
(*ret_panel)->mirror = panel_st7701_mirror;
(*ret_panel)->disp_on_off = panel_st7701_disp_on_off;
(*ret_panel)->user_data = st7701;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(st7701);
}
return ret;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, (uint8_t []){0x3B, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x10, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x20, 0x06}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xB1, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x8D}, 1, 0},
{0xB1, (uint8_t []){0x48}, 1, 0},
{0xB2, (uint8_t []){0x89}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x49}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x32}, 1, 0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 100},
{0xE0, (uint8_t []){0x00, 0x00, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x05, 0xC0, 0x07, 0xC0, 0x04, 0xC0, 0x06, 0xC0, 0x00, 0x44, 0x44}, 11, 0},
{0xE2, (uint8_t []){0x00, 0x00, 0x33, 0x33, 0x01, 0xC0, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x0D, 0xF1, 0x10, 0x98, 0x0F, 0xF3, 0x10, 0x98, 0x09, 0xED, 0x10, 0x98, 0x0B, 0xEF, 0x10, 0x98}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x0C, 0xF0, 0x10, 0x98, 0x0E, 0xF2, 0x10, 0x98, 0x08, 0xEC, 0x10, 0x98, 0x0A, 0xEE, 0x10, 0x98}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x00}, 7, 0},
{0xED, (uint8_t []){0xFF, 0x04, 0x56, 0x7F, 0xBA, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xAB, 0xF7, 0x65, 0x40, 0xFF}, 16, 0},
{0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
};
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701)
{
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
bool is_cmd_overwritten = false;
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
// Set color format
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
st7701->madctl_val
}, 1), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t []) {
st7701->colmod_val
}, 1), TAG, "Write cmd failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
return ESP_OK;
}
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (!st7701->flags.enable_io_multiplex) {
ESP_RETURN_ON_ERROR(panel_st7701_send_init_cmds(st7701), TAG, "send init commands failed");
}
// Init RGB panel
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete RGB panel
st7701->del(panel);
free(st7701);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// Perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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));
}
// Reset RGB panel
ESP_RETURN_ON_ERROR(st7701->reset(panel), TAG, "reset RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
if (st7701->flags.mirror_by_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
} else {
// Control mirror through RGB panel
ESP_RETURN_ON_ERROR(st7701->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
}
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (st7701->flags.display_on_off_use_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control display on/off through LCD command
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");
} else {
// Control display on/off through display control signal
ESP_RETURN_ON_ERROR(st7701->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
}
return ESP_OK;
}
#endif
@@ -0,0 +1,183 @@
#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 "driver/i2c_master.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "Arduino.h"
#include "esp_lcd_st7701.h"
#include "st7701_lcd.h"
#define LCD_H_RES 480
#define LCD_V_RES 800
#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 (0)
#define EXAMPLE_PIN_NUM_BK_LIGHT GPIO_NUM_23
#define BSP_LCD_BACKLIGHT GPIO_NUM_23
#define LCD_LEDC_CH LEDC_CHANNEL_0
static const char *TAG = "example";
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
st7701_lcd::st7701_lcd(int8_t lcd_rst)
{
_lcd_rst = lcd_rst;
}
void st7701_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 st7701_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 st7701_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 st7701_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 = ST7701_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 = ST7701_PANEL_IO_DBI_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io_handle));
// 创建JD9365控制面板
esp_lcd_dpi_panel_config_t dpi_config = ST7701_480_360_PANEL_60HZ_DPI_CONFIG(MIPI_DPI_PX_FORMAT);
st7701_vendor_config_t vendor_config = {
// .init_cmds = lcd_cmd,
// .init_cmds_size = sizeof(lcd_cmd) / sizeof(st7701_lcd_init_cmd_t),
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
.flags = {
.use_mipi_interface = 1,
}
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_5,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
// if (dsi_cfg->flags.avoid_tearing) {
// cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback;
// } else {
// cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
// }
// /* Register done callback */
// esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv);
// 打开背光
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
}
void st7701_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 st7701_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 st7701_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 st7701_lcd::te_on()
{
esp_lcd_panel_io_tx_param(io_handle, 0x35,new (uint8_t[]){0x00}, 1);
}
void st7701_lcd::te_off()
{
esp_lcd_panel_io_tx_param(io_handle, 0x34,new (uint8_t[]){0x00}, 0);
}
uint16_t st7701_lcd::width()
{
return LCD_H_RES;
}
uint16_t st7701_lcd::height()
{
return LCD_V_RES;
}
void st7701_lcd::get_handle(bsp_lcd_handles_t *ret_handles)
{
ret_handles->io = io_handle;
ret_handles->mipi_dsi_bus = NULL;
ret_handles->panel = panel_handle;
ret_handles->control = NULL;
}
@@ -0,0 +1,36 @@
#ifndef _ST7701_LCD_H
#define _ST7701_LCD_H
#include <stdio.h>
#include "esp_lcd_types.h"
#include "esp_lcd_mipi_dsi.h"
typedef struct {
esp_lcd_dsi_bus_handle_t mipi_dsi_bus; /*!< MIPI DSI bus handle */
esp_lcd_panel_io_handle_t io; /*!< ESP LCD IO handle */
esp_lcd_panel_handle_t panel; /*!< ESP LCD panel (color) handle */
esp_lcd_panel_handle_t control; /*!< ESP LCD panel (control) handle */
} bsp_lcd_handles_t;
class st7701_lcd
{
public:
st7701_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();
void get_handle(bsp_lcd_handles_t *ret_handles);
private:
int8_t _lcd_rst;
};
#endif
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_lcd_touch.h"
static const char *TAG = "TP";
/*******************************************************************************
* Function definitions
*******************************************************************************/
/*******************************************************************************
* Local variables
*******************************************************************************/
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
assert(tp->read_data != NULL);
return tp->read_data(tp);
}
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
bool touched = false;
assert(tp != NULL);
assert(x != NULL);
assert(y != NULL);
assert(tp->get_xy != NULL);
touched = tp->get_xy(tp, x, y, strength, point_num, max_point_num);
if (!touched) {
return false;
}
/* Process coordinates by user */
if (tp->config.process_coordinates != NULL) {
tp->config.process_coordinates(tp, x, y, strength, point_num, max_point_num);
}
/* Software coordinates adjustment needed */
bool sw_adj_needed = ((tp->config.flags.mirror_x && (tp->set_mirror_x == NULL)) ||
(tp->config.flags.mirror_y && (tp->set_mirror_y == NULL)) ||
(tp->config.flags.swap_xy && (tp->set_swap_xy == NULL)));
/* Adjust all coordinates */
for (int i = 0; (sw_adj_needed && i < *point_num); i++) {
/* Mirror X coordinates (if not supported by HW) */
if (tp->config.flags.mirror_x && tp->set_mirror_x == NULL) {
x[i] = tp->config.x_max - x[i];
}
/* Mirror Y coordinates (if not supported by HW) */
if (tp->config.flags.mirror_y && tp->set_mirror_y == NULL) {
y[i] = tp->config.y_max - y[i];
}
/* Swap X and Y coordinates (if not supported by HW) */
if (tp->config.flags.swap_xy && tp->set_swap_xy == NULL) {
uint16_t tmp = x[i];
x[i] = y[i];
y[i] = tmp;
}
}
return touched;
}
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state)
{
assert(tp != NULL);
assert(state != NULL);
*state = 0;
if (tp->get_button_state) {
return tp->get_button_state(tp, n, state);
} else {
return ESP_ERR_NOT_SUPPORTED;
}
return ESP_OK;
}
#endif
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap)
{
assert(tp != NULL);
tp->config.flags.swap_xy = swap;
/* Is swap supported by HW? */
if (tp->set_swap_xy) {
return tp->set_swap_xy(tp, swap);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap)
{
assert(tp != NULL);
assert(swap != NULL);
/* Is swap supported by HW? */
if (tp->get_swap_xy) {
return tp->get_swap_xy(tp, swap);
} else {
*swap = tp->config.flags.swap_xy;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_x = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_x) {
return tp->set_mirror_x(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_x) {
return tp->get_mirror_x(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_x;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_y = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_y) {
return tp->set_mirror_y(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_y) {
return tp->get_mirror_y(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_y;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
if (tp->del != NULL) {
return tp->del(tp);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback)
{
esp_err_t ret = ESP_OK;
assert(tp != NULL);
/* Interrupt pin is not selected */
if (tp->config.int_gpio_num == GPIO_NUM_NC) {
return ESP_ERR_INVALID_ARG;
}
tp->config.interrupt_callback = callback;
if (callback != NULL) {
ret = gpio_install_isr_service(0);
/* ISR service can be installed from user before, then it returns invalid state */
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "GPIO ISR install failed");
return ret;
}
/* Add GPIO ISR handler */
ret = gpio_intr_enable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
ret = gpio_isr_handler_add(tp->config.int_gpio_num, (gpio_isr_t)tp->config.interrupt_callback, tp);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
} else {
/* Remove GPIO ISR handler */
ret = gpio_isr_handler_remove(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR remove handler failed");
ret = gpio_intr_disable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR disable failed");
}
return ESP_OK;
}
@@ -0,0 +1,370 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch
*/
#pragma once
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS (1)
#define CONFIG_ESP_LCD_TOUCH_MAX_POINTS (5)
/**
* @brief Touch controller type
*
*/
typedef struct esp_lcd_touch_s esp_lcd_touch_t;
typedef esp_lcd_touch_t *esp_lcd_touch_handle_t;
/**
* @brief Touch controller interrupt callback type
*
*/
typedef void (*esp_lcd_touch_interrupt_callback_t)(esp_lcd_touch_handle_t tp);
/**
* @brief Touch Configuration Type
*
*/
typedef struct {
uint16_t x_max; /*!< X coordinates max (for mirroring) */
uint16_t y_max; /*!< Y coordinates max (for mirroring) */
gpio_num_t rst_gpio_num; /*!< GPIO number of reset pin */
gpio_num_t int_gpio_num; /*!< GPIO number of interrupt pin */
struct {
unsigned int reset: 1; /*!< Level of reset pin in reset */
unsigned int interrupt: 1;/*!< Active Level of interrupt pin */
} levels;
struct {
unsigned int swap_xy: 1; /*!< Swap X and Y after read coordinates */
unsigned int mirror_x: 1; /*!< Mirror X after read coordinates */
unsigned int mirror_y: 1; /*!< Mirror Y after read coordinates */
} flags;
/*!< User callback called after get coordinates from touch controller for apply user adjusting */
void (*process_coordinates)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
/*!< User callback called after the touch interrupt occured */
esp_lcd_touch_interrupt_callback_t interrupt_callback;
} esp_lcd_touch_config_t;
typedef struct {
uint8_t points; /*!< Count of touch points saved */
struct {
uint16_t x; /*!< X coordinate */
uint16_t y; /*!< Y coordinate */
uint16_t strength; /*!< Strength */
} coords[CONFIG_ESP_LCD_TOUCH_MAX_POINTS];
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
uint8_t buttons; /*!< Count of buttons states saved */
struct {
uint8_t status; /*!< Status of button */
} button[CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS];
#endif
portMUX_TYPE lock; /*!< Lock for read/write */
} esp_lcd_touch_data_t;
/**
* @brief Declare of Touch Type
*
*/
struct esp_lcd_touch_s {
/**
* @brief Read data from touch controller (mandatory)
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*read_data)(esp_lcd_touch_handle_t tp);
/**
* @brief Get coordinates from touch controller (mandatory)
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of strengths
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool (*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);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state (optional)
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates (optional)
* If set, then not used SW swapping.
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_swap_xy)(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped (optional)
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_swap_xy)(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_x)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X (optional)
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_x)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_y)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y (optional)
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_y)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete Touch
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*del)(esp_lcd_touch_handle_t tp);
/**
* @brief Configuration structure
*/
esp_lcd_touch_config_t config;
/**
* @brief Communication interface
*/
esp_lcd_panel_io_handle_t io;
/**
* @brief Data structure
*/
esp_lcd_touch_data_t data;
};
/**
* @brief Read data from touch controller
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG parameter error
* - ESP_FAIL sending command error, slave hasn't ACK the transfer
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode
* - ESP_ERR_TIMEOUT operation timeout because the bus is busy
*/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp);
/**
* @brief Read coordinates from touch controller
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of the strengths (can be NULL)
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by controller
* - ESP_ERR_INVALID_ARG if bad button index
*/
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete touch (free all allocated memory and restart HW)
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp);
/**
* @brief Register user callback called after the touch interrupt occured
*
* @param tp: Touch handler
* @param callback: Interrupt callback
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,270 @@
/*
* 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);
/*******************************************************************************
* 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;
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*
}
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: GT911
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create a new GT911 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD/Touch panel IO handle
* @param config: Touch configuration
* @param out_touch: Touch instance handle
* @return
* - ESP_OK on success
* - ESP_ERR_NO_MEM if there is no memory for allocating main structure
*/
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);
/**
* @brief I2C address of the GT911 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS (0x5D)
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 16, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,103 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_lcd_touch_gt911.h"
#include "gt911_touch.h"
#define CONFIG_LCD_HRES 480
#define CONFIG_LCD_VRES 800
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));
i2c_master_bus_handle_t i2c_handle = NULL;
i2c_master_get_bus_handle(1,&i2c_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_LOGI(TAG, "Initialize touch IO (I2C)");
esp_lcd_new_panel_io_i2c(i2c_handle, &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,18 @@
#ifndef _GT911_TOUCH_H
#define _GT911_TOUCH_H
#include <stdio.h>
class gt911_touch
{
public:
gt911_touch(int8_t sda_pin, int8_t scl_pin, int8_t rst_pin = -1, int8_t int_pin = -1);
void begin();
bool getTouch(uint16_t *x, uint16_t *y);
void set_rotation(uint8_t r);
private:
int8_t _sda, _scl, _rst, _int;
};
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,8 @@
#此样例使用的屏幕是微雪的10.1寸屏幕,具体型号位:10.1-DSI-TOUCH-A
# 请使用arduino_esp32_v3.2.1版本
#lvgl v9.2.2
# 要将lvgl文件夹中的demos文件夹移动到同目录下的src文件夹中
@@ -0,0 +1,708 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_ops.h"
#if SOC_LCDCAM_RGB_LCD_SUPPORTED
#include "esp_lcd_panel_rgb.h"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_mipi_dsi.h"
#endif
#include "src/touch/esp_lcd_touch.h"
#include "esp_timer.h"
#include "esp_log.h"
#if CONFIG_IDF_TARGET_ESP32P4
#include "esp_private/esp_cache_private.h"
#include "driver/ppa.h"
#endif
#include "lvgl.h"
#include "lvgl_private.h"
#include "lvgl_port_v9.h"
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define BLOCK_SIZE_SMALL (32)
#define BLOCK_SIZE_LARGE (256)
static const char *TAG = "lv_port";
typedef struct {
esp_lcd_panel_handle_t lcd_handle;
esp_lcd_touch_handle_t tp_handle;
bool is_init;
} lvgl_port_task_param_t;
typedef esp_err_t (*get_lcd_frame_buffer_cb_t)(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...);
#if LVGL_PORT_PPA_ROTATION_ENABLE
static ppa_client_handle_t ppa_srm_handle = NULL;
static size_t data_cache_line_size = 0;
#endif
static SemaphoreHandle_t lvgl_mux; // LVGL mutex
static TaskHandle_t lvgl_task_handle = NULL;
static lvgl_port_interface_t lvgl_port_interface = LVGL_PORT_INTERFACE_RGB;
#if LVGL_PORT_AVOID_TEAR_ENABLE
static get_lcd_frame_buffer_cb_t lvgl_get_lcd_frame_buffer = NULL;
#endif
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
static void *get_next_frame_buffer(esp_lcd_panel_handle_t panel_handle)
{
static void *next_fb = NULL;
static void *fb[2] = { NULL };
if (next_fb == NULL) {
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 2, &fb[0], &fb[1]));
next_fb = fb[1];
} else {
next_fb = (next_fb == fb[0]) ? fb[1] : fb[0];
}
return next_fb;
}
#if !LVGL_PORT_PPA_ROTATION_ENABLE
static void rotate_image(const void *src, void *dst, int width, int height, int rotation, int bpp)
{
int bytes_per_pixel = bpp / 8;
int block_w = rotation == 90 || rotation == 270 ? BLOCK_SIZE_SMALL : BLOCK_SIZE_LARGE;
int block_h = rotation == 90 || rotation == 270 ? BLOCK_SIZE_LARGE : BLOCK_SIZE_SMALL;
for (int i = 0; i < height; i += block_h) {
int max_height = i + block_h > height ? height : i + block_h;
for (int j = 0; j < width; j += block_w) {
int max_width = j + block_w > width ? width : j + block_w;
for (int x = i; x < max_height; x++) {
for (int y = j; y < max_width; y++) {
void *src_pixel = (uint8_t *)src + (x * width + y) * bytes_per_pixel;
void *dst_pixel;
switch (rotation) {
case 270:
dst_pixel = (uint8_t *)dst + ((width - 1 - y) * height + x) * bytes_per_pixel;
break;
case 180:
dst_pixel = (uint8_t *)dst + ((height - 1 - x) * width + (width - 1 - y)) * bytes_per_pixel;
break;
case 90:
dst_pixel = (uint8_t *)dst + (y * height + (height - 1 - x)) * bytes_per_pixel;
break;
default:
return;
}
if (bpp == 16) {
*(uint16_t *)dst_pixel = *(uint16_t *)src_pixel;
} else if (bpp == 24) {
((uint8_t *)dst_pixel)[0] = ((uint8_t *)src_pixel)[0];
((uint8_t *)dst_pixel)[1] = ((uint8_t *)src_pixel)[1];
((uint8_t *)dst_pixel)[2] = ((uint8_t *)src_pixel)[2];
}
}
}
}
}
}
#endif
IRAM_ATTR static void rotate_copy_pixel(const uint16_t *from, uint16_t *to, uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotation)
{
#if LVGL_PORT_PPA_ROTATION_ENABLE
ppa_srm_rotation_angle_t ppa_rotation;
int x_offset = 0, y_offset = 0;
// Determine rotation settings once and reuse
switch (rotation) {
case 90:
ppa_rotation = PPA_SRM_ROTATION_ANGLE_270;
x_offset = h - y_end - 1;
y_offset = x_start;
break;
case 180:
ppa_rotation = PPA_SRM_ROTATION_ANGLE_180;
x_offset = w - x_end - 1;
y_offset = h - y_end - 1;
break;
case 270:
ppa_rotation = PPA_SRM_ROTATION_ANGLE_90;
x_offset = y_start;
y_offset = w - x_end - 1;
break;
default:
ppa_rotation = PPA_SRM_ROTATION_ANGLE_0;
break;
}
// Fill operation config for PPA rotation, without recalculating each time
ppa_srm_oper_config_t oper_config = {
.in.buffer = from,
.in.pic_w = w,
.in.pic_h = h,
.in.block_w = x_end - x_start + 1,
.in.block_h = y_end - y_start + 1,
.in.block_offset_x = x_start,
.in.block_offset_y = y_start,
.in.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,
.out.buffer = to,
.out.buffer_size = ALIGN_UP_BY(sizeof(lv_color_t) * w * h, data_cache_line_size),
.out.pic_w = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? h : w,
.out.pic_h = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? w : h,
.out.block_offset_x = x_offset,
.out.block_offset_y = y_offset,
.out.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565,
.rotation_angle = ppa_rotation,
.scale_x = 1.0,
.scale_y = 1.0,
.rgb_swap = 0,
.byte_swap = 0,
.mode = PPA_TRANS_MODE_BLOCKING,
};
ESP_ERROR_CHECK(ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config));
#else
// Fallback: optimized transpose for non-PPA systems
rotate_image(from, to, w, h, rotation, LV_COLOR_DEPTH);
#endif
}
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
#if LVGL_PORT_AVOID_TEAR_ENABLE
static void switch_lcd_frame_buffer_to(esp_lcd_panel_handle_t panel_handle, void *fb)
{
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LVGL_PORT_H_RES, LVGL_PORT_V_RES, fb);
}
#if LVGL_PORT_DIRECT_MODE
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
typedef struct {
uint16_t inv_p;
uint8_t inv_area_joined[LV_INV_BUF_SIZE];
lv_area_t inv_areas[LV_INV_BUF_SIZE];
} lv_port_dirty_area_t;
typedef enum {
FLUSH_STATUS_PART,
FLUSH_STATUS_FULL
} lv_port_flush_status_t;
typedef enum {
FLUSH_PROBE_PART_COPY,
FLUSH_PROBE_SKIP_COPY,
FLUSH_PROBE_FULL_COPY,
} lv_port_flush_probe_t;
static lv_port_dirty_area_t dirty_area;
static void flush_dirty_save(lv_port_dirty_area_t *dirty_area)
{
lv_disp_t *disp = lv_refr_get_disp_refreshing();
dirty_area->inv_p = disp->inv_p;
for (int i = 0; i < disp->inv_p; i++) {
dirty_area->inv_area_joined[i] = disp->inv_area_joined[i];
dirty_area->inv_areas[i] = disp->inv_areas[i];
}
}
/**
* @brief Probe dirty area to copy
*
* @note This function is used to avoid tearing effect, and only work with LVGL direct-mode.
*
*/
static lv_port_flush_probe_t flush_copy_probe(lv_display_t *disp)
{
static lv_port_flush_status_t prev_status = FLUSH_STATUS_PART;
lv_port_flush_status_t cur_status;
lv_port_flush_probe_t probe_result;
lv_disp_t *disp_refr = lv_refr_get_disp_refreshing();
uint32_t flush_ver = 0;
uint32_t flush_hor = 0;
for (int i = 0; i < disp_refr->inv_p; i++) {
if (disp_refr->inv_area_joined[i] == 0) {
flush_ver = (disp_refr->inv_areas[i].y2 + 1 - disp_refr->inv_areas[i].y1);
flush_hor = (disp_refr->inv_areas[i].x2 + 1 - disp_refr->inv_areas[i].x1);
break;
}
}
/* Check if the current full screen refreshes */
cur_status = ((flush_ver == disp->ver_res) && (flush_hor == disp->hor_res)) ? (FLUSH_STATUS_FULL) : (FLUSH_STATUS_PART);
if (prev_status == FLUSH_STATUS_FULL) {
if ((cur_status == FLUSH_STATUS_PART)) {
probe_result = FLUSH_PROBE_FULL_COPY;
} else {
probe_result = FLUSH_PROBE_SKIP_COPY;
}
} else {
probe_result = FLUSH_PROBE_PART_COPY;
}
prev_status = cur_status;
return probe_result;
}
static inline void *flush_get_next_buf(void *panel_handle)
{
return get_next_frame_buffer(panel_handle);
}
/**
* @brief Copy dirty area
*
* @note This function is used to avoid tearing effect, and only work with LVGL direct-mode.
*
*/
static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_area)
{
lv_coord_t x_start, x_end, y_start, y_end;
for (int i = 0; i < dirty_area->inv_p; i++) {
/* Refresh the unjoined areas*/
if (dirty_area->inv_area_joined[i] == 0) {
x_start = dirty_area->inv_areas[i].x1;
x_end = dirty_area->inv_areas[i].x2;
y_start = dirty_area->inv_areas[i].y1;
y_end = dirty_area->inv_areas[i].y2;
rotate_copy_pixel(src, dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
}
}
}
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
void *next_fb = NULL;
lv_port_flush_probe_t probe_result = FLUSH_PROBE_PART_COPY;
/* Action after last area refresh */
if (lv_disp_flush_is_last(disp)) {
/* Check if the `full_refresh` flag has been triggered */
if (disp->render_mode == LV_DISPLAY_RENDER_MODE_FULL) {
/* Reset flag */
disp->render_mode = LV_DISPLAY_RENDER_MODE_DIRECT;
// Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer
next_fb = flush_get_next_buf(panel_handle);
rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
/* Switch the current LCD frame buffer to `next_fb` */
switch_lcd_frame_buffer_to(panel_handle, next_fb);
/* Waiting for the current frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
/* Synchronously update the dirty area for another frame buffer */
flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area);
flush_get_next_buf(panel_handle);
} else {
/* Probe the copy method for the current dirty area */
probe_result = flush_copy_probe(disp);
if (probe_result == FLUSH_PROBE_FULL_COPY) {
/* Save current dirty area for next frame buffer */
flush_dirty_save(&dirty_area);
/* Set LVGL full-refresh flag and set flush ready in advance */
disp->render_mode = LV_DISPLAY_RENDER_MODE_FULL;
disp->rendering_in_progress = false;
lv_disp_flush_ready(disp);
/* Force to refresh whole screen, and will invoke `flush_callback` recursively */
lv_refr_now(lv_refr_get_disp_refreshing());
} else {
/* Update current dirty area for next frame buffer */
next_fb = flush_get_next_buf(panel_handle);
flush_dirty_save(&dirty_area);
flush_dirty_copy(next_fb, color_map, &dirty_area);
/* Switch the current LCD frame buffer to `next_fb` */
switch_lcd_frame_buffer_to(panel_handle, next_fb);
/* Waiting for the current frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (probe_result == FLUSH_PROBE_PART_COPY) {
/* Synchronously update the dirty area for another frame buffer */
flush_dirty_save(&dirty_area);
flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area);
flush_get_next_buf(panel_handle);
}
}
}
}
lv_disp_flush_ready(disp);
}
#else
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
/* Action after last area refresh */
if (lv_disp_flush_is_last(disp)) {
/* Switch the current LCD frame buffer to `color_map` */
switch_lcd_frame_buffer_to(panel_handle, color_map);
/* Waiting for the last frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
lv_disp_flush_ready(disp);
}
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_BUFFER_NUMS == 2
static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
/* Switch the current LCD frame buffer to `color_map` */
switch_lcd_frame_buffer_to(panel_handle, color_map);
/* Waiting for the last frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
lv_disp_flush_ready(disp);
}
#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_BUFFER_NUMS == 3
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0
static void *lvgl_port_rgb_last_buf = NULL;
static void *lvgl_port_rgb_next_buf = NULL;
static void *lvgl_port_flush_next_buf = NULL;
#endif
void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
void *next_fb = get_next_frame_buffer(panel_handle);
/* Rotate and copy dirty area from the current LVGL's buffer to the next LCD frame buffer */
rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE);
/* Switch the current LCD frame buffer to `next_fb` */
switch_lcd_frame_buffer_to(panel_handle, next_fb);
#else
if (disp->buf_act == disp->buf_1) {
disp->buf_2->data = lvgl_port_flush_next_buf;
} else {
disp->buf_1->data = lvgl_port_flush_next_buf;
}
lvgl_port_flush_next_buf = color_map;
/* Switch the current LCD frame buffer to `color_map` */
switch_lcd_frame_buffer_to(panel_handle, color_map);
lvgl_port_rgb_next_buf = color_map;
#endif
lv_disp_flush_ready(disp);
}
#endif
#else
void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
/* Just copy data from the color map to the LCD frame buffer */
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
if (lvgl_port_interface != LVGL_PORT_INTERFACE_MIPI_DSI_DMA) {
lv_disp_flush_ready(disp);
}
}
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
{
#if LVGL_PORT_PPA_ROTATION_ENABLE
// Initialize the PPA
ppa_client_config_t ppa_srm_config = {
.oper_type = PPA_OPERATION_SRM,
};
ESP_ERROR_CHECK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle));
ESP_ERROR_CHECK(esp_cache_get_alignment(MALLOC_CAP_DMA|MALLOC_CAP_SPIRAM, &data_cache_line_size));
#endif
assert(panel_handle);
// alloc draw buffers used by LVGL
void *buf1 = NULL;
void *buf2 = NULL;
int buffer_size = 0;
ESP_LOGD(TAG, "Malloc memory for LVGL buffer");
#if LVGL_PORT_AVOID_TEAR_ENABLE
// To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for RGB output
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
#if (LVGL_PORT_LCD_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0) && LVGL_PORT_FULL_REFRESH
// With the usage of three buffers and full-refresh, we always have one buffer available for rendering, eliminating the need to wait for the RGB's sync signal
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 3, &lvgl_port_rgb_last_buf, &buf1, &buf2));
lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf;
lvgl_port_flush_next_buf = buf2;
#elif (LVGL_PORT_LCD_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0)
// Here we are using three frame buffers, one for LVGL rendering, and the other two for RGB driver (one of them is used for rotation)
void *fbs[3];
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 3, &fbs[0], &fbs[1], &fbs[2]));
buf1 = fbs[2];
#else
ESP_ERROR_CHECK(lvgl_get_lcd_frame_buffer(panel_handle, 2, &buf1, &buf2));
#endif
#else
// Normmaly, for RGB LCD, we just use one buffer for LVGL rendering
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_BUFFER_HEIGHT;
buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS);
// buffer_size = LVGL_PORT_H_RES * LVGL_PORT_BUFFER_HEIGHT;
// buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
ESP_LOGI(TAG, "LVGL buffer size: %dKB", buffer_size * sizeof(lv_color_t) / 1024);
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
ESP_LOGD(TAG, "Register display driver to LVGL");
lv_display_t *display = lv_display_create(
#if (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 90) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 270)
LVGL_PORT_H_RES, LVGL_PORT_V_RES
#else
LVGL_PORT_V_RES, LVGL_PORT_H_RES
#endif
);
lv_display_set_buffers(
display, buf1, buf2, buffer_size * sizeof(lv_color_t),
#if LVGL_PORT_FULL_REFRESH
LV_DISPLAY_RENDER_MODE_FULL
#elif LVGL_PORT_DIRECT_MODE
LV_DISPLAY_RENDER_MODE_DIRECT
#else
LV_DISPLAY_RENDER_MODE_PARTIAL
#endif
);
lv_display_set_flush_cb(display, flush_callback);
lv_display_set_user_data(display, panel_handle);
return display;
}
static void touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)lv_indev_get_user_data(indev_drv);
assert(tp);
uint16_t touchpad_x;
uint16_t touchpad_y;
uint8_t touchpad_cnt = 0;
/* Read data from touch controller into memory */
esp_lcd_touch_read_data(tp);
/* Read data from touch controller */
bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, &touchpad_x, &touchpad_y, NULL, &touchpad_cnt, 1);
if (touchpad_pressed && touchpad_cnt > 0) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGD(TAG, "Touch position: %d,%d", touchpad_x, touchpad_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
static lv_indev_t *indev_init(esp_lcd_touch_handle_t tp)
{
assert(tp);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*See below.*/
lv_indev_set_user_data(indev, tp);
lv_indev_set_read_cb(indev, touchpad_read); /*See below.*/
return indev;
}
static void tick_increment(void *arg)
{
/* Tell LVGL how many milliseconds have elapsed */
lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS);
}
static esp_err_t tick_init(void)
{
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &tick_increment,
.name = "LVGL tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
return esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000);
}
static void lvgl_port_task(void *arg)
{
ESP_LOGD(TAG, "Starting LVGL task");
lvgl_port_task_param_t *param = (lvgl_port_task_param_t *)arg;
lv_init();
ESP_ERROR_CHECK(tick_init());
lv_display_t *disp = display_init(param->lcd_handle);
assert(disp);
if (param->tp_handle) {
lv_indev_t *indev = indev_init(param->tp_handle);
assert(indev);
#if EXAMPLE_LVGL_PORT_ROTATION_90
esp_lcd_touch_set_swap_xy(param->tp_handle, true);
esp_lcd_touch_set_mirror_x(param->tp_handle, true);
#elif EXAMPLE_LVGL_PORT_ROTATION_180
esp_lcd_touch_set_mirror_x(param->tp_handle, false);
esp_lcd_touch_set_mirror_y(param->tp_handle, false);
#elif EXAMPLE_LVGL_PORT_ROTATION_270
esp_lcd_touch_set_swap_xy(param->tp_handle, true);
esp_lcd_touch_set_mirror_y(param->tp_handle, false);
#endif
}
param->is_init = true;
uint32_t task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS;
while (1) {
if (lvgl_port_lock(-1)) {
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
}
if (task_delay_ms > LVGL_PORT_TASK_MAX_DELAY_MS) {
task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < LVGL_PORT_TASK_MIN_DELAY_MS) {
task_delay_ms = LVGL_PORT_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle, lvgl_port_interface_t interface)
{
lvgl_port_task_param_t lvgl_task_param = {
.lcd_handle = lcd_handle,
.tp_handle = tp_handle,
.is_init = false
};
lvgl_port_interface = interface;
#if LVGL_PORT_AVOID_TEAR_ENABLE
switch (interface) {
#if SOC_LCDCAM_RGB_LCD_SUPPORTED
case LVGL_PORT_INTERFACE_RGB:
lvgl_get_lcd_frame_buffer = esp_lcd_rgb_panel_get_frame_buffer;
break;
#endif
#if SOC_MIPI_DSI_SUPPORTED
case LVGL_PORT_INTERFACE_MIPI_DSI_DMA:
case LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA:
lvgl_get_lcd_frame_buffer = esp_lcd_dpi_panel_get_frame_buffer;
break;
#endif
default:
ESP_LOGE(TAG, "Invalid interface type");
return ESP_ERR_INVALID_ARG;
}
#endif
lvgl_mux = xSemaphoreCreateRecursiveMutex();
assert(lvgl_mux);
ESP_LOGI(TAG, "Create LVGL task");
BaseType_t core_id = (LVGL_PORT_TASK_CORE < 0) ? tskNO_AFFINITY : LVGL_PORT_TASK_CORE;
BaseType_t ret = xTaskCreatePinnedToCore(lvgl_port_task, "lvgl", LVGL_PORT_TASK_STACK_SIZE, &lvgl_task_param,
LVGL_PORT_TASK_PRIORITY, &lvgl_task_handle, core_id);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create LVGL task");
return ESP_FAIL;
}
while (!lvgl_task_param.is_init) {
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK;
}
bool lvgl_port_lock(int timeout_ms)
{
assert(lvgl_mux && "lvgl_port_init must be called first");
const TickType_t timeout_ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE;
}
void lvgl_port_unlock(void)
{
assert(lvgl_mux && "lvgl_port_init must be called first");
xSemaphoreGiveRecursive(lvgl_mux);
}
bool lvgl_port_notify_lcd_vsync(void)
{
BaseType_t need_yield = pdFALSE;
#if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0)
if (lvgl_port_rgb_next_buf != lvgl_port_rgb_last_buf) {
lvgl_port_flush_next_buf = lvgl_port_rgb_last_buf;
lvgl_port_rgb_last_buf = lvgl_port_rgb_next_buf;
}
#elif LVGL_PORT_AVOID_TEAR_ENABLE
// Notify that the current LCD frame buffer has been transmitted
if (lvgl_task_handle) {
xTaskNotifyFromISR(lvgl_task_handle, ULONG_MAX, eNoAction, &need_yield);
}
#else
if (lvgl_port_interface == LVGL_PORT_INTERFACE_MIPI_DSI_DMA) {
lv_display_t *disp = lv_disp_get_default();
lv_disp_flush_ready(disp);
}
#endif
return (need_yield == pdTRUE);
}
@@ -0,0 +1,181 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "src/touch/esp_lcd_touch.h"
#include "lvgl.h"
#include "pins_config.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LVGL port interface type
*
*/
typedef enum {
LVGL_PORT_INTERFACE_RGB,
LVGL_PORT_INTERFACE_MIPI_DSI_DMA,
LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA,
LVGL_PORT_INTERFACE_MAX,
} lvgl_port_interface_t;
/**
* LVGL related parameters, can be adjusted by users
*
*/
#define LVGL_PORT_H_RES (480)
#define LVGL_PORT_V_RES (800)
#define LVGL_PORT_TICK_PERIOD_MS (EXAMPLE_LVGL_PORT_TICK)
/**
* LVGL timer handle task related parameters, can be adjusted by users
*
*/
#define LVGL_PORT_TASK_MAX_DELAY_MS (EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS) // The maximum delay of the LVGL timer task, in milliseconds
#define LVGL_PORT_TASK_MIN_DELAY_MS (EXAMPLE_LVGL_PORT_TASK_MIN_DELAY_MS) // The minimum delay of the LVGL timer task, in milliseconds
#define LVGL_PORT_TASK_STACK_SIZE (EXAMPLE_LVGL_PORT_TASK_STACK_SIZE_KB * 1024) // The stack size of the LVGL timer task, in bytes
#define LVGL_PORT_TASK_PRIORITY (EXAMPLE_LVGL_PORT_TASK_PRIORITY) // The priority of the LVGL timer task
#define LVGL_PORT_TASK_CORE (EXAMPLE_LVGL_PORT_TASK_CORE) // The core of the LVGL timer task,
// `-1` means the don't specify the core
/**
*
* LVGL buffer related parameters, can be adjusted by users:
* (These parameters will be useless if the avoid tearing function is enabled)
*
* - Memory type for buffer allocation:
* - MALLOC_CAP_SPIRAM: Allocate LVGL buffer in PSRAM
* - MALLOC_CAP_INTERNAL: Allocate LVGL buffer in SRAM
* (The SRAM is faster than PSRAM, but the PSRAM has a larger capacity)
*
*/
#if CONFIG_EXAMPLE_LVGL_PORT_BUF_PSRAM
#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_SPIRAM)
#elif CONFIG_EXAMPLE_LVGL_PORT_BUF_INTERNAL
#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#endif
#define LVGL_PORT_BUFFER_HEIGHT (CONFIG_EXAMPLE_LVGL_PORT_BUF_HEIGHT)
/**
* Avoid tering related configurations, can be adjusted by users.
*
*/
#define LVGL_PORT_AVOID_TEAR_ENABLE (EXAMPLE_LVGL_PORT_AVOID_TEAR_ENABLE) // Set to 1 to enable
#if LVGL_PORT_AVOID_TEAR_ENABLE
/**
* Set the avoid tearing mode:
* - 0: Disable avoid tearing function
* - 1: LCD double-buffer & LVGL full-refresh
* - 2: LCD triple-buffer & LVGL full-refresh
* - 3: LCD double-buffer & LVGL direct-mode (recommended)
*
*/
#define LVGL_PORT_AVOID_TEAR_MODE (EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE)
/**
* Set the PPA rotation enable:
* - 0: Disable PPA rotation
* - 1: Enable PPA rotation
*
*/
#define LVGL_PORT_PPA_ROTATION_ENABLE (EXAMPLE_LVGL_PORT_PPA_ROTATION_ENABLE)
/**
* Set the rotation degree of the LCD panel when the avoid tearing function is enabled:
* - 0: 0 degree
* - 90: 90 degree
* - 180: 180 degree
* - 270: 270 degree
*
*/
#define EXAMPLE_LVGL_PORT_ROTATION_DEGREE (EXAMPLE_LVGL_PORT_ROTATION_DEGREE_)
/**
* Below configurations are automatically set according to the above configurations, users do not need to modify them.
*
*/
#if LVGL_PORT_AVOID_TEAR_MODE == 1
#define LVGL_PORT_LCD_BUFFER_NUMS (2)
#define LVGL_PORT_FULL_REFRESH (1)
#elif LVGL_PORT_AVOID_TEAR_MODE == 2
#define LVGL_PORT_LCD_BUFFER_NUMS (3)
#define LVGL_PORT_FULL_REFRESH (1)
#elif LVGL_PORT_AVOID_TEAR_MODE == 3
#define LVGL_PORT_LCD_BUFFER_NUMS (2)
#define LVGL_PORT_DIRECT_MODE (1)
#endif /* LVGL_PORT_AVOID_TEAR_MODE */
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0
#define EXAMPLE_LVGL_PORT_ROTATION_0 (1)
#else
#if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 90
#define EXAMPLE_LVGL_PORT_ROTATION_90 (1)
#elif EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 180
#define EXAMPLE_LVGL_PORT_ROTATION_180 (1)
#elif EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 270
#define EXAMPLE_LVGL_PORT_ROTATION_270 (1)
#endif
#ifdef LVGL_PORT_LCD_BUFFER_NUMS
#undef LVGL_PORT_LCD_BUFFER_NUMS
#define LVGL_PORT_LCD_BUFFER_NUMS (3)
#endif
#endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */
#else
#define LVGL_PORT_LCD_BUFFER_NUMS (1)
#define LVGL_PORT_FULL_REFRESH (0)
#define LVGL_PORT_DIRECT_MODE (0)
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
/**
* @brief Initialize LVGL port
*
* @param[in] lcd_handle: LCD panel handle
* @param[in] tp_handle: Touch panel handle
*
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - Others: Fail
*/
esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle, lvgl_port_interface_t interface);
/**
* @brief Take LVGL mutex
*
* @param[in] timeout_ms: Timeout in [ms]. 0 will block indefinitely.
*
* @return
* - true: Mutex was taken
* - false: Mutex was NOT taken
*/
bool lvgl_port_lock(int timeout_ms);
/**
* @brief Give LVGL mutex
*
*/
void lvgl_port_unlock(void);
/**
* @brief Notifies the LVGL task when the transmission of the RGB frame buffer is completed.
*
* @return
* - true: The tasks need to be re-scheduled
* - false: The tasks don't need to be re-scheduled
*/
bool lvgl_port_notify_lcd_vsync(void);
void lvgl_sw_rotation_main(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,206 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c_master.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_heap_caps.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 "esp_cache.h"
#include "esp_heap_caps.h"
#include "esp_private/esp_cache_private.h"
#include "src/touch/esp_lcd_touch_gt911.h"
#include "src/lcd/esp_lcd_st7701.h"
#include "lvgl_port_v9.h"
#include "demos/lv_demos.h"
#include "driver/ppa.h"
#define TAG "main"
#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3) // LDO_VO3 is connected to VDD_MIPI_DPHY
#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
#define BSP_LCD_DPI_BUFFER_NUMS (1)
#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)
i2c_master_bus_handle_t i2c_handle = NULL;
#define BSP_LCD_BACKLIGHT GPIO_NUM_23
#define LCD_LEDC_CH LEDC_CHANNEL_0
static esp_err_t bsp_display_brightness_init(void)
{
// Setup LEDC peripheral for PWM backlight control
const ledc_channel_config_t LCD_backlight_channel = {
.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 LCD_backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = 1,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&LCD_backlight_timer));
ESP_ERROR_CHECK(ledc_channel_config(&LCD_backlight_channel));
return ESP_OK;
}
static esp_err_t bsp_display_brightness_set(int brightness_percent)
{
if (brightness_percent > 100) {
brightness_percent = 100;
}
if (brightness_percent < 0) {
brightness_percent = 0;
}
ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH));
return ESP_OK;
}
static esp_err_t bsp_display_backlight_off(void)
{
return bsp_display_brightness_set(0);
}
static esp_err_t bsp_display_backlight_on(void)
{
return bsp_display_brightness_set(100);
}
IRAM_ATTR static bool mipi_dsi_lcd_on_vsync_event(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
return lvgl_port_notify_lcd_vsync();
}
void lvgl_sw_rotation_main(void)
{
bsp_display_brightness_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, &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_LOGI(TAG, "MIPI DSI PHY Powered on");
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_LOGI(TAG, "Install MIPI DSI LCD control panel");
// we use DBI interface to send LCD commands and parameters
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);
esp_lcd_dpi_panel_event_callbacks_t cbs = {
#if LVGL_PORT_AVOID_TEAR_MODE
.on_refresh_done = mipi_dsi_lcd_on_vsync_event,
#else
.on_color_trans_done = mipi_dsi_lcd_on_vsync_event,
#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(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, // Shared with LCD reset
.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;
ESP_LOGI(TAG,"interface is %d",interface);
ESP_ERROR_CHECK(lvgl_port_init(disp_panel, tp_handle, interface));
bsp_display_brightness_set(100);
if(lvgl_port_lock(-1))
{
// lv_demo_music();
// lv_demo_benchmark();
lv_demo_widgets();
lvgl_port_unlock();
}
}
@@ -0,0 +1,21 @@
#pragma GCC push_options
#pragma GCC optimize("O3")
#include <Arduino.h>
#include "lvgl.h"
#include "demos/lv_demos.h"
#include "pins_config.h"
#include "lvgl_port_v9.h"
void setup()
{
Serial.begin(115200);
Serial.println("ESP32P4 MIPI DSI LVGL");
lvgl_sw_rotation_main();
}
void loop()
{
}
@@ -0,0 +1,31 @@
#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 6 //KB
#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
#define EXAMPLE_LVGL_PORT_AVOID_TEAR_MODE 3 //range 1 to 3
#define EXAMPLE_LVGL_PORT_ROTATION_DEGREE_ 90 // 0,90,180 or 270
#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
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_types.h"
#include "esp_lcd_st7701_interface.h"
#include "esp_lcd_st7701.h"
static const char *TAG = "st7701";
esp_err_t esp_lcd_new_panel_st7701(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, "version: %d.%d.%d", ESP_LCD_ST7701_VER_MAJOR, ESP_LCD_ST7701_VER_MINOR, ESP_LCD_ST7701_VER_PATCH);
ESP_RETURN_ON_FALSE(panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config, ESP_ERR_INVALID_ARG, TAG, "`vendor_config` is necessary");
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
#if SOC_LCD_RGB_SUPPORTED
if (!vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_rgb(io, panel_dev_config, ret_panel);
}
#endif
#if SOC_MIPI_DSI_SUPPORTED
if (vendor_config->flags.use_mipi_interface) {
ret = esp_lcd_new_panel_st7701_mipi(io, panel_dev_config, ret_panel);
}
#endif
return ret;
}
@@ -0,0 +1,199 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "hal/lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#if SOC_LCD_RGB_SUPPORTED
#include "esp_lcd_panel_rgb.h"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_mipi_dsi.h"
#endif
#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 */
} st7701_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 st7701_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 */
union {
#if SOC_LCD_RGB_SUPPORTED
const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
#endif
#if SOC_MIPI_DSI_SUPPORTED
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;
#endif
};
struct {
unsigned int use_mipi_interface: 1; /*<! Set to 1 if using MIPI interface, default is RGB interface */
unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
* Otherwise, the function will be implemented by software.
* This flag is only valid for the RGB interface.
*/
union {
unsigned int auto_del_panel_io: 1;
unsigned int enable_io_multiplex: 1;
}; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
* If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
* Please set it to 1 to release the panel IO and its pins (except CS signal).
* This flag is only valid for the RGB interface.
*/
} flags;
} st7701_vendor_config_t;
/**
* @brief Create LCD panel for model ST7701
*
* @note When `enable_io_multiplex` is set to 1, this function will first initialize the ST7701 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
* @note When `enable_io_multiplex` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the ST7701 and RGB.
* @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 (`vendor_config` and `rgb_config` are necessary)
* @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_st7701(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 3-wire SPI panel IO configuration structure
*
* @param[in] line_cfg SPI line configuration
* @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
*
*/
#define ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
{ \
.line_config = line_cfg, \
.expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
.spi_mode = scl_active_edge ? 1 : 0, \
.lcd_cmd_bytes = 1, \
.lcd_param_bytes = 1, \
.flags = { \
.use_dc_bit = 1, \
.dc_zero_on_data = 0, \
.lsb_first = 0, \
.cs_high_active = 0, \
.del_keep_cs_inactive = 1, \
}, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Default Configuration Macros for RGB Interface /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief RGB timing structure
*
* @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
* / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
* / bits_per_pixel
*
*/
#define ST7701_480_480_PANEL_60HZ_RGB_TIMING() \
{ \
.pclk_hz = 16 * 1000 * 1000, \
.h_res = 480, \
.v_res = 480, \
.hsync_pulse_width = 10, \
.hsync_back_porch = 10, \
.hsync_front_porch = 20, \
.vsync_pulse_width = 10, \
.vsync_back_porch = 10, \
.vsync_front_porch = 10, \
.flags.pclk_active_neg = false, \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// Default Configuration Macros for MIPI-DSI Interface //////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief MIPI-DSI bus configuration structure
*/
#define ST7701_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 = 500, \
}
/**
* @brief MIPI-DBI panel IO configuration structure
*
*/
#define ST7701_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 ST7701_480_360_PANEL_60HZ_DPI_CONFIG(px_format) \
{ \
.virtual_channel = 0, \
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
.dpi_clock_freq_mhz = 34, \
.pixel_format = px_format, \
.num_fbs = 1, \
.video_timing = { \
.h_size = 480, \
.v_size = 800, \
.hsync_pulse_width = 12, \
.hsync_back_porch = 42, \
.hsync_front_porch = 42, \
.vsync_pulse_width = 2, \
.vsync_back_porch = 8, \
.vsync_front_porch = 166, \
}, \
.flags = {.use_dma2d = true,} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "esp_lcd_panel_vendor.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ST7701_CMD_SDIR (0xC7)
#define ST7701_CMD_SS_BIT (1 << 2)
#define ST7701_CMD_CND2BKxSEL (0xFF)
#define ST7701_CMD_BKxSEL_BYTE0 (0x77)
#define ST7701_CMD_BKxSEL_BYTE1 (0x01)
#define ST7701_CMD_BKxSEL_BYTE2 (0x00)
#define ST7701_CMD_BKxSEL_BYTE3 (0x00)
#define ST7701_CMD_CN2_BIT (1 << 4)
#define ST7701_CMD_BKxSEL_BK0 (0x00)
#if SOC_LCD_RGB_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with RGB interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_rgb(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);
#endif
#if SOC_MIPI_DSI_SUPPORTED
/**
* @brief Initialize ST7701 LCD panel with MIPI interface
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config LCD panel device configuration
* @param[out] ret_panel LCD panel handle
* @return
* - ESP_OK: Success
* - Otherwise: Fail
*/
esp_err_t esp_lcd_new_panel_st7701_mipi(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);
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,478 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_MIPI_DSI_SUPPORTED
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
static const char *TAG = "st7701_mipi";
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep);
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 st7701_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);
} st7701_panel_t;
esp_err_t esp_lcd_new_panel_st7701_mipi(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 argument");
st7701_vendor_config_t *vendor_config = (st7701_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;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order");
break;
}
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x55;
break;
case 18: // RGB666
st7701->colmod_val = 0x66;
break;
case 24: // RGB888
st7701->colmod_val = 0x77;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->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
st7701->del = panel_handle->del;
st7701->init = panel_handle->init;
// Overwrite the functions of MIPI DPI panel
panel_handle->del = panel_st7701_del;
panel_handle->init = panel_st7701_init;
panel_handle->reset = panel_st7701_reset;
panel_handle->mirror = panel_st7701_mirror;
panel_handle->invert_color = panel_st7701_invert_color;
panel_handle->disp_on_off = panel_st7701_disp_on_off;
panel_handle->disp_sleep = panel_st7701_sleep;
panel_handle->user_data = st7701;
*ret_panel = panel_handle;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
}
return ret;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete MIPI DPI panel
st7701->del(panel);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
free(st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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(20)); // spec, wait at least 5ms before sending new command
}
return ESP_OK;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
// {0xC0, (uint8_t []){0x2c, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x10, 0x0C}, 2, 0},
// {0xC2, (uint8_t []){0x21, 0x0A}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x00, 0x0B, 0x12, 0x0D, 0x10, 0x06, 0x02, 0x08, 0x07, 0x1F, 0x04, 0x11, 0x0F, 0x29, 0x31, 0x1E}, 16, 0},
// {0xB1, (uint8_t []){0x00, 0x0B, 0x13, 0x0D, 0x11, 0x06, 0x03, 0x08, 0x07, 0x20, 0x04, 0x12, 0x11, 0x29, 0x31, 0x1E}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x72}, 1, 0},
// {0xB2, (uint8_t []){0x84}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4D}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x20}, 1, 0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x80, 0x00, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x05, 0x00, 0x07, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x33, 0x33}, 11, 0},
// {0xE2, (uint8_t []){0x00, 0x00, 0x30, 0x30, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 12, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x0C, 0x78, 0x00, 0xE0, 0x0E, 0x7A, 0x00, 0xE0, 0x08, 0x74, 0x00, 0xE0, 0x0A, 0x76, 0x00, 0xE0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x0D, 0x79, 0x00, 0xE0, 0x0F, 0x7B, 0x00, 0xE0, 0x09, 0x75, 0x00, 0xE0, 0x0B, 0x77, 0x00, 0xE0}, 16, 0},
// {0xE9, (uint8_t []){0x36, 0x00}, 2, 0},
// {0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x40}, 7, 0},
// {0xED, (uint8_t []){0xA1, 0xC2, 0xFB, 0x0F, 0x67, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x76, 0xF0, 0xBF, 0x2C, 0x1A}, 16, 0},
// {0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
// {0xE8, (uint8_t []){0x00, 0x0E}, 2, 0},
// {0xE8, (uint8_t []){0x00, 0x0C}, 2, 20},
// {0xE8, (uint8_t []){0x00, 0x00}, 2, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x11, (uint8_t []){0x00}, 0, 120},
// {0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
/*
*/
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
{0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x10, 0x08}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x80, 0x09, 0x53, 0x0C, 0xD0, 0x07, 0x0C, 0x09, 0x09, 0x28, 0x06, 0xD4, 0x13, 0x69, 0x2B, 0x71}, 16, 0},
{0xB1, (uint8_t []){0x80, 0x94, 0x5A, 0x10, 0xD3, 0x06, 0x0A, 0x08, 0x08, 0x25, 0x03, 0xD3, 0x12, 0x66, 0x6A, 0x0D}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x5D}, 1, 0},
{0xB1, (uint8_t []){0x58}, 1, 0},
{0xB2, (uint8_t []){0x87}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x4E}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x21}, 1, 0},
{0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
{0xBB, (uint8_t []){0x03}, 1,0},
{0xBC, (uint8_t []){0x00}, 1,0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 0},
{0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
{0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
{0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
{0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
{0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// {0x3A, (uint8_t []){0x66}, 1, 0},
{0x11, (uint8_t []){0x00}, 1, 120},
{0x29, (uint8_t []){0x00}, 1, 20},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x13},5,0},
// {0xEF, (uint8_t []){0x08}, 1, 0},
// {0xFF, (uint8_t []){0x77,0x01,0x00,0x00,0x10},5,0},
// {0xC0, (uint8_t []){0x63, 0x00}, 2, 0},
// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0},
// {0xC2, (uint8_t []){0x17, 0x08}, 2, 0},
// {0xCC, (uint8_t []){0x10}, 1, 0},
// {0xB0, (uint8_t []){0x40, 0xC9, 0x94, 0x0E, 0x10, 0x05, 0x0B, 0x09, 0x08, 0x26, 0x04, 0x52, 0x10, 0x69, 0x6B, 0x69}, 16, 0},
// {0xB1, (uint8_t []){0x40, 0xD2, 0x98, 0x0C, 0x92, 0x07, 0x09, 0x08, 0x07, 0x25, 0x02, 0x0E, 0x1C, 0x6E, 0x78, 0x55}, 16, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
// {0xB0, (uint8_t []){0x5D}, 1, 0},
// {0xB1, (uint8_t []){0x4E}, 1, 0},
// {0xB2, (uint8_t []){0x87}, 1, 0},
// {0xB3, (uint8_t []){0x80}, 1, 0},
// {0xB5, (uint8_t []){0x4E}, 1, 0},
// {0xB7, (uint8_t []){0x85}, 1, 0},
// {0xB8, (uint8_t []){0x21}, 1, 0},
// {0xB9, (uint8_t []){0x10, 0x1F}, 2, 0},
// {0xBB, (uint8_t []){0x03}, 1,0},
// {0xBC, (uint8_t []){0x00}, 1,0},
// {0xC1, (uint8_t []){0x78}, 1, 0},
// {0xC2, (uint8_t []){0x78}, 1, 0},
// {0xD0, (uint8_t []){0x88}, 1, 0},
// {0xE0, (uint8_t []){0x00, 0x3A, 0x02}, 3, 0},
// {0xE1, (uint8_t []){0x04, 0xA0, 0x00, 0xA0, 0x05,0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
// {0xE2, (uint8_t []){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
// {0xE3, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE5, (uint8_t []){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
// {0xE6, (uint8_t []){0x00, 0x00, 0x33, 0x33}, 4, 0},
// {0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
// {0xE8, (uint8_t []){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
// {0xEB, (uint8_t []){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
// {0xEC, (uint8_t []){0x08, 0x01}, 2, 0},
// {0xED, (uint8_t []){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
// {0xEF, (uint8_t []){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
// // {0x3A, (uint8_t []){0x66}, 1, 0},
// {0x11, (uint8_t []){0x00}, 1, 120},
// {0x29, (uint8_t []){0x00}, 1, 20},
};
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
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_LOGI(TAG, "LCD ID: %02X %02X %02X", ID[0], ID[1], ID[2]);
// back to CMD_Page 0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
st7701->colmod_val,
}, 1), TAG, "send command failed");
ESP_LOGI(TAG, " st7701->madctl_val: 0x%x, st7701->colmod_val: 0x%x", st7701->madctl_val, st7701->colmod_val);
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init MIPI DPI panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
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_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->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;
}
static esp_err_t panel_st7701_sleep(esp_lcd_panel_t *panel, bool sleep)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (sleep) {
command = LCD_CMD_SLPIN;
} else {
command = LCD_CMD_SLPOUT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
"io tx param failed");
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
#endif
@@ -0,0 +1,386 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_LCD_RGB_SUPPORTED
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_log.h"
#include "esp_lcd_st7701.h"
#include "esp_lcd_st7701_interface.h"
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 current value of LCD_CMD_COLMOD register
const st7701_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
struct {
unsigned int mirror_by_cmd: 1;
unsigned int enable_io_multiplex: 1;
unsigned int display_on_off_use_cmd: 1;
unsigned int reset_level: 1;
} flags;
// To save the original functions of RGB panel
esp_err_t (*init)(esp_lcd_panel_t *panel);
esp_err_t (*del)(esp_lcd_panel_t *panel);
esp_err_t (*reset)(esp_lcd_panel_t *panel);
esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
} st7701_panel_t;
static const char *TAG = "st7701_rgb";
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701);
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool off);
esp_err_t esp_lcd_new_panel_st7701_rgb(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");
st7701_vendor_config_t *vendor_config = (st7701_vendor_config_t *)panel_dev_config->vendor_config;
ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
ESP_RETURN_ON_FALSE(!vendor_config->flags.enable_io_multiplex || !vendor_config->flags.mirror_by_cmd,
ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `enable_io_multiplex` cannot work together");
esp_err_t ret = ESP_OK;
st7701_panel_t *st7701 = (st7701_panel_t *)calloc(1, sizeof(st7701_panel_t));
ESP_RETURN_ON_FALSE(st7701, ESP_ERR_NO_MEM, TAG, "no mem for st7701 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->rgb_ele_order) {
case LCD_RGB_ELEMENT_ORDER_RGB:
st7701->madctl_val = 0;
break;
case LCD_RGB_ELEMENT_ORDER_BGR:
st7701->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
break;
}
st7701->colmod_val = 0;
switch (panel_dev_config->bits_per_pixel) {
case 16: // RGB565
st7701->colmod_val = 0x50;
break;
case 18: // RGB666
st7701->colmod_val = 0x60;
break;
case 24: // RGB888
st7701->colmod_val = 0x70;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7701->io = io;
st7701->init_cmds = vendor_config->init_cmds;
st7701->init_cmds_size = vendor_config->init_cmds_size;
st7701->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7701->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
st7701->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
st7701->flags.enable_io_multiplex = vendor_config->flags.enable_io_multiplex;
st7701->flags.reset_level = panel_dev_config->flags.reset_active_high;
if (st7701->flags.enable_io_multiplex) {
if (st7701->reset_gpio_num >= 0) { // Perform hardware reset
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->flags.reset_level);
} else { // Perform software reset
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
}
vTaskDelay(pdMS_TO_TICKS(120));
/**
* In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
* (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
* `esp_lcd_new_rgb_panel()` is called.
*/
ESP_GOTO_ON_ERROR(panel_st7701_send_init_cmds(st7701), err, TAG, "send init commands failed");
// After sending the initialization commands, the 3-wire SPI interface can be deleted
ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
st7701->io = NULL;
ESP_LOGD(TAG, "delete panel IO");
}
// Create RGB panel
ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
// Save the original functions of RGB panel
st7701->init = (*ret_panel)->init;
st7701->del = (*ret_panel)->del;
st7701->reset = (*ret_panel)->reset;
st7701->mirror = (*ret_panel)->mirror;
st7701->disp_on_off = (*ret_panel)->disp_on_off;
// Overwrite the functions of RGB panel
(*ret_panel)->init = panel_st7701_init;
(*ret_panel)->del = panel_st7701_del;
(*ret_panel)->reset = panel_st7701_reset;
(*ret_panel)->mirror = panel_st7701_mirror;
(*ret_panel)->disp_on_off = panel_st7701_disp_on_off;
(*ret_panel)->user_data = st7701;
ESP_LOGD(TAG, "new st7701 panel @%p", st7701);
return ESP_OK;
err:
if (st7701) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(st7701);
}
return ret;
}
static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xEF, (uint8_t []){0x08}, 1, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, (uint8_t []){0x3B, 0x00}, 2, 0},
{0xC1, (uint8_t []){0x10, 0x02}, 2, 0},
{0xC2, (uint8_t []){0x20, 0x06}, 2, 0},
{0xCC, (uint8_t []){0x10}, 1, 0},
{0xB0, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xB1, (uint8_t []){0x00, 0x13, 0x5A, 0x0F, 0x12, 0x07, 0x09, 0x08, 0x08, 0x24, 0x07, 0x13, 0x12, 0x6B, 0x73, 0xFF}, 16, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t []){0x8D}, 1, 0},
{0xB1, (uint8_t []){0x48}, 1, 0},
{0xB2, (uint8_t []){0x89}, 1, 0},
{0xB3, (uint8_t []){0x80}, 1, 0},
{0xB5, (uint8_t []){0x49}, 1, 0},
{0xB7, (uint8_t []){0x85}, 1, 0},
{0xB8, (uint8_t []){0x32}, 1, 0},
{0xC1, (uint8_t []){0x78}, 1, 0},
{0xC2, (uint8_t []){0x78}, 1, 0},
{0xD0, (uint8_t []){0x88}, 1, 100},
{0xE0, (uint8_t []){0x00, 0x00, 0x02}, 3, 0},
{0xE1, (uint8_t []){0x05, 0xC0, 0x07, 0xC0, 0x04, 0xC0, 0x06, 0xC0, 0x00, 0x44, 0x44}, 11, 0},
{0xE2, (uint8_t []){0x00, 0x00, 0x33, 0x33, 0x01, 0xC0, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x00}, 13, 0},
{0xE3, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE4, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE5, (uint8_t []){0x0D, 0xF1, 0x10, 0x98, 0x0F, 0xF3, 0x10, 0x98, 0x09, 0xED, 0x10, 0x98, 0x0B, 0xEF, 0x10, 0x98}, 16, 0},
{0xE6, (uint8_t []){0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE7, (uint8_t []){0x44, 0x44}, 2, 0},
{0xE8, (uint8_t []){0x0C, 0xF0, 0x10, 0x98, 0x0E, 0xF2, 0x10, 0x98, 0x08, 0xEC, 0x10, 0x98, 0x0A, 0xEE, 0x10, 0x98}, 16, 0},
{0xEB, (uint8_t []){0x00, 0x01, 0xE4, 0xE4, 0x44, 0x88, 0x00}, 7, 0},
{0xED, (uint8_t []){0xFF, 0x04, 0x56, 0x7F, 0xBA, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xAB, 0xF7, 0x65, 0x40, 0xFF}, 16, 0},
{0xEF, (uint8_t []){0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F}, 6, 0},
{0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x11, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 0},
// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x12}, 5, 0}, /* This part of the parameters can be used for screen self-test */
// {0xD1, (uint8_t []){0x81}, 1, 0},
// {0xD2, (uint8_t []){0x08}, 1, 0},
};
static esp_err_t panel_st7701_send_init_cmds(st7701_panel_t *st7701)
{
esp_lcd_panel_io_handle_t io = st7701->io;
const st7701_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
bool is_command2_disable = true;
bool is_cmd_overwritten = false;
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0x00
}, 5), TAG, "Write cmd failed");
// Set color format
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
st7701->madctl_val
}, 1), TAG, "Write cmd failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t []) {
st7701->colmod_val
}, 1), TAG, "Write cmd failed");
// vendor specific initialization, it can be different between manufacturers
// should consult the LCD supplier for initialization sequence code
if (st7701->init_cmds) {
init_cmds = st7701->init_cmds;
init_cmds_size = st7701->init_cmds_size;
} else {
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_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 only when command2 is disable
if (is_command2_disable && (init_cmds[i].data_bytes > 0)) {
switch (init_cmds[i].cmd) {
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
st7701->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
st7701->colmod_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));
// Check if the current cmd is the command2 disable cmd
if ((init_cmds[i].cmd == ST7701_CMD_CND2BKxSEL) && (init_cmds[i].data_bytes > 4)) {
is_command2_disable = !(((uint8_t *)init_cmds[i].data)[4] & ST7701_CMD_CN2_BIT);
}
}
ESP_LOGD(TAG, "send init commands success");
return ESP_OK;
}
static esp_err_t panel_st7701_init(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (!st7701->flags.enable_io_multiplex) {
ESP_RETURN_ON_ERROR(panel_st7701_send_init_cmds(st7701), TAG, "send init commands failed");
}
// Init RGB panel
ESP_RETURN_ON_ERROR(st7701->init(panel), TAG, "init RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
if (st7701->reset_gpio_num >= 0) {
gpio_reset_pin(st7701->reset_gpio_num);
}
// Delete RGB panel
st7701->del(panel);
free(st7701);
ESP_LOGD(TAG, "del st7701 panel @%p", st7701);
return ESP_OK;
}
static esp_err_t panel_st7701_reset(esp_lcd_panel_t *panel)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
// Perform hardware reset
if (st7701->reset_gpio_num >= 0) {
gpio_set_level(st7701->reset_gpio_num, st7701->flags.reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7701->reset_gpio_num, !st7701->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));
}
// Reset RGB panel
ESP_RETURN_ON_ERROR(st7701->reset(panel), TAG, "reset RGB panel failed");
return ESP_OK;
}
static esp_err_t panel_st7701_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
uint8_t sdir_val = 0;
if (st7701->flags.mirror_by_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control mirror through LCD command
if (mirror_x) {
sdir_val = ST7701_CMD_SS_BIT;
} else {
sdir_val = 0;
}
if (mirror_y) {
st7701->madctl_val |= LCD_CMD_ML_BIT;
} else {
st7701->madctl_val &= ~LCD_CMD_ML_BIT;
}
// Enable the Command2 BK0
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3,
ST7701_CMD_BKxSEL_BK0 | ST7701_CMD_CN2_BIT,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_SDIR, (uint8_t[]) {
sdir_val,
}, 1), TAG, "send command failed");;
// Disable Command2
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7701_CMD_CND2BKxSEL, (uint8_t []) {
ST7701_CMD_BKxSEL_BYTE0, ST7701_CMD_BKxSEL_BYTE1, ST7701_CMD_BKxSEL_BYTE2, ST7701_CMD_BKxSEL_BYTE3, 0,
}, 5), TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
st7701->madctl_val,
}, 1), TAG, "send command failed");;
} else {
// Control mirror through RGB panel
ESP_RETURN_ON_ERROR(st7701->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
}
return ESP_OK;
}
static esp_err_t panel_st7701_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
st7701_panel_t *st7701 = (st7701_panel_t *)panel->user_data;
esp_lcd_panel_io_handle_t io = st7701->io;
int command = 0;
if (st7701->flags.display_on_off_use_cmd) {
ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
// Control display on/off through LCD command
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");
} else {
// Control display on/off through display control signal
ESP_RETURN_ON_ERROR(st7701->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
}
return ESP_OK;
}
#endif
@@ -0,0 +1,183 @@
#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 "driver/i2c_master.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "Arduino.h"
#include "esp_lcd_st7701.h"
#include "st7701_lcd.h"
#define LCD_H_RES 480
#define LCD_V_RES 800
#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 (0)
#define EXAMPLE_PIN_NUM_BK_LIGHT GPIO_NUM_23
#define BSP_LCD_BACKLIGHT GPIO_NUM_23
#define LCD_LEDC_CH LEDC_CHANNEL_0
static const char *TAG = "example";
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
st7701_lcd::st7701_lcd(int8_t lcd_rst)
{
_lcd_rst = lcd_rst;
}
void st7701_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 st7701_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 st7701_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 st7701_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 = ST7701_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 = ST7701_PANEL_IO_DBI_CONFIG();
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io_handle));
// 创建JD9365控制面板
esp_lcd_dpi_panel_config_t dpi_config = ST7701_480_360_PANEL_60HZ_DPI_CONFIG(MIPI_DPI_PX_FORMAT);
st7701_vendor_config_t vendor_config = {
// .init_cmds = lcd_cmd,
// .init_cmds_size = sizeof(lcd_cmd) / sizeof(st7701_lcd_init_cmd_t),
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
.flags = {
.use_mipi_interface = 1,
}
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_5,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// esp_lcd_dpi_panel_event_callbacks_t cbs = {0};
// if (dsi_cfg->flags.avoid_tearing) {
// cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback;
// } else {
// cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback;
// }
// /* Register done callback */
// esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv);
// 打开背光
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
}
void st7701_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 st7701_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 st7701_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 st7701_lcd::te_on()
{
esp_lcd_panel_io_tx_param(io_handle, 0x35,new (uint8_t[]){0x00}, 1);
}
void st7701_lcd::te_off()
{
esp_lcd_panel_io_tx_param(io_handle, 0x34,new (uint8_t[]){0x00}, 0);
}
uint16_t st7701_lcd::width()
{
return LCD_H_RES;
}
uint16_t st7701_lcd::height()
{
return LCD_V_RES;
}
void st7701_lcd::get_handle(bsp_lcd_handles_t *ret_handles)
{
ret_handles->io = io_handle;
ret_handles->mipi_dsi_bus = NULL;
ret_handles->panel = panel_handle;
ret_handles->control = NULL;
}
@@ -0,0 +1,36 @@
#ifndef _ST7701_LCD_H
#define _ST7701_LCD_H
#include <stdio.h>
#include "esp_lcd_types.h"
#include "esp_lcd_mipi_dsi.h"
typedef struct {
esp_lcd_dsi_bus_handle_t mipi_dsi_bus; /*!< MIPI DSI bus handle */
esp_lcd_panel_io_handle_t io; /*!< ESP LCD IO handle */
esp_lcd_panel_handle_t panel; /*!< ESP LCD panel (color) handle */
esp_lcd_panel_handle_t control; /*!< ESP LCD panel (control) handle */
} bsp_lcd_handles_t;
class st7701_lcd
{
public:
st7701_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();
void get_handle(bsp_lcd_handles_t *ret_handles);
private:
int8_t _lcd_rst;
};
#endif
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_lcd_touch.h"
static const char *TAG = "TP";
/*******************************************************************************
* Function definitions
*******************************************************************************/
/*******************************************************************************
* Local variables
*******************************************************************************/
/*******************************************************************************
* Public API functions
*******************************************************************************/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
assert(tp->read_data != NULL);
return tp->read_data(tp);
}
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
bool touched = false;
assert(tp != NULL);
assert(x != NULL);
assert(y != NULL);
assert(tp->get_xy != NULL);
touched = tp->get_xy(tp, x, y, strength, point_num, max_point_num);
if (!touched) {
return false;
}
/* Process coordinates by user */
if (tp->config.process_coordinates != NULL) {
tp->config.process_coordinates(tp, x, y, strength, point_num, max_point_num);
}
/* Software coordinates adjustment needed */
bool sw_adj_needed = ((tp->config.flags.mirror_x && (tp->set_mirror_x == NULL)) ||
(tp->config.flags.mirror_y && (tp->set_mirror_y == NULL)) ||
(tp->config.flags.swap_xy && (tp->set_swap_xy == NULL)));
/* Adjust all coordinates */
for (int i = 0; (sw_adj_needed && i < *point_num); i++) {
/* Mirror X coordinates (if not supported by HW) */
if (tp->config.flags.mirror_x && tp->set_mirror_x == NULL) {
x[i] = tp->config.x_max - x[i];
}
/* Mirror Y coordinates (if not supported by HW) */
if (tp->config.flags.mirror_y && tp->set_mirror_y == NULL) {
y[i] = tp->config.y_max - y[i];
}
/* Swap X and Y coordinates (if not supported by HW) */
if (tp->config.flags.swap_xy && tp->set_swap_xy == NULL) {
uint16_t tmp = x[i];
x[i] = y[i];
y[i] = tmp;
}
}
return touched;
}
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state)
{
assert(tp != NULL);
assert(state != NULL);
*state = 0;
if (tp->get_button_state) {
return tp->get_button_state(tp, n, state);
} else {
return ESP_ERR_NOT_SUPPORTED;
}
return ESP_OK;
}
#endif
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap)
{
assert(tp != NULL);
tp->config.flags.swap_xy = swap;
/* Is swap supported by HW? */
if (tp->set_swap_xy) {
return tp->set_swap_xy(tp, swap);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap)
{
assert(tp != NULL);
assert(swap != NULL);
/* Is swap supported by HW? */
if (tp->get_swap_xy) {
return tp->get_swap_xy(tp, swap);
} else {
*swap = tp->config.flags.swap_xy;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_x = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_x) {
return tp->set_mirror_x(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_x) {
return tp->get_mirror_x(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_x;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror)
{
assert(tp != NULL);
tp->config.flags.mirror_y = mirror;
/* Is mirror supported by HW? */
if (tp->set_mirror_y) {
return tp->set_mirror_y(tp, mirror);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror)
{
assert(tp != NULL);
assert(mirror != NULL);
/* Is swap supported by HW? */
if (tp->get_mirror_y) {
return tp->get_mirror_y(tp, mirror);
} else {
*mirror = tp->config.flags.mirror_y;
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp)
{
assert(tp != NULL);
if (tp->del != NULL) {
return tp->del(tp);
}
return ESP_OK;
}
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback)
{
esp_err_t ret = ESP_OK;
assert(tp != NULL);
/* Interrupt pin is not selected */
if (tp->config.int_gpio_num == GPIO_NUM_NC) {
return ESP_ERR_INVALID_ARG;
}
tp->config.interrupt_callback = callback;
if (callback != NULL) {
ret = gpio_install_isr_service(0);
/* ISR service can be installed from user before, then it returns invalid state */
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "GPIO ISR install failed");
return ret;
}
/* Add GPIO ISR handler */
ret = gpio_intr_enable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
ret = gpio_isr_handler_add(tp->config.int_gpio_num, (gpio_isr_t)tp->config.interrupt_callback, tp);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
} else {
/* Remove GPIO ISR handler */
ret = gpio_isr_handler_remove(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR remove handler failed");
ret = gpio_intr_disable(tp->config.int_gpio_num);
ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR disable failed");
}
return ESP_OK;
}
@@ -0,0 +1,370 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch
*/
#pragma once
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS (1)
#define CONFIG_ESP_LCD_TOUCH_MAX_POINTS (5)
/**
* @brief Touch controller type
*
*/
typedef struct esp_lcd_touch_s esp_lcd_touch_t;
typedef esp_lcd_touch_t *esp_lcd_touch_handle_t;
/**
* @brief Touch controller interrupt callback type
*
*/
typedef void (*esp_lcd_touch_interrupt_callback_t)(esp_lcd_touch_handle_t tp);
/**
* @brief Touch Configuration Type
*
*/
typedef struct {
uint16_t x_max; /*!< X coordinates max (for mirroring) */
uint16_t y_max; /*!< Y coordinates max (for mirroring) */
gpio_num_t rst_gpio_num; /*!< GPIO number of reset pin */
gpio_num_t int_gpio_num; /*!< GPIO number of interrupt pin */
struct {
unsigned int reset: 1; /*!< Level of reset pin in reset */
unsigned int interrupt: 1;/*!< Active Level of interrupt pin */
} levels;
struct {
unsigned int swap_xy: 1; /*!< Swap X and Y after read coordinates */
unsigned int mirror_x: 1; /*!< Mirror X after read coordinates */
unsigned int mirror_y: 1; /*!< Mirror Y after read coordinates */
} flags;
/*!< User callback called after get coordinates from touch controller for apply user adjusting */
void (*process_coordinates)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
/*!< User callback called after the touch interrupt occured */
esp_lcd_touch_interrupt_callback_t interrupt_callback;
} esp_lcd_touch_config_t;
typedef struct {
uint8_t points; /*!< Count of touch points saved */
struct {
uint16_t x; /*!< X coordinate */
uint16_t y; /*!< Y coordinate */
uint16_t strength; /*!< Strength */
} coords[CONFIG_ESP_LCD_TOUCH_MAX_POINTS];
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
uint8_t buttons; /*!< Count of buttons states saved */
struct {
uint8_t status; /*!< Status of button */
} button[CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS];
#endif
portMUX_TYPE lock; /*!< Lock for read/write */
} esp_lcd_touch_data_t;
/**
* @brief Declare of Touch Type
*
*/
struct esp_lcd_touch_s {
/**
* @brief Read data from touch controller (mandatory)
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*read_data)(esp_lcd_touch_handle_t tp);
/**
* @brief Get coordinates from touch controller (mandatory)
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of strengths
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool (*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);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state (optional)
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates (optional)
* If set, then not used SW swapping.
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_swap_xy)(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped (optional)
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_swap_xy)(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_x)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X (optional)
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_x)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
* If set, then not used SW mirroring.
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*set_mirror_y)(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y (optional)
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*get_mirror_y)(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete Touch
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success, otherwise returns ESP_ERR_xxx
*/
esp_err_t (*del)(esp_lcd_touch_handle_t tp);
/**
* @brief Configuration structure
*/
esp_lcd_touch_config_t config;
/**
* @brief Communication interface
*/
esp_lcd_panel_io_handle_t io;
/**
* @brief Data structure
*/
esp_lcd_touch_data_t data;
};
/**
* @brief Read data from touch controller
*
* @note This function is usually blocking.
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG parameter error
* - ESP_FAIL sending command error, slave hasn't ACK the transfer
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode
* - ESP_ERR_TIMEOUT operation timeout because the bus is busy
*/
esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp);
/**
* @brief Read coordinates from touch controller
*
* @param tp: Touch handler
* @param x: Array of X coordinates
* @param y: Array of Y coordinates
* @param strength: Array of the strengths (can be NULL)
* @param point_num: Count of points touched (equals with count of items in x and y array)
* @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
*
* @return
* - Returns true, when touched and coordinates readed. Otherwise returns false.
*/
bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
/**
* @brief Get button state
*
* @param tp: Touch handler
* @param n: Button index
* @param state: Button state
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by controller
* - ESP_ERR_INVALID_ARG if bad button index
*/
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
/**
* @brief Swap X and Y after read coordinates
*
* @param tp: Touch handler
* @param swap: Set swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap);
/**
* @brief Are X and Y coordinates swapped
*
* @param tp: Touch handler
* @param swap: Get swap value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap);
/**
* @brief Mirror X after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored X
*
* @param tp: Touch handler
* @param mirror: Get X mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Mirror Y after read coordinates
*
* @param tp: Touch handler
* @param mirror: Set Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror);
/**
* @brief Is mirrored Y
*
* @param tp: Touch handler
* @param mirror: Get Y mirror value
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror);
/**
* @brief Delete touch (free all allocated memory and restart HW)
*
* @param tp: Touch handler
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp);
/**
* @brief Register user callback called after the touch interrupt occured
*
* @param tp: Touch handler
* @param callback: Interrupt callback
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,270 @@
/*
* 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);
/*******************************************************************************
* 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;
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*
}
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD touch: GT911
*/
#pragma once
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create a new GT911 touch driver
*
* @note The I2C communication should be initialized before use this function.
*
* @param io LCD/Touch panel IO handle
* @param config: Touch configuration
* @param out_touch: Touch instance handle
* @return
* - ESP_OK on success
* - ESP_ERR_NO_MEM if there is no memory for allocating main structure
*/
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);
/**
* @brief I2C address of the GT911 controller
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS (0x5D)
/**
* @brief Touch IO configuration structure
*
*/
#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG() \
{ \
.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \
.control_phase_bytes = 1, \
.dc_bit_offset = 0, \
.lcd_cmd_bits = 16, \
.flags = \
{ \
.disable_control_phase = 1, \
} \
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,103 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_lcd_touch_gt911.h"
#include "gt911_touch.h"
#define CONFIG_LCD_HRES 800
#define CONFIG_LCD_VRES 1280
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));
i2c_master_bus_handle_t i2c_handle = NULL;
i2c_master_get_bus_handle(1,&i2c_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_LOGI(TAG, "Initialize touch IO (I2C)");
esp_lcd_new_panel_io_i2c(i2c_handle, &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,18 @@
#ifndef _GT911_TOUCH_H
#define _GT911_TOUCH_H
#include <stdio.h>
class gt911_touch
{
public:
gt911_touch(int8_t sda_pin, int8_t scl_pin, int8_t rst_pin = -1, int8_t int_pin = -1);
void begin();
bool getTouch(uint16_t *x, uint16_t *y);
void set_rotation(uint8_t r);
private:
int8_t _sda, _scl, _rst, _int;
};
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

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