Powrót

Kurs Arduino – Grafika na OLED

W poprzednim odcinku (UR022) poznawaliśmy tajemnice wyświetlaczy OLED. Z powodzeniem, ale nie omówiliśmy jeszcze wszystkich tajemnic i wszystkich niespodzianek.

Wcześniej przygotowaliśmy „ręcznie” obrazek do wyświetlenia, ale niestety, po wgraniu programu do Arduino ekran wyglądał jak na fotografii 8.

Fotografia 8

Aby wyjaśnić problem, musimy zajrzeć do biblioteki GFX, a konkretnie do pliku Adafruit_GFX.cpp. Znajdziemy tam definicję metody drawBitmap(), co jest pokazane w szkicu 2.

Szkic 2:

/*!
@brief      Draw a PROGMEM-resident 1-bit image (...)

 

    @param    x   Top left corner x coordinate @param    y   Top left corner y coordinate @param    bitmap  byte array with monochrome bitmap @param    w   Width of bitmap in pixels @param    h   Height of bitmap in pixels @param    color 16-bit 5-6-5 Color to draw with */ void Adafruit_GFX::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color) {

int16_t byteWidth = (w+7)/8; //Bitmap scanline pad = whole byte uint8_t byte = 0;

 startWrite(); for(int16_t j=0; j<h; j++, y++) {

 

   for(int16_t i=0; i<w; i++) {

 

     if(i & 7) byte <<= 1; else byte = pgm_read_byte(&bitmap[j*byteWidth + i/8]); if(byte & 0x80) writePixel(x+i, y, color);    }    } endWrite(); }

Wykorzystujemy dwie pomocnicze zmienne: byteWidth będzie przechowywać informację, ile pełnych bajtów potrzeba do zapisania jednej linii, a do zmiennej byte będą kopiowane z pamięci programu kolejne bajty treści obrazka. Zmienna licznikowa j posłuży do zliczania linii – wierszy, natomiast zmienna i – do określania kolejności wyświetlania pikseli w jednym wierszu.

Sama procedura rysowania bardzo negatywnie zaskakuje, bo polega na żmudnym i dość czasochłonnym zaświecaniu pojedynczych punktów za pomocą elementarnej metody writePixel()! Wyświetlanie kolejnych punktów, zgodnie z rysunkiem 6, realizują dwie pętle: „mniejsza” z licznikiem i wyświetla punkty jednej linii, a „większa” pętla z licznikiem j wyświetla kolejne linie – wiersze. Gdy trzy najmłodsze bity licznika i są równe zeru, polecenie pgm_read_byte() odczytuje z pamięci FLASH kolejne bajty,  umieszcza je w zmiennej byte i sprawdza stan najstarszego bitu tej zmiennej. Jeżeli bit ma wartość 1 (byte & 0x80), polecenie writePixel() wysyła do wyświetlacza rozkaz modyfikacji stanu komórki bufora wyświetlającego piksel o współrzędnych x+i,y.

Problem ze szkicem 1 jest taki, że niezależnie od szerokości obrazu (w) pętla z licznikiem i zawsze zaczyna pracę od najstarszego piksela któregoś z odczytanych bajtów. Inaczej mówiąc, bity nowej linii – wiersza muszą zaczynać się w nowym, kolejnym bajcie. Trzeba uzupełnić do pełnych bajtów (wielokrotności ośmiu) nie całość obrazka, jak to zrobiliśmy, tylko opis wszystkich linii tego obrazka. Poprawioną wersję pokazuje rysunek 9.

Rysunek 9

Uzyskane liczby możemy wpisać do szkicu 1 (A2201.ino) i wtedy program wyświetli prawidłowy obrazek, jak na fotografii 10.

Fotografia 10

Oczywiście istnieją programy, które znakomicie ułatwiają tworzenie tablic, zawierających obrazki – grafikę. W szczególności w Internecie można znaleźć szereg stron, które zasadniczo przeznaczone są do tworzenia dowolnych własnych znaków – symboli, które będą używane do wyświetlania tekstu. Jest to ułatwienie między innymi podczas tworzenia „polskich liter”, a raczej symboli przedstawiających  znaki polskiego alfabetu, których nie ma w kodzie ASCII. Do tego szczegółu wrócimy, a na razie inna ważna sprawa.
Otóż często potrzebna jest monochromatyczna, „zero-jedynkowa” grafika o różnej wielkości. Dla małych wyświetlaczy OLED zwykle ma ona 128×64 piksele.

Dla najmniej zorientowanych: obrazek można przygotować w różnych programach, np. za pomocą windowsowego programu Paint. Obraz automatycznie otwierany po uruchomieniu programu jest duży i kolorowy, dlatego trzeba kliknąć: Plik i potem Właściwości (rysunek 11) i w okienku zmienić rozmiar i tryb kolorów według rysunku 12.

Rysunek11

Rysunek 12

Potem trzeba  powiększyć na ekranie maleńki obraz lupką, wybrać narzędzie Ołówek, kolor czarny i rysować. Przykład na rysunku 13.

Rysunek 13

Rysunek 14

Taki obraz trzeba zapisać (Ctrl+S) jako plik .png albo dwukolorowa bitmapa – rysunek 14. Już wiemy, że do szkicu Arduino potrzebna jest też tablica w pamięci FLASH:

const uint8_t nazwa [] PROGMEM = {...};

Potrzebną tablicę (array) uzyskamy z jednego z wielu dostępnych konwerterów, które potrafią przeanalizować plik w którymś z popularnych formatów (.jpg, .gif, .png, .bmp), a także przeskalować – zmniejszyć i według ustawień użytkownika zamienić go na potrzebny ciąg bitów. Przykład: https://sourceforge.net/projects/lcd-image-converter/files/. Jeden z takich konwerterów – program polskiego autora, można pobrać ze strony: http://en.radzio.dxp.pl/bitmap_converter/. Natomiast polecany też przez Adafruit skrypt image2cpp można znaleźć na stronie: http://javl.github.io/image2cpp/. Ten skrypt działa też  off-line, po zapisaniu strony jako pliku HTML.

Rysunek 15

Jak pokazuje rysunek 15, najpierw wczytujemy plik, w naszym przypadku stworzony właśnie KursArduino.png. Napis ma świecić, a nie być czarny, więc zaznaczamy okienko: Invert image colors. W okienku Code output format zamiast plain bytes wybieramy Arduino code (co pozwoliłoby wczytać wiele obrazków i scalić je w jednym pliku). Po tych dwóch zmianach klikamy Generate code i w dolnym okienku otrzymujemy gotowy kod, który kopiujemy wprost do szkicu Arduino. W materiałach dodatkowych można znaleźć pliki obrazka oraz szkic A2203.ino, który wyświetla taki obrazek na wyświetlaczu – fotografia 16.

Fotografia 16

To było dla mniej zaawansowanych. Natomiast bardziej zaawansowani zapewne spróbują też przeskalować i przekonwertować na dwukolorową bitmapę obrazy pełnokolorowe i półtonowe, w tym fotografie. Zasadniczo konwersja jest łatwa, ale często wyniki nie są optymalne. Zwłaszcza przy próbie sensownego odwzorowania półtonów. W skrypcie z rysunku 15 można zmieniać (0…255) tylko próg Brightness/alpha threshold, który domyślnie ma wartość 128.

Osoby, które mają wiedzę i dostęp do lepszych programów graficznych, najpierw przygotują obrazek bitmapowy (czarny/biały) w finalnej postaci i docelowej rozdzielczości i poddadzą go opisanej „najprostszej zamianie na bity”.

Szkic A2204.ino zawiera szkic, którego efekt działania pokazany jest na fotografii 17.

Fotografia17

Szału nie ma, ale można bez trudu rozpoznać fragment najsłynniejszego na świecie obrazu. Obraz został skonwertowany za pomocą skryptu image2cpp z dostępnego w Elportalu pliku Mona_Lisa4_128_64.png, który powstał w programie Photoshop przy wykorzystaniu tzw. roztrząsania dyfuzyjnego (diffusion dithering) przy zamianie obrazu (półtonowego, szarego) z trybu RGB na indeksowany o liczbie kolorów 2 z eksperymentalnie dobranymi  ustawieniami palety i roztrząsania. Można też przy zamianie obrazka w skali szarości na czarno-białą bitmapę wybrać opcję: roztrząsanie dyfuzyjne.

Natomiast fotografia 18 pokazuje wersję ze szkicu A2205.ino, zrobioną przez skrypt image2cpp wprost z (większego i lepszego) pliku Mona_Lisa2.jpg. Jak widać, skrypt bardzo słabo radzi sobie z półtonami.

Fotografia 18

Na fotografii 19 można znaleźć podobny przykład z obrazkiem wstępnie przygotowanym w Photoshopie.

Fotografia 19

Fotografia 20 zrobiona wprost z pliku E0.png potwierdza, że skrypt image2cpp nie radzi sobie z półtonami. Zawierające te obrazki szkice A2206.ino oraz A2207.ino też są dostępne w Elportalu w wersjach na SSD1305 oraz SH1106.

Fotografia 20

Jak widać, nasze maleńkie zero-jedynkowe wyświetlacze 128×64 nie pozwolą wiernie odwzorować półtonów, niemniej do wielu celów wystarczą.

Zachęcam do ćwiczeń!

Na koniec wspomnę jeszcze o interesujących dla elektronika przykładach realizacji VU-metru, wiele przykładów można znaleźć w Internecie. Fotografia 21 pokazuje przykład ze strony https://github.com/adamples/VU_meter, czyli  użytkownika o znajomym brzmieniu adamples.

Fotografia 21

Inny przykład można znaleźć na stronie: http://www.handsontec.com/dataspecs/module/13-Oled.pdf, gdzie wykorzystny jest szyld z mikrofonem i wzmacniaczem, a sygnał podawany jest na przetwornik ADC. Przy wyświetlaniu wyniku pomiaru najpierw na ekranie rysowany jest obrazek skali, będący bitmapą 128×64. Potem na tym obrazku dorysowana jest linia – wskazówka za pomocą metody .drawLine(). Aby linia odpowiadała wychyleniu wskazówki, należy podać odpowiednie współrzędne jej początku i końca. Łatwo sprawdzić w udostępnionym tam szkicu, że są one wyliczane w zaskakująco prosty sposób.

Omawiane kwestie są ważne także dlatego, że opanowanie podstaw grafiki mikroprocesorowej otwiera drogę do zrozumienia problemu czcionek – fontów.

Zajmujemy się tym w dalszych odcinkach kursu.

 

Piotr Górecki