Lopaka Basics 02: Tekst, czcionki i ikony

Zegar, pogoda i obrazek księżyca, wszystko na ekranie wielkości znaczka pocztowego. Tekst, czcionki, ikony i obrazy sprawiają, że mały ekran jest przydatny, a na mikrokontrolerze każdy z nich kosztuje pamięć.
W tym odcinku dowiesz się, czym jest czcionka na mikrokontrolerze, gdzie jest przechowywana i ile miejsca zajmuje. Następnie zbudujesz w Lopaka mały poranny pulpit z dwoma ekranami, paskiem statusu z odwróconym tekstem i własnym obrazkiem zamienionym w kropki, a potem uruchomisz go na symulowanym Arduino UNO.
Co zbudujesz: pulpit OLED 128×64 z dwoma ekranami. Poranek pokazuje godzinę, datę, ikony Wi-Fi i baterii oraz pasek pogody. Noc pokazuje obrazek księżyca i godzinę. Czego potrzebujesz: przeglądarki i projektu symulatora z odcinka 1. Prawdziwa płytka jest opcjonalna.
Czcionka to zestaw obrazków
Jeden obrazek na znak
Mikrokontroler nie wie, czym są litery. Aby pokazać „07:45”, biblioteka graficzna potrzebuje małego obrazka dla każdego znaku: jednego dla 0, jednego dla 7, jednego dla : i tak dalej. Czcionka na mikrokontrolerze to zestaw malutkich obrazków, po jednym dla każdego znaku, jaki może narysować.
Większy tekst, większe obrazki
Każdy obrazek to siatka pikseli. Większa czcionka oznacza większe siatki: ta sama cyfra 2 ma 5×9 pikseli w małej czcionce i 12×18 w dużej. Każdy z tych obrazków musi być przechowywany na chipie, więc większa czcionka zajmuje więcej pamięci.

Dwa rodzaje pamięci
Flash to półka na książki, RAM to biurko
Mikrokontroler ma dwa rodzaje pamięci:
- Flash przechowuje twój program, czcionki i obrazy. Działa jak półka na książki: wszystko, czego program może potrzebować, jest tam przechowywane i pozostaje tam po wyłączeniu zasilania.
- RAM to małe biurko, na którym odbywa się praca: wartości, nad którymi program pracuje właśnie teraz.
Na Arduino UNO półka mieści 32 KB, a biurko tylko 2 KB.
Ile kosztuje czcionka
Mała czcionka zajmuje około jednego kilobajta flash, duża od dwóch do czterech. Zmierzyliśmy to, kompilując szkic dla UNO z jednym tekstem w jednej czcionce i porównując go ze szkicem bez tekstu: 4x6 dodało 1,1 KB, profont22 2,1 KB, profont29 2,5 KB, a timR24 3,7 KB.

Każda czcionka jest przechowywana raz, niezależnie od tego, ile tekstów jej używa. Praktyczna zasada: dwie lub trzy czcionki na ekran. Aby zobaczyć, których czcionek używa twój projekt, spójrz na linie setFont w panelu kodu Lopaka: każda inna nazwa czcionki to kolejna czcionka na półce.
Krok 1: nowy projekt
Ten sam start co w odcinku 1: kliknij + Nowy projekt, wybierz platformę U8g2, pozostaw rozmiar ekranu 128×64 i nadaj projektowi tytuł „Morning Dashboard”. Kliknij Zapisz.
Krok 2: tekst
Godzina dużą czcionką
Wybierz narzędzie tekst (Aa), kliknij obszar rysowania i zaznacz nową warstwę string 1. W inspektorze zamień tekst na 07:45, otwórz listę Czcionka i wybierz profont29, czcionkę łatwą do odczytania z drugiego końca pokoju.
Kliknij przycisk wyrównania wyśrodkuj w poziomie u góry inspektora i ustaw Y na 40. Kod brzmi teraz:
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
Obie liczby można sprawdzić ręcznie. Każdy znak profont29 ma 16 pikseli szerokości, więc „07:45” ma 5 × 16 = 80 pikseli szerokości, a (128 − 80) / 2 = 24 umieszcza je pośrodku. Liczba 40 to linia bazowa, linia, na której stoją cyfry, jak w odcinku 1: cyfry wypełniają wiersze powyżej niej.
Data małą czcionką
Dodaj kolejny tekst, FRI 24 OCT. Nowy tekst zaczyna się od ostatniej użytej czcionki, więc przełącz go na małą 5x8. Wyśrodkuj go w poziomie i ustaw Y na 11. Dziesięć znaków po 5 pikseli daje 50 pikseli, co daje drawStr(39, 11, "FRI 24 OCT").
Twoja własna czcionka
Chcesz innego wyglądu? Pierwsza pozycja listy Czcionka, Importuj czcionkę..., przyjmuje plik TrueType (.ttf; działają też .otf, .woff i .bdf), a Lopaka zamienia go w piksele o wybranej przez ciebie wysokości. W wideo zaimportowaliśmy Press Start 2P, darmową czcionkę pikselową, i ustawiliśmy Wysokość czcionki na 8. Podgląd pokazuje całą czcionkę: 95 malutkich obrazków, każdy drukowalny znak od spacji do ~.

Kliknij Importuj, a czcionka jest gotowa do użycia. Zaznaczony tekst przełącza się na nią, a kod otrzymuje jedną dodatkową linię, #include "Press_Start_2P_8.h". Ten nagłówek zawiera dane czcionki. Przycisk o tej samej nazwie pojawia się pod kodem: pobierz plik i umieść go obok swojego szkicu, w przeciwnym razie szkic się nie skompiluje.
Pulpit zachowuje wbudowane czcionki. Jeśli wypróbowałeś import na godzinie, przełącz ją z powrotem na profont29.
Krok 3: ikony
Szukaj i dodawaj
Lopaka ma wbudowanych ponad dwa tysiące ikon. Otwórz kartę Obrazy po lewej, wpisz w pole wyszukiwania i kliknij ikonę, aby dodać ją do ekranu. Dodaj trzy:
wifi_25(szukaj „wifi”),battery_30(szukaj „battery”),weather_sun(szukaj „sun”).
Wszystkie trzy znajdują się w zestawie Gaai.
Umieść każdą ikonę na swoim miejscu
Przeciągnij ikony na miejsce na obszarze rysowania: Wi-Fi w lewym górnym rogu, baterię w prawym górnym rogu, słońce w lewym dolnym rogu.
Ikona ma 32 bajty
Ikona też jest malutkim obrazkiem. Słońce ma 15 pikseli szerokości i 16 wysokości, a na jednokolorowym ekranie każdy piksel to jeden bit: włączony lub wyłączony. Osiem pikseli mieści się w jednym bajcie. Wiersz 15 pikseli potrzebuje dwóch bajtów, a ostatni bit pozostaje nieużyty, więc cała ikona to 2 bajty × 16 wierszy = 32 bajty.
![]()
W kodzie ikona staje się tą listą liczb. Biblioteka je odczytuje i rysuje piksele:
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);drawXBMP przyjmuje pozycję, szerokość i wysokość obrazka oraz tablicę. W tym formacie, zwanym XBM, pierwszy piksel wiersza jest najniższym bitem bajtu. Dlatego podświetlony wiersz czytany jest jako 00010000 od lewej do prawej, ale przechowywany jako 0x08.
Pozostałe dwie ikony mają 19×16 i 24×16 pikseli. Obie potrzebują trzech bajtów na wiersz, po 48 bajtów każda.
Krok 4: odwrócony tekst
Biały pasek z ciemnym tekstem
Aby utworzyć pasek statusu, narysuj wypełniony prostokąt i umieść na nim tekst:
- Wybierz narzędzie prostokąt, przeciągnij prostokąt na obszarze rysowania i ustaw X
0, Y46, W128, H18. Włącz Wypełniony. Pasek pokrywa teraz dolne 18 wierszy ekranu, wiersze od 46 do 63. - Nowy prostokąt jest warstwą na wierzchu i zasłania słońce. Naciśnij Ctrl + ↓ (Cmd + ↓ na Macu), aby przesunąć go o jedną warstwę w dół, pod
weather_sun. - Zaznacz
weather_suni włącz Odwrócony (XOR). - Dodaj tekst
Sunny 23C(z dwiema spacjami), wybierz czcionkę6x10, ustaw X28i Y59oraz włącz Odwrócony (XOR).
Słońce i tekst przebijają się przez biały pasek na czarno.

XOR: tam, gdzie kształty się nakładają, piksele się odwracają
Tryb odwrócony wykorzystuje sztuczkę zwaną XOR. Wszędzie tam, gdzie nowy kształt nakłada się na coś już narysowanego, piksele się odwracają: zapalony piksel pod kształtem gaśnie, a ciemny się zapala. Na jednokolorowym ekranie w ten sposób otrzymujesz ciemny tekst na jasnym pasku.

W kodzie jest to jedna linia przed słońcem:
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");setDrawColor(2) przełącza U8g2 w tryb XOR, a tryb pozostaje włączony, dopóki kod go ponownie nie zmieni. Dlatego tekst po słońcu nie potrzebuje drugiego wywołania. Kolejność warstw ma tu znaczenie: pasek musi być narysowany jako pierwszy, aby słońce i tekst miały co odwracać.
Krok 5: twój własny obraz
Prześlij obrazek
Możesz też przynieść własne obrazki. W karcie Obrazy kliknij Prześlij i wybierz plik obrazu. Lopaka otwiera okno importu, które konwertuje go na wyświetlacz. Użyliśmy obrazka księżyca przypominającego zdjęcie.
Ustaw Wymiary na W 48 i H 48 oraz włącz Odwróć paletę: księżyc jest jasny na ciemnym tle, a dzięki temu księżyc staje się zapalonymi pikselami na OLED.
Dithering zamienia szarość w kropki
Ten ekran nie ma szarości. Każdy piksel jest czarny albo biały. Najprostsza konwersja tnie w połowie jasności: jaśniejsze piksele włączają się, ciemniejsze wyłączają. Na księżycu zachowuje to tylko białą plamę, a całe cieniowanie znika.
Dithering to naprawia. Miesza czarne i białe kropki, gęściej tam, gdzie obrazek jest jaśniejszy, więc z daleka twoje oko widzi odcienie szarości.

Wybierz styl z listy Dithering: Floyd-Steinberg, Atkinson, Bayer 8x8 lub Brak ditheringu. Zachowaliśmy Floyd-Steinberg. Kliknij Importuj.

Przy 48 na 48 pikseli obrazek zajmuje 48 / 8 × 48 = 288 bajtów.
Drugi ekran na noc
Dajmy księżycowi własny ekran. Projekt może zawierać tyle ekranów, ile potrzebujesz.
- Kliknij dwukrotnie nazwę ekranu nad obszarem rysowania i zmień nazwę
Screen 1naMorning. - Kliknij Dodaj ekran w panelu Warstwy i zmień nazwę nowego ekranu na
Night.
Umieść księżyc na ekranie Night na X 6, Y 8 i dodaj dwa teksty:
Good nightczcionką6x10na X62, Y26,22:30czcionkąprofont22na X62, Y50.

Każdy ekran staje się własną funkcją w kodzie: drawMorning() i drawNight(), nazwanymi od ekranów. Panel kodu ma kartę dla każdego ekranu. Twój szkic decyduje, który pokazać i kiedy.
Krok 6: PROGMEM
Jedno ustawienie warte poznania: Deklaruj jako PROGMEM, na liście przełączników obok kodu. Jest włączone domyślnie.

Na płytce AVR, takiej jak UNO, stała tablica jest kopiowana do RAM przy starcie programu, chyba że jest oznaczona do flash. U8X8_PROGMEM przechowuje dane obrazu we flash, na półce, więc nie przepełnia małego biurka RAM, a drawXBMP (z literą P) odczytuje je stamtąd. Przy wyłączonym przełączniku Lopaka pisze drawXBM i tablice bez U8X8_PROGMEM.
Różnica dla tego pulpitu: cztery obrazy zajmują 416 bajtów. Po skompilowaniu dla UNO szkic używa 78% RAM z PROGMEM i 98% bez niego. Bez niego pozostaje tylko 32 bajty na wszystko, co program robi podczas działania, a szkic może się zawiesić lub zachowywać dziwnie.

Krok 7: uruchom to
Kod
Kliknij Kopiuj na karcie Morning, wklej to do swojego szkicu, a następnie zrób to samo na karcie Night. Kopiuj pobiera kod otwartej karty. Oto kod, który Lopaka generuje dla dwóch ekranów:
// [BEGIN lopaka generated]
static const unsigned char image_battery_30_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0xf0,0xff,0x7f,0x08,0x00,0x80,0x08,0x00,0xaa,0x0e,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x0e,0x00,0xaa,0x08,0x00,0xaa,0x08,0x00,0x80,0xf0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00};
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};
static const unsigned char image_wifi_25_bits[] U8X8_PROGMEM = {0x80,0x0f,0x00,0x60,0x30,0x00,0x18,0xc0,0x00,0x84,0x0f,0x01,0x62,0x30,0x02,0x11,0x40,0x04,0x0a,0x87,0x02,0xc4,0x18,0x01,0x28,0xa0,0x00,0x10,0x47,0x00,0xa0,0x2f,0x00,0xc0,0x1d,0x00,0x80,0x0a,0x00,0x00,0x07,0x00,0x00,0x02,0x00,0x00,0x00,0x00};
void drawMorning(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// string 1
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
// string 2
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(39, 11, "FRI 24 OCT");
// wifi_25
u8g2.drawXBMP(2, 0, 19, 16, image_wifi_25_bits);
// battery_30
u8g2.drawXBMP(102, 0, 24, 16, image_battery_30_bits);
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");
}
// [END lopaka generated]
// [BEGIN lopaka generated]
static const unsigned char image_moon_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd0,0x0a,0x00,0x00,0x00,0x00,0x7f,0x57,0x00,0x00,0x00,0xc0,0xee,0xad,0x00,0x00,0x00,0xf0,0xbf,0xbb,0x05,0x00,0x00,0xd8,0xfb,0x56,0x09,0x00,0x00,0xfc,0xbf,0xbf,0x12,0x00,0x00,0xfe,0xff,0xd5,0x06,0x00,0x00,0xdf,0x6f,0xbf,0x2a,0x00,0x80,0x7f,0xbb,0xd5,0x42,0x00,0xc0,0xdf,0x7f,0xbf,0x2a,0x00,0xc0,0xff,0xfe,0xd6,0x8a,0x00,0xe0,0xff,0xdf,0xbb,0x2a,0x00,0x60,0xff,0xff,0x6e,0x95,0x00,0xf0,0xff,0xbf,0xb7,0x4a,0x00,0xd0,0xff,0xef,0xdd,0x12,0x02,0xf0,0xff,0xff,0xb7,0x56,0x00,0xf0,0xff,0xbd,0x6e,0x09,0x01,0xf8,0xbf,0xef,0x5b,0x53,0x00,0xe8,0xfd,0xff,0xd6,0x14,0x10,0x78,0xff,0xdd,0xad,0x8a,0x00,0xe8,0x6f,0x77,0x5b,0x11,0x00,0xf8,0xff,0xdf,0x56,0x45,0x04,0xd0,0x5e,0x7d,0xab,0x10,0x00,0xf8,0xfb,0xd7,0x56,0x05,0x00,0x50,0xef,0xbe,0x55,0x41,0x00,0xf0,0xbd,0x6b,0x55,0x0a,0x04,0x50,0xf7,0xd6,0xaa,0x00,0x00,0xd0,0xad,0xbd,0x0a,0x25,0x00,0xa0,0x76,0xab,0x52,0x00,0x02,0x60,0xdb,0x56,0x25,0x01,0x00,0x40,0xad,0x55,0x49,0x08,0x00,0x80,0x6a,0x55,0x15,0x82,0x00,0x80,0x5a,0x55,0x82,0x00,0x00,0x00,0xa5,0x4a,0x29,0x00,0x00,0x00,0x2a,0x29,0x00,0x00,0x00,0x00,0x48,0x92,0x02,0x20,0x00,0x00,0xa4,0x04,0x08,0x04,0x00,0x00,0x00,0x51,0x80,0x00,0x00,0x00,0x20,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void drawNight(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// moon
u8g2.drawXBMP(6, 8, 48, 48, image_moon_bits);
// string 9
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(62, 26, "Good night");
// string 10
u8g2.setFont(u8g2_font_profont22_tr);
u8g2.drawStr(62, 50, "22:30");
}
// [END lopaka generated]6x10 występuje w obu funkcjach, ale jest to ta sama czcionka i jest przechowywana we flash raz. Cały projekt używa czterech czcionek: profont29, 5x8, 6x10 i profont22.
Szkic
Zacznij od szkicu z poprzedniego odcinka i zmień loop() tak, aby pokazywał każdy ekran przez trzy sekundy:
#include <U8g2lib.h>
#include <Wire.h>
// 128x64 OLED display on I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// Paste the Lopaka code here
void setup() {
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
// show each screen for 3 seconds
if ((millis() / 3000) % 2 == 0) drawMorning();
else drawNight();
u8g2.sendBuffer();
}millis() liczy milisekundy od uruchomienia płytki. Podzielone przez 3000 rośnie o jeden co trzy sekundy, a % 2 zamienia to w 0, 1, 0, 1. Przy 0 szkic rysuje Morning, przy 1 Night. Jak w odcinku 1, obie funkcje rysują między clearBuffer() a sendBuffer().
Uruchom to w symulatorze
Otwórz projekt symulatora z odcinka 1: Arduino UNO, OLED SSD1306 podłączony do GND, 5V, A5 (SCL) i A4 (SDA) oraz bibliotekę U8g2. Zamień szkic na powyższy, wklej w jego miejsce kod Lopaka i naciśnij Play. Wyświetlacz przełącza się między dwoma ekranami co trzy sekundy.

Sprawdziliśmy oba ekrany piksel po pikselu: obszar rysowania Lopaka, ekran narysowany przez bibliotekę U8g2 i symulowany wyświetlacz są identyczne.
Ile pamięci to wszystko zajmuje
Całość, z czterema czcionkami, trzema ikonami i obrazkiem, mieści się w mniej niż połowie flash UNO. Kompilator raportuje:
Sketch uses 15394 bytes (47%) of program storage space. Maximum is 32256 bytes.
Global variables use 1600 bytes (78%) of dynamic memory, leaving 448 bytes for local variables. Maximum is 2048 bytes.Większość z 1600 bajtów RAM to bufor ramki o rozmiarze 1024 bajtów z odcinka 1. W tym szkicu to w porządku.
Podsumowanie
- Czcionki i ikony to obrazki: każdy znak czcionki i każda ikona to mała siatka pikseli, jeden bit na piksel.
- Obrazki kosztują pamięć: czcionka zajmuje od jednego do czterech kilobajtów flash, więc używaj dwóch lub trzech na ekran i przechowuj obrazy we flash za pomocą PROGMEM.
- Dithering zamienia szarość w kropki: wybierz styl ditheringu przy imporcie zdjęcia, a jednokolorowy ekran pokaże odcienie.
Następny odcinek: Kolor. Ten sam rodzaj interfejsu na kolorowym wyświetlaczu TFT oraz co oznacza RGB565. Przeczytaj odcinek 3 lub obejrzyj go na YouTube.
Gotowy, aby spróbować? Otwórz Lopaka i zbuduj własny pulpit.