Dzisiaj poznajemy podstawy LVGL, czyli "lekkiej i wszechstronnej biblioteki graficznej dla embedded". W celu ułatwienia nam pracy, w tym temacie posłużę się jej gotową integracją z płytką ESP32-2432S028R a same elementy interfejsu będę tworzyć w SquareLine Studio. Zobaczymy tu, jak może wyglądać tworzenie interfejsu użytkownika na płytkę ESP32 za pomocą wizualnego edytora SquareLine a potem przeanalizujemy jak on działa.
UWAGA: Ten temat opiera się o SquareLine Studio które w darmowej wersji jest nieco okrojone. Jeśli chcecie pisać w LVGL ręcznie, bez SquareLine Studio, to polecam poczekać na kolejną część, gdzie zrobię program bez edytora graficznego UI.
Ten temat to kontynuacja historii o ESP32-2432S028R. Oto poprzednie części:
ESP32 i wyświetlacz dotykowy - tutorial - część 1 - jak programować? Podstawy
ESP32 i wyświetlacz dotykowy - część 2 - jak rysować piksele, linie, kształty, kwestia wydajności
ESP32 i wyświetlacz dotykowy - tutorial cz. 3 - interakcje, gry i zabawy
ESP32 i wyświetlacz dotykowy - tutorial cz. 4 - pogoda z internetu, API, JSON
Ten temat będzie dotyczył LVGL, więc od razu warto przytoczyć ich dokumentację:
https://docs.lvgl.io/master/intro/index.html
Przykłady LVGL:
https://docs.lvgl.io/master/examples.html
Każdy przykład powyżej ma swój kod źródłowy, możecie je również uruchomić w projekcie który tu zaprezentuję.
Uruchomienie przykładu esp32-smartdisplay-demo
Moglibyśmy ręcznie integrować LVGL z naszym projektem, ale po co? Tym bardziej, że staram się celować z tym tematem w potrzeby początkujących.... z tego też powodu użyjemy gotowego przykładu dostępnego za darmo na Github. Do pobrania mamy cały projekt w PlatformIO:
https://github.com/rzeldent/esp32-smartdisplay-demo
Pobieramy repozytorium jako zip lub "klonujemy" je poprzez Git.
Otwieramy w PlatformIO:
Ten projekt wspiera szeroką gamę płytek o różnorodnych wyświetlaczach. Najważniejsze jednak, że wspiera też i naszą ESP32-2432S028R.
Na początku nieco się głowiłem, którą wersję mam uruchomić, bo jest tutaj esp32-2432S028R, esp32-2432S028Rv2 o esp32-2432S028Rv3. U mnie dopiero ta trzecia opcja dała obraz. Po prostu kompilujemy i wgrywamy i patrzymy jaki jest efekt:
No schludnie to nie wygląda, ale efekt jest, grafika, kod QR, a nawet interaktywny przycisk.
Przeanalizujmy kod przykładu. Nagłówki:
Kod: C / C++
Integrację LVGL za nas już wykonano - znajduje się ona w esp32_smartdisplay.h.
Funkcja obsługująca zdarzenie kliknięcia przycisku (potem zobaczymy, jak ją się dopina do przycisku):
Kod: C / C++
Powyższa funkcja też zmienia tekst labelu (wersja set_text_fmt obsługuje ForMaTowanie takie jak sprintf, printf).
Argumentem funkcji jest lv_event_t, czyli obiekt reprezentujący zdarzenie.
Obsługa przycisku od obracania:
Kod: C / C++
lv_disp_set_rotation ustawia rotację ekranu (enumeracja lv_disp_rot_t)
No i funkcja setup - inicjalizacja programu:
Kod: C / C++
Wywoływane też jest tam smartdisplay_init i ui_init, które może być napisane przez nas lub przez SquareLine Studio:
Kod: C / C++
Poniżej z kolei używane jest rozszerzenie do przygotowania kodu QR. Wszystko opiera się o gotowe mechanizmy:
Kod: C / C++
Dokumentacja QR code: https://docs.lvgl.io/master/libs/qrcode.html
Został kod głównej pętli.
Kod: C / C++
W głównej pętli najważniejsze jest regularne wywoływanie lv_timer_handler czyli aktualizacja LVGL. Oprócz tego dodane jest tam sztucznie obsługiwanie zdarzenia co 500 ms w którym m. in. zmieniany jest kolor diody i aktualizowany jest tekst na ekranie.
Ale to nie wszystko... ten projekt nie bazuje na samym kodzie C, mamy tu też plik SPJ:
Jest to projekt UI wykonany w SquareLine Studio. SquareLine Studio zamienia go potem na kod C, który dopiero później kompilujemy normalnie pod ESP. Sprytne! Tu nie ma żadnego "pośredniego formatu" UI tak jak HTML czy XML, od razu mamy kod.
Zastanówmy się zatem gdzie jest ustawiane zdarzenie wciśnięcia przycisku, chociażby OnAddOneClicked?
Jest to automatycznie podpinane przez SquareLine Studio:
Powyższy kod pokazuje jak tworzony jest interfejs w kodzie C. Tworzone są osobno panele i relacje między nimi oraz podpinane są funkcje obsługi zdarzeń.
Ale ten kod jest automatycznie generowany z wspomnianego pliku SPJ, poniżej odpowiedni fragment:
Ale jeszcze czegoś nam brakuje. Co ustawia callback ui_event_btnCount?
To z kolei jest w folderze "screens", tworzenie "ekranu" ustawia callback:
Kod: C / C++
Analizując głębiej:
- lv_obj_set_width - ustawia szerokość obiektu
- lv_obj_set_height - ustawia wysokość obiektu
- lv_obj_set_align - ustawia wyrównanie obiektu względem jego rodzica
- lv_obj_set_x - ustawia pozycję X obiektu
- lv_obj_set_y - ustawia pozycję Y obiektu
- lv_obj_add_flag - dodaje określone flagi do obiektu (np. włącza scrollbar)
- lv_obj_clear_flag - usuwa określone flagi z obiektu
- lv_label_set_text - ustawia tekst wyświetlany labelu
- lv_img_set_src - ustawia źródło obrazu do wyświetlenia
- lv_obj_add_event_cb - dodaje callback obsługujący zdarzenia dla obiektu
To dużo wyjaśnia. Cały powyższy kod jest generowany automatycznie. W ten sposób SquareLine tworzy UI w LVGL.
Podobnie z obrazkami - obrazki zamieniane są bezpośrednio na bajty w pamięci:
Instalacja SquareLine Studio
Pora kończyć nasze programistyczne "śledztwo" oraz wypróbować SquareLine Studio w praktyce. Program jest płatny, ale jest dostępna darmowa wersja próbna oraz darmowa lecz okrojona wersja Personal (nie do zastosowań komercyjnych):
https://squareline.io/pricing/licenses#licenseTable
Czy warto dać temu szansę? Przekonajmy się.
Pobieramy:
Trial starczy na 30 dni:
Po uruchomieniu wita nas prawie pusty ekran, na szczęście jednak nieco przykładów jest dostępnych:
Wyglądają naprawdę nieźle...
Naprawa projektu SquareLine Studio
W tym punkcie chciałem po prostu otworzyć wspomniany projekt SPF z Github i spróbować go przerobić, ale o dziwo SquareLine tutaj zawiódł. Po prostu nie reagował na otwieranie projektu. Do tej pory nie wiem co jest tam nie tak, ale udało mi się to naprawić w prosty sposób:
1. utworzyłem inny projekt SquareLine studio
2. zapisałem go w osobnym folderze
3. nadpisałem jego plik SPF plikiem z Githuba
4. przekopiowałem folder assets ręcznie
Odpalamy i... działa:
Nie wiem co z poprzednim projektem było nie tak, może brakowało jakiegoś pliku metadata, ktoś dodał za dużo plików do .gitignore? Można gdybać.
Podstawy SquareLine Studio
Wreszcie mamy otwarty projekt SquareLine Studio więc pora spróbować go jakoś zmodyfikować.
Zobaczymy, na co tak dokładnie pozwoli nam ten program. Sprawdzimy jak wygląda tworzenie w nim różnych elementów, podpinanie do nich zdarzeń, itd.
Każdy element można przesuwać, edytować i konfigurować:
Można wstawić tu inne ikonki, oczywiście też interaktywne (można potem je edytować z poziomu kodu):
Potem można zapisać i wyeksportować projekt, ale... znów jest problem:
Co to za ścieżka? To nie moja. Projekt pamięta ścieżkę poprzedniego użytkownika. Otworzyłem plik SPF - rzeczywiście, jest ona tutaj zaszyta:
Wkleiłem tam odpowiednią u mnie ścieżkę (we wnętrzu projektu PlatformIO).
Export się powiódł:
W naszym projekcie PlatformIO pojawił się obrazek:
Kompilujemy i wgrywamy:
Dziala! To naprawdę proste, tyle trzeba im przyznać.
Przykłady elementów UI z SquareLine Studio
Może zacznijmy od elementu suwaka, czyli Slider. Suwak można "złapać' i przesunąć, co skutkuje wywołaniem jego zdarzenia zmiany. Dodatkow można też ustawić jego wartości (bieżącą, min, max) oraz je odczytywać.
Po zaznaczeniu suwaka możemy edytować jego parametry oraz dodawać zdarzenia. Jednym z możliwych zdarzeń jest "VALUE_CHANGED", czyli zmiana wartości. Wpisujemy tam nazwę nasz funkcji do wywołania. Pole "Don't export function" pozwala użyć istniejącej funkcji, w przeciwnym razie SquareLine Studio ją tworzy.
Eksportujemy...
Rzeczywiście, funkcja została wygenerowana. Możemy coś do niej dopisać. Może spróbujmy wyświetlać wartość z suwaka na jednym z labeli?
Kod: C / C++
Swoją drogą widać fajny sposób na pobranie obiektu ze zdarzenia - lv_event_get_target.
Funkcje typu get i set są też opisane w dokumentacji. Wgrywamy i testujemy:
Nasz pasek działa:
Podobnie mamy np. komponent checkbox - pole do zaznaczenia. Wybieramy je z listy i przeciągamy na nasz widok.
Dopisujemy mu zdarzenie zmiany:
Obsługa zdarzenia w kodzie - wystarczy sprawdzić stan obiektu:
Kod: C / C++
Tak prezentuje się to w praktyce:
Kliknięcie w checkbox zmienia jego stan:
[ 8680][I][main.cpp:49] MyCheckChange(): Checkbox is checked
[ 9340][I][main.cpp:51] MyCheckChange(): Checkbox is unchecked
[ 11110][I][main.cpp:49] MyCheckChange(): Checkbox is checked
[ 11620][I][main.cpp:51] MyCheckChange(): Checkbox is unchecked
Podobnie mamy kontrolkę Switch - czyli przełącznik. Tutaj też są dwa stany. Analogicznie, wybieramy go z listy:
Dodajemy:
Ustawiamy zdarzenie:
Podobnie dopisujemy funkcję (obsługę zmiany ) i testujemy, działa:
[ 52807][I][main.cpp:50] MyToggleChange(): Toggle is ON
[ 55027][I][main.cpp:52] MyToggleChange(): Toggle is OFF
[ 55867][I][main.cpp:50] MyToggleChange(): Toggle is ON
Podstawy podstawami, ale tu też są bardziej zaawansowane kontrolki. Przykładowo jest wybór koloru:
Przeciągamy go na ekran:
Tak jak wcześniej, dodajemy mu zdarzenie zmiany stanu:
Na próbę wgrałem to w takiej postaci na wyświetlacz:
Teraz trzeba tylko pomyśleć jak odebrać informacje o kolorze. Na tej platformie kolory są 16-bitowe, poniżej wgląd w unię/struktury z nagłówka użytej biblioteki:
Kod: C / C++
W naszym projekcie mamy zdefiniowane LV_COLOR_16_SWAP jako 1, więc zielony kolor jest podzielony na dwa obszary w pamięci. Biorąc to pod uwagę, napisałem funkcję która sprowadza wartości kolorów do tego samego zakresu i wyświetla je w momencie zmiany kontrolki:
Kod: C / C++
Testujemy:
[259878][I][main.cpp:41] MyColorChange(): Selected color: R=28, G=63, B=0
[259960][I][main.cpp:41] MyColorChange(): Selected color: R=32, G=63, B=0
[260041][I][main.cpp:41] MyColorChange(): Selected color: R=32, G=63, B=0
[260092][I][main.cpp:41] MyColorChange(): Selected color: R=32, G=63, B=0
[263961][I][main.cpp:41] MyColorChange(): Selected color: R=62, G=6, B=0
[264012][I][main.cpp:41] MyColorChange(): Selected color: R=62, G=4, B=0
[264071][I][main.cpp:41] MyColorChange(): Selected color: R=62, G=6, B=0
Kolory odpowiadają temu co pokazuje kontrolka, chociaż mogłem je jeszcze przerzucić na pełne bajty (do zakresu 0-255).
Teraz może coś bardziej interaktywnego - pole tekstowe. Spróbujmy po prostu je dodać, przeciągnąć na nasz widok. Pole tekstowe tutaj to TextArea.
Kompilujemy, oto rezultat:
Niestety klawiatura nie pokazuje się sama... wygląda na to, że to wymaga jednak dodatkowego konfigurowania w kodzie. Krótkie sprawdzenie dokumentacji potwierdza moje obawy:
https://docs.lvgl.io/master/widgets/keyboard.html
Ale o tym już innym razem...
Na koniec mała wskazówka
Jeśli nie chcemy by funkcja wywoływana z GUI caly czas tworzyła się w pliku ze zdarzeniami to wystarczy zaznaczyć:
Dzięki temu możemy utworzyć ją raz u nas w kodzie w innym pliku i tylko z niej korzystać.
Oczywiście LVGL oferuje znacznie więcej, można tworzyć widoki ze scrollem (suwakami), można przełączać między ekranami, itd, ale o tym już innym razem.
Podsumowanie
Zaprezentowałem tutaj podstawy LVGL w połączeniu z SquareLine Studio. LVGL oferuje szeroką gamę gotowych kontrolek i mechanizmów z których możemy szybko utworzyć UI naszego projektu. SquareLine Studio oferuje wygodny, wizualny edytor widok interfejsu dla naszej aplikacji, który potem można eksportować bezpośrednio do kodu C, który kompilujemy chociażby w PlatformIO tak jak każdy inny kod który byśmy sami pisali. SquareLine Studio konwertuje nawet obrazki do tablic bajtów w plikach C i nawet z tym nie ma żadnego problemu.
Niestety SquareLine Studio za darmo dostępne jest tylko w wersji okrojonej bądź 30-dniowej, a cena pełnej wersji jest raczej nieosiągalna dla hobbystów. Z tego powodu w kolejnych częściach będziemy już pewnie tworzyć interfejsy ręcznie, tj. po prostu będziemy samodzielnie pisać kod w stylu tego co dzisiaj podejrzeliśmy.
Planuję m. in. omówić chociażby wspomnianą już kwestię klawiatury.
Jak na razie mogę tylko dociekliwym raz jeszcze polecić przykłady z dokumentacji LVGL:
https://docs.lvgl.io/master/examples.html
wszystko jest do odpalenia w projekcie który poleciłem, gotowe z miejsca.
Na koniec zapytam - wolicie tworzyć UI z kodu, czy w edytorze wizualnym? A może znacie jakąś alternatywę dla SquareLine Studio godną uwagi?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.