ESP32 CYD (ESP32-2432S028R): ST7789 zamiast ILI9341 — jak rozpoznać sterownik ekranu
Mam dwie płytki ESP32-2432S028R, popularnie „Cheap Yellow Display”. Z przodu wyglądają tak samo, ale ta z dwoma gniazdami USB ma pod szkłem inny kontroler ekranu: ST7789, a nie ILI9341. Konfiguracja pierwszej płytki dała na drugiej zamienione kolory, a sterownik ILI9342_DRIVER dał obraz, który „działał”, tylko był sprany. Łatałem gammę poprawkami z forów, a przyczynę pokazał dopiero odczyt rejestrów ID: spike na mniej niż 100 linii kodu. Poniżej cała droga, surowe bajty z odczytu i konfiguracja, która działa bez łatek.
Teza: „rysuje” to nie znaczy „właściwy sterownik”
Kontrolery ILI9341 i ST7789 mają wspólną podstawę komend MIPI DCS: adresowanie okna, zapis pamięci, MADCTL, włączanie wyświetlacza. Dlatego sterownik pisany pod jeden kontroler często coś narysuje na drugim. Różnice zaczynają się w sekwencji inicjalizacji: rejestry zasilania, tabele gamma i kolejność kolorów. Efektem jest obraz poprawny geometrycznie, ale z błędnymi kolorami. Taki błąd łatwo wziąć za „specyfikę tej matrycy” i zacząć poprawiać objaw.
Harry Percival w Test-Driven Development with Python pisze: „I'm always suspicious of a test I haven't seen fail” (s. 55). Sterownik, który rysuje, działa tu jak test, którego nigdy nie widziało się na czerwono. Ekran testowy z paskami RGB przechodzi na obu kontrolerach, więc nie rozróżnia, który z nich siedzi pod szkłem.
Kolejność, którą teraz stosuję przy każdej nowej płytce CYD:
- Odczytaj ID kontrolera.
- Dobierz sterownik do odczytanego ID.
- Dopiero potem kolejność kolorów, rotację i gammę.
Płytki: dwie takie same z wierzchu
| Płytka A (pierwsza) | Płytka B (wariant 2USB) | |
|---|---|---|
| Złącza | tylko micro-USB | micro-USB + USB-C (stąd nazwa „2USB”) |
| Moduł | ESP32-D0WD-V3 rev 3.1, 4 MB flash, CH340 | ESP32-D0WD-V3 rev 3.1, 4 MB flash, CH340 |
| Działająca konfiguracja TFT_eSPI | ILI9341_DRIVER + TFT_BGR, rotacja 1 | ST7789_DRIVER + TFT_BGR, rotacja 1 |
| Piny TFT i dotyku | wspólne | wspólne |
Środowisko: PlatformIO [email protected], TFT_eSPI 2.5.43, LVGL 9.6.0, SPI 40 MHz. Piny TFT są takie same na obu płytkach: SCLK 14, MOSI 13, MISO 12, CS 15, DC 2, podświetlenie 21. Dotyk XPT2046 na osobnej magistrali HSPI też działa tak samo. Różni się wyłącznie kontroler.
Objaw: zamienione i sprane kolory
Najpierw wgrałem na płytkę B konfigurację z płytki A. Każdy krok sprawdzałem na prostym ekranie testowym: paski RED/GREEN/BLUE podpisane nazwami, linijka z opisanymi pasami co 40 px i ramka na skrajnych pikselach.
ILI9341_DRIVER+TFT_BGR: czerwony i niebieski zamienione.ILI9341_DRIVER+TFT_RGB, rotacja 1: kolory zgodne z podpisami, ale obraz pionowy na poziomo trzymanej płytce, a linijka kończy się na 200.- To samo z rotacją 2: obraz poziomy, ale zapisanych tylko 240 z 320 kolumn. Po prawej zostają resztki po poprzednich rotacjach.
#define TFT_WIDTH 320przyILI9341_DRIVER: bez efektu.ILI9341_Defines.hnadpisuje wymiary, kompilator zgłasza tylko"TFT_WIDTH" redefined.
Pierwsza konfiguracja, która wypełniła cały ekran, to ILI9342_DRIVER (w TFT_eSPI: inicjalizacja ILI9341 z wymiarami 320×240), TFT_RGB i rotacja 2. Linijka doszła do 280, ramka była widoczna dookoła, kolory zgadzały się z podpisami. Wyglądało na rozwiązane.
Nie było. Wgrałem ten sam obraz RGB565 320×240, bajt w bajt, na obie płytki i położyłem je obok siebie. Na płytce A obraz wyglądał dobrze. Na płytce B czerń była szaroniebieska, kolory sprane, a faktura „przekontrastowana”.
Uwaga do wszystkich ocen obrazu w tym wpisie, także tych z listy wyżej: robiłem je wzrokowo, jedna osoba, jedno porównanie, bez luksomierza i bez zdjęć przy stałej ekspozycji. To wystarcza do wskazania wyraźnie lepszego wariantu, ale nie do pomiaru, o ile lepszy jest.
Łatki gammy, które nie naprawiają przyczyny
Skoro ten sam plik wygląda inaczej na dwóch płytkach, problemem nie jest obraz. Wiem to, bo najpierw i tak poprawiałem obraz: przyciemnienie, gamma w konwersji, rozmycie, konwersja bez ditheringu. Za każdym razem wynik nadal był „high contrast”.
Następnym podejrzanym była gamma panelu. Napisałem spike, w którym każde tapnięcie przełącza tabelę gamma, a obok leżała płytka A z tym samym obrazem jako wzorzec. Sprawdziłem cztery warianty:
| Wariant | Wynik (wzrokowo) |
|---|---|
| Domyślne tabele ILI9341 z TFT_eSPI | sprane kolory, szara czerń |
| Tabele ILI9342 z LovyanGFX (github.com) | czerń dobra, jasne i średnie tony prześwietlone, zieleń #2E7D32 wygląda na morską |
Tabele ILI9341_2_DRIVER z TFT_eSPI | gorzej niż wariant niżej |
Domyślne tabele + 0x26 (GAMMASET) z 2, delay(120), 0x26 z 1 | najlepszy z czterech, ale nie idealny |
Ostatni wariant to poprawka krążąca w wątkach o płytce 2USB (github.com). Działała, więc trafiła do firmware jako funkcja setPanelGamma(). Nie umiałem jednak wyjaśnić, dlaczego przełączenie krzywej gamma na 2 i z powrotem na 1 cokolwiek zmienia, skoro kończy w tym samym stanie. Kod, którego działania nie rozumiem, traktuję jako sygnał, że naprawiam objaw. Percival przy niestabilnych testach radzi „when in doubt, bump the timeout” (s. 601). Moim zdaniem po takiej zmianie test przechodzi, ale przestaje być widać, czy problemem była wolna maszyna, czy prawdziwy wyścig. setPanelGamma() było moim wydłużonym timeoutem.
Odczyt ID kontrolera ST7789
Kontrolery wyświetlaczy mają rejestry identyfikacyjne, ale rodziny ILI i ST odczytuje się różnie:
- ILI9341/9342 zwraca ID w rejestrze
0xD3(00 93 41albo00 93 42). TFT_eSPI ma do tegoreadcommand8(), ale korzysta ono ze sztuczki z rejestrem indeksowym0xD9, specyficznej dla ILI9341. - ST7789 zwraca ID przez
0x04(RDDID) i osobno przez0xDA/0xDB/0xDC(RDID1–3). W odczytach 24-bitowych przed danymi idzie jeden takt dummy.
Sterownik nie powinien wpływać na wynik, dlatego odczyt zrobiłem programowym SPI: bit-bang na pinach TFT, około 250 kHz. Celowo wolno: przy odczycie diagnostycznym nie potrzebuję szybkości, a nie chcę, żeby wynik zależał od timingu. Spike wysyła komendę przy DC w stanie niskim, przełącza DC i zbiera surowe bity z MISO:
static const int BB_SCLK = 14, BB_MOSI = 13, BB_MISO = 12, BB_CS = 15, BB_DC = 2;
static const int BIT_US = 2; // ~250 kHz
// Reads `bits` raw bits (MSB first) after the command; data is sampled on the rising edge.
static void bbRead(uint8_t cmd, int bits, uint8_t* out) {
memset(out, 0, (bits + 7) / 8);
digitalWrite(BB_CS, LOW);
digitalWrite(BB_DC, LOW);
bbWrite(cmd); // 8 bits on MOSI, MSB first
digitalWrite(BB_DC, HIGH);
for (int i = 0; i < bits; i++) {
delayMicroseconds(BIT_US);
digitalWrite(BB_SCLK, HIGH);
if (digitalRead(BB_MISO)) out[i / 8] |= 0x80 >> (i % 8);
delayMicroseconds(BIT_US);
digitalWrite(BB_SCLK, LOW);
}
digitalWrite(BB_CS, HIGH);
}
Odczyt idzie po tft.init(), bo kontroler musi być wybudzony. Przed odczytem piny trzeba odebrać TFT_eSPI i ustawić jako zwykłe GPIO (pinMode). Dla porównania spike odczytuje też 0xD3 i 0x04 przez readcommand8(). Dwa przebiegi dały identyczne wyniki:
A RDDID 0x04: 08 42 A9 00 00 (<<1: 10 85 52 00)
A RDDST 0x09: 60 29 82 00 00 (<<1: C0 53 04 00)
A RDID4 0xD3: 00 00 00 00 00
A RDID1 0xDA: 10 00
A RDID2 0xDB: 85 FF
A RDID3 0xDC: 52 00
B readcommand8 0xD3: 00 00 00 00 0x04: 00 00 00 00
Surowe odczyty i wszystkie oceny w tym wpisie to moje pomiary z 2026-10-02 na jednej płytce. Wartości oczekiwane ID pochodzą z kart katalogowych ILI9341 i ST7789.
Interpretacja:
- ID2 =
0x85, ID3 =0x52to sygnatura ST7789 (Sitronix). 0x04daje85 52dopiero po przesunięciu o jeden bit. To zgadza się z taktem dummy przed 24-bitowymi danymi w ST7789. Surowy strumień08 42 A9sam z siebie nic nie mówi.0xD3zwraca same zera, a ILI9341/9342 zwróciłyby tam93 41/93 42. Metoda B (readcommand8) też daje zera, bo sztuczka z0xD9na ST7789 nie działa.- ID1 =
0x10zamiast wartości z karty katalogowej. Według dokumentacji ST7789 to ID producenta modułu, programowane przez producenta, więc wniosek opieram na ID2 i ID3.
Ograniczenia: ID nie rozróżnia wariantów ST7789 (V, V2 itd.). Wiadomo tylko tyle, że to rodzina Sitronix, a nie Ilitek. Poza tym ID to wartość, którą zgłasza sam panel. Odczyt rozstrzyga też tylko wtedy, gdy zwraca sygnaturę. Płytka A zwraca same zera (sekcja „Czego nie wiemy”). Przy tanich modułach traktuję ID jako hipotezę, którą potwierdza dopiero zachowanie: poprawny obraz na sterowniku dobranym do tego ID. Tutaj to potwierdzenie przyszło w następnym kroku.
Ten wynik prawdopodobnie tłumaczy sprane kolory. Inicjalizacja ILI9341 wysyła do ST7789 komendy, które pod tymi samymi kodami mają w ST7789 inne znaczenie, m.in. tabele gamma 0xE0/0xE1 o innym układzie bajtów. Które z tych komend zawiniły, nie sprawdzałem. Zmiana sterownika (niżej) potwierdza tylko tyle, że winna była inicjalizacja ILI9341, a nie konkretny rejestr. Najbardziej prawdopodobne jest jednak, że łatki gammy z poprzedniej sekcji poprawiały obce kontrolerowi tabele.
Konfiguracja TFT_eSPI i LVGL dla wariantu 2USB
Po zmianie sterownika na ST7789_DRIVER znów przetestowałem kombinacje kolejności kolorów, inwersji i rotacji:
TFT_RGB: czerwony i niebieski zamienione we wszystkich rotacjach 0–3.invertDisplay(true): kolory odwrócone (RED żółty, GREEN fioletowy, BLUE turkusowy).- Rotacja 3 daje obraz do góry nogami przy normalnym trzymaniu płytki, a rotacje 0 i 2 dają obraz pionowy.
Konfiguracja, która działa. TFT_eSPI ładuje ją przez -include, bez edycji plików biblioteki:
// include/tft_setup.h (cały plik) — ESP32-2432S028R, wariant 2USB (kontroler ST7789)
#pragma once
#define ST7789_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1
#define TFT_BL 21
#define TFT_BACKLIGHT_ON 1
#define TFT_RGB_ORDER TFT_BGR // RGB showed red/blue swapped on ST7789_DRIVER
#define TFT_INVERSION_OFF
#ifndef SPI_FREQUENCY
#define SPI_FREQUENCY 40000000
#endif
#define SPI_READ_FREQUENCY 20000000
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
; platformio.ini (fragment)
platform = [email protected]
lib_deps =
bodmer/TFT_eSPI@^2.5.43
build_unflags =
-DILI9341_DRIVER
-DST7789_DRIVER
-DILI9342_DRIVER
build_flags =
-DUSER_SETUP_LOADED=1
-include $PROJECT_DIR/include/tft_setup.h
W kodzie: tft.setRotation(1). W LVGL 9.6 tworzymy wyświetlacz w natywnych wymiarach panelu i obracamy go po stronie LVGL:
lv_display_t* disp = lv_tft_espi_create(240, 320, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90); // driver maps 90° to setRotation(1)
Dwie pułapki, które kosztowały mnie czas:
- Edycja
tft_setup.hnie przebudowuje TFT_eSPI. Nagłówek wchodzi przez-include, więc PlatformIO nie widzi zależności. Po każdej zmianie trzeba uruchomićpio run -t clean. build_unflagsz wariantami sterowników. Ja usuwam w nim wszystkie trzy (ILI9341,ST7789,ILI9342), żeby żadna flaga-D…_DRIVERz innego miejsca konfiguracji nie konkurowała z nagłówkiem. Wymienienie też-DILI9342_DRIVERwynika z mojej konfiguracji, nie z dokumentacji TFT_eSPI.
Efekt: cały ekran 320×240, kolory zgodne z podpisami, czarna czerń. Gamma bez żadnych łatek, więc setPanelGamma() usunąłem z firmware. Dotyk nie wymagał zmian: touch.setRotation(1) i ta sama mapa surowych współrzędnych, bo orientacja obrazu jest identyczna jak wcześniej przy ILI9342_DRIVER z rotacją 2. W firmware makropadu 22 tapnięcia na 6 różnych kafli trafiły w zamierzony kafel, a 0 w przerwy między nimi.
Czego nie wiemy
-
Dlaczego płytka B wygląda teraz lepiej niż płytka A. Na tym samym obrazku ma, wzrokowo, „trochę wyższy kontrast albo jaśniejsze podświetlenie”. Obie płytki mają podświetlenie na 100 %. Hipotezy, żadnej niesprawdzonej: inny typ matrycy (IPS czy TN, czego nie da się wyczytać z rejestrów), tabele gamma ST7789 w TFT_eSPI dające wyższy kontrast, mocniejsze podświetlenie. Rozstrzygnięcie wymaga pomiaru: luksomierza albo zdjęć bieli i czerni obu płytek przy stałej ekspozycji.
-
Jaki kontroler ma płytka A. Po publikacji sprawdziłem to tym samym spike'iem. Najpierw zrobiłem kopię całego flasha (
esptool read_flash 0 0x400000, dwa odczyty o tym samym SHA-256), a po teście ją przywróciłem i porównałem hash. Wynik z trzech przebiegów:A RDDID 0x04: 00 00 00 00 00A RDDST 0x09: E0 29 82 00 00 (<<1: C0 53 04 00)A RDID4 0xD3: 00 00 00 00 00A RDID1 0xDA: 00 00 A RDID2 0xDB: 00 00 A RDID3 0xDC: 00 00B readcommand8 0xD3: 00 00 00 00 0x04: 00 00 00 00To nie jest ST7789 z sygnaturą
85/52. Nie jest to też ILI9341, który zwróciłby93 41w0xD3. Linia MISO działa, bo0x09zwraca dane, więc to nie brak połączenia. Kontrolera płytki A nie umiem więc zidentyfikować z rejestrów. Może to klon ILI9341 bez obsługi rejestrów ID, może inny kontroler zgodny z inicjalizacją ILI9341. Żadnej z tych hipotez nie sprawdziłem. Wiem tylko, żeILI9341_DRIVERdaje na niej poprawny obraz, a po tej historii to słabszy dowód, niż mi się wcześniej wydawało. -
Dlaczego sztuczka z
0x26cokolwiek poprawiała. GAMMASET to komenda wspólna obu rodzin. Nie sprawdzałem, co dokładnie zmienia jej powtórny zapis na ST7789 po inicjalizacji ILI9341. Ten sterownik już mnie nie dotyczy, więc pytanie zostaje otwarte. -
Czy każda płytka 2USB ma ST7789. W sieci krąży opinia „2USB = ST7789” i moja płytka ją potwierdza. Jedna płytka to jednak próba jednostkowa, a producenci tych modułów zmieniają komponenty bez zmiany oznaczenia.
Wniosek praktyczny
Jeśli masz CYD i obraz wygląda „prawie dobrze”, nie zaczynaj od gammy ani kolejności kolorów. Zacznij od odczytu ID:
- Wgraj spike, który programowym SPI czyta
0x04,0xD3i0xDA–0xDC(kod wyżej; cały spike ma 99 linii). xx 00 93 41/xx 00 93 42w0xD3(surowy odczyt razem z bajtem dummy) oznacza rodzinę ILI.0x85/0x52w0xDB/0xDCoznacza ST7789. Same zera wszędzie (jak na mojej płytce A) oznaczają „nie wiadomo”, a nie „ILI9341”. Wtedy zostaje ocena zachowania z kroku 3.- Dobierz sterownik do ID, a dopiero potem kolejność kolorów (paski RGB z podpisami) i rotację (linijka z ramką na skrajnych pikselach).
- Porównuj warianty w jednym spike'u przełączanym tapnięciem, obok płytki-wzorca. Pojedyncze wgrania, ocenianie z pamięci, prowadzą do łatek takich jak moja
setPanelGamma().
Druga płytka z tej historii to teraz makropad: ekran dotykowy z kaflami, który łączy się z komputerem jako klawiatura BLE HID. O nim napiszę osobno.