E-papier, czyli papier ze zmiennym drukiem

Wyświetlacze typu e-paper są dość innowacyjnym produktem na rynku elektroniki DIY. Różnią się zasadniczo od powszechnie spotykanych wszelkich odmian LCD. Wyświetlacze symulujące papier są droższe niż tradycyjne LCD lub OLEDy, w przeciwieństwie do poprzednich wyświetlaczy nie emitują światła, tylko odbijają je jak papier. E-papier wyróżnia się wysokim kontrastem i niskim poborem prądu, jednak przy słabym oświetleniu jest prawie nieczytelny, co może być sporym problemem przy niektórych projektach. Z kolei nic nie przebije czytelności wyświetlacza e-ink (bo tak też są nazywane) w pełnym słońcu. 

Połączenie wyświetlacza z Arduino

Do podłączenia wyświetlacza z płytką używamy aż 8 pinów i pracujemy w logice 3.3V. Moduł wyświetlacza jest 5V tolerant, co znaczy, że możemy bez obaw o uszkodzenie wyświetlacza podłączyć go do Arduino UNO.

Tym razm nie wstawię graficznego schematu połączenia, ponieważ piny znajdują się pod wyświetlaczem, na szczęście są dobrze opisane. Pin BUSY podłączamy do pinu 7, RST do 9, DC do 8, CS do 10, CLK do 13, DIN do 11, GND do GND, a VCC do 3.3V.

Tworzenie kodu do obsługi wyświetlacza

Jak już się przyzwyczailiśmy, w przypadku podłączenia niestandardowych elementów potrzebne będą nam biblioteki. My użyjemy biblioteki GxEPD, która obsługuje sporą liczbę modeli wyświetlaczy.

Biblioteka udostępnia nam sterowanie wyświetlaczami e-paper na rozmaite sposoby, od wyświetlania osobnych pikseli do rysowania całych grafik. Tak jak ma to miejsce we wszystkich bibliotekach które umożliwiają korzystanie z wielu rodzajów danego podzespołu na początku każdego programu musimy zdefiniować z jakim modułem mamy do czynienia. W przypadku wyświetlacza e-ink 2.9 cali, 296×128 niezbędna będzie linijka

 #include <GxGDEW029Z10/GxGDEW029Z10.h>    // 2.9" b/w/r

Jest to deklaracja wyświetlacza typu e-paper o przekątnej ekranu 2.9 cala i obsługiwanych trzech kolorach, nasz wyświetlacz operuje kolorem czarnym, białym i żółtym. W programie kolor żółty musimy definiować jako czerwony, ponieważ biblioteka nie umożliwia sterowania kolorem właściwym dla naszego wyświetlacza, jednak nie stanowi to problemu, ponieważ wszystko działa prawidłowo.

Jeśli masz inny wyświetlacz, by wybrać prawidłowy wyświetlacz z listy w bibliotece musimy znać przekątną ekranu (podana jest na rewersie płytki) oraz kolory które obsługuje. Gdy znamy już wielkość oraz obsługiwane kolory metodą prób i błędów szukamy właściwej klasy dla wyświetlacza. Nie musicie się bać, ponieważ z reguły dla jednej konfiguracji wyświetlacza jest jedna, góra dwie klasy do sprawdzenia.

Kod który wgramy jako pierwszy będzie miał za zadanie wyświetlić “Witaj! To jest e-paper”, z tym, że napis “e-paper” wyświetlony ma być w kolorze żółtym. Program jest stosunkowo prosty, jednak musimy pamiętać aby każdy ekran, który chcemy wyświetlić umieszczony był w osobnej funkcji.

Pamięć RAM w Arduino jest zbyt mała, by zmieścił się tam cały obraz, dlatego biblioteka radzi sobie z tym wysyłając dane po kawałku. W tym celu potrzebuje wywołać funkcję rysującą wielokrotnie. Dzięki temu pisząc program nie musisz się przejmować cięciem obrazu w kawałki w zależności od wielkości bufora. Wadą jest to, że funkcja rysująca może być wywołana kilkanaście, kilkadziesiąt razy przy jednym odświeżeniu ekranu. Dlaczego? Bo na Arduino nie ma gdzie zapisać całego ekranu, dlatego biblioteka wywołuje funkcję rysującą/piszącą wiele razy, za każdym razem biorąc tylko kawałek danych i wysyłając ten mały kawałek do modułu.

Nie muszę Ci mówić, że funkcja rysująca musi być szybka i zajmować się tylko rysowaniem. Jeśli będziesz tam realizować inne zadania, cały program znacząco zwolni.

Drugi warunek poprawnego działania jest taki aby funkcja rysująca za każdym razem zwracała taki sam wynik. To znaczy, żeby za każdym razem rysowała/pisała to samo. Jeśli nie – na ekranie mogą powstać artefakty, bo po kolejnych wywołaniach biblioteka wytnie kawałek z nieco innego obrazu i całość nie złoży się w spójny rezultat.

Jeśli podłączasz wyświetlacz do mikrokontrolera takiego jak ESP, z dużą ilością (w porównaniu z UNO) pamięci RAM, to ten problem nie występuje. Biblioteka buforuje cały obraz i wysyła go w jednym kawałku do wyświetlacza

W programie używane są również czcionki, dodane są one z biblioteki Adafruit_GFX. Warto ją pobrać, ponieważ zasób czcionek, które oferuje jest ogromny. Na dole artykułu znajduje się link do listy czcionek (na Learn Adafruit, artykuł po angielsku).

Program który musimy wgrać oraz efekt pokazany jest poniżej

#include <GxEPD.h> // implementacja biblioteki
#include <GxGDEW029Z10/GxGDEW029Z10.h>// określenie rodzaju wyświetlacza 2.9" b/w/r
#include <Fonts/FreeMonoBold12pt7b.h> //dodanie czcionki
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>
GxIO_Class io(SPI, /*CS=*/ 10, /*DC=*/ 8, /*RST=*/ 9); //określenie sposobu podłączenia
GxEPD_Class display(io, /*RST=*/ 9, /*BUSY=*/ 7);
void setup(){
  Serial.begin(9600);
  Serial.println(F("INIT"));
  display.init(); //uruchomienie wyświetlacza
  Serial.println(F("Po setup"));
  display.drawPaged(tekst);
  Serial.println(F("Po draw"));
}
void tekst(){
  const char* name = "FreeMonoBold9pt7b";
  const GFXfont* f = &FreeMonoBold12pt7b;
  display.fillScreen(GxEPD_WHITE); //ustalenie koloru tła
  display.setTextColor(GxEPD_BLACK);//ustalenie koloru czcionki
  display.setFont(f);//ustalenie rodzaju czcionki
  display.setRotation(45);//orierntacja ekranu -  pozioma
  display.setCursor(0, 14);//ustalenie położenia kursora
  display.println(F("Witaj!"));//wyświetlenie tekstu
  display.setCursor(0, 35);//ustalenie położenia kursora
  display.print(F("To jest"));//wyświetlenie tekstu
  display.setTextColor(GxEPD_RED);//ustalenie koloru czcionki
  display.print(F(" e-paper"));//wyświetlenie tekstu
  display.update();//aktualizacja obrazu na wyświetlaczu
//  delay(1000);
}
void loop() {
  Serial.println("TICK");
  delay(5000);
}

Gdy układ który zbudowaliśmy działa i wszystko wyświetla się prawidłowo, możemy rozbudować nasz program. Teraz wyświetlimy kilka ekranów i każdy z nich szczegółowo omówimy, pamiętajmy też, że każdy z ekranów musimy umieścić w osobnej funkcji.

Pierwszy ekran będzie ekranem powitalnym, wyświetli dużą czcionką napis “Witaj!” na środku ekranu. Funkcja tej sekcji pokazany jest poniżej.

void ekran1()
{
  const char* name = "FreeMonoBold18t7b";
  const GFXfont* f = &FreeMonoBold18pt7b;
  display.fillScreen(GxEPD_WHITE); //ustalenie koloru tła
  display.setTextColor(GxEPD_BLACK);//ustalenie koloru czcionki
  display.setFont(f);//ustalenie rodzaju czcionki
  display.setRotation(45);//orierntacja ekranu -  pozioma
  display.setCursor(90, 70);//ustalenie położenia kursora
  display.println("Witaj!");//wyświetlenie tekstu
  display.update();//aktualizacja obrazu na wyświetlaczu
  delay(4000);
}

Na drugim ekranie pojawi się tekst “Jeśli jesteś w stanie sam napisać tą funkcje to nie będziesz miał problemu z wyświetlaczami e-paper”, z tym, że kolor tekstu ma być najpierw czarny, a chwili ma wyświetlić się żółty. Aby całość działała jak należy funkcja powinna wyglądać tak:

void ekran2()
{
  const char* name = "FreeMonoBold9t7b";
  const GFXfont* f = &FreeMonoBold9pt7b;
  display.fillScreen(GxEPD_WHITE); //ustalenie koloru tła
  display.setTextColor(GxEPD_BLACK);//ustalenie koloru czcionki
  display.setFont(f);//ustalenie rodzaju czcionki
  display.setRotation(45);//orierntacja ekranu -  pozioma
  display.setCursor(0, 15);//ustalenie położenia kursora
  display.println("Jesli jestes w stanie sam napisac tego voida to nie bedziesz mial problemu z wyswietlaczami e-paper");//wyświetlenie tekstu
  display.update();//aktualizacja obrazu na wyświetlaczu
  delay(4000);
}

Uważni dostrzegą, że nie ma tutaj żółtego koloru, to prawda. Kolor zmienimy wyświetlając dokładnie ten sam tekst, używając koloru żółtego. Musimy utworzyć trzecią funkcje, wygląda on tak samo, tylko zmieniamy identyfikator koloru czcionki z “GxEPD_BLACK” na “GxEPD_RED” oraz nazwę funkcji z ekran2 na ekran3.

Ostatnim ekranem jest wyświetlenie obrazu, który pokaże cztery kwadraty w rogach ekranu oraz tekstu “Do zobaczenia!” na środku ekranu. Dwa kwadraty mają być czarne, dwa żółte, tekst ma być czarny. Warto dodać, że w bibliotece wbudowana jest funkcja rysowania, więc tworzenie różnych figur jest niezwykle proste, funkcja powinna wyglądać tak.

void ekran4()
{
    const char* name = "FreeMonoBold9t7b";
    const GFXfont* f = &FreeMonoBold18pt7b;
    display.fillScreen(GxEPD_WHITE); //ustalenie koloru tła
    display.setTextColor(GxEPD_RED);//ustalenie koloru czcionki
    display.setFont(f);//ustalenie rodzaju czcionki
    display.setRotation(45);
    display.fillScreen(GxEPD_WHITE);
    display.fillRect(0, 0, 40, 40, GxEPD_BLACK);//rysowanie czarnego kwadratu
    display.fillRect(display.width() - 40, 0, 40, 40, GxEPD_RED);//rysowanie żółtego kwadratu
    display.fillRect(display.width() - 40, display.height() - 40, 40, 40, GxEPD_BLACK);
    display.fillRect(0, display.height() - 40, 40, 40, GxEPD_RED);
    display.setTextColor(GxEPD_BLACK);//ustalenie koloru czcionki
    display.setCursor(5, 72);//ustalenie położenia kursora
    display.println("Do zobaczenia!");//wyświetlenie tekstu
    display.update();
    delay(5000);
}

Gdy funkcje znajdą się w naszym programie pozostało nam tylko zmienić funkcje loop i dodać do niej wszystkie utworzone programy tak jak poniżej.

void loop() {
display.drawPaged(ekran1);
delay(1000);
display.drawPaged(ekran2);
delay(1000);
display.drawPaged(ekran3);
delay(1000);
display.drawPaged(ekran4);
delay (1000);
}

Jeśli wszystko działa prawidłowo to mamy pewność, że dobrze skonstruowaliśmy program, oczywiście ekrany możemy przerabiać według naszego uznania. Jak widać obsługa wyświetlacza e-paper tylko trochę różni się od obsługi LCD czy też OLEDa. 

Materiały użyte w artykule:

Biblioteka użyta w artykule: