Przejdź do treści

Lopaka Basics 04: Dane na żywo ​

Wyświetlacz ILI9341 320×240 pokazujący pulpit Living room z czerwoną kropką LIVE, Temperature 36.3 °C, Humidity 67%, niebieskim paskiem wilgotności i podpisem Updated every 0.5 s

Dwa ekrany. Ta sama płytka, ten sam czujnik, te same liczby. Ale jeden z nich miga za każdym razem, gdy zmienia się liczba. Zwolnij, a zobaczysz dlaczego: cały obraz jest czyszczony, a potem malowany z powrotem, kawałek po kawałku.

W tym odcinku dowiesz się, co robi loop(), dlaczego kolorowy wyświetlacz migocze, gdy OLED tego nie robi, oraz trzy zasady, które to zatrzymują. Następnie zamienisz liczby z projektu Lopaka w zmienne, zasilisz je z czujnika temperatury i wilgotności i uruchomisz na symulowanym ESP32 bez ani jednego mrugnięcia.

Co zbudujesz: kolorowy monitor pokojowy 320×240 z liczbami temperatury i wilgotności na żywo oraz paskiem wilgotności, aktualizowany dwa razy na sekundę. Czego potrzebujesz: przeglądarki. Uruchamiamy kod na symulowanym ESP32 z wyświetlaczem ILI9341 i czujnikiem DHT22; prawdziwa płytka jest opcjonalna.

setup() i loop() ​

Każdy szkic Arduino ma dwie części:

  • setup() uruchamia się raz, przy włączeniu zasilania.
  • loop() uruchamia się po nim, w kółko, tak długo, jak płytka ma zasilanie.

Dane na żywo żyją w loop(): odczytaj czujnik, zamień liczby w tekst, narysuj je i powtórz. Każde okrążenie rysuje ekran od nowa.

setup() uruchamia się raz, przy włączeniu zasilania; loop() to pierścień z trzema stacjami: odczytaj czujnik, utwórz tekst i narysuj, a licznik okrążeń pokazuje 4

OLED najpierw rysuje w pamięci ​

OLED z pierwszych odcinków nigdy nie migał. Jego biblioteka, U8g2, najpierw rysuje w pamięci: bufor ramki o rozmiarze jednego kilobajta dla 128 × 64 pikseli, jak widziałeś w odcinku 1. Na wyświetlacz trafia tylko gotowy obraz. Tryb stron robi to samo w ośmiu paskach po 128 bajtów, jeden po drugim.

Biblioteka graficzna rysuje do bufora ramki o rozmiarze 1024 bajtów w RAM, 128 × 64 piksele podzielone przez 8, a OLED otrzymuje tylko gotowe piksele

Kolorowa ramka potrzebowałaby 150 KB ​

Kolorowy ekran 320×240 przechowuje dwa bajty na piksel, więc jedna ramka zajmuje 320 × 240 × 2 = 153 600 bajtów, około 150 KB. To prawie połowa RAM, jaką ESP32 daje twojemu szkicowi. Dlatego TFT_eSPI maluje prosto na wyświetlacz, jak Adafruit GFX w odcinku 3.

Pamięć na jedną ramkę: ramka OLED to malutki pasek 1 KB, ramka kolorowego TFT to długi pasek, 320 × 240 × 2 bajty = 150 KB na ramkę

Widzisz każdy krok ​

Gdy biblioteka maluje prosto na wyświetlacz, widzisz każdy krok rysowania. Jeśli szkic czyści ekran na początku każdego okrążenia, wyświetlacz najpierw staje się czarny, a obraz wraca kawałek po kawałku.

Zwolnione tempo migoczącego ekranu: wyświetlacz jest czyszczony do czarnego, a pulpit jest malowany z powrotem kawałek po kawałku, na razie tylko tytuł, kontury kart, liczby 36.3 i 48%

W naszym teście (ESP32 w symulatorze, SPI na 27 MHz) wyczyszczenie ekranu 320×240 zajęło 48 milisekund, a narysowanie pulpitu kolejne 68. Prawie 50 milisekund czerni przy każdej aktualizacji wystarcza, aby twoje oko to wychwyciło.

Brak bufora, prosto na wyświetlacz: ekran jest w połowie przemalowany, oś czasu poniżej pokazuje czyszczenie i rysowanie

Trzy zasady przeciw migotaniu ​

  1. Nie czyść ekranu wewnątrz loop(). Żadnego fillScreen() przy każdym okrążeniu.
  2. Nadaj każdej liczbie na żywo kolor tła. Bez niego nowe cyfry są rysowane na starych i piętrzą się w bałagan. Z nim każda cyfra jest rysowana w ramce koloru tła, która wymazuje to, co było pod nią.
  3. Trzymaj inne kształty z dala spod liczby na żywo. Wypełniona karta pod liczbą byłaby malowana na niej przy każdym okrążeniu, a liczba migałaby. Karta z konturem nie dotyka pikseli za cyframi.

Lopaka dba o dwie pierwsze.

Trzy zasady: nie czyść ekranu wewnątrz loop(), fillScreen przekreślone; tło za każdą liczbą na żywo, czarna ramka wymazuje stare cyfry; nic innego pod liczbą na żywo, wypełniona pomarańczowa karta jest błędna, a karta z konturem jest poprawna

Krok 1: zaprojektuj monitor pokojowy ​

Nowy projekt TFT_eSPI ​

Kliknij + Nowy projekt i wybierz platformę TFT_eSPI, M5, Lovyan. TFT_eSPI to jedna z najpopularniejszych bibliotek graficznych dla kolorowych wyświetlaczy na ESP32. Ustaw Tytuł na „Room Monitor”, a Rozmiar ekranu na 320×240, zachowaj czarny Kolor tła i kliknij Zapisz.

Okno Utwórz nowy projekt z Platformą TFT_eSPI, M5, Lovyan, tytułem Room Monitor, Rozmiarem ekranu 320×240 i Kolorem tła #000000

Narysuj pulpit ​

Narysuj ekran w ten sam sposób co ostatnio: tytuł, dwie karty z konturem, dwie etykiety, dwie duże liczby, znak stopnia, pasek wilgotności, podpis i czerwony znak LIVE. Oto wartości, których użyliśmy:

WarstwaNarzędzieWartości w inspektorzeCzcionkaKolor
Living roomtekstX 12, Y 30FreeSansBold12pt7b#FFFFFF
karta temperaturyprostokątX 12, Y 46, W 142, H 120, R 10#FF8200
karta wilgotnościprostokątX 166, Y 46, W 142, H 120, R 10#4C8DFF
TemperaturetekstX 24, Y 72FreeSans9pt7b#FF8200
HumiditytekstX 178, Y 72FreeSans9pt7b#4C8DFF
23.5tekstX 24, Y 140FreeSansBold24pt7b#FFFFFF
48%tekstX 178, Y 140FreeSansBold24pt7b#FFFFFF
CtekstX 132, Y 122FreeSansBold12pt7b#FFFFFF
znak stopniaelipsaX 124, Y 104, RX 3, RY 3#FFFFFF
pasekprostokąt, WypełnionyX 12, Y 182, W 142, H 18#4C8DFF
pusta część paskaprostokąt, WypełnionyX 154, Y 182, W 154, H 18#1F2937
Updated every 0.5 stekstX 12, Y 228FreeSans9pt7b#6B7280
Kropka LIVEelipsa, WypełnionyX 260, Y 16, RX 4, RY 4#EF4444
LIVEtekstX 272, Y 27FreeSans9pt7b#EF4444

Pierwszy tekst w projekcie TFT_eSPI zaczyna się małą czcionką Adafruit 5x7, a każdy kolejny tekst zaczyna się od ostatniej wybranej czcionki.

Karty mają kontury, zgodnie z zasadą 3. Pasek to dwa prostokąty obok siebie, część wypełniona i część pusta, więc żaden piksel paska nie jest malowany dwa razy. Gdybyś narysował cały ciemny pasek, a na nim niebieską część, niebieska część migałaby przy każdym okrążeniu.

Współrzędna Y tekstu ​

Spójrz na dużą liczbę temperatury w kodzie:

cpp
tft.drawString("23.5", 24, 104);

Inspektor mówi Y 140, kod mówi 104. Jak w U8g2, Y w inspektorze to linia bazowa, linia, na której stoją cyfry. Funkcja drawString w TFT_eSPI przyjmuje zamiast tego górę tekstu, więc Lopaka konwertuje liczbę za ciebie. Kolory też są konwertowane: #FF8200 staje się 0xFC00 w RGB565, dwubajtowym formacie kolorów z odcinka 3.

Na razie te liczby są zwykłym tekstem. Nigdy się nie zmienią.

Krok 2: warstwy stają się zmiennymi ​

Nazwij warstwę ​

Aby ożywić liczbę, zamień ją w zmienną. Najpierw nazwij warstwę: kliknij dwukrotnie string 6, tekst 23.5, w panelu Warstwy i zmień jego nazwę na temp.

Kliknij przycisk zmiennej ​

Zaznacz warstwę i kliknij przycisk zmiennej na prawym końcu jej pola tekstowego w inspektorze (jego podpowiedź mówi Dodaj zmienną). Przycisk staje się pomarańczowy, a w kodzie zmieniają się dwie rzeczy:

cpp
const char* temp_text = "23.5";
cpp
// temp
tft.drawString(temp_text, 24, 104);

Lopaka deklaruje zmienną na górze kodu, nazwaną od warstwy, z tekstem jako wartością początkową. Wywołanie rysowania używa teraz zmiennej zamiast stałego tekstu. Twój szkic może umieścić dowolny tekst w temp_text, a następne wywołanie funkcji rysowania go pokaże.

Warstwa temp zaznaczona z włączonym przyciskiem zmiennej obok jej pola tekstowego; kod zaczyna się od const char* temp_text = "23.5"

Wilgotność i pasek ​

Zrób to samo dla wilgotności i nadaj paskowi zmienne rozmiary:

  1. Zmień nazwę string 7, tekstu 48%, na hum i kliknij przycisk zmiennej obok jego tekstu: hum_text.
  2. Zmień nazwę wypełnionego paska na hum_bar i kliknij przycisk zmiennej obok W: hum_bar_w.
  3. Zmień nazwę pustej części paska na hum_rest i kliknij przyciski zmiennych obok X i W: hum_rest_x i hum_rest_w.

Pasek otrzymuje zmienną szerokość, a jego pusta część zmienną pozycję i szerokość, więc szkic nadal maluje każdy piksel paska raz, niezależnie od wilgotności.

cpp
int hum_bar_w = 142;
int hum_rest_w = 154;
int hum_rest_x = 154;
const char* hum_text = "48%";
const char* temp_text = "23.5";
cpp
// hum_bar
tft.fillRect(12, 182, hum_bar_w, 18, 0x4C7F);
// hum_rest
tft.fillRect(hum_rest_x, 182, hum_rest_w, 18, 0x1946);

Tekst staje się const char*, liczba taka jak szerokość staje się int. Nazwa warstwy i właściwość tworzą nazwę zmiennej: hum_bar + w.

Warstwa hum_rest zaznaczona z włączonymi przyciskami zmiennych dla X i W; kod zaczyna się od int hum_bar_w = 142, int hum_rest_w = 154, int hum_rest_x = 154 oraz dwóch zmiennych tekstowych

Tło za każdą liczbą ​

Zaznacz temp i ustaw jego kolor Tło na #000000, czarny jak ekran. Zrób to samo dla hum. Kod ustawia teraz dwa kolory dla tekstu, cyfry i ramkę za nimi:

cpp
// temp
tft.setTextColor(0xFFFF, 0x0);
tft.setFreeFont(&FreeSansBold24pt7b);
tft.drawString(temp_text, 24, 104);
// hum
tft.drawString(hum_text, 178, 104);

0xFFFF to biały, 0x0 to czarny. Liczba wilgotności używa tych samych kolorów, więc Lopaka nie powtarza wywołania. To zasada 2.

Warstwa temp z Tłem ustawionym na #000000 w inspektorze; kod pokazuje tft.setTextColor(0xFFFF, 0x0) przed drawString(temp_text, 24, 104)

Wyłącz Wyczyść/Wypełnij wyświetlacz ​

Teraz zasada 1. Na liście przełączników obok kodu znajdź Wyczyść/Wypełnij wyświetlacz. W nowym projekcie TFT_eSPI jest włączony, a funkcja rysowania zaczyna się od wypełnienia całego ekranu:

cpp
void drawScreen_1(void) {
    tft.fillScreen(0x0);

Ta jedna linia powoduje mruganie: funkcja działa przy każdym okrążeniu loop(), więc ekran staje się czarny przy każdym okrążeniu. Wyłącz Wyczyść/Wypełnij wyświetlacz, a linia znika.

Panel kodu z włączoną opcją Wyczyść/Wypełnij wyświetlacz: funkcja rysowania zaczyna się od tft.fillScreen(0x0); opcja Dołącz czcionki jest wyłączona

Wyłącz Dołącz czcionki ​

Jeszcze jeden przełącznik: wyłącz Dołącz czcionki. TFT_eSPI dostarcza już te czcionki FreeFonts, więc szkic nie potrzebuje plików czcionek.

Krok 4: twoja własna pętla ​

Pisz wokół wygenerowanego bloku ​

Reszta to kilka linii twojego kodu. Cały szkic możesz napisać tutaj, w panelu kodu. Lopaka przechowuje twój kod wokół wygenerowanego bloku, między // [BEGIN lopaka generated] a // [END lopaka generated], i pozostaje on, gdy zmienisz projekt lub przeładujesz stronę.

Kliknij nad linią BEGIN i wpisz dyrektywy include oraz obiekty:

cpp
#include <TFT_eSPI.h>
#include <DHTesp.h>

TFT_eSPI tft;
DHTesp dht;
char t[8], h[8];

Następnie, pod linią END, dodaj setup() i loop():

cpp
void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextPadding(94);
  dht.setup(27, DHTesp::DHT22);
}

void loop() {
  TempAndHumidity d = dht.getTempAndHumidity();
  snprintf(t, sizeof t, "%.1f", d.temperature);
  snprintf(h, sizeof h, "%.0f%%", d.humidity);
  temp_text = t;
  hum_text = h;
  hum_bar_w = d.humidity * 2.96;
  hum_rest_x = 12 + hum_bar_w;
  hum_rest_w = 296 - hum_bar_w;
  drawScreen_1();
  delay(500);
}

Panel kodu po znaczniku END lopaka generated: void setup() z tft.begin, setRotation, fillScreen, setTextPadding(94) i dht.setup, a następnie void loop() odczytujące czujnik; opcje Dołącz czcionki i Wyczyść/Wypełnij wyświetlacz są wyłączone

Co robi setup() ​

setup() uruchamia wyświetlacz, obraca go do orientacji poziomej za pomocą setRotation(1) i maluje go na czarno raz, a następnie uruchamia czujnik na pinie 27. To jedno fillScreen tutaj jest w porządku: działa przy włączeniu zasilania, a nie przy każdym okrążeniu.

setTextPadding(94) nadaje każdej liczbie na żywo stałą ramkę o szerokości 94 pikseli. TFT_eSPI wypełnia tło tekstu tylko na szerokość nowego tekstu. Gdy „23.5” zmieni się na węższą wartość, taką jak „9.8”, prawy koniec starych cyfr pozostałby na ekranie. 94 piksele to szerokość wartości dwucyfrowej, takiej jak „48%”, w tej czcionce, a ramka kończy się przed znakiem stopnia.

Co robi loop() ​

loop() odczytuje temperaturę i wilgotność, zapisuje je do zmiennych tekstowych, zamienia wilgotność w szerokość paska i wywołuje funkcję rysowania. Następnie czeka pół sekundy i zaczyna od nowa:

  • snprintf zamienia liczbę w tekst: \"%.1f\" zachowuje jedno miejsce po przecinku, więc 23,46 staje się „23.5”, a \"%.0f%%\" daje „48%”. %% wypisuje znak procenta.
  • temp_text = t; wskazuje zmienną na ten tekst. Bufory t i h są zadeklarowane poza loop(), więc tekst nadal istnieje, gdy funkcja rysowania go odczytuje.
  • Pasek ma 296 pikseli długości przy 100%, od x = 12 do 308, więc jeden procent to 2,96 piksela. Pusta część zaczyna się tam, gdzie kończy się część wypełniona, i zajmuje resztę.
  • delay(500) daje dwie aktualizacje na sekundę.

Krok 5: uruchom to ​

W symulatorze ​

Kliknij Kopiuj nad kodem. Kopiuje cały panel, łącznie z twoim kodem. W symulatorze z odcinka 1:

  1. Utwórz nowy projekt ESP32.
  2. Dodaj wyświetlacz ILI9341 i podłącz go: VCC i LED do 3V3, GND do GND, CS do 15, RST do 2, D/C do 0, MOSI do 13, SCK do 14, MISO do 12.
  3. Dodaj czujnik DHT22: VCC do 3V3, GND do GND, SDA do 27.
  4. W Library Manager dodaj TFT_eSPI oraz DHT sensor library for ESPx (bibliotekę z DHTesp.h).
  5. Wklej kod do sketch.ino i naciśnij Play.

TFT_eSPI odczytuje swoje piny z pliku ustawień w folderze biblioteki. Symulator używa domyślnych ustawień biblioteki: ILI9341 na tych pinach ESP32.

Kliknij DHT22 i przeciągnij jego suwaki. Liczby i pasek podążają za suwakami bez ani jednego mrugnięcia.

Symulator uruchamiający szkic: suwaki DHT22 są ustawione na 18,2 °C i 31,0%, a ILI9341 obok ESP32 pokazuje Temperature 18.2 °C i Humidity 31% z krótkim niebieskim paskiem

Kompletny kod z przycisku Kopiuj, z blokiem Lopaka pośrodku:

cpp
#include <TFT_eSPI.h>
#include <DHTesp.h>

TFT_eSPI tft;
DHTesp dht;
char t[8], h[8];

// [BEGIN lopaka generated]
int hum_bar_w = 142;
int hum_rest_w = 154;
int hum_rest_x = 154;
const char* hum_text = "48%";
const char* temp_text = "23.5";
void drawScreen_1(void) {
    // string 1
    tft.setTextColor(0xFFFF);
    tft.setTextSize(1);
    tft.setFreeFont(&FreeSansBold12pt7b);
    tft.drawString("Living room", 12, 12);
    // rect 2
    tft.drawRoundRect(12, 46, 142, 120, 10, 0xFC00);
    // rect 3
    tft.drawRoundRect(166, 46, 142, 120, 10, 0x4C7F);
    // string 4
    tft.setTextColor(0xFC00);
    tft.setFreeFont(&FreeSans9pt7b);
    tft.drawString("Temperature", 24, 58);
    // string 5
    tft.setTextColor(0x4C7F);
    tft.drawString("Humidity", 178, 58);
    // temp
    tft.setTextColor(0xFFFF, 0x0);
    tft.setFreeFont(&FreeSansBold24pt7b);
    tft.drawString(temp_text, 24, 104);
    // hum
    tft.drawString(hum_text, 178, 104);
    // string 8
    tft.setTextColor(0xFFFF);
    tft.setFreeFont(&FreeSansBold12pt7b);
    tft.drawString("C", 132, 104);
    // ellipse 9
    tft.drawEllipse(127, 107, 3, 3, 0xFFFF);
    // hum_bar
    tft.fillRect(12, 182, hum_bar_w, 18, 0x4C7F);
    // hum_rest
    tft.fillRect(hum_rest_x, 182, hum_rest_w, 18, 0x1946);
    // string 12
    tft.setTextColor(0x6B90);
    tft.setFreeFont(&FreeSans9pt7b);
    tft.drawString("Updated every 0.5 s", 12, 214);
    // ellipse 13
    tft.fillEllipse(264, 20, 4, 4, 0xEA28);
    // string 14
    tft.setTextColor(0xEA28);
    tft.drawString("LIVE", 272, 13);

}
// [END lopaka generated]

void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextPadding(94);
  dht.setup(27, DHTesp::DHT22);
}

void loop() {
  TempAndHumidity d = dht.getTempAndHumidity();
  snprintf(t, sizeof t, "%.1f", d.temperature);
  snprintf(h, sizeof h, "%.0f%%", d.humidity);
  temp_text = t;
  hum_text = h;
  hum_bar_w = d.humidity * 2.96;
  hum_rest_x = 12 + hum_bar_w;
  hum_rest_w = 296 - hum_bar_w;
  drawScreen_1();
  delay(500);
}

Po skompilowaniu dla ESP32 szkic używa 24% przestrzeni programu i 6% RAM.

Ten sam szkic z włączoną opcją Wyczyść/Wypełnij wyświetlacz ​

Podzielony ekran poniżej uruchamia ten sam szkic dwa razy: po lewej z włączoną opcją Wyczyść/Wypełnij wyświetlacz, po prawej nasz. Jedyna różnica to linia tft.fillScreen(0x0); na początku drawScreen_1(). Lewy ekran jest uchwycony w połowie przerysowywania, z widoczną tylko pierwszą cyfrą. Po prawej zmieniają się tylko cyfry.

Dwa symulowane wyświetlacze obok siebie: przy włączonej opcji Wyczyść/Wypełnij wyświetlacz lewy ekran jest uchwycony w połowie przerysowywania z widoczną tylko cyfrą 3; przy wyłączonej opcji prawy ekran pokazuje 36.3 °C i 48%

Na prawdziwej płytce ​

Zainstaluj TFT_eSPI oraz DHT sensor library for ESPx z Library Manager w Arduino IDE, podłącz wyświetlacz i czujnik w ten sam sposób i wgraj. Rzeczy do sprawdzenia na prawdziwym sprzęcie:

  • Ustawienia TFT_eSPI. Biblioteka pobiera sterownik wyświetlacza i piny z pliku User_Setup.h we własnym folderze. Domyślnie jest to ILI9341 z pinami powyżej. W przypadku innego wyświetlacza lub innych pinów zmień ten plik, a nie szkic.
  • Piny rozruchowe. Na prawdziwym ESP32 GPIO 0 i GPIO 12 decydują też o tym, jak płytka się uruchamia. Jeśli płytka nie uruchamia się przy podłączonym wyświetlaczu, przenieś D/C i MISO na inne piny w User_Setup.h.
  • Czujnik. DHT22 daje nowy odczyt co najwyżej co dwie sekundy. Biblioteka zwraca w międzyczasie ostatni odczyt, więc ekran nadal przerysowuje się dwa razy na sekundę. Jeśli liczby pokazują nan, czujnik nie odpowiedział: sprawdź jego okablowanie.

Podsumowanie ​

  1. loop() przerysowuje twój ekran w nieskończoność: odczytaj, utwórz tekst, narysuj, powtórz.
  2. Nazwij warstwę, otrzymaj zmienną: przycisk zmiennej zamienia tekst lub rozmiar w zmienną nazwaną od warstwy, a twoja pętla ją zasila.
  3. Brak czyszczenia i tło oznaczają brak mrugania: wyłącz opcję Wyczyść/Wypełnij wyświetlacz i nadaj każdej liczbie na żywo kolor Tła.

Następny odcinek: Z przeglądarki na płytkę. Zdejmujemy projekt z ekranu i przenosimy na prawdziwą płytkę oraz omawiamy pięć powodów, dla których nowy wyświetlacz pozostaje czarny. Przeczytaj odcinek 5 lub obejrzyj go na YouTube.

Gotowy, aby spróbować? Otwórz Lopaka i ożyw własny ekran.