Kontakt Twoje zapytanie

Pierwsze kroki z biblioteką LVGL

Data publikacji 2026-11-08

LVGL to otwarta, darmowa biblioteka do tworzenia graficznych interfejsów użytkownika na urządzenia wbudowane. Posiada bardzo wiele funkcji i integracji, dzięki czemu świetnie nadaje się do budowy zarówno prostych, jak i złożonych interfejsów. Oferuje szeroki wybór komponentów. Dzięki gotowym integracjom z zewnętrznymi bibliotekami możliwe jest użycie w projekcie obrazków w formatach PNG, JPEG lub WEBP; animacji w formacie Lottie; czcionek w formacie TrueType, OpenType czy WOFF; możliwe jest nawet ładowanie modeli 3D w formacie glTF. Biblioteka wspiera mikrokontrolery producentów takich jak: STMicroelectronics (seria STM32), Espressif lub NXP.

LVGL potrafi wykorzystać możliwości nowoczesnych mikroprocesorów do zastosowań HMI. Posiada zaawansowany silnik renderujący, który wspiera rysowanie łuków, elementów półprzeźroczystych oraz gradientów. Dodatkowo ma wbudowane sterowniki do dedykowanych układów graficznych obecnych w niektórych mikroprocesorach.

Bibliotekę można ograniczyć, aby mogła zostać zastosowana w słabszych układach. W pliku konfiguracyjnym można wyłączyć wszystkie nieużywane elementy, wsparcie dla różnych formatów koloru, zewnętrzne biblioteki i inne funkcje, aby zmniejszyć rozmiar pliku wsadowego. Można również zastosować mniej zaawansowany silnik renderujący, aby zmniejszyć obciążenie procesora. Biblioteka nie wymaga dużo pamięci, choć zależy to od liczby utworzonych elementów.

  1. Pierwsze kroki

W pierwszej kolejności należy zainstalować bibliotekę i dołączyć ją do projektu. Procedura będzie się różnić w zależności od typu projektu. Przykładowo w projektach wykorzystujących ESP-IDF wystarczy dodać zależność (dependency) lvgl/lvgl. Możliwe jest również bezpośrednie dodanie kodu biblioteki do projektu.

Kolejnym krokiem jest konfiguracja. Odbywa się przez edycję pliku lv_conf.h lub za pomocą narzędzia Kconfig. W konfiguracji można ustawić ilości dostępnej pamięci i sposób jej alokacji, włączyć, bądź wyłączyć różne funkcjonalności, w tym zewnętrzne biblioteki lub dostępne elementy interfejsu.

Przed użyciem należy zainicjalizować bibliotekę wywołując funkcję lv_init(). Następnie należy utworzyć instancję wyświetlacza lv_display_t używając funkcji lv_display_create(). Pierwszy utworzony po inicjalizacji wyświetlacz staje się wyświetlaczem domyślnym. Niektóre funkcje zamiast instancji lv_display_t akceptują wartości NULL i w takim wypadku korzystają z domyślnego. Razem z wyświetlaczem tworzony jest ekran, czyli obiekt UI stanowiący korzeń drzewa obiektów na danym wyświetlaczu. Instancję aktywnego ekranu można uzyskać korzystając z funkcji lv_display_get_screen_active().

Interfejs jest budowany z elementów zwanych w LVGL obiektami (objects). Elementy są ułożone w hierarchię mającą postać drzewa. Funkcje do tworzenia obiektów przyjmują jako parametr element rodzica. Rodzicem może być dowolny element, w szczególności element pusty, co pozwala na grupowanie elementów. Mechanizm jest zbliżony do użycia tagu div w języku HTML. Szczegółowy opis i listę elementów można znaleźć w dokumentacji biblioteki.

Elementy na ekranie można ustawiać w sposób absolutny, ustawiając bezpośrednio jego koordynaty funkcją lv_obj_set_pos()/lv_obj_set_x()/lv_obj_set_y() lub w sposób dynamiczny, w odniesieniu do rodzica lv_obj_align() albo innego obiektu lv_obj_align_to(). Warto zaznaczyć, że pozycja zostanie automatycznie wyliczona na nowo tylko w przypadku powiązania z rodzicem (w przypadku, kiedy ten zmieni swoje położenie).

Wygląd elementów można zmieniać używając stylów. Właściwości stylów, można ustawić bezpośrednio dla danego obiektu, lub utworzyć instancję lv_style_t, ustawić odpowiednie właściwości i zastosować do wielu komponentów za pomocą funkcji lv_obj_add_style(). Mechanizm jest podobny do klas w języku CSS, w szczególności, że style w LVGL również działają kaskadowo. Pełna lista właściwości stylów znajduje się się dokumentacji biblioteki.

W celu dodania obsługi urządzeń wejścia (panel dotykowy, myszka, klawiatura) należy utworzyć instancję lv_indev_t i podłączyć do niej (lv_indev_set_read_cb()) funkcję callback, która będzie odczytywać dane z urządzenia. W przypadku paneli dotykowych obsługujących kilka punktów dotyku jednocześnie (multitouch), należy dla każdego punktu dodać osobną instancję lv_indev_t. Urządzenia wejścia są obsługiwane w trybie aktywnego czekania (polling), czyli będą automatycznie odczytywane co pewien czas.

  1. Licznik góra/dół

Poniżej zaprezentowano jak zaimplementować prostą aplikację. Aplikacja przechowuje stan licznika i wyświetla go na etykiecie. Użytkownik może zmniejszyć licznik przyciskiem „-” lub zwiększyć przyciskiem „+”.

Dla ułatwienia wykorzystano możliwość użycia biblioteki SDL jako platformy uruchomieniowej. Dzięki temu testy można przeprowadzać bezpośrednio na komputerze, bez potrzeby posiadania mikroprocesora z wyświetlaczem. Bibliotekę SDL2 należ zainstalować, a następnie ustawić LV_USE_SDL na 1 w pliku konfiguracyjnym, aby włączyć integracje z nią.

Na początku programu należy wywołać funkcję inicjalizującą, utworzyć wyświetlacz, a także urządzenie wejścia. W przypadku SDL2 obie te rzeczy są już w LVGL zaimplementowane i wystarczy wywołać gotowe funkcje. Na końcu funkcji main umieszczona została obsługa pętli zdarzeń (event-loop). Odpowiada ona za odświeżanie UI w odpowiedzi na różne zdarzenia, na przykład wykrycie i przetworzenie kliknięcia z panelu dotykowego.


Pierwszy element jaki zostanie dodany to etykieta do wyświetlania wyniku. Dodana zostanie bezpośrednio jako dziecko domyślnego aktywnego ekranu. Etykieta zostanie ustawiona na środku ekranu funkcją lv_obj_align. Funkcja dodatkowo przyjmuje przesunięcie w pikselach od punktu odniesienia, w tym wypadku ustawione w obu osiach na zero. Zastosowano funkcję lv_label_set_text_static, a nie lv_label_set_text, ponieważ jako argument przekazano literał tekstowy, który jest dostępny przez cały czas wykonywania programu, nie ma więc potrzeby kopiowania tekstu do wewnętrznej pamięci etykiety.

Następnie zostaną dodane dwa przyciski oznaczone „+” i „-”. Przy dodawaniu przycisków skorzystano z możliwości dodania przesunięcia w lv_obj_align, aby nieco odsunąć je od krawędzi. Sam w sobie przycisk nie przechowuje tekstu, czy obrazków wyświetlonych na nim. Są to osobne elementy, które mają przycisk jako rodzica. W tym wypadku wystarczyło dodać etykietę, ponieważ LVGL dostarcza zestaw symboli w postaci czcionki. Pełna lista symboli dostępna jest w dokumentacji biblioteki.



Aby aplikacja stała się interaktywna, do guzików trzeba dodać funkcję zwrotną (callback) wywoływaną w momencie wciśnięcia. Wartość licznika jest przechowywana w globalnej zmiennej g_counter. Do uzyskania dostępu do etykiety wykorzystano funkcje dostępu do drzewa elementów lv_obj_get_parent i lv_obj_get_child. Argument 0 oznacza dostęp do najstarszego (pierwszego w kolejności dodawania) dziecka. Funkcja przyjmuje również wartości ujemne, które odwracają kolejność dostępu, czyli -1 oznacza dostęp do ostatniego w kolejności dodawania dziecka.

Do połączenia funkcji zwrotnych ze zdarzeniem służy funkcja lv_obj_add_event_cb. Trzeci parametr tej funkcji to filtr zdarzeń, jest więc możliwe dodanie jednej funkcji do przetwarzania różnych zdarzeń. Czwarty parametr to znany w języku C parametr typu void* służący do przekazania do funkcji zwrotnych parametrów użytkownika.

Wygląd elementów można zmieniać ustawiając właściwości bezpośrednio dla danego elementu, lub za pomocą stylów. Style przydają się kiedy kilka elementów ma mieć taki sam wygląd, ponieważ pozwalają uniknąć powtarzania się. Działają kaskadowo, czyli wartości właściwości jednego stylu mogą zostać nadpisane przez drugi dodany później. W przykładzie są dodane dwa guziki, które mają mieć ten sam wygląd. Dobrym pomysłem jest więc użyć w ich przypadku stylów.

Elementy mają różne stany zależnie od tego co się z nimi dzieje, na przykład: kiedy użytkownik najechał na element kursorem, kiedy został wciśnięty lub kiedy nic się nie dzieje. Do różnych stanów można przypisać różne style, które będą działały w sposób kaskadowy. Przy dodawaniu stylu do obiektu należy podać selektor, w jakim stanie styl jest aktywny. Mechanizm działa podobnie jak pseudoklasy w języku CSS. Dla pojedynczych zmian lub elementów można ustawić je bezpośrednio na elemencie.

Logikę aplikacji można zaimplementować korzystając z wzorca projektowego obserwator. LVGL dostarcza obiekt obserwowany lv_subject_t, do którego można podłączyć obserwatorów. Niektóre elementy maja wbudowane specjalne funkcje ułatwiające wykonywanie działań na obiektach obserwowanych. Przykładowo, tekst etykiety może zostać powiązany z takim obiektem, dzięki czemu będzie automatycznie aktualizowany za każdym razem, kiedy zmieni się obserwowana wartość. Innym przykładem jest przycisk, który po wciśnięciu może zmienić aktualną wartość. Można też podłączyć własny obserwator w postaci funkcji zwrotnej.

Implementacja wyglądała by następująco: zmienna globalna przechowująca licznik zostanie zamieniona na instancję lv_subject_t. Tekst etykiety zostanie powiązany z obiektem obserwowanym za pomocą funkcji lv_label_bind_text. Guziki po kliknięciu będą automatycznie dodawać do aktualnej wartości +1 lub -1. Posłuży do tego funkcja lv_obj_add_subject_increment_event, która dodaje do obiektu inkrementację wartości obiektu obserwowanego o podaną stałą, jako obsługę podanego w argumencie zdarzenia.

Poniżej podano jaki kod należy dodać, aby skorzystać z lv_observer_t. Funkcje zwrotne s_on_down_clicked oraz s_on_up_clicked nie będą w tym wypadku potrzebne, tak samo jak początkowa inicjalizacja tekstu etykiety.

  1. Przykład aplikacji: edytor tekstu

Jako przykład bardziej rozbudowanej aplikacji, wykorzystującej: czcionki wektorowe, integrację z systemem plików oraz rozbudowane elementy UI, zostanie przedstawiony edytor tekstu. UI jest z jednej strony inspirowane tradycyjną aplikacją desktopową, przez zastosowanie paska narzędzi, z drugiej strony jest dostosowana do ekranów dotykowych dzięki klawiaturze ekranowej. Do budowy wykorzystano gotowe elementy z biblioteki bez wprowadzania większych zmian, jak również zastosowano własne elementy stworzone na podstawie tych gotowych.

Do ułożenia elementów użyto układu typu flex. Klawiatura, pole tekstowe i pasek narzędzi są ułożone w kolumnę. Sam w sobie pasek narzędzi jest kontenerem flex typu wierszowego. Automatycznie wyliczane odstępy między elementami są wspierane i zostały wykorzystane do umiejscowienia przycisku zamknięcia. Układ flex w LVGL jest zbliżony do tego znanego z języka CSS. Jest w tym wypadku rozwiązaniem bardzo wygodnym pozwalającym łatwo umiejscowić komponenty na ekranie.

Przykładem zaawansowanego użycia komponentów jest okienko do wyboru pliku do zapisu (jak i odczytu). Do stworzenia wykorzystano element lv_msgbox, który w swojej podstawowej wersji służy do wyświetlania krótkich wiadomości użytkownikowi. Główna część elementu w najprostszym przypadku jest etykietą z tekstem wiadomości. Można jednak zamiast etykiety dodać więcej elementów i potraktować okienko wiadomości jako okno ogólnego przeznaczenia. Lista plików jest zbudowana w oparciu o tabelę z własną funkcją rysującą. Dzięki temu wymaga ona znacznie mniej pamięci, niż taka w oparciu o listę etykiet.

Dzięki bibliotece stb_truetype, LVGL wspiera czcionki wektorowe w formacie TrueType. W celu skorzystania z tej funkcji, w konfiguracji należy włączyć silnik czcionek Tiny TTF. Czcionkę elementu (np. pola tekstowego) można zmienić w trakcie pracy aplikacji. Zmodyfikowany element zostanie automatycznie oznaczony do ponownego narysowania. Silnik Tiny TTF wspiera zmianę rozmiaru czcionki. Rozmiar może zostać zmieniony w dowolnym momencie po załadowaniu czcionki. Należy pamiętać o ręcznym oznaczeniu, że nastąpiła zmiana stylu, żeby elementy, które z niego korzystają zostaną automatycznie odświeżone. Dzięki wsparciu kodowania UTF-8 można w pełni wykorzystać możliwości wielojęzycznych czcionek. Szeroki wybór otwartych czcionek w formacie TrueType można znaleźć na stronie Google Fonts.

Aplikacja wykorzystuje moduł lv_fs do obsługi plików, który dostarcza wspólny interfejs dla wielu systemów plików. Dostępnych jest kilka implementacji, między innymi: oparta o interfejs standardowej biblioteki języka C, API POSIX lub Win32. Obsługiwanych może być kilka implementacji jednocześnie. Ścieżki zaczynają się od wielkiej litery identyfikującej sterownik systemu plików. Przykładowo do litery P można przypisać implementację POSIX, a W implementację Win32. W takim wypadku ścieżka P:/home/user/file.txt zostanie obsłużona przez POSIX, a W:C:\Users\user\file.txt przez Win32.

  1. Podsumowanie

LVGL ma szerokie zastosowania: od prostych interfejsów na sprzęt o bardzo ograniczonych zasobach, po interfejsy bardzo rozbudowane, przypominające aplikacje mobilne. Projektowanie usprawnia szeroki wybór gotowych komponentów, które jednakże łatwo można zmodyfikować, zależnie od potrzeb. Pracę ułatwia również system stylów oraz układy flex i grid oparte na tych znanych z języka CSS. Wsparcie systemu plików, alokacji i logów niezależnie od platformy pozwala na pisanie aplikacji w pełni przenośnych lub łatwych do przeniesienia na inną platformę. Możliwość uruchomienia na komputerze z systemem Linux, Windows lub Mac pozwala przyśpieszyć projektowanie UI. Jest to więc bardzo dobry wybór do stworzenia profesjonalnych interfejsów graficznych w systemach wbudowanych.