Czy da się podłączyć zwykłą klawiaturę USB do urządzenia z ESP32 i połączyć ją z wyświetlaczem dotykowym? Tak - to wcale nie jest takie trudne. W tym temacie krok po kroku pokażę, jak uruchomić tryb Hosta USB, zintegrować klawiaturę z LVGL i poprawnie obsłużyć zdarzenia wejścia.
Prezentację oparłem na płytce z ESP-S3, a dokładniej Waveshare ESP32-S3-Touch-LCD-5, którą już wcześniej osobno omawiałem. Całość podzieliłem na etapy tak, aby sposób wdrażania klawiatury był bardziej przystępny i zrozumiały. Temat zakłada podstawową znajomość C++, ale w razie czego na końcu zamieszczę kod mojego przykładu.
Warto również wiedzieć, czym jest HID, gdyż ten temat będzie dotyczyć urządzeń HID. HID (Human Interface Device) to standard klasy urządzeń USB przeznaczonych do komunikacji z użytkownikiem. Do tej grupy należą m.in. klawiatury, myszy, joysticki, gamepady oraz inne peryferia. Ich ogromną zaletą jest to, że system (lub mikrokontroler) nie musi znać konkretnego modelu urządzenia – wystarczy obsługa klasy HID, aby poprawnie odebrać dane wejściowe.
HID komunikuje się poprzez proste pakiety danych zwane raportami. Ich struktura jest opisana w tzw. Report Descriptor, który urządzenie wysyła do hosta podczas enumeracji. To właśnie ten deskryptor mówi, ile bajtów ma raport, co oznacza każdy bit oraz jakie typy zdarzeń mogą się pojawić (np. klawisz wciśnięty, zwolniony, modyfikatory typu Shift czy Ctrl). Dla nas sprowadza się to jednak tylko do znajomości formatu raportu urządzenia który chcemy obsłużyć, i do odczytania z tego raportu klawiszy. Większość pracy zrealizuje za nas gotowa biblioteka od ESP IDF.
Które układy z serii ESP obsługują USB? Podział umieściłem w tabelce, zrealizowane na bazie dokumentacji od espressif.
| Układ | USB OTG High-Speed | USB OTG Full-Speed | USB-Serial-JTAG | Full-Speed PHY | High-Speed PHY | ESP32-P4 | √ | √ | √ | √ | √ | ESP32-S3 | X | √ | √ | √ | X | ESP32-S2 | X | √ | X | √ | X | ESP32-C6 | X | X | √ | √ | X | ESP32-C3 | X | X | √ | √ | X | ESP32-C2 | X | X | X | X | X | ESP32 | X | X | X | X | X | ESP8266 | X | X | X | X | X |
Jak widać sprzętowe USB może być jednym z argumentów za wyborem nowszej wersji ESP, zwykły ESP32 go nie obsługuje. Stąd w temacie wybór S3, na nim można wygodnie uruchomić HID. Najpierw jednak środowisko pracy. Zaczynamy.
Krok 1: LVGL
Pierwszym etapem jest uruchomienie LVGL, czyli wyświetlania i sterownika ekranu dotykowego. Użyłem do tego zewnętrznej biblioteki:
https://github.com/esp-arduino-libs/ESP32_Display_Panel
Omawiałem to już w osobnym temacie:
Waveshare ESP32-S3-Touch-LCD-5 - Wi-Fi, BLE, CAN, RS485 i ekran dotykowy 800x480
Krok 2: ArduinoOTA
Drugim etapem jest uruchomienie aktualizacji wsadu przez Wi-Fi. Jest to niezbędne dla komfortowej pracy, bo na płytce mamy tylko jedno złącze USB, więc nie dałoby się jednocześnie programować i obsługiwać klawiatury, a odłączanie z każdą próbą sprzętu nie jest wygodne. ArduinoOTA już omawiałem w temacie:
Jak programować płytkę Wemos D1 (ESP8266) w kształcie Arduino? ArduinoOTA w PlatformIO
Tu przygotowałem połączenie ArduinoOTA z LVGL. Poniższy fragment inicjuje wyświetlanie, łączy ESP ze zdefiniowanym w kodzie punktem Wi-Fi i pokazuje nadany IP na ekranie. Dodatkowo cały czas nasłuchuje, czy nie jest wgrywana aktualizacja.
Kod: C / C++
Najważniejsze jest połączenie z Wi-Fi, tego nie możemy zepsuć, bo będziemy znów wgrywali wsad przewodowo, oraz regularne sprawdzanie pakietów OTA - ArduinoOTA.handle w głównej pętli.
Powiązane platformio.ini:
[platformio]
default_envs = Board_WaveShare5
[env:Board_WaveShare5]
framework = arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
board = BOARD_CUSTOM_8MB
monitor_speed = 115200
upload_protocol = espota
upload_port = 192.168.0.162
monitor_port = com38
lib_ldf_mode=deep
lib_deps =
https://github.com/esp-arduino-libs/ESP32_Display_Panel.git#v1.0.2
https://github.com/esp-arduino-libs/ESP32_IO_Expander.git#v1.1.0
https://github.com/esp-arduino-libs/esp-lib-utils.git#v0.2.0
https://github.com/lvgl/lvgl.git#v8.4.0
build_flags =
-DLV_CONF_INCLUDE_SIMPLE
-DLV_LVGL_H_INCLUDE_SIMPLE
-DBOARD_WAVESHARE5=1
-I src
monitor_filters = esp32_exception_decoder
Ustawienie upload_port na adres IP to nie błąd - w ten sposób mówimy ArduinoOTA, gdzie jest płytka.
Krok 3: Wykrycie urządzenia USB
Teraz uruchamiamy tryb USB Host w ESP32-S3 i sprawdzamy, czy do portu USB faktycznie zostało podłączone jakiekolwiek urządzenie. Jest to absolutna podstawa – zanim zaczniemy myśleć o klawiaturze HID, raportach i mapowaniu klawiszy do LVGL, musimy mieć pewność, że:
- stos USB Host działa poprawnie
- urządzenie przechodzi enumerację
- potrafimy odczytać jego deskryptory
Nasza płytka ma już złącze USB, ale tu uwaga - nie można pomylić tego z płytkami gdzie na USB jest wyprowadzony konwerter USB na UART. Tu nam zależy na sprzętowym USB, żaden CH340 tu nie przejdzie.
Dodatkowo, jako że płytka ma złącze żeńskie USB C, to przyda się przejściówka męskie USB C na USB A żeńskie:
ESP32-S3 ma sprzętowy kontroler USB OTG, więc nie potrzebujemy żadnych zewnętrznych układów – wystarczy poprawnie skonfigurować bibliotekę USB Host z ESP-IDF, która jest dostępna również w środowisku Arduino.
Kod: C / C++
Dodajemy nagłówek, a potem:
- uruchamiamy bibliotekę USB Host (usb_host_install())
- rejestrujemy klienta USB (usb_host_client_register())
-ustawiamy callback usb_host_client_event_cb, który będzie informował nas o podłączeniu i utracie urządzenia klienta
Funkcją usb_read_device_info tworzymy opis urządzenia pokazywany na ekranie.
Kod: C / C++
Dodatkowo wywołujemy wszystko w init:
Kod: C / C++
oraz przetwarzamy zdarzenia USB w głównej pętli (usb_host_poll):
Kod: C / C++
Krok 4: Podstawowe zdarzenia klawiatury
Teraz potrzebne są dwa dodatkowe mechanizmy:
- przechwycenie klawiszy z klawiatury USB
- obsługa pola tekstowego LVGL
Tak się składa, że LVGL ma gotowy mechanizm pola tekstowego wraz z pełnoprawną edycją. Działa tam nawet kursor. Wystarczy zarejestrować sterownik przez strukturę lv_indev_drv_t i ustawić mu typ LV_INDEV_TYPE_KEYPAD. Dodatkowo ustawiamy funkcje, która będzie odczytywać zdarzenia klawiszy z bufora kołowego, tutaj kb_indev_read_cb.
Kod: C / C++
No tak, ale nie mamy żadnej kolejki - trzeba będzie ją samodzielnie zaimplementować. Poniżej na razie callback, który korzysta z mojej funkcji kb_ring_pop oraz z mojego bufora kołowego (warunek kb_ring_head != kb_ring_tail) do odczytywania klawiszy.
Kod: C / C++
Oto właściwa implementacja bufora kołowego - zdarzenia są wstawiane za "głową" bufora, a jego "ogon" wskazuje na ostatnie nieprzetworzone zdarzenie. Bufor ma skończony rozmiar, w razie przepełnienia nie dodaje kolejnych zdarzeń.
Kod: C / C++
Zostało jeszcze odebrać zdarzenia od klawiatury. Tylko skąd mamy wiedzieć, jak parsować pakiety HID? Można to wyczytać w dokumentacji:
https://usb.org/sites/default/files/hid1_11.pdf
Klawiatura raportuje zbiór wciśniętych w danym momencie klawiszy. Samodzielnie możemy potem określić, stan których klawiszy się zmienił, tj. które zostały zwolnione a które wciśnięte.
Kod: C / C++
Rezultat:
Klawiatura jest poprawnie widziana. Działają litery, cyfry i znaki specjalne. Można robić duże litery, choć caps lock natywnie nie działa. Działają strzałki, można poruszać kursorem, można usuwać znaki klawiszami delete i backspace,
Dodatek: eksperyment z myszką
Analogicznie można obsłużyć myszkę zgodną z HID. Znowu musimy odnieść się do dokumentacji HID i dodać własny parsing pakietu w hid_transfer_cb.
https://learn.microsoft.com/en-us/windows-har...ers/hid/keyboard-and-mouse-hid-client-drivers
Cytat:
Byte D7 D6 D5 D4 D3 D2 D1 D0 Comment 1 0 0 Ysign Xsign 1 M R L X/Y signs and R/L/M buttons 2 X7 X6 X5 X4 X3 X2 X1 X0 X data byte 3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 Y data bytes 4 Z7 Z6 Z5 Z4 Z3 Z2 Z1 Z0 Z/wheel data byte
Tak użyte dane można potem wyświetlić na ekranie, w ramach przykładu dodałem wyświetlanie poszczególnych odebranych raportów. Warto zwrócić uwagę, że nie mamy od myszki informacji o "absolutnym" położeniu kursora, jedynie mamy jego przesunięcie.
Kod: C / C++
Powyższy kod poprawnie dekoduje raporty od myszki i odczytuje z nich przesunięcie w osiach X, Y oraz Z (kółko). Odczytywany jest również stan przycisków (wciśnięty lub nie).
Podsumowanie
Sprzętowe USB w układach z serii ESP (ESP32-S2/S3/C3/P4) otwiera nowe możliwości, a jedną z nich jest pełnoprawna obsługa urządzeń HID takich jak właśnie pokazana klawiatura i myszka. Protokół HID jest bardzo prosty w użyciu, bo wszystko jest schowane za gotowymi bibliotekami, a my tylko odczytujemy bajty z raportów. W połączeniu z wyświetlaczem pozwala to zrobić namiastkę prostego mikrokomputerka działającego tak jak przykładowo ogólnoznane edytory tekstowe. W podobny sposób można też ożywić joystick lub gamepad.
Następnym krokiem tutaj może być próba uruchomienia ESP z zewnętrznym hubem USB tak, aby móc podłączyć jednocześnie i myszkę, i klawiaturę, ale tego spróbuję w osobnym temacie.
Załączam kod moich demek. Po więcej informacji odsyłam do dokumentacji od Espressif.
https://docs.espressif.com/projects/esp-iot-s.../en/latest/usb/usb_overview/usb_overview.html
https://github.com/espressif/esp-idf/blob/master/examples/peripherals/usb/host/hid/README.md
Jakie widzicie praktyczne zastosowania do roli hosta USB na ESP?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.