Ostatnio poteoretyzowałem na temat OLEDa z 16-stopniową skalą szarości. Wiemy już jak uzyskiwane jest tych 16 kolorów oraz jak zorganizowana jest pamięć RAM wyświetlacza. Pora wyświetlić coś na nim, co nie? Spróbujemy w pierwszej kolejności użyć biblioteki graficznej od zwykłego monochromatycznego OLEDa, której używałem do SSD1306. Do dzieła!
Taki OLED ze skalą szarości możesz kupić u mnie.
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
Podłączenie i Cube
Do dzisiejszego projektu wykorzystałem Nucleo F401RE.
Takie Nucleo kupisz u mnie w sklepie.
Wersje użytych programów:
- STM32CubeIDE 1.3.0
- STM32CubeMX 5.6.0 zaszyty w IDE
- Biblioreka HAL F4
Podłączenie jest tak proste, że nie będę rysował. Ja się wpiąłem do I²C1 z przekierowanymi pinami na te ze złącza Arduino.
I²C1 póki co pozostawiłem w domyślnej konfiguracji 100 kHz. Przyjdzie później czas na zmiany i porównania.
Inicjalizacja OLED SSD1327
Początkowo chciałem napisać samemu procedurę inicjalizacji. Przecież to tylko kilka rejestrów i mając dokumentację można osiągnąć prędzej czy później zadowalający rezultat. No właśnie… później. Chwilę się pogłowiłem i zorientowałem się, że kontroler z pixelami połączony jest w dosyć nieoczywisty sposób. Pierwsze co mnie spotkało, to domyślne wyświetlanie obrazu do góry nogami, ale to było dosyć proste do zrobienia.
Dopiero później zauważyłem, że pixele są nieco poprzesuwane… Walczyłem dobre 10 minut, aż stwierdziłem, że się poddaję. Zacząłem szukać w Internecie jakiegoś gotowego kodu, z którego mógłbym zaciągnąć procedurę inicjalizacji.
Pamiętaj, że nie ma nic złego w posiłkowaniu się gotowcami, jeśli wiemy co one robią i do czego mogą nam posłużyć. Często nie ma sensu wymyślać koła na nowo. Po to zazwyczaj producenci udostępniają kod demonstracyjny, aby korzystać. No właśnie – producenci. Niestety przy takim OLED firm No Name możemy nie znaleźć kodu producenta. Posiłkować się trzeba np. bibliotekami Arduino.
Jakie ogromne było moje zdziwienie, gdy zobaczyłem, że ten OLED nie jest jakoś popularny wśród Arduinowców! Na szczęście biblioteka u8g2 ma zaszyte w sobie inicjalizacje do tych OLEDów. Co prawda trochę enignatycznie napisane, ale dałem radę rozszyfrować to na ludzki język. Wziąłem więc libkę, przepisałem i… zadziałało. Dzięki koledzy od Arduino za pomoc! 🙂
Swoją drogą spróbowałbym uruchomić kiedyś tę bibliotekę na STM32 pod HALem. Co Ty na co? Daj znać w komentarzu!
Komendy samego kontrolera SSD1327 są bardzo podobne do tych z SSD1306, więc zachowałem konwencję pisania biblioteki. Dzięki temu sporo pracy mi odpadło. Tak wygląda sama inizjalizacja.
void SSD1327_Init(void) { SSD1327_Command(SSD1327_DISPLAYOFF); // Display Off SSD1327_Command(SSD1327_SETMULTIPLEX); SSD1327_Command(0x5F); SSD1327_Command(SSD1327_SETDISPLAYSTARTLINE); SSD1327_Command(0x00); SSD1327_Command(SSD1327_SETDISPLAYOFFSET); SSD1327_Command(0x20); SSD1327_Command(SSD1327_SEGREMAP); SSD1327_Command(0x51); SSD1327_SetContrast(0x7F); SSD1327_Command(SSD1327_SETPHASELENGTH); SSD1327_Command(0x22); SSD1327_Command(SSD1327_SETFRONTCLOCKDIVIDER_OSCILLATORFREQUENCY); SSD1327_Command(0x50); SSD1327_Command(SSD1327_SELECTDEFAULTLINEARGRAYSCALETABLE); SSD1327_Command(SSD1327_SETPRECHARGEVOLTAGE); SSD1327_Command(0x10); SSD1327_Command(SSD1327_SETSETVCOMVOLTAGE); SSD1327_Command(0x05); SSD1327_Command(SSD1327_SETSECONDPRECHARGEPERTIOD); SSD1327_Command(0x0a); SSD1327_Command(SSD1327_FUNCTIONSELECTIONB); SSD1327_Command(0x62); SSD1327_Command(SSD1327_SETCOLUMNADDRESS); SSD1327_Command(0x00); SSD1327_Command(0x3F); SSD1327_Command(SSD1327_SETROWADDRESS); SSD1327_Command(0x00); SSD1327_Command(0x5F); SSD1327_Command(SSD1327_NORMALDISPLAY); // Set Normal Display SSD1327_Command(SSD1327_DISPLAYALLON_RESUME); // Entire Display ON #if GRAPHIC_ACCELERATION_COMMANDS == 1 SSD1327_StopScroll(); #endif SSD1327_DisplayON(1); }
Po uruchomieniu tego kodu na wyświetlaczu pojawia się… galaktyka!
Jeśli jeszcze nie wiesz skąd ona się bierze to już tłumaczę. Pamięć RAM ma to do siebie, że po podaniu napięcia wszystkie komórki ustawiają się w “losowe” wartości. Jako że RAM odzwierciedla to, co jest widoczne na wyświetlaczu, stąd mamy taki obraz. Wystarczy więc wyczyścić ten RAM, aby było jednolicie – np. zgasić wszystkie piksele.
Buforowanie, ustawianie piksela i transfer do RAM wyświetlacza
W poprzednim wpisie zaznaczyłem mały problem z ustawianiem koloru pojedynczego w tym wyświetlaczu. Polega on na tym, że w jednym bajcie RAM trzymana jest informacja o dwóch sąsiadujących pikselach. Stąd dobrym rozwiązaniem będzie buforowanie takiego obrazu.
Bufor jaki będzie trzymany w pamięci mikrokontrolera ma rozmiar (128 * 96) / 2 bajtów.
#define SSD1327_BUFFERSIZE (SSD1327_LCDHEIGHT * SSD1327_LCDWIDTH / 2) static uint8_t buffer[SSD1327_BUFFERSIZE];
Teraz pomyślmy jak ustawić pojedynczy piksel w takin buforze. Wiemy, że kolejne piksele A i B są trzymane w bajcie w ten sposób: 0xAB. Jak więc wskazać poprawną połówkę bajtu dla piksela? Można przykładowo użyć operacji modulo. Wtedy parzyste (plus zerowy) piksele zaadresujemy do jednej połówki, a nieparzyste do drugiej.
Dochodzi jeszcze maskowanie tych pikseli, których nie chcemy modyfikować w buforze oraz wybór komórki bufora. Maskowanie połowy bajtu jest dosyć oczywiste. Jak więc znaleźć tą odpowiednią komórkę w tablicy bufora? Na podstawie współrzędnych tego piksela, czyli X i Y.
X jest to pozycja w poziomie, Y w pionie. Wyświetlacz skanujemy od lewej do prawej po każdej linii schodząc w dół. Jeśli mielibyśmy wybrać komórkę tablicy tylko dla wiersza zerowego, obliczanie wyglądałoby tak: buffer[X/2]. Dla piksela nr 0 i 1 będzie to buffer[0]. Dalsze również się zgadzają.
Jak dodać więc numer wiersza? Należy przesunąć numer komórki o ilość bajtów dla pełnych linii, które są przed danym Y. Do tego potrzebna jest informacja o szerokości wyświetlacza.
Ilość bajtów dla pełnych linii poprzedzających tą wybraną o numerze Y, to Y*(SSD1327_LCDWIDTH/2).
Skoro trzeba dodać, to wybór komórki bufora w całości wygląda tak.
uint8_t SelectedCell = buffer[X/2 + Y*(SSD1327_LCDWIDTH/2)];
Składając te informacje do kupy wychodzi nam co powinna zawierać funkcja rusyjąca pojedynczy piksel na buforze.
// // Draw pixel in the buffer // void SSD1327_DrawPixel(int16_t x, int16_t y, uint8_t Color) { if ((x < 0) || (x >= SSD1327_LCDWIDTH) || (y < 0) || (y >= SSD1327_LCDHEIGHT)) return; uint8_t SelectedCell = buffer[x/2 + y*(SSD1327_LCDWIDTH/2)]; if(x % 2) { SelectedCell &= ~(0x0F); SelectedCell |= (0x0F & Color); } else { SelectedCell &= ~(0xF0); SelectedCell |= (0xF0 & (Color<<4)); } buffer[x/2 + y*(SSD1327_LCDWIDTH/2)] = SelectedCell; }
To, z której strony w tym bajcie ma być młodszy piksel determinuje inicjalizacja. Jest tam rejestr z wyborem, gdzie idzie starszy z punktów. Sprawdziłem to empirycznie i według wyników badań dla mojej inicjalizacji powyższy kod jest prawdziwy.
Teraz trzeba to przesłać do OLED.
SSD1327 różni się od SSD1306 tym, że nie ma stronicowania pamięci RAM. Nie musimy się więc martwić o to, czy inicjalizacja włączyła to stronicowanie. Mamy więc cały czas dostęp co całej pamięci RAM.
Wystarczy ustawić wskaźnik pisania po RAM na sam początek i wypchnąć cały bufor. Tyle!
// // Send buffer to OLDE GCRAM // void SSD1327_Display(void) { SSD1327_Command(SSD1327_SETCOLUMNADDRESS); SSD1327_Command(0x00); SSD1327_Command(0x3F); SSD1327_Command(SSD1327_SETROWADDRESS); SSD1327_Command(0x00); SSD1327_Command(0x5F); HAL_I2C_Mem_Write(ssd1337_i2c, SSD1327_I2C_ADDRESS, 0x40, 1, (uint8_t*)&buffer, SSD1327_BUFFERSIZE, 1000); }
Dopiszmy jeszcze na szybko funkcję czyszczącą cały ekran(bufor) na wybrany kolor. Trzeba cały czas pamiętać o tym jak są zapisywane piksele.
// // Clear the buffer // void SSD1327_Clear(uint8_t Color) { if(Color > WHITE) Color = WHITE; memset(buffer, (Color << 4 | Color), SSD1327_BUFFERSIZE); }
Sprawdźmy działanie! Wszystkie zdjęcia były zrobione przy stałych ustawieniach aparatu.
Kolor: 15 (max). Biel.
Kolor 7 (środek). No jest szaro.
Kolor 2 (prawie czerń). Tu już prawie nic nie widać, ale uwierz mi, że lekka poświata jest widoczna 🙂 To będzie fajnie widoczne np. na zdjęciach.
Biblioteka GFX
Można użyć biblioteki, która jest dla wyświetlaczy monochromatycznych. Wystarczy do nagłówka dopiąć funkcję rysującą piksel oraz wymiary wyświetlacza.
#define GFX_DrawPixel(x,y,color) SSD1327_DrawPixel(x,y,color) #define WIDTH SSD1327_LCDWIDTH #define HEIGHT SSD1327_LCDHEIGHT #define PIXEL_BLACK BLACK #define PIXEL_WHITE WHITE #define PIXEL_INVERSE INVERSE
Wtedy napisy czy figury działają bardzo dobrze. Tu przykład z rysowaniem prostokątów we wszystkich kolorach.
Niestety domyślnie nie będzie działało rysowanie obrazów. Niestety bibliotekę tą napisałem tak, że przyjmuje ona tylko dwa kolory – biały i czarny. W końcu jest BW 🙂
Do generowania i rysowania obrazów przejdę w kolejnych wpisach. Dla małej zajawki tak prezentuje się logo msalamon.pl czy zdjęcie – np. mój kot.
Podsumowanie
Organizacja pikseli może dać trochę do pomyślenia, ale jeśli pomyślisz raz, a dobrze to już nigdy więcej nie musisz. Pamiętaj, aby pisząc kod pisać go w taki sposób, aby pomagał on na późniejszych etapach pisania aplikacji.
Nie bój się równierz szukać w Internecie i korzystać z pracy innych. Miej tylko świadomość co cudzy kod robi 🙂
W kolejnym wpisie pokażę Ci z jakigo programu korzystam do generowania bitmap dla mikrokontrolerów. Jest to w 100% darmowy program i potrafi naprawdę wiele!
Później takie obrazy wrzucimy na mikrokontroler, a docelowo na wyświetlacz.
Jeśli artykuł Ci się spodobał, kup coś u mnie! 🙂 https://sklep.msalamon.pl/
Pełny projekt wraz z biblioteką znajdziesz jak zwykle na moim GitHubie: LINK
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
2 komentarze
Grzegorz · 12/12/2020 o 14:37
Bardzo fajny artykuł. Próbuję obsłużyć wyświetlacz 3.12″ z kontrolerem SSD1322 z użyciem biblioteki U8G2 i Arduino IDE. W zasadzie da się to zrobić ale mam problem z wyświetlaniem bitmap. W jaki sposób zmusić STM żeby je wyświetlał choćby w 1 kolorze?
Pozdrawiam,
Grzegorz
Mateusz Salamon · 14/12/2020 o 08:08
Musiałbyś mieć 1-bitową bitmapę i ten jeden bit przerabiać w locie na nibble czy inną wielkość. Nie analizowałem jak jest zbudowany SSD1322, jednak to nie jest trudne. A – i porzuć myśl, że to STMa musisz zmusić 🙂 STM nie ma nic do tego. To musisz na poziomie bibliotek graficznych zrobić.