Przejdź do treści

Lopaka Basics 01: Twój pierwszy ekran ​

Moduł OLED SSD1306 pokazujący obramowane HELLO! z podpisem my first screen

Termostaty, smartwatche, drukarki 3D, stacje pogodowe: prawie każdy gadżet ma mały ekran i ktoś musiał go narysować. Lopaka Basics to dziesięcioczęściowa seria dla osób, które nigdy nie miały do czynienia z mikrokontrolerem i nigdy nie otwierały Lopaka.

Każdy odcinek podąża tą samą ścieżką: narysuj to, pobierz kod, zobacz to na ekranie. W tym pierwszym zobaczysz, co dzieje się wewnątrz gadżetu, gdy pokazuje obraz, a następnie zaprojektujesz mały ekran w Lopaka i uruchomisz wygenerowany kod na symulowanym Arduino.

Co zbudujesz: ekran OLED 128×64 z ramką, dużym napisem „HELLO!”, podpisem, linią podziału i małą kropką statusu. Czego potrzebujesz: przeglądarki. Prawdziwa płytka jest opcjonalna; uruchamiamy kod w darmowym symulatorze online.

Co kryje się wewnątrz gadżetu ​

Malutki komputer, który uruchamia jeden program ​

Większość małych urządzeń ma wewnątrz płytkę mikrokontrolera: Arduino UNO, ESP32, Raspberry Pi Pico lub coś podobnego. Mikrokontroler to malutki komputer. W przeciwieństwie do twojego laptopa uruchamia jeden program — twój firmware. W szkicu Arduino funkcja loop() tego programu powtarza się tak długo, jak płytka ma zasilanie.

Twój program rysuje wszystko, co pojawia się na ekranie. Płytka nie daje ci pulpitu, okien ani gotowych przycisków na start.

Arduino UNO podłączone do wyświetlacza OLED 128×64 czterema przewodami: GND, VCC, SCL i SDA

Ekran to siatka pikseli ​

Wyświetlacz, którego używamy w pierwszych odcinkach, jest jednym z najpopularniejszych w elektronice hobbystycznej: mały monochromatyczny OLED o szerokości 128 pikseli i wysokości 64 pikseli. Popularny moduł to płytka 0,96 cala z kontrolerem SSD1306. Daje to 8192 piksele, a każdy z nich jest włączony albo wyłączony. Bez odcieni szarości wszystko na nim, łącznie z tekstem, wygląda ostro i kanciasto.

Wersja I2C modułu łączy się z płytką czterema przewodami: dwoma do zasilania (GND i VCC) i dwoma do danych (SCL i SDA).

Siatka 128 na 64 piksele wyświetlacza OLED: 8192 piksele, każdy włączony albo wyłączony

Każdy piksel ma adres ​

Aby zapalić konkretny piksel, twój program musi powiedzieć, który. Każdy piksel ma adres złożony z dwóch liczb:

  • x liczy od lewej krawędzi,
  • y liczy w dół od górnej krawędzi.

Zatem (0, 0) to lewy górny róg. Dwa szczegóły sprawiają problem większości początkujących:

  1. Y rośnie w dół, odwrotnie niż w matematyce szkolnej. Piksel w (40, 20) znajduje się 40 pikseli w prawo i 20 pikseli w dół od rogu.
  2. Liczenie zaczyna się od zera. Na wyświetlaczu 128×64 ostatnia kolumna to 127, a ostatni wiersz to 63.

Współrzędne pikseli na wyświetlaczu 128×64: x biegnie od 0 do 127 w prawo, y biegnie od 0 do 63 w dół, z zaznaczonymi punktami (0, 0) i (40, 20)

Biblioteka graficzna zamienia polecenia w piksele ​

Nie przełączasz 8192 pikseli samodzielnie. Twój program wywołuje bibliotekę graficzną i komunikuje się z nią kształtami: narysuj ramkę tutaj, napisz ten tekst tam. Biblioteka ustala, które piksele to oznacza, i wysyła je do wyświetlacza.

W przypadku małych wyświetlaczy monochromatycznych jedną z najpopularniejszych bibliotek jest U8g2. Dwie linie wystarczą, aby narysować początek naszego ekranu:

cpp
u8g2.drawFrame(2, 2, 124, 60);      // a frame: x, y, width, height
u8g2.drawStr(28, 32, "HELLO!");     // text at x = 28, y = 32

Biblioteka graficzna rysuje ramkę i słowo HELLO! z dwóch linii kodu U8g2

Stary sposób: zgaduj, kompiluj, wgrywaj, patrz ​

Każdą liczbę w tym kodzie wpisujesz ręcznie. Zgadujesz, gdzie tekst powinien się zaczynać, kompilujesz szkic, wgrywasz go na płytkę i patrzysz na ekran. Tekst jest trzy piksele za daleko w prawo? Zmień liczbę i przejdź rundę od nowa: kompiluj, wgrywaj, patrz.

Stary sposób ręcznego kodowania ekranu: pętla zgadywania liczb, kompilowania, wgrywania i patrzenia na ekran, już w rundzie 8

Każda runda kosztuje kompilację i wgranie, często dziesiątki sekund, a do zobaczenia czegokolwiek potrzebujesz podłączonej płytki. Nawet prosty ekran taki jak nasz może wymagać dziesiątek rund, zanim wszystko się wyrówna.

Lopaka usuwa tę pętlę. To edytor działający w twojej przeglądarce. Rysujesz ekran, a Lopaka pisze kod dla twojej biblioteki graficznej podczas rysowania.

Otwórz lopaka.app i zaloguj się za pomocą e-maila, Google lub GitHub. Lopaka przechowuje twoje projekty w chmurze, więc możesz otwierać je z dowolnego komputera.

Strona startowa Lopaka: Moje projekty z przyciskiem Nowy projekt u góry i Galeria projektów społeczności poniżej

Krok 1: utwórz nowy projekt ​

Kliknij + Nowy projekt. Pierwszym i najważniejszym wyborem jest Platforma, czyli biblioteka graficzna, której będzie używał twój firmware. Decyduje to o tym, jaki kod wygeneruje Lopaka, więc wybierz bibliotekę, którą zainstalujesz na płytce.

Dla każdej biblioteki Lopaka pisze kod w sposób, jakiego oczekuje ta biblioteka.

W przypadku małego czarno-białego OLED wybierz U8g2. Jego opis nazywa go „domyślnym wyborem dla monochromatycznych paneli OLED i LCD, takich jak SSD1306, SH1106 i ST7565, gdzie każdy piksel jest po prostu włączony lub wyłączony”, a kod można wyeksportować jako Arduino C++ lub ESP-IDF C.

Następnie wypełnij resztę okna:

  • Tytuł: „My First Screen”.
  • Rozmiar ekranu: 128×64. Dla U8g2 jest już wybrany; lista zawiera dziesiątki innych rozmiarów. Przełącznik Niestandardowy pozwala wpisać dowolny rozmiar, a Zaokrąglony jest przeznaczony dla okrągłych wyświetlaczy.

Kliknij Zapisz.

Okno Utwórz nowy projekt z Platformą ustawioną na U8g2, tytułem My First Screen i rozmiarem ekranu 128×64

Krok 2: szybkie zwiedzanie edytora ​

To jest edytor. Wszystko, czego potrzebujesz w tym odcinku, mieści się na jednym ekranie:

Edytor Lopaka z częściami zaznaczonymi na pomarańczowo: projekt, biblioteka i rozmiar u góry, narzędzia rysowania, warstwy, obszar rysowania, właściwości, wygenerowany kod i opcje kodu

  • Górny pasek. Nazwa projektu, a po niej dwie plakietki: biblioteka (u8g2) i rozmiar ekranu (128×64). Ikona koła zębatego obok nich otwiera ustawienia projektu. Po prawej są Kopia i Udostępnij.
  • Narzędzia rysowania nad obszarem rysowania: cofnij i ponów, narzędzie zaznaczania, a następnie pędzel, tekst (Aa), prostokąt, elipsa (ikona koła), linia, trójkąt i wielokąt.
  • Obszar rysowania pośrodku to twój ekran 128×64 z linijkami wzdłuż krawędzi. Gdy zaznaczysz kształt, linijki zaznaczają jego dokładne krawędzie w pikselach.
  • Warstwy po lewej. Każdy narysowany kształt staje się tutaj warstwą, pogrupowaną pod swoim ekranem. Dodaj ekran tworzy kolejny ekran w tym samym projekcie; karty Obrazy i Animacje poniżej przechowują twoje bitmapy i animacje.
  • Właściwości po prawej, nazywane też inspektorem: dokładna pozycja, rozmiar i styl tego, co jest zaznaczone.
  • Wygenerowany kod na dole. Aktualizuje się na żywo podczas rysowania. Obok niego znajduje się przełącznik Składnia (Arduino C++ lub ESP-IDF C dla U8g2) oraz przełączniki kontrolujące, co trafia do kodu.

Krok 3: narysuj ekran ​

Ramka i twoja pierwsza linia kodu ​

Wybierz narzędzie prostokąt i przeciągnij po obszarze rysowania. Na ekranie pojawia się ramka, a w tym samym momencie w kodzie pojawia się nowa linia:

cpp
u8g2.drawFrame(2, 2, 124, 60);

Te cztery liczby to x, y, szerokość i wysokość: te same adresy pikseli z początku artykułu. Pomiń precyzyjne przeciąganie i wpisz dokładne wartości do inspektora: X 2, Y 2, W 124, H 60. Linijki podążają za twoimi liczbami i zaznaczają krawędzie kształtu na 2 i 126 w poziomie oraz 2 i 62 w pionie. Linijki liczą linie między pikselami, więc prawa krawędź na 126 oznacza, że ostatnia zapalona kolumna to 125.

Prostokąt zaznaczony na obszarze rysowania z X 2, Y 2, W 124 i H 60 w inspektorze oraz pasującą linią drawFrame podświetloną w kodzie

Ramka zaczynająca się na x = 2 i szeroka na 124 piksele obejmuje kolumny od 2 do 125. Pozostawia to dwie puste kolumny po każdej stronie ekranu o szerokości 128 pikseli oraz ten sam dwupikselowy margines u góry i u dołu. Ręcznie wykonywałbyś te obliczenia w głowie przed każdą kompilacją.

Pozostałe właściwości prostokąta też zmieniają kod:

  • R zaokrągla rogi, a wywołanie staje się drawRFrame.
  • Wypełniony zamienia kontur w solidny blok, drawBox.
  • Odwrócony (XOR) odwraca piksele pod kształtem zamiast je włączać, co przydaje się przy podświetlonych pozycjach menu.

Tekst i czcionki ​

Wybierz narzędzie tekst (Aa) i kliknij obszar rysowania. Pojawia się nowa warstwa ze słowem Text; zaznacz ją, jeśli nie jest jeszcze zaznaczona, i wpisz HELLO! w pole tekstowe w inspektorze. Pierwszy tekst w projekcie używa domyślnej czcionki 4x6, która jest malutka: każdy znak ma cztery piksele szerokości. Każdy tekst dodany później zaczyna się od ostatniej wybranej czcionki.

Otwórz listę Czcionka. Lopaka oferuje 42 wbudowane czcionki U8g2, od mikrotekstu 4×6 po duże kroje wyświetlaczowe. Pierwsza pozycja na liście, Importuj czcionkę..., dodaje własną. Wybierz profont22.

Lista czcionek otwarta w inspektorze, od 4x6 do profont29, z opcją Importuj czcionkę u góry

W kodzie przed tekstem pojawia się linia setFont, aby biblioteka wiedziała, którą czcionką go narysować:

cpp
u8g2.setFont(u8g2_font_profont22_tr);
u8g2.drawStr(56, 35, "HELLO!");

Wyrównaj i obserwuj, jak liczby się poruszają ​

Teraz umieść tekst przyciskami wyrównania u góry inspektora: dwa przyciski środkowe umieszczają tekst pośrodku ekranu w poziomie i w pionie. Albo przeciągnij go w dowolne miejsce.

Obserwuj kod, gdy to robisz. Każdy ruch aktualizuje liczby naraz, a wyśrodkowanie ustawia X na 28. Widzisz wynik podczas zmiany, bez kompilacji i wgrywania.

HELLO! wyśrodkowane na ekranie przyciskami wyrównania; kod brzmi teraz drawStr(28, 39, "HELLO!")

Spójrz na linijki na tym zrzucie: zaznaczają ramkę wokół liter od 25 do 39, a kod mówi drawStr(28, 39, ...). W U8g2 współrzędna y tekstu to jego linia bazowa, linia, na której stoją litery, a litery wypełniają wiersze powyżej niej. Tekst umieszczony ręcznie na y = 0 ląduje nad ekranem — klasyczny pierwszy błąd. Inspektor Lopaka używa tej samej linii bazowej dla Y, więc liczba z pola trafia wprost do kodu, a obszar rysowania pokazuje, gdzie wylądują litery.

W gotowym projekcie przesuwamy „HELLO!” nieco wyżej, na y = 32, aby zrobić miejsce na linię i podpis poniżej.

Podpis, linia i kropka statusu ​

Ukończ ekran trzema kolejnymi warstwami:

  • Podpis. Kolejna warstwa tekstowa, my first screen. Zaczyna się od profont22, ostatniej użytej czcionki, więc przełącz ją na mniejszą 6x10, wyśrodkuj w poziomie i ustaw Y na 52: drawStr(19, 52, "my first screen"). Ponieważ ten tekst używa innej czcionki, Lopaka dodaje kolejne setFont przed nim. Jeśli dwa teksty z rzędu używają tej samej czcionki, wywołanie się nie powtarza.
  • Linia pod „HELLO!”: drawLine(24, 39, 103, 39). W przypadku linii cztery liczby to punkt początkowy i końcowy.
  • Mała kropka w prawym górnym rogu, utworzona narzędziem elipsy, z włączoną opcją Wypełniony.

W inspektorze kropka ma X 117, Y 10, RX 2 i RY 2: lewy górny róg ramki wokół niej oraz jej dwa promienie. Funkcja elipsy U8g2 przyjmuje środek, więc kod mówi drawFilledEllipse(119, 12, 2, 2). Każda biblioteka ma takie konwencje. Lopaka konwertuje je za ciebie, a ty pracujesz z tym, co widzisz na obszarze rysowania.

Spójrz teraz na panel warstw: rect 1, string 2, string 3, line 4, ellipse 5. Każdy kształt jest warstwą, a każda warstwa jest linią kodu, z nazwą jako komentarzem nad nią.

Krok 4: pobierz kod ​

Gdy ekran wygląda dobrze, kliknij Kopiuj nad kodem. Otrzymasz gotową funkcję nazwaną od ekranu:

cpp
// [BEGIN lopaka generated]
void drawScreen_1(void) {
    u8g2.setFontMode(1);
    u8g2.setBitmapMode(1);
    // rect 1
    u8g2.drawFrame(2, 2, 124, 60);
    // string 2
    u8g2.setFont(u8g2_font_profont22_tr);
    u8g2.drawStr(28, 32, "HELLO!");
    // string 3
    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(19, 52, "my first screen");
    // line 4
    u8g2.drawLine(24, 39, 103, 39);
    // ellipse 5
    u8g2.drawFilledEllipse(119, 12, 2, 2);
}
// [END lopaka generated]

Panel wygenerowanego kodu z przyciskiem Kopiuj oraz przełącznikami PROGMEM, funkcji opakowującej, zmiennych, czcionek, obrazów, pomocników animacji, komentarzy i czyszczenia wyświetlacza

Cztery szczegóły w tym kodzie:

  • Czcionki są już ustawione. Każdy tekst otrzymuje czcionkę wybraną w edytorze, z właściwą nazwą czcionki U8g2. W u8g2_font_profont22_tr litera t oznacza przezroczyste tło, a r oznacza zredukowany zestaw znaków: podstawowe litery łacińskie, cyfry i znaki interpunkcyjne, co utrzymuje czcionkę małą w pamięci.
  • setFontMode(1) i setBitmapMode(1) sprawiają, że tekst i obrazy są przezroczyste, więc litera narysowana nad linią nie wymazuje jej swoim tłem.
  • Znaczniki BEGIN i END pokazują, która część twojego szkicu pochodzi z Lopaka. Gdy zmienisz projekt, zamień wszystko między nimi.
  • Przełączniki obok kodu kontrolują, co do niego trafia: opakowanie rysowania w funkcję, deklarowanie obrazów jako PROGMEM, dołączanie czcionek i danych obrazów, komentarze z nazwami warstw i więcej. Ustawienia domyślne są odpowiednie dla tego odcinka.

Krok 5: uruchom to ​

Dokąd trafia funkcja: bufor ramki ​

Twój szkic musi wywołać skopiowaną funkcję. Minimalny szkic Arduino dla naszego wyświetlacza:

cpp
#include <U8g2lib.h>
#include <Wire.h>

// 128x64 SSD1306 OLED on I2C, full frame buffer
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();
  drawScreen_1();
  u8g2.sendBuffer();
}

Długa nazwa obiektu wyświetlacza czytana jest od lewej do prawej: kontroler SSD1306, panel 128X64, moduł generyczny (NONAME), pełny bufor ramki F, podłączony przez sprzętowe I2C. U8G2_R0 oznacza brak rotacji, a U8X8_PIN_NONE mówi, że moduł nie ma podłączonego pinu reset.

W loop() wywołaj swoją funkcję między clearBuffer() a sendBuffer():

  1. clearBuffer() czyści obraz.
  2. drawScreen_1() rysuje wszystko w pamięci, a nie na wyświetlaczu.
  3. sendBuffer() wysyła gotowy obraz do wyświetlacza za jednym razem.

Ten obraz w pamięci nazywany jest buforem ramki. Wyświetlacz otrzymuje tylko kompletne ramki, więc nigdy nie widzisz ekranu narysowanego w połowie. Ceną jest pamięć: 128 × 64 piksele po jednym bicie to 1024 bajty, połowa 2 KB RAM Arduino UNO. Cały nasz szkic zajmuje 12 218 bajtów flash (37%) i 1578 bajtów RAM (77%) na UNO, a większość tego RAM to bufor ramki. W tak małym szkicu to w porządku. Na ESP32 ten sam szkic używa 7% RAM. Wrócimy do pamięci i do rysowania przy mniejszym jej użyciu później w serii.

Szkic z wyjaśnionym buforem ramki: clearBuffer czyści obraz, drawScreen_1 rysuje w pamięci, sendBuffer wysyła go do wyświetlacza; 1024 bajty to połowa RAM Arduino UNO

Nie masz jeszcze płytki? Uruchom to w symulatorze ​

Możesz zobaczyć swój ekran w działaniu bez żadnego sprzętu. Wokwi to darmowy symulator online, który kompiluje i uruchamia prawdziwy kod Arduino:

  1. Utwórz nowy projekt Arduino UNO.
  2. Dodaj wyświetlacz OLED SSD1306 i podłącz cztery przewody: GND do GND, VCC do 5V, SCL do A5 i SDA do A4.
  3. Dodaj bibliotekę U8g2 w karcie Library Manager.
  4. Wklej powyższy szkic do sketch.ino, z kodem Lopaka w jego miejscu.
  5. Naciśnij Play.

Wyświetlacz pokazuje to, co narysowałeś, działając jako prawdziwy kod Arduino. Wkleiliśmy kod z Lopaka bez zmiany ani jednego znaku, a symulowany wyświetlacz odpowiadał obszarowi rysowania Lopaka piksel w piksel.

Symulator uruchamiający szkic: symulowany OLED podłączony do Arduino UNO pokazuje HELLO! i my first screen

Na prawdziwej płytce ​

Ponieważ jest to prawdziwy kod, ten sam szkic działa też na prawdziwej płytce. Podłącz wyświetlacz w ten sam sposób, zainstaluj U8g2 z Library Manager w Arduino IDE i wgraj. Dwie rzeczy do sprawdzenia na prawdziwym sprzęcie:

  • Kontroler. Większość modułów 0,96 cala używa SSD1306. Wiele modułów 1,3 cala wygląda tak samo, ale używa SH1106; w ich przypadku zmień SSD1306 na SH1106 w konstruktorze: U8G2_SH1106_128X64_NONAME_F_HW_I2C.
  • Zasilanie. Większość tych modułów akceptuje zarówno 3,3 V, jak i 5 V, ale niektóre gole płytki są tylko na 3,3 V. Sprawdź opis swojego modułu.

Późniejszy odcinek serii przeprowadza ten krok krok po kroku, łącznie z tym, co sprawdzić, gdy ekran pozostaje czarny.

Podsumowanie ​

Przepływ pracy pozostaje taki sam dla każdego tworzonego ekranu:

  1. Wybierz swoją bibliotekę: platforma wybrana przy tworzeniu projektu decyduje, jaki kod otrzymasz. W przypadku monochromatycznego OLED zacznij od U8g2.
  2. Narysuj swój ekran: każdy kształt jest warstwą, każda warstwa jest linią kodu, a inspektor pokazuje dokładne liczby.
  3. Skopiuj kod: wywołaj funkcję między clearBuffer() a sendBuffer() i uruchom ją w symulatorze lub na prawdziwej płytce.

Następny odcinek: Tekst, czcionki i ikony. Budujemy mini stację pogodową i dowiadujemy się, dlaczego każda czcionka kosztuje pamięć. Przeczytaj odcinek 2 lub obejrzyj go na YouTube.

Gotowy, aby spróbować? Otwórz Lopaka i narysuj swój pierwszy ekran.