Lopaka Basics 08: Interfejs dotykowy z LVGL

Wygląda to jak aplikacja na telefon, ale działa na mikrokontrolerze z małym ekranem dotykowym. Obracasz pokrętło palcem, przesuwasz od ciepłego do zimnego, dotykasz przycisku, a pokrętło przesuwa się do nowej wartości.
W tym odcinku zobaczysz, jak framework UI zwany LVGL zamienia mały ekran w interfejs dotykowy i co odróżnia go od bibliotek graficznych z wcześniejszych odcinków. Następnie ułożysz ekran w Lopaka, dodasz kilka linii własnego kodu dla dotyków i uruchomisz go na symulowanym ESP32 z prawdziwym panelem dotykowym.
Co zbudujesz: ekran dotykowy 240×320 sterujący światłem w salonie: pokrętło jasności z dużą liczbą, suwak Warm ↔ Cool, przełącznik zasilania i dwa przyciski scen, Relax i Focus. Czego potrzebujesz: przeglądarki i symulatora online z odcinka 1. Prawdziwa płytka jest opcjonalna: ESP32 i wyświetlacz ILI9341 2,8 cala z pojemnościowym panelem dotykowym FT6206.
Długopis albo zestaw części
Biblioteka graficzna rysuje obrazek przycisku
Dotąd rysowaliśmy każdy ekran biblioteką graficzną, taką jak U8g2 czy Adafruit GFX. Biblioteka graficzna to długopis: prostokąt tutaj, tekst tam. Przycisk narysowany w ten sposób jest tylko obrazkiem przycisku.
Dotyk też jest twoim problemem. Gdy palec ląduje na ekranie, twój kod musi odczytać współrzędne, sprawdzić, czy mieszczą się wewnątrz prostokąta (x > 124 && x < 224 && y > 274 …), narysować przycisk w wciśniętym wyglądzie i przerysować ekran, wszystko ręcznie.
Framework UI daje ci prawdziwe obiekty
LVGL to framework UI. Nie rysujesz przycisku, tworzysz go: lv_button_create(). Od tego momentu przycisk jest obiektem, który zna swój rozmiar, kolory i stan.
LVGL odczytuje dotyk, znajduje obiekt pod palcem, pokazuje go jako wciśnięty i mówi twojemu kodowi, co się stało: „Focus został kliknięty”. Przerysowuje też tylko tę część ekranu, która się zmieniła, a nie cały obraz.

Krok 1: nowy projekt LVGL
Kliknij + Nowy projekt i skonfiguruj okno:
- Platforma: LVGL (v9).
- Tytuł: „Living Room”.
- Rozmiar ekranu: włącz Niestandardowy i wpisz W
240, H320. Ekran stoi pionowo, jak telefon. - Kolor tła:
#0E1116, ciemny niebieskoczarny.
Kliknij Zapisz. Kolor tła to pierwsza linia wygenerowanego kodu:
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x0E1116), LV_PART_MAIN);lv_screen_active() to sam obiekt ekranu, a każdy dodany widżet będzie na nim żył.
Krok 2: dodaj widżety
W projekcie LVGL pasek narzędzi zawiera prawdziwe widżety: obok narzędzia zaznaczania i malowania są tekst (Aa), Obszar tekstowy, Panel, Przycisk, Przełącznik, Suwak, Pole wyboru i Łuk. Kliknij narzędzie, a widżet pojawi się na ekranie. Następnie ustaw jego pozycję, rozmiar i kolory w inspektorze po prawej.
Pokrętło
Wybierz narzędzie Łuk. Ustaw X 30, Y 52, W 180 i H 180, a następnie Wartość 70. Ustaw Kolor wypełnienia na #2196F3, a Tło na #2A3039.

Pokrętło o średnicy 180 pikseli na ekranie o szerokości 240 pikseli pozostawia 30 pikseli po każdej stronie, więc X 30 je wyśrodkowuje. Pozostałe trzy liczby towarzyszą łukowi:
- Start
0i End270sprawiają, że ścieżka to trzy czwarte koła. - Rotate
135obraca ją tak, aby przerwa była na dole. LVGL liczy kąty zgodnie z ruchem wskazówek zegara od prawej, więc łuk zaczyna się na dole po lewej i kończy na dole po prawej. - Wartość
70to stopień wypełnienia ścieżki, w skali od 0 do 100.
Kolor wypełnienia maluje wypełnioną część, a Tło resztę ścieżki.
Etykiety, przełącznik, suwak i dwa przyciski
Dodaj resztę ekranu. Nadaj każdemu widżetowi kolory w inspektorze na bieżąco:
- Liczba: tekst
70%w kolorze białym (#FFFFFF) wewnątrz pokrętła, na X104, Y150. - Tytuł: tekst
Living roomw kolorze białym w lewym górnym rogu. - Przełącznik w prawym górnym rogu, 52×28 pikseli, z Kolorem wypełnienia
#2196F3i Tłem#3A404A. Zaznaczony jest włączony: światło zaczyna się jako włączone. - Suwak pod pokrętłem, szeroki na 208 pikseli i wysoki na 8, z Kolorem wypełnienia
#2196F3. - Dwa przyciski wzdłuż dołu,
RelaxiFocus, każdy 100×36 pikseli. Ustaw R na17, kolor tekstu na#FFFFFF, a Tło na#2A3039. Promień prawie połowy wysokości zamienia każdy przycisk w pastylkę. - Trzy małe etykiety w kolorze szarym (
#8A93A0):Brightnesspod liczbą,WarmiCoolnad dwoma końcami suwaka.
Każdy widżet pojawia się pośrodku ekranu, gdy go dodasz, a pola X i Y przenoszą go na miejsce.
Krok 3: nazwij warstwy
Każdy widżet jest warstwą w panelu Warstwy, a nazwa warstwy staje się nazwą zmiennej w kodzie. Kliknij dwukrotnie warstwę i nadaj sześciu, których będziesz potrzebować w kodzie, krótkie nazwy:
| Warstwa | Nazwa | W kodzie |
|---|---|---|
| łuk | dial | arc_dial |
tekst 70% | level | label_level |
| przełącznik | power | switch_power |
| suwak | warmth | slider_warmth |
| przycisk Relax | relax | button_relax |
| przycisk Focus | focus | button_focus |
Lopaka umieszcza typ widżetu przed nazwą. Tytuł i szare etykiety zachowują domyślne nazwy; nic się do nich nie odwołuje.
Wszystko jest obiektem
LVGL widzi zbudowany ekran jako zestaw obiektów. Wszystko na ekranie jest obiektem, a obiekty żyją w drzewie. Ekran przechowuje pokrętło, liczbę, suwak, przełącznik i przyciski. Każdy przycisk przechowuje własną etykietę, więc oba poruszają się razem.

Style i stany
Każdy obiekt ma style: kolor, promień, czcionkę. Style mogą zależeć od stanu obiektu. Ten sam przycisk Focus wygląda inaczej domyślnie, gdy jest wciśnięty i gdy jest wyłączony, a przełącznik zmienia się, gdy jest zaznaczony. LVGL przełącza się między tymi wyglądami samodzielnie.

Zdarzenia
Na koniec zdarzenia. Gdy suwak się porusza, wysyła zdarzenie VALUE_CHANGED. Gdy przycisk zostanie dotknięty, wysyła CLICKED. Twoja funkcja działa i decyduje, co się stanie dalej: on_warmth() przyciemnia pokrętło, on_focus() wysyła je na 100%.
Lopaka pisze obiekty i ich style. Zdarzenia są twoje.

Krok 4: twój kod
Co generuje Lopaka
Otwórz panel kodu. Każdy widżet to jedno wywołanie tworzenia, po którym następują jego pozycja, rozmiar i kolory:
// [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]Jest to kod przy wyłączonej opcji Funkcja opakowująca, jak będzie potrzebne za chwilę. Kilka szczegółów w nim:
- Części. Łuk ma ścieżkę (
LV_PART_MAIN) i część wypełnioną (LV_PART_INDICATOR); suwak i przełącznik mają też gałkę (LV_PART_KNOB). Każde wywołanie stylu nazywa część, którą maluje. Kolor przełącznika dotyczyLV_PART_INDICATOR | LV_STATE_CHECKED, części wypełnionej, gdy przełącznik jest włączony: styl dla stanu. - Przycisk i jego etykieta.
lv_label_create(button_relax)tworzy etykietę wewnątrz przycisku, a nie na ekranie, alv_obj_center()utrzymuje ją pośrodku. To drzewo obiektów z wyjaśnienia, w kodzie. - Dlaczego liczba mówi 132.
lv_obj_set_pos()umieszcza obiekt według jego lewego górnego rogu. W inspektorze Lopaka Y tekstu to dół jego linii, jak na każdej platformie. Linia tekstu ma 18 pikseli wysokości, więc Y150w inspektorze staje się132w kodzie. - Domyślne pozostają pominięte. Lopaka pomija style pasujące do domyślnych LVGL. Dlatego przyciski nie mają linii dla swojego białego tekstu: biały jest już domyślnym kolorem tekstu przycisku.
Wyłącz funkcję opakowującą
Przy włączonej opcji Funkcja opakowująca Lopaka opakowuje kod w void drawScreen_1(void) { … }. Wyłącz ją: wtedy wygenerowane linie znajdują się wewnątrz funkcji, którą sam piszesz, z twoim kodem powyżej i poniżej nich. Lopaka przechowuje to, co wpiszesz poza znacznikami BEGIN i END, i aktualizuje tylko część między nimi, gdy zmienisz projekt.
Twój kod nad znacznikami
Nad // [BEGIN lopaka generated] wpisz konfigurację wyświetlacza i kilka małych funkcji:
#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()przesuwa pokrętło i zapisuje liczbę:%d%%wypisuje wartość, po której następuje znak procenta.tint()zamienia pozycję suwaka, od 0 do 100, w kolor: ciepły pomarańczowy na 0, ciepła biel na 50, chłodny niebieski na 100.lv_color_mix()miesza dwa kolory; jego ostatni argument, od 0 do 255, mówi, ile pierwszego koloru wziąć.scene()animuje pokrętło i suwak do nowych wartości w 700 milisekund, zwalniając na końcu. Relax przechodzi na 30% i ciepło, Focus na 100% i zimno.on_power()działa, gdy przełącznik się zmieni. Przy wyłączonym świetle wyłącza każdy inny obiekt na ekranie i przygasza go do 30%.
Ostatnia linia otwiera build_ui(), funkcję, w której teraz znajduje się wygenerowany kod.
Twój kod pod znacznikami
Pod // [END lopaka generated] zamknij funkcję i dodaj style, zdarzenia oraz zwykłe setup() i loop():
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);
}
Pierwsze trzy linie kopiują wygenerowane obiekty do zmiennych globalnych, aby funkcje powyżej mogły do nich dotrzeć. Wygenerowane zmienne istnieją tylko wewnątrz build_ui().
Style. Jedna linia stylu sprawia, że liczba jest duża: lv_font_montserrat_48 to czcionka Montserrat LVGL o rozmiarze 48 pikseli. Większa liczba jest też szersza, więc lv_obj_align() wyśrodkowuje ją na górze pokrętła zamiast pozostawiać ją w starym lewym górnym rogu. Następne linie malują ścieżkę suwaka jako gradient od ciepłego do zimnego, ukrywają jego część wypełnioną i sprawiają, że gałka jest biała.
Zdarzenia. Jedna linia na widżet łączy go z funkcją: lv_obj_add_event_cb(object, function, event, NULL). Pokrętło aktualizuje liczbę, suwak przyciemnia pokrętło, przyciski przełączają sceny, a przełącznik przyciemnia ekran. Pokrętło, suwak i przełącznik wysyłają LV_EVENT_VALUE_CHANGED; przyciski wysyłają LV_EVENT_CLICKED.
Pętla. lv_timer_handler() to miejsce, w którym LVGL wykonuje swoją pracę: odczytuje dotyk, wysyła zdarzenia, uruchamia animacje i przerysowuje to, co się zmieniło. Wywołuj ją co kilka milisekund i utrzymuj loop() wolne od długich opóźnień, w przeciwnym razie ekran przestanie reagować.
Krok 5: skopiuj kod
Kliknij Kopiuj nad kodem. Otrzymasz cały szkic: twój kod, wygenerowaną część między znacznikami oraz setup() i loop().
Krok 6: uruchom to
Plik wyświetlacza
Szkic zaczyna się od #include "touch_display.h". Ten plik łączy LVGL z tym wyświetlaczem i jego chipem dotykowym. Jest taki sam dla każdego projektu z tym wyświetlaczem, więc piszesz go raz:
// 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);
}Ma dwa zadania:
flush()wysyła piksele na wyświetlacz. LVGL nie przechowuje całego ekranu w pamięci: pełny obraz 240×320 z dwoma bajtami na piksel zająłby 153 600 bajtów. Rysuje do bufora o połowie ekranu, 240 × 160 × 2 = 76 800 bajtów, i przekazuje jeden obszar naraz. Gdy zmienia się tylko liczba, tylko ten obszar jest rysowany i wysyłany.read_touch()mówi LVGL, gdzie jest palec. Chip FT6206 zgłasza punkt dotyku. Na tym panelu jego współrzędne biegną odwrotnie do współrzędnych wyświetlacza, więc240 - p.xi320 - p.yje odwracają.
lv_tick_set_cb(millis) daje LVGL zegar dla jego animacji, a touch.begin(40) ustawia, jak mocny musi być dotyk, aby się liczył.
Plik ustawień
LVGL odczytuje swoje ustawienia z pliku o nazwie lv_conf.h. Weź lv_conf_template.h z biblioteki LVGL, zapisz go jako lv_conf.h i zmień dwie linie:
#if 1 /* Set this to "1" to enable content */#define LV_FONT_MONTSERRAT_48 1Pierwsza linia włącza plik. Druga dodaje czcionkę 48-pikselową dla dużej liczby: LVGL kompiluje tylko czcionki, które włączysz, a szablon włącza tylko Montserrat 14, domyślną czcionkę każdej etykiety.
Uruchom to w symulatorze
W symulatorze online z odcinka 1 utwórz nowy projekt ESP32 i dodaj wyświetlacz ILI9341 Cap Touch: ekran ILI9341 z panelem dotykowym FT6206. Podłącz go do ESP32:
- VCC do 3V3, GND do GND, LED (podświetlenie) do 5,
- CS do 23, D/C do 21, RST do 22,
- SCK do 18, MOSI do 19, MISO do 17,
- SDA do 4 i SCL do 16 dla panelu dotykowego.
Dodaj biblioteki lvgl, Adafruit ILI9341 i Adafruit FT6206 Library. Utwórz dwa nowe pliki, touch_display.h i lv_conf.h, o powyższej zawartości. Wklej kod z Lopaka do sketch.ino i naciśnij Play.
Po zbudowaniu mysz staje się palcem. Obróć pokrętło: liczba podąża za nim. Przesuń od ciepłego do zimnego: pokrętło zmienia kolor. Dotknij Focus: pokrętło przesuwa się na 100%. Następnie wyłącz światło, a reszta ekranu przygasa.

Na prawdziwej płytce
Ten sam szkic działa na prawdziwym ESP32 z modułem ILI9341 2,8 cala. W Arduino IDE zainstaluj te same trzy biblioteki i wybierz płytkę ESP32 Dev Module. Szkic zajmuje około połowy flash i 27% RAM; 64 KB tego RAM to własna pamięć LVGL na obiekty i style. Trzy rzeczy do sprawdzenia:
- Gdzie trafia
lv_conf.h. W Arduino IDE nie trafia obok szkicu. Umieść go w folderzelibraries, obok folderulvgl, a nie wewnątrz niego. Jeśli LVGL go nie znajdzie, brakuje czcionki 48-pikselowej i szkic się nie kompiluje. - Chip dotykowy. Wiele niedrogich modułów ILI9341 2,8 cala ma rezystancyjny panel dotykowy z chipem XPT2046 zamiast pojemnościowego FT6206. W ich przypadku
read_touch()potrzebuje biblioteki dla XPT2046; reszta szkicu pozostaje taka sama. - Kierunek dotyku. Jeśli dotknięcie ląduje po złej stronie ekranu, twój panel nie jest odbity jak ten. Użyj
p.xip.ybezpośrednio wread_touch()albo odbij tylko jedną z nich.
Kiedy LVGL jest wart swojej ceny
LVGL potrzebuje pamięci. Jego własna dokumentacja wymaga co najmniej 32 KB RAM. Arduino UNO ma 2 KB, więc samo LVGL potrzebuje 16 razy więcej niż całe UNO.

ESP32 daje twojemu szkicowi 320 KB RAM, a ten interfejs używa 27% z tego. Wybierz narzędzie według zadania:
- Kilka liczb na OLED? Biblioteka graficzna wystarczy.
- Dotyk, widżety i animacja? To LVGL.

Podsumowanie
- Obiekty, nie piksele: LVGL buduje ekran z obiektów takich jak łuki, suwaki i przyciski. Tworzysz przycisk zamiast go rysować.
- Style dla każdego stanu: kolory, promień i czcionki decydują, jak obiekt wygląda domyślnie, gdy jest wciśnięty, zaznaczony lub wyłączony.
- Zdarzenia łączą dotknięcie z twoim kodem: jedna linia
lv_obj_add_event_cbna widżet. Lopaka rozmieszcza widżety, a ty piszesz zdarzenia.
Następny odcinek: Poza Arduino. Jeden projekt, wiele platform, od ESPHome po MicroPython i Flipper Zero. Przeczytaj odcinek 9 lub obejrzyj go na YouTube.
Gotowy, aby spróbować? Otwórz Lopaka i ułóż własny ekran dotykowy.