Lopaka Basics 08: Touch-Oberfläche mit LVGL

Das sieht aus wie eine App auf einem Telefon, aber es läuft auf einem Mikrocontroller mit einem kleinen Touchscreen. Du drehst den Regler mit einem Finger, schiebst von warm zu kühl, tippst einen Button und der Regler fährt zu einem neuen Wert.
In dieser Episode siehst du, wie ein UI-Framework namens LVGL einen kleinen Screen in eine Touch-Oberfläche verwandelt und was es von den Grafikbibliotheken der früheren Episoden unterscheidet. Dann legst du den Screen in Lopaka an, fügst wenige Zeilen eigenen Codes für die Berührungen hinzu und führst es auf einem simulierten ESP32 mit einem echten Touch-Panel aus.
Was du bauen wirst: einen 240×320-Touchscreen, der ein Wohnzimmerlicht steuert: einen Helligkeitsregler mit großer Zahl, einen Warm ↔ Cool-Schieberegler, einen Netzschalter und zwei Szenen-Buttons, Relax und Focus. Was du brauchst: einen Browser und den Online-Simulator aus Episode 1. Ein echtes Board ist optional: ein ESP32 und ein 2,8-Zoll-ILI9341-Display mit einem FT6206-Kapazitiv-Touch-Panel.
Ein Stift oder ein Satz Teile
Eine Grafikbibliothek zeichnet ein Bild eines Buttons
Bisher zeichneten wir jeden Screen mit einer Grafikbibliothek wie U8g2 oder Adafruit GFX. Eine Grafikbibliothek ist ein Stift: ein Rechteck hier, etwas Text dort. Ein so gezeichneter Button ist nur ein Bild eines Buttons.
Die Berührung ist auch dein Problem. Wenn ein Finger auf dem Screen landet, muss dein Code die Koordinaten lesen, prüfen, ob sie in das Rechteck fallen (x > 124 && x < 224 && y > 274 …), den Button in seinem gedrückten Look zeichnen und den Screen neu zeichnen, alles von Hand.
Ein UI-Framework gibt dir echte Objekte
LVGL ist ein UI-Framework. Du zeichnest keinen Button, du erstellst einen: lv_button_create(). Von da an ist der Button ein Objekt, das seine eigene Größe, Farben und seinen Zustand kennt.
LVGL liest die Berührung, findet das Objekt unter dem Finger, zeigt es gedrückt und sagt deinem Code, was passiert ist: „Focus wurde geklickt“. Es zeichnet auch nur den Teil des Screens neu, der sich geändert hat, nicht das ganze Bild.

Schritt 1: Ein neues LVGL-Projekt
Klicke auf + Neues Projekt und richte den Dialog ein:
- Plattform: LVGL (v9).
- Titel: „Living Room“.
- Screen-Größe: Schalte Benutzerdefiniert ein und tippe W
240, H320. Der Screen steht aufrecht, wie ein Telefon. - Hintergrundfarbe:
#0E1116, ein dunkles Blauschwarz.
Klicke auf Speichern. Die Hintergrundfarbe ist die erste Zeile des generierten Codes:
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x0E1116), LV_PART_MAIN);lv_screen_active() ist das Screen-Objekt selbst, und jedes Widget, das du hinzufügst, wird darauf leben.
Schritt 2: Füge die Widgets hinzu
Bei einem LVGL-Projekt hält die Werkzeugleiste echte Widgets: Neben Auswahlwerkzeug und Malen sind Text (Aa), Textbereich, Panel, Button, Schalter, Schieberegler, Checkbox und Bogen. Klicke ein Werkzeug und das Widget erscheint auf dem Screen. Setze dann seine Position, Größe und Farben im Inspektor rechts.
Der Drehregler
Wähle das Bogen-Werkzeug. Setze X 30, Y 52, W 180 und H 180, dann Wert 70. Setze Füllfarbe auf #2196F3 und Hintergrund auf #2A3039.

Ein 180-Pixel-Regler auf einem 240-Pixel-Screen lässt 30 Pixel auf jeder Seite, also zentriert X 30 ihn. Die anderen drei Zahlen kommen mit dem Bogen:
- Start
0und Ende270machen die Spur zu drei Vierteln eines Kreises. - Drehen
135dreht ihn so, dass die Lücke unten ist. LVGL zählt Winkel im Uhrzeigersinn von rechts, also beginnt der Bogen unten links und endet unten rechts. - Wert
70ist, wie viel der Spur gefüllt ist, auf einer Skala von 0 bis 100.
Füllfarbe malt den gefüllten Teil und Hintergrund den Rest der Spur.
Beschriftungen, ein Schalter, ein Schieberegler und zwei Buttons
Füge den Rest des Screens hinzu. Gib jedem Widget seine Farben im Inspektor, während du gehst:
- Die Zahl: ein Text
70%in Weiß (#FFFFFF) im Regler, bei X104, Y150. - Der Titel: ein Text
Living roomin Weiß in der oberen linken Ecke. - Ein Schalter in der oberen rechten Ecke, 52×28 Pixel, mit Füllfarbe
#2196F3und Hintergrund#3A404A. Aktiviert ist an: Das Licht startet eingeschaltet. - Ein Schieberegler unter dem Regler, 208 Pixel breit und 8 hoch, mit Füllfarbe
#2196F3. - Zwei Buttons am unteren Rand,
RelaxundFocus, je 100×36 Pixel. Setze R auf17, die Textfarbe auf#FFFFFFund Hintergrund auf#2A3039. Ein Radius von fast der halben Höhe verwandelt jeden Button in eine Pille. - Drei kleine Beschriftungen in Grau (
#8A93A0):Brightnessunter der Zahl,WarmundCoolüber den zwei Enden des Schiebereglers.
Jedes Widget erscheint in der Mitte des Screens, wenn du es hinzufügst, und die X- und Y-Felder bewegen es an seinen Platz.
Schritt 3: Benenne die Ebenen
Jedes Widget ist eine Ebene im Ebenen-Panel, und der Ebenenname wird zum Variablennamen im Code. Doppelklicke eine Ebene und gib den sechs, die du in deinem Code brauchen wirst, kurze Namen:
| Ebene | Name | Im Code |
|---|---|---|
| der Bogen | dial | arc_dial |
der 70%-Text | level | label_level |
| der Schalter | power | switch_power |
| der Schieberegler | warmth | slider_warmth |
| der Relax-Button | relax | button_relax |
| der Focus-Button | focus | button_focus |
Lopaka stellt den Widget-Typ vor den Namen. Der Titel und die grauen Beschriftungen behalten ihre Standardnamen; nichts verweist auf sie.
Alles ist ein Objekt
LVGL sieht den Screen, den du gebaut hast, als Satz von Objekten. Alles auf dem Screen ist ein Objekt, und Objekte leben in einem Baum. Der Screen hält Regler, Zahl, Schieberegler, Schalter und Buttons. Jeder Button hält seine eigene Beschriftung, also bewegen sich die zwei zusammen.

Stile und Zustände
Jedes Objekt hat Stile: Farbe, Radius, Schriftart. Stile können vom Zustand des Objekts abhängen. Derselbe Focus-Button sieht standardmäßig anders aus, während er gedrückt ist und wenn er deaktiviert ist, und der Schalter wechselt, wenn er aktiviert ist. LVGL wechselt zwischen diesen Looks von selbst.

Ereignisse
Schließlich Ereignisse. Wenn der Schieberegler sich bewegt, sendet er ein VALUE_CHANGED-Ereignis. Wenn ein Button angetippt wird, sendet er CLICKED. Deine Funktion läuft und entscheidet, was als Nächstes passiert: on_warmth() tönt den Regler, on_focus() sendet ihn zu 100 %.
Lopaka schreibt die Objekte und ihre Stile. Die Ereignisse sind deine.

Schritt 4: Dein Code
Was Lopaka generiert
Öffne das Code-Panel. Jedes Widget ist ein Erstellungsaufruf, gefolgt von Position, Größe und Farben:
// [BEGIN lopaka generated]
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x0E1116), LV_PART_MAIN);
// arcLvgl: dial
lv_obj_t * arc_dial = lv_arc_create(lv_screen_active());
lv_obj_set_pos(arc_dial, 30, 52);
lv_obj_set_size(arc_dial, 180, 180);
lv_arc_set_rotation(arc_dial, 135);
lv_arc_set_bg_angles(arc_dial, 0, 270);
lv_arc_set_value(arc_dial, 70);
lv_obj_set_style_arc_color(arc_dial, lv_color_hex(0x2196F3), LV_PART_INDICATOR);
lv_obj_set_style_arc_color(arc_dial, lv_color_hex(0x2A3039), LV_PART_MAIN);
// string: level
lv_obj_t * label_level = lv_label_create(lv_screen_active());
lv_obj_set_style_text_color(label_level, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_pos(label_level, 104, 132);
lv_label_set_text(label_level, "70%");
// string: string 3
lv_obj_t * label_string_3 = lv_label_create(lv_screen_active());
lv_obj_set_style_text_color(label_string_3, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_pos(label_string_3, 16, 14);
lv_label_set_text(label_string_3, "Living room");
// switch: power
lv_obj_t * switch_power = lv_switch_create(lv_screen_active());
lv_obj_set_pos(switch_power, 172, 12);
lv_obj_set_size(switch_power, 52, 28);
lv_obj_add_state(switch_power, LV_STATE_CHECKED);
lv_obj_set_style_bg_color(switch_power, lv_color_hex(0x2196F3), LV_PART_INDICATOR | LV_STATE_CHECKED);
lv_obj_set_style_bg_color(switch_power, lv_color_hex(0x3A404A), LV_PART_MAIN);
// slider: warmth
lv_obj_t * slider_warmth = lv_slider_create(lv_screen_active());
lv_slider_set_value(slider_warmth, 50, LV_ANIM_OFF);
lv_obj_set_pos(slider_warmth, 16, 252);
lv_obj_set_size(slider_warmth, 208, 8);
lv_obj_set_style_bg_color(slider_warmth, lv_color_hex(0x2196F3), LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider_warmth, lv_color_hex(0x2196F3), LV_PART_KNOB);
lv_obj_set_style_bg_color(slider_warmth, lv_color_hex(0x2196F3), LV_PART_MAIN);
// button: relax
lv_obj_t * button_relax = lv_button_create(lv_screen_active());
lv_obj_set_pos(button_relax, 16, 274);
lv_obj_set_size(button_relax, 100, 36);
lv_obj_set_style_radius(button_relax, 17, LV_PART_MAIN);
lv_obj_set_style_bg_color(button_relax, lv_color_hex(0x2A3039), LV_PART_MAIN);
lv_obj_t * button_relax_label = lv_label_create(button_relax);
lv_label_set_text(button_relax_label, "Relax");
lv_obj_center(button_relax_label);
// button: focus
lv_obj_t * button_focus = lv_button_create(lv_screen_active());
lv_obj_set_pos(button_focus, 124, 274);
lv_obj_set_size(button_focus, 100, 36);
lv_obj_set_style_radius(button_focus, 17, LV_PART_MAIN);
lv_obj_set_style_bg_color(button_focus, lv_color_hex(0x2A3039), LV_PART_MAIN);
lv_obj_t * button_focus_label = lv_label_create(button_focus);
lv_label_set_text(button_focus_label, "Focus");
lv_obj_center(button_focus_label);
// string: string 8
lv_obj_t * label_string_8 = lv_label_create(lv_screen_active());
lv_obj_set_style_text_color(label_string_8, lv_color_hex(0x8A93A0), LV_PART_MAIN);
lv_obj_set_pos(label_string_8, 84, 188);
lv_label_set_text(label_string_8, "Brightness");
// string: string 9
lv_obj_t * label_string_9 = lv_label_create(lv_screen_active());
lv_obj_set_style_text_color(label_string_9, lv_color_hex(0x8A93A0), LV_PART_MAIN);
lv_obj_set_pos(label_string_9, 16, 226);
lv_label_set_text(label_string_9, "Warm");
// string: string 10
lv_obj_t * label_string_10 = lv_label_create(lv_screen_active());
lv_obj_set_style_text_color(label_string_10, lv_color_hex(0x8A93A0), LV_PART_MAIN);
lv_obj_set_pos(label_string_10, 194, 226);
lv_label_set_text(label_string_10, "Cool");
// [END lopaka generated]Das ist der Code mit ausgeschalteter Wrapper-Funktion, wie wir ihn gleich brauchen werden. Ein paar Details darin:
- Teile. Ein Bogen hat eine Spur (
LV_PART_MAIN) und einen gefüllten Teil (LV_PART_INDICATOR); ein Schieberegler und ein Schalter haben auch einen Knopf (LV_PART_KNOB). Jeder Stilaufruf nennt den Teil, den er malt. Die Schalterfarbe gilt fürLV_PART_INDICATOR | LV_STATE_CHECKED, den gefüllten Teil, während der Schalter an ist: ein Stil für einen Zustand. - Der Button und seine Beschriftung.
lv_label_create(button_relax)erstellt die Beschriftung im Button, nicht auf dem Screen, undlv_obj_center()hält sie in der Mitte. Das ist der Objektbaum aus der Erklärung, im Code. - Warum die Zahl 132 sagt.
lv_obj_set_pos()platziert ein Objekt an seiner oberen linken Ecke. Im Inspektor von Lopaka ist das Y eines Textes der untere Rand seiner Zeile, wie auf jeder Plattform. Die Textzeile ist 18 Pixel hoch, also wird aus Y150im Inspektor132im Code. - Standards bleiben draußen. Lopaka lässt Stile aus, die zu den eigenen Standards von LVGL passen. Deshalb haben die Buttons keine Zeile für ihren weißen Text: Weiß ist bereits die Standardfarbe des Textes eines Buttons.
Schalte die Wrapper-Funktion aus
Mit eingeschalteter Wrapper-Funktion verpackt Lopaka den Code in void drawScreen_1(void) { … }. Schalte sie aus: Dann sitzen die generierten Zeilen in einer Funktion, die du selbst schreibst, mit deinem eigenen Code darüber und darunter. Lopaka behält, was du außerhalb der BEGIN- und END-Markierungen tippst, und aktualisiert nur den Teil dazwischen, wenn du das Design änderst.
Dein Code über den Markierungen
Tippe über // [BEGIN lopaka generated] die Display-Einrichtung und ein paar kleine Funktionen:
#include "touch_display.h"
lv_obj_t *dial, *level, *warmth;
void show_level(void *obj, int32_t v) {
lv_arc_set_value(dial, v);
lv_label_set_text_fmt(level, "%d%%", v);
}
void tint(int w) {
lv_color_t warm = lv_color_hex(0xFF8A3D), white = lv_color_hex(0xFFF1DC), cool = lv_color_hex(0x4FC3F7);
lv_color_t c = w < 50 ? lv_color_mix(white, warm, w * 255 / 50)
: lv_color_mix(cool, white, (w - 50) * 255 / 50);
lv_obj_set_style_arc_color(dial, c, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(dial, c, LV_PART_KNOB);
}
void set_warmth(void *obj, int32_t w) {
lv_slider_set_value(warmth, w, LV_ANIM_OFF);
tint(w);
}
void scene(int to_level, int to_warmth) {
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_duration(&a, 700);
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
lv_anim_set_exec_cb(&a, show_level);
lv_anim_set_values(&a, lv_arc_get_value(dial), to_level);
lv_anim_start(&a);
lv_anim_set_exec_cb(&a, set_warmth);
lv_anim_set_values(&a, lv_slider_get_value(warmth), to_warmth);
lv_anim_start(&a);
}
void on_dial(lv_event_t *e) { show_level(NULL, lv_arc_get_value(dial)); }
void on_warmth(lv_event_t *e) { tint(lv_slider_get_value(warmth)); }
void on_relax(lv_event_t *e) { scene(30, 10); }
void on_focus(lv_event_t *e) { scene(100, 90); }
void on_power(lv_event_t *e) {
lv_obj_t *sw = lv_event_get_target_obj(e);
bool on = lv_obj_has_state(sw, LV_STATE_CHECKED);
lv_obj_t *scr = lv_screen_active();
for (uint32_t i = 0; i < lv_obj_get_child_count(scr); i++) {
lv_obj_t *o = lv_obj_get_child(scr, i);
if (o == sw) continue;
lv_obj_set_state(o, LV_STATE_DISABLED, !on);
lv_obj_set_style_opa(o, on ? LV_OPA_COVER : LV_OPA_30, 0);
}
}
void build_ui() {show_level()bewegt den Regler und schreibt die Zahl:%d%%druckt den Wert gefolgt von einem Prozentzeichen.tint()verwandelt die Schiebereglerposition, 0 bis 100, in eine Farbe: warmes Orange bei 0, warmes Weiß bei 50, kühles Blau bei 100.lv_color_mix()mischt zwei Farben; sein letztes Argument, von 0 bis 255, sagt, wie viel der ersten Farbe genommen wird.scene()animiert Regler und Schieberegler in 700 Millisekunden zu neuen Werten und wird am Ende langsamer. Relax geht zu 30 % und warm, Focus zu 100 % und kühl.on_power()läuft, wenn der Schalter wechselt. Mit ausgeschaltetem Licht deaktiviert es jedes andere Objekt auf dem Screen und blendet es auf 30 % aus.
Die letzte Zeile öffnet build_ui(), die Funktion, in der der generierte Code jetzt sitzt.
Dein Code unter den Markierungen
Schließe unter // [END lopaka generated] die Funktion und füge Stile, Ereignisse und das übliche setup() und loop() hinzu:
dial = arc_dial;
level = label_level;
warmth = slider_warmth;
// styles
lv_obj_set_style_text_font(level, &lv_font_montserrat_48, 0);
lv_obj_align(level, LV_ALIGN_TOP_MID, 0, 116);
lv_obj_set_style_bg_color(warmth, lv_color_hex(0xFF8A3D), LV_PART_MAIN);
lv_obj_set_style_bg_opa(warmth, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_grad_color(warmth, lv_color_hex(0x4FC3F7), LV_PART_MAIN);
lv_obj_set_style_bg_grad_dir(warmth, LV_GRAD_DIR_HOR, LV_PART_MAIN);
lv_obj_set_style_bg_opa(warmth, LV_OPA_TRANSP, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(warmth, lv_color_white(), LV_PART_KNOB);
tint(lv_slider_get_value(warmth));
// events
lv_obj_add_event_cb(arc_dial, on_dial, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(slider_warmth, on_warmth, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(switch_power, on_power, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(button_relax, on_relax, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(button_focus, on_focus, LV_EVENT_CLICKED, NULL);
}
void setup() {
display_begin();
build_ui();
}
void loop() {
lv_timer_handler();
delay(5);
}
Die ersten drei Zeilen kopieren die generierten Objekte in die globalen Variablen, damit die Funktionen oben sie erreichen können. Die generierten Variablen existieren nur in build_ui().
Stile. Eine Stilzeile macht die Zahl groß: lv_font_montserrat_48 ist die Montserrat-Schriftart von LVGL mit 48 Pixeln. Eine größere Zahl ist auch breiter, also zentriert lv_obj_align() sie oben am Regler, statt sie an ihrer alten oberen linken Ecke zu lassen. Die nächsten Zeilen malen die Spur des Schiebereglers als Warm-zu-Kühl-Verlauf, verstecken seinen gefüllten Teil und machen den Knopf weiß.
Ereignisse. Eine Zeile pro Widget verbindet es mit einer Funktion: lv_obj_add_event_cb(object, function, event, NULL). Der Regler aktualisiert die Zahl, der Schieberegler tönt den Regler, die Buttons wechseln Szenen und der Schalter dimmt den Screen. Regler, Schieberegler und Schalter senden LV_EVENT_VALUE_CHANGED; die Buttons senden LV_EVENT_CLICKED.
Die Schleife. lv_timer_handler() ist, wo LVGL seine Arbeit tut: Es liest die Berührung, sendet die Ereignisse, führt die Animationen aus und zeichnet neu, was sich geändert hat. Rufe es jede paar Millisekunden und halte loop() frei von langen Verzögerungen, sonst hört der Screen auf zu reagieren.
Schritt 5: Kopiere den Code
Klicke über dem Code auf Kopieren. Du erhältst den ganzen Sketch: deinen Code, den generierten Teil zwischen den Markierungen und setup() und loop().
Schritt 6: Führe ihn aus
Die Display-Datei
Der Sketch beginnt mit #include "touch_display.h". Diese Datei verbindet LVGL mit diesem Display und seinem Touch-Chip. Sie ist für jedes Projekt mit diesem Display dieselbe, also schreibst du sie einmal:
// Display + touch glue for LVGL: ILI9341 (SPI) + FT6206 (I2C), 240x320 portrait.
#include <lvgl.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_FT6206.h>
Adafruit_ILI9341 tft(23, 21, 22); // CS, DC, RST
Adafruit_FT6206 touch;
#define DRAW_BUF_SIZE (240 * 160 * 2) // half the screen
static uint8_t *draw_buf;
static void flush(lv_display_t *d, const lv_area_t *a, uint8_t *px) {
uint32_t w = a->x2 - a->x1 + 1, h = a->y2 - a->y1 + 1;
tft.startWrite();
tft.setAddrWindow(a->x1, a->y1, w, h);
tft.writePixels((uint16_t *)px, w * h);
tft.endWrite();
lv_display_flush_ready(d);
}
static void read_touch(lv_indev_t *i, lv_indev_data_t *data) {
if (touch.touched()) {
TS_Point p = touch.getPoint();
data->point.x = 240 - p.x; // the touch panel is mirrored
data->point.y = 320 - p.y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
void display_begin() {
pinMode(5, OUTPUT);
digitalWrite(5, HIGH); // backlight
SPI.begin(18, 17, 19); // SCK, MISO, MOSI
Wire.begin(4, 16); // SDA, SCL
tft.begin();
tft.setRotation(0);
touch.begin(40);
lv_init();
lv_tick_set_cb(millis);
lv_display_t *disp = lv_display_create(240, 320);
lv_display_set_flush_cb(disp, flush);
draw_buf = (uint8_t *)malloc(DRAW_BUF_SIZE);
lv_display_set_buffers(disp, draw_buf, NULL, DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, read_touch);
}Sie hat zwei Aufgaben:
flush()sendet Pixel ans Display. LVGL hält nicht den ganzen Screen im Speicher: Ein volles 240×320-Bild mit zwei Bytes pro Pixel würde 153.600 Bytes brauchen. Es zeichnet in einen Puffer von der Hälfte des Screens, 240 × 160 × 2 = 76.800 Bytes, und übergibt einen Bereich nach dem anderen. Wenn sich nur die Zahl ändert, wird nur dieser Bereich gezeichnet und gesendet.read_touch()sagt LVGL, wo der Finger ist. Der FT6206-Chip meldet den Berührungspunkt. Auf diesem Panel laufen seine Koordinaten entgegengesetzt zu denen des Displays, also drehen240 - p.xund320 - p.ysie um.
lv_tick_set_cb(millis) gibt LVGL eine Uhr für seine Animationen, und touch.begin(40) setzt, wie fest eine Berührung sein muss, um zu zählen.
Die Einstellungsdatei
LVGL liest seine Einstellungen aus einer Datei namens lv_conf.h. Nimm lv_conf_template.h aus der LVGL-Bibliothek, speichere sie als lv_conf.h und ändere zwei Zeilen:
#if 1 /* Set this to "1" to enable content */#define LV_FONT_MONTSERRAT_48 1Die erste Zeile schaltet die Datei ein. Die zweite fügt die 48-Pixel-Schriftart für die große Zahl hinzu: LVGL kompiliert nur die Schriftarten ein, die du aktivierst, und die Vorlage aktiviert nur Montserrat 14, die Standardschriftart jeder Beschriftung.
Führe ihn im Simulator aus
Erstelle im Online-Simulator aus Episode 1 ein neues ESP32-Projekt und füge das Display ILI9341 Cap Touch hinzu: einen ILI9341-Screen mit einem FT6206-Touch-Panel. Verdrahte es mit dem ESP32:
- VCC mit 3V3, GND mit GND, LED (die Hintergrundbeleuchtung) mit 5,
- CS mit 23, D/C mit 21, RST mit 22,
- SCK mit 18, MOSI mit 19, MISO mit 17,
- SDA mit 4 und SCL mit 16 für das Touch-Panel.
Füge die Bibliotheken lvgl, Adafruit ILI9341 und Adafruit FT6206 Library hinzu. Erstelle zwei neue Dateien, touch_display.h und lv_conf.h, mit den Inhalten oben. Füge den Code aus Lopaka in sketch.ino ein und drücke Play.
Sobald es gebaut ist, wird die Maus zu einem Finger. Drehe den Regler: Die Zahl folgt. Schiebe von warm zu kühl: Der Regler wechselt die Farbe. Tippe Focus: Der Regler fährt zu 100 %. Schalte dann das Licht aus, und der Rest des Screens blendet aus.

Auf einem echten Board
Derselbe Sketch läuft auf einem echten ESP32 mit einem 2,8-Zoll-ILI9341-Modul. Installiere in der Arduino IDE dieselben drei Bibliotheken und wähle das Board ESP32 Dev Module. Der Sketch braucht etwa die Hälfte des Flash und 27 % des RAM; 64 KB dieses RAM sind der eigene Speicher von LVGL für Objekte und Stile. Drei Dinge, die du prüfen solltest:
- Wohin
lv_conf.hgehört. In der Arduino IDE gehört sie nicht neben den Sketch. Lege sie in deinenlibraries-Ordner, neben denlvgl-Ordner, nicht hinein. Wenn LVGL sie nicht findet, fehlt die 48-Pixel-Schriftart und der Sketch kompiliert nicht. - Der Touch-Chip. Viele günstige 2,8-Zoll-ILI9341-Module haben ein resistives Touch-Panel mit einem XPT2046-Chip statt des kapazitiven FT6206. Bei diesen braucht
read_touch()eine Bibliothek für den XPT2046; der Rest des Sketches bleibt derselbe. - Die Richtung der Berührung. Wenn ein Tipp auf der falschen Seite des Screens landet, ist dein Panel nicht wie dieses gespiegelt. Nutze
p.xundp.ydirekt inread_touch()oder spiegle nur eines davon.
Wann LVGL sich lohnt
LVGL braucht Speicher. Seine eigene Dokumentation verlangt mindestens 32 KB RAM. Ein Arduino UNO hat 2 KB, also braucht LVGL allein 16 Mal den ganzen UNO.

Ein ESP32 gibt deinem Sketch 320 KB RAM, und diese Oberfläche nutzt 27 % davon. Wähle das Werkzeug nach der Aufgabe:
- Ein paar Zahlen auf einem OLED? Eine Grafikbibliothek reicht.
- Touch, Widgets und Animation? Das ist LVGL.

Zusammenfassung
- Objekte, nicht Pixel: LVGL baut einen Screen aus Objekten wie Bögen, Schiebereglern und Buttons. Du erstellst einen Button, statt ihn zu zeichnen.
- Stile für jeden Zustand: Farben, Radius und Schriftarten entscheiden, wie ein Objekt standardmäßig aussieht, während es gedrückt, aktiviert oder deaktiviert ist.
- Ereignisse verbinden einen Tipp mit deinem Code: eine
lv_obj_add_event_cb-Zeile pro Widget. Lopaka legt die Widgets an, und du schreibst die Ereignisse.
Nächste Episode: Über Arduino hinaus. Ein Design, viele Plattformen, von ESPHome bis MicroPython und Flipper Zero. Lies Episode 9 oder schaue sie auf YouTube.
Bereit, es zu versuchen? Öffne Lopaka und lege deinen eigenen Touchscreen an.