fbpx

Pracując z różnymi wyświetlaczami graficznymi niemal na pewno przyjdzie taki moment, że będziesz potrzebował jakiejś grafiki do wyświetlenia. W końcu po to bierzemy taki wyświetlacz, aby pokazać na nim jakieś ikony czy inne bitmapy. No dobra, ale czy możemy sobie wrzucić plik BMP, czy JPEG do naszego mikrokontrolera? No nie! W takim razie jak sobie poradzić z takimi grafikami? Czego użyć. Już pokazuję 🙂

Spis treści całego cyklu o OLED na SSD1327:

OLED ze skalą szarości na SSD1327 cz.1
OLED ze skalą szarości na SSD1327 cz.2
Jak przygotować obraz dla wyświetlacza LCD lub TFT?
OLED ze skalą szarości na SSD1327 cz.3

Reprezentacja obrazów w mikrokontrolerze

Wrzucając obrazek do mikrokontrolera należy go uprzednio do tego przygotować. Na ogół takie grafiki zajmują sporo miejsca, więc umieszczamy je w pamięci Flash mikrokonrolera. Do wrzucenia na Flash w STM32 wystarczy dodać przy zmiennej czy tablicy specyfikator const. Kompilator automatycznie umieści nam te zmienne w obszarze pamięci Flash. Proste, prawda?

Ok to już pewnie wywnioskowałeś, że do trzymania obrazków wykorzystamy zmienne. Do tego nie pojedyncze zmienne bo całe tablice. Jak one są zbudowane? Trzeba zastanowić się czym jest obraz.

Weźmy pod lupę obrazki monochromatyczne jak na przykład takie na proste wyświetlacze OLED. Jeden piksel może mieć dwa kolory – czarny lub biały. Do zapisania jednego piksela potrzebujemy tak naprawdę tylko jednego bitu. Chcąc mieć obraz na cały OLED, powiedzmy 128×64 px to musimy mieć informację o 8192 pikselach. Musielibyśmy mieć 8192 zmiennych informujących o kolorze każdego z pikseli.

Nie do końca. Skoro jeden piksel = jeden bit, to czemu tego nie pakować? Przecież bajty mieszczą aż 8 bitów. Tak właśnie się robi! Dzięki temu jeden taki obraz 128×64 px zajmuje już “tylko” 1024 bajtów.

Który bajt dla którego piksela?

Ogromne znaczenie ma organizacja pikseli w takiej tablicy. Ja na ogół trzymam się takiej konwencji numerowania pikseli na wyświetlaczach. Taką organizację najczęściej wymusza organizacja pamięci w wyświetlaczu. Łatwiej robić obliczenia dla jednego piksela, a później hurtem wysłać całą tablicę bez kombinacji.

Tak właśnie numeruję piksele w wyświetlaczu. Teraz jak to ułożyć w tablicy? Tak samo, jak w buforze dla OLED! Pierwsze bajt obejmuje piksele w pierwszym wierszu (0,0) do (7,0), drugi (8,0) do (15,0) itd.

Drugą linie, więc Y=1 zaczyna się od bajtu (127/8)+1, czyli bajt nr 16 w tablicy. Spróbujmy napisać równanie wybierające nam dokładny bajt w buforze.

Najpierw X. Kolejny bajt w buforze zmienia się co 8 Xów, czyli image[(x/8)].

Teraz Y. Z każdą kolejną linią skaczemy dalej o (rozdzielczość w X / 8) razy ilość linii w pionie, czyli [Y*(LCDWIDTH/8)].

Teraz tą głębokość w X oraz w Y trzeba zsumować. Daje to nam wybór bajtu jako image[(x/8) + (Y*(LCDWIDTH/8))]

Znaleźliśmy odpowiedni bajt. Teraz wśród tego bajtu mamy aż 8 pixeli. Których? Xów! Gdzie będzie najmłodszy piksel w bajcie? Na najstarszej pozycji bajtu, czyli niejako “odwrotnie”. Jak znaleźć więc wartość tego pojedynczego pixela? Skorzystać z maski.

Dla zerowego pixela potrzebujemy najstarszy bajt, więc maska będzie wyglądała tak – 0x80 (0b10000000). Dla drugiego piksela będzie to 0x40 (0b01000000). Trzeciego 0x20 (0b00100000). Widzisz zależność? Jedynka wędruje na pozycję piksela licząc z lewej strony. Wystarczy więc ją przesuwać o numer piksela. Ale halo halo! Mamy punkty 8-15. Jak z nich skorzystać?

Trzeba będzie posłużyć się resztą z dzielenie, czyli modulo. Będziemy dzielić X, bo z niego potrzebujemy tę informację. Przez ile podzielimy? Przez 8.

Koniec końców kolor pojedynczego piksela to (image[(x/8) + (Y*(LCDWIDTH/8))] & (0x80 >> (X%8)).

Taki skomplikowany twór nam wyszedł. Dla wyświetlaczy kolorowych, gdzie jeden bajt = jeden kolor składowy piksela jest troszkę łatwiej 🙂 Lub jeden piskel = 2 bajty, bo częstym układem kolorów jest RGB565. Wtedy każdy kolejny bajt dla Xów to X*2, linijka ma LCDWIDTH*2 bajtów. Nie ma wtedy też dzielenia i modulo, ale jest walka z odpowiednim przesuwaniem kolorów, aby mieściły się poprawnie w tych dwóch bajtach.

Tworzenie obrazów – Image2Lcd

Jak więc stworzyć z takiego BMP odpowiednią tablicę? Na piechotę? Oczywiście, że nie! Istnieją różne programy do tego, a ja najchętniej używam darmowego Image2Lcd.

Program ma już swoje lata i trochę trzeba się naszukać w Internecie za tym programem, bo wiele linków jest już martwych. Pobrać ten program razem z kluczem możesz pobrać tutaj. 

Program po zainstalowaniu wygląda jakby był napisany z 10 lat temu 🙂

Wszystko, co Cię interesuje jest tak naprawdę po lewej stronie.

Output file type decyduje o tym, jaki będzie plik wyjściowy przetworzonego obrazu. Dla MCU interesuje nas C array (*.c), który wrzucamy do naszego projektu z plikami źródłowymi oraz Binary(*.bin), który możemy wrzucić np. na kartę pamięci.

Scan mode ustawia nam kolejność oraz ułożenie kolejnych bajtów. To jest to, o czym pisałem wyżej przy układaniu bajtów. Tutaj jest spora pomoc w postaci rusunku tych bajtów wraz z zaznaczeniem MSB(czerwony) i LSC(niebieski). To, co ja używam najczęściej to właśnie Horizontal Scan, ale nie zawsze. Przykładowo domyślne ułożenie bajtów w pamięci RAM OLED z kontrolerem SSD1306 to Data hor,Byte ver. Tak też mam ułożony bufor w RAM dla tych OLEDów.

BitsPixel determinuje ilość kolorów na piksel. Monochrome to to, co nas interesuje przy zwykłym OLEDzie. 4 kolory to 2 bity, 16 kolorów 4 bity, 256 kolorów – 8 bitów, 4096 kolorów – 12 bitów no i już bardziej czytelnie oznaczone 16, 24 i 32 bity na piksel. Tu co ważne piksele będą domyślnie upakowane, więc to, co chcemy np. przy monochromie.

Max Width and Height pozwala nam na zmniejszenie obrazu, który konwertujemy. Zamiast zmniejszać obraz źródłowy, wystarczy ograniczyć go tutaj.

Dalej jest kilka ptaszków do zaznaczenia. Pierwszy służy do załączania nagłówka m.in. z rozmiarami obrazu do całej tablicy. Pierwsze 6 bajtów wtedy to właśnie ten nagłówek.

Kolejne 3 ptaszki to zmiany kolejności skanu bajtów oraz zamiana MSB i LSB w pojedynczych bajtach.

Ostatni ptaszek MSB First przydaje się, gdy piksel ma więcej niż 1 bajt jak np. w kolorach. Wtedy możemy zdecydować o kolejności tych bajtów.

Działanie Image2Lcd

Jak stworzyć obraz dla mikrokontrolera? Otwieramy dowolny plik przez Open. Weźmy moje logo. Może być kolorowe.

Po lewej stronie ustawiłem wyjście jako plik C, obraz mono, bo mam pod ręką OLED mono oraz ograniczyłem szerokość do 128 pikseli. Automatycznie podgląd źródła (po lewej) i podgląd wynikowego obrazu (po prawej) zmniejszyły mi się do tego wymiaru.

Na dole masz suwaczki. W zależności, jaki chcesz mieć wynikowy format kolorystyczny, używasz odpowiedniej zakładki. Podkręciłem kontrast i jasność tak, aby charakterystyczne elementy mojego logo się odcięły.

Ale haaaaalooooo! Co to za znak wodny?! No właśnie. Musisz pamiętać o tym, aby wprowadzić klucz rejestrujący. Kiedyś ten program był płatny, ale po tym, jak się zestarzał udostępniono klucz i jest darmowy 🙂

Rejestrujesz z dołu po prawej w zakładce Register. Klucz jest w paczce z programem.

Teraz jest bez znaku wodnego. Zauważyłem, że czasami to zarejestrowanie nie jest pamiętane i trzeba robić to ponownie, więc trzymaj klucz gdzieś w notatkach.

Teraz zapisując plik używając przycisku Save dostaniesz skonwertowany plik *.C. Wygląda on tak.

const unsigned char gImage_image[608] = { /* 0X00,0X01,0X80,0X00,0X26,0X00, */
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X05,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0XFE,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X6F,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X29,0XC7,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X07,0X9B,0XC8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X03,0X3D,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X01,0X4E,0X7E,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X3C,0XFF,0X79,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X19,0XFF,0XBE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X08,0X73,0XFF,0XDE,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0XE7,0XFF,0XEF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0XCF,0XFF,0XF7,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X43,0X98,0XFE,0X3B,0XC8,0X00,0X00,0X00,0X0C,0X00,0X00,0X00,0X00,0X00,0X01,0X80,
0X4F,0X30,0XC4,0X1D,0XF0,0X00,0X00,0X00,0X0C,0X00,0X00,0X00,0X00,0X00,0X01,0X80,
0X26,0X70,0X00,0X1E,0XF0,0X00,0X60,0X00,0X0C,0X00,0X01,0X80,0X00,0X00,0XD1,0X80,
0X1C,0XF0,0X02,0X1F,0X78,0X76,0XF3,0XC7,0XCC,0X7D,0XDB,0XC7,0X9F,0X81,0XF9,0X80,
0X39,0XF0,0X42,0X1F,0XB8,0X7F,0X76,0X6C,0XCC,0XCD,0XFD,0XCD,0X9F,0X81,0XD9,0X80,
0X33,0XF0,0XC2,0X1F,0XD8,0X77,0X77,0X00,0XCC,0X0D,0XDD,0XDD,0XDD,0XC1,0X99,0X80,
0X33,0XF0,0XC2,0X1F,0XD8,0X77,0X73,0XC7,0XCC,0X7D,0XDD,0XD9,0X99,0X81,0X99,0X80,
0X39,0XF0,0XC2,0X1F,0XB8,0X77,0X61,0XCC,0XCC,0XCC,0XDD,0XDD,0X99,0X81,0X99,0X80,
0X1C,0XF0,0XC6,0X1F,0X78,0X36,0X64,0XEC,0XCC,0XCC,0XD9,0X9D,0X99,0X9D,0XB1,0X80,
0X3E,0X78,0X86,0X1E,0XE0,0X36,0X67,0XC7,0XCC,0X78,0XD9,0X8F,0X19,0X9D,0XE1,0X80,
0X0F,0X38,0X86,0X1D,0XF0,0X00,0X01,0X00,0X0C,0X00,0X00,0X00,0X00,0X01,0X81,0X80,
0X07,0X9F,0XFC,0X3B,0XC8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0X80,0X00,
0X03,0XCF,0XFF,0XF7,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0X80,0X00,
0X01,0XE7,0XFF,0XEF,0XA0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X0A,0XF3,0XFF,0XDE,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X79,0XFF,0XBA,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X3C,0XFF,0X79,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X01,0X4E,0X7E,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X0F,0X3D,0XD0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X07,0X9B,0XC8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X29,0XC7,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X01,0XEE,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0XFE,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X05,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X02,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
};

Ja pierwsze co robię to zmieniam typ zmiennej na uint8_t oraz kasuję przedrostek gImage_ z tablicy. Tak lubię.

const uuint8_t image[608] = { /* 0X00,0X01,0X80,0X00,0X26,0X00, */

Plik ma 608 bajtów. Jaki rozmiar ma obraz? Możemy albo zobaczyć w programie.

Można też lepiej. Można zaznaczyć Include head data i mieć to w naszej tablicy obrazu. Dzięki temu możemy napisać kod tak, że sam sobie sprawdzi wymiary. Jeśli nie zaznaczysz to ten nagłówek jest i tak w tablicy, ale zakomentowany.

const uint8_t image[608] = { /* 0X00,0X01,0X80,0X00,0X26,0X00, */

Te 6 bajtów to /* 0X00,0X01,0X80,0X00,0X26,0X00, */ – czy MSB jest pierwsze – NIE. Ma to wpływ na kolejność bajtów w rozmiarach obrazu!

/* 0X00,0X01,0X80,0X00,0X26,0X00, */ – bitów na piksel = 1

/* 0X00,0X01,0X80,0X00,0X26,0X00, */ – pikseli w poziomie – 0x0080 = 128

/* 0X00,0X01,0X80,0X00,0X26,0X00, */ – pikseli w pionie – 0x0026 = 38

Taką tablicę możesz używać już w swoim projekcie. Podobnie to działa z kolorowymi obrazami.

Podsumowanie

Jak widzisz tworzenie obrazu dla mikrokontrolera to bułka z masłem! Po co szukać i cudować jak można było spytać mnie jak to robić 🙂

Teraz wystarczy generować, wrzucać na Flash i wyświetlać swoje grafiki. Brakuje tylko jakiegoś rodzaju kompresji jak na przykład RLE. Sam z niej niesamowicie rzadko korzystam, więc nie mam w tej chwili rozwiązania dla tego problemu. Jeśli będzie potrzeba to coś poszukam.

Jeśli artykuł Ci się spodobał, kup coś u mnie! 🙂 https://sklep.msalamon.pl/

Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.

Spis treści całego cyklu o OLED na SSD1327:

OLED ze skalą szarości na SSD1327 cz.1
OLED ze skalą szarości na SSD1327 cz.2
Jak przygotować obraz dla wyświetlacza LCD lub TFT?
OLED ze skalą szarości na SSD1327 cz.3

Podobne artykuły

.

2 komentarze

zawodowy_opróżniacz_lodówek · 20/10/2021 o 22:51

Są też mniej “zamknięte” sposoby 😉
Plik można wyeksportować z GIMPa jako “raw image data”, a ten go potraktować np otwartoźródłowym http://www.chami.com/tips/delphi/052098D.html
Pewnie crossplatformowe rozwiązania (np w pythonie) też się znajdą, ale akurat z tego zestawu sam z powodzeniem korzystam.

    Mateusz Salamon · 21/10/2021 o 12:36

    No powiem Ci, że są nawet jeszcze inne sposoby 😉 Sam GIMP potrafi wypluć gotowy kod w C 😉 Ja tylko pokazałem czego używa mi się najwygodniej.

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *