fbpx

Wiele osób rozwodzi się nad pięknem wyświetlaczy OLED bowiem mają w sobie coś niesamowitego. Nieskończony kontrast i wysokia częstotliwość odświeżania są niesamowitymi atutami tej technologii. W sprzedaży możemy znaleźć mnóstwo malutkich monochromatycznych OLEDów w rozmiarach od ok 0.49″ do nawet około 4″. Ich cena w ostatnich latach mocno spadła przez co są szalenie popularne. Sprawdźmy jak ujarzmić OLED SSD1306 za pomocą STM32!

Technologia OLED

Chyba najważniejszą cechą tej technologii jest fakt, że każdy pixel to pojedyncza organiczna dioda świecąca. Wiąże się to z brakiem konieczności oświetlenia matrycy od tyłu dzięki temu wyświetlacz jest cieniutki oraz składa się w zasadzie z samego szkła. Dzięki temu wyświetlacze te mogą osiągać teoretycznie nieskończony kontrast. W skrócie – czarny pixel nie świeci, a biały to biały.

Brak prądożernego podświetlenia oznacza energooszczędność bowiem ten element pochłania największe ilości prądu w klasycznych wyświetlaczach LCD.

Lecz nie ma nic za darmo… Przez to, że pixele to diody dochodzi to zjawiska wypalania się ich. Diody z czasem tracą jasność. W zależności od koloru, zejście do połowy początkowej jasności może trwać ok 20-100 tys. godzin. Jest to zaznaczone w każdej karcie katalogowej. Wydaje się to sporą wartością i faktycznie jest. Niestety ludzkie oko potrafi zobaczyć względną różnicę w jasności już po obniżeniu jasności o kilka procent. Stąd widzimy wypalenia na obszarach, gdzie OLED wyświetla stały obraz. Widoczne będzie to za rok-dwa(o ile już nie jest) w telewizorach OLED które raczkują i będzie to dosyć sporym problemem w pierwszych modelach.

Bardzo dobry artykuł na temat OLEDów napisał kiedyś mój kolega. Zapraszam do lektury(link).

Kontroler OLED SSD1306

Nigdy nie mamy dostępu do żywej matrycy. Zawsze po drodze stoi jakiś kontroler. Tak jest przy wyświetlaczach alfanumerycznych(HD44780), TFT oraz OLED. Kontroler ma za zadanie Odebrać od układu sterującego instrukcje w jaki sposób ma ustawić pixele na matrycy. Często posiadają wbudowane dodatkowe funkcje.

Popularnym producentem kontrolerów do wyświetlaczy OLED jest Solomon Systech Limited. Produkują one bardzo dobre, popularne kontrolery o oznaczeniach SSD. Mają w swojej ofercie kilkanaście układów zdolnych opanować matryce OLED. Różnią się wielkościami obsługiwanych matryc, interfejsami czy możliwymi stopmiami szarości. Zainteresowanych odsyłam do broszury – SSD_OLED_IC_Catalog . Niewątpliwie na rynku najpopularniejszym jest SSD1306. Jest on stosunkowo prosty. Obsługuje maksymalnie matryce o rozmiarze 128×64 punktów. Posiada 256-stopniową skalę jasności i uwaga, to nie jest skala szarości. On po prostu całą matrycę przyciemnia/rozjaśnia. Posiada wbudowaną pamięć RAM dla wyświetlanego obrazu. Porozmawiać z nim można za pomocą 8-bitowego interfejsu równoległego, I²C lub SPI(3/4 przewodowego) z czego interfejs równoległy rzadko jest implementowany w chińskich modułach. I dobrze bo uważam, że I²C i SPI są wystarczająco szybkie. Ważną cechą jest wbudowana przetwornica napięcia bowiem panele OLED wymagają do działania napięcia ok. 12 V. Dzięki tej przetwornicy nie trzeba się tym martwić, chociaż ponoć potrafią piszczeć. Mi na szczęście nigdy się taki nie trafił.

Kontroler ten posiada kilka wbudowanych efektów:

    • Scrollowanie ekranu np. w celu prostego wygaszacza ekranu – równomierne zużycie pixeli.
    • Funcja Fade Out oraz Blinking
  • Zoom

Więcej cennych informacji znajdziesz w nocie katalogowej układu – SSD1306 Datasheet . Zajmę się wyświetlaczem opartym o ten właśnie sterownik. Będzie to 0.96″.

Sterowanie

Chińskie moduły dają do wyboru dwa interfejsy – I²C i SPI. Komunikacja dzieli się na komendy oraz dane. Wybór typu przesyłanej informacji w I²C dokonuje się przez zapis pod odpowiedni rejestr(0x00 dla danych i 0x40 dla komend). W SPI jest trochę inaczej bo do wyboru jest komunikacja 3 lub 4-ro przewodowa. Przy czterech przewodach oprócz standardowych syngałów interfejsu SPI jest jeszcze sygnał D/C, który determinuje co wysyłane jest do wyświetlacza. Jeżeli wybrany został tryb 3-przewodowy, trzeba wysłać dodatkowy bit na początku każdego bajtu. Robi się wtedy komunikacja 9-bitowa. No to co? Lecę z koksem!

Biblioteka

Kod udostępnia kilka definicji, które można zmieniać takie jak:

  • wybór interfejsu i jego ustawienia jak np. adres I2C
  • Rozdzielczość matrycy
  • Wybór funkcji graficznych oferowanych przez kontroler wyświetlacza

Pozostała kontrola realizowana jest przez odpowiednie funkcje. Tworzenie obrazu oparte jest na buforze RAM w MCU. Mieszczą się w nim informacje o każdym pixelu – tzw. ramka. Jest to wyświetlacz monochromatyczny, więc cały bufor zajmuje tylko 1 kB RAM. Nie jest to dużo dla STM32, a zastosowanie takiego bufora daje ogromną wygodę użytkowania. Dzięki niemu można w łatwy sposób zrealizować nakładanie się obrazów(argument transparency).

Podstawową funkcja jest inicjalizacja. W zależności od interfejsu przyjmuje ona jako argument wskaźnik na odpowiednią strukturę I²C lub SPI. Uwaga. W funkcji tej ustawiane są m. in. rozmiar matrycy, napięcie COM czy kierunki rysowania które mogą się różnić zwłaszcza mając inne rozdzielczości i rozmiary. Upewnij się, że masz odpowiednie i ewentualnie zmień na takie, które zaleca producent Twojego wyświetlacza. Mniejsze rozdzielczości mogą mieć inną organizację pixeli względem pamięci. Jednak dla większości przypadków ustawienia te powinny być poprawne.

4 funkcje konfiguracyjne umożliwiają:

  • Włączenie/wyłączenie pixeli.
  • Odwrócenie kolorów
  • Obrót wyświetlacza o 180°
  • Ustawienie kontrastu – 256-stopniowa jasność wyświetlacza

Następnie najważniejsze funkcje wyświetlacza.

void SSD1306_DrawPixel(int16_t x, int16_t y, uint8_t Color);

Rysowanie pojedynczego pixela w buforze RAM. Funkcja ta rusyje pixel o podanych współrzędnych i kolorze w pamięci RAM. Nie jest on wysyłany do wyświetlacza. Dzięki temu można przygotowywać całą grafikę zanim zostanie ona wysłana do RAM’u OLED.

void SSD1306_Clear(uint8_t Color);

Wypełnienie bufora RAM wybranym kolorem. Do dyspozycji jest jedynie WHITE i BLACK. Każda inna wartość będzie ignorowana. Służy ona do kasowania zawartości ramki.

void SSD1306_Display(void);

Wysłanie całego bufora do pamięci RAM wyświetlacza. Dopiero w momencie wywołania tej funkcji przygotowany wcześniej bufor jest wysyłany do wyświetlacza i jego obraz ostatecznie pojawia się na matrycy.

void SSD1306_Bitmap(uint8_t *bitmap);

Ta funkcja jest podobna do SSD1306_Display z tą różnicą, że możemy wysłać obraz, który znajduje się poza buforem, np. w pamięci Flash. Musi on mieć rozmiar zgodny z rozmiarem matrycy. Inaczej zostanie wysłana jakaś kasza z pamięci lub wystąpi HardFault.

W dalszej kolejności zaimplementowałem funkcje graficzne które oferuje kontroler. Jest więc scrollowanie kontentu wyświetlacza w różnych kierunkach.

void SSD1306_StartScrollRight(uint8_t StartPage, uint8_t EndPage, scroll_horizontal_speed Speed);
void SSD1306_StartScrollLeft(uint8_t StartPage, uint8_t EndPage, scroll_horizontal_speed Speed);
void SSD1306_StartScrollRightUp(uint8_t StartPage, uint8_t EndPage, scroll_horizontal_speed HorizontalSpeed, uint8_t VerticalOffset);
void SSD1306_StartScrollLeftUp(uint8_t StartPage, uint8_t EndPage, scroll_horizontal_speed HorizontalSpeed, uint8_t VerticalOffset);
void SSD1306_StopScroll(void);

Argumenty typu scroll_horizontal_speed decydują co ile klatek następuje animacja. Nie jest to liczba klatek które wysyłasz do kontrolera. Są to klatki odświeżania kontrolera m. in. na podstawie wartości Display Clock oraz Multiplex Ratio podawanych przy inicjalizacji. Dla ułatwienia są one w postaci enumeracyjnej. Zachęcam do eksperymentowania z tymi funkcjami.

typedef enum
{
	SCROLL_EVERY_5_FRAMES,
	SCROLL_EVERY_64_FRAMES,
	SCROLL_EVERY_128_FRAMES,
	SCROLL_EVERY_256_FRAMES,
	SCROLL_EVERY_3_FRAMES,
	SCROLL_EVERY_4_FRAMES,
	SCROLL_EVERY_25_FRAMES,
	SCROLL_EVERY_2_FRAMES
} scroll_horizontal_speed;

Ostatnią sekcją są “zaawansowane” komendy graficzne. Pierwszą jest Fade Out.

void SSD1306_StartFadeOut(uint8_t Interval);

Polega ona na stopniowym wygaszeniu wyświetlacza, czyli zmniejszaniu kontrastu. Zmiana dokonuje się od podanej wartości Interval do zera i w nim pozostaje do odwołania.

Podobnym efektem jest mruganie.

void SSD1306_StartBlinking(uint8_t Interval);

Efekt jest podobny do Fade Out z tym, że wyświetlacz wraca do wartości Interval i “mruga” w zapętleniu.

Po wygaszeniu lub ustawieniu mrugania wyświeltacz nie wraca do “normalnego” stanu. Trzeba go z niego wyciągnąć funkcją

void SSD1306_StopFadeOutOrBlinking(void);

Pozostaje ostatnia, najbardziej bezsensowna jak dla mnie funkcja Zoom In.

void SSD1306_ZoomIn(uint8_t Zoom);

Działa ona jedynie przy pełnej możliwej matrycy, czyli 128×64 a polega ona na tym, że górna połowa wyświetlacza (128×32) jest rozciągana w dół. Sam zobacz jak to działa. Czy będzie to przydatne?

Biblioteka graficzna

Biblioteka graficzna, którą używam jest bazowana na różnych przykładach z Internetu. Można ją stosować zarówno z STM32 jak i AVR. Wymaga ona przekazania trzech wartości, aby działała poprawnie. Jest to funkcja, która rysuje pojedynczy pixel oraz wymiary wyświetlacza w pixelach.

#define GFX_DrawPixel(x,y,color) SSD1306_DrawPixel(x,y,color)
#define WIDTH SSD1306_LCDWIDTH
#define HEIGHT SSD1306_LCDHEIGHT

Bibloteka w moim przykładzie będzie rysowała w buforze RAM ponieważ funkcja pixela to robi. Do wysłania tego do kontrolera OLED będzie trzeba już użyć funkcji SSD1306_Display().

Umieściłem również kilka przełączników w celu decydowania które funkcje rysowania będą potrzebne. Można trochę miejsca we Flashu przyoszczędzić rezygnując z kompilacji zbędnych rzeczy. O tym co będzie kompilowane decydują przełączniki(#define) typu USING_XXX. Ustawienie zera powoduje wycięcie danej funkcji, a jedynka z kolei dorzuci je do kodu. Niektóre funkcje wymagają innych i zastosowałem mały “automat” który to ogarnia. Do dyspozycji są:

  • Stringi w różnych wielkościach
  • Obrazy
  • Obrócony obrazy z krokiem 1° – bardzo prymitywna funkcja, któa potrafi zostawić puste pixele przy obrocie
  • Rysowanie figur geometrycznych(kwardaty, koła, trójkąty – puste, pełne, zaokrąglone lub nie)

Zachęcam do zabawy z biblioteką i zgłaszania mi błędów lub propozycji modyfikacji. Teraz przejdę do głównej części wpisu.

I²C

Na pierwszy ogień wezmę pod lupę I²C. Z STM32 potrzebuję jedynie dwóch przewodów dla zaspokojenia tego interfejsu, więc jest to kusząca opcja do default’owego użytku. Nota SSD1306 mówi o tym, że maksymalny obsługiwany zegar I²C wynosi 400 kHz. W dzisiejszych testach posłużę się płytką Nucleo z STM32L476RG na pokładzie. Umożliwia on komunikację I²C w Standard Mode(100 bit/s), Fast Mode(400 bit/s) oraz Fast Mode Plus(1 Mbit/s). Spróbuję zrobić overclock zegara I²C kontrolera SSD1306, a co! Zobaczymy co z tego wyjdzie.

Jako, że użyłem gotowego modułu z wyświetlaczem to schemat dla podłączenia go do I2C1 jest banalnie prosty.

Najpierw sprawdzę na “pewnej” wartości zegara – 100 kHZ. Konfiguracja Cube jest banalna.

W późniejszych krokach będę zmieniał prędkość przez menu I2C Speed Mode, ale nie będę tego pokazywał na screenach. Przesłanie kompletnej ramki dla częstotliwości zegara I²C równej 100 kHz wygląda następująco:

Czas wysłania wszystkich pixeli wynosi około 103 ms. Całkiem sporo biorąc pod uwagę fakt, że wysyłając ramki jedną za drugą otrzymamy tylko 10 klatek na sekundę przy obciążeniu MCU 100%. Zwiększę trochę prędkość.

Jest już zdecydowanie lepiej. 27,4 ms jest już fajną wartością która da ok 36 klatek na sekundę. Mimo tego, że karta SSD1306 mówi, że maksymalną częstotliwością zegara I2C może być 400 kHz, spróbuję trochę przetaktować komunikację. STM32L476RG oferuje standard Fast Mode Plus. Oto efekt.

Czas przesłania ramki to tylko 12,38 ms. Liczba ta pozwala na uzyskanie około 80 FPS. Jest już to całkiem spora liczba. Na ogół nie jest potrzebna aż tak duża liczba klatek na sekundę, więc można ją ograniczyć zwalniając nieco CPU na inne operacje. No właśnie, czy można trochę ulżyć procesorowi przy transferze? Oczywiście! Za pomocą DMA 🙂 Tą samą operację wykonam teraz wysyłając cały bufor przy pomocy DMA. Zobacz jak bardzo spada zużycie CPU.

100 kHz:

Jak widzisz procesor jest zajęty tylko przez jedną milisekundę. Jest to czas w którym wysyłam po I2C do wyświetlacza informację, że będę wysyłał ramkę. Można ten czas odrobinę skrócić.

400 kHz:

0,3 ms zajętości CPU dla 400 kHz jest świetnym wynikiem. Pozostaje jeszcze 1000 kHz.

W prakryce STM32L476RG ustawia zegar I2C na wartość 800 kHz mimo, że w Cube ustawione jest 1000. Stąd wynik “tylko” dwukrotnie niższy – 0,15 ms. Jak to jest szybko? Ano bardzo.

Jeśli nadal nie wiesz jakie korzyści wiążą się z użyciem DMA, pozwól, że przedstawię ile głównych pętli przeleci CPU podczas każdej sekundy działania z wyświetlaczem. Będą to wyniki z wyłączonym oraz z włączonym DMA. Pierwsze zdjęcia dotyczą blokującego transferu, czyli bez DMA.

Jak widzisz program wykona tylko tyle pętli głównych ile jest w stanie wyświetlić klatek na sekunde na wyświetlaczu. Jest to w zasadzie logiczne, bo CPU czeka aż transfer fo OLED się zakończy. Teraz wyniki z DMA. Transfer kolejnej ramki odbywa się tylko jeżeli transfer poprzedniej się zakończył. W tym samym czasie cały czas chodzi główna pętla while.

Nie dość, że liczba FPS podskoczyła to w program wykonuje ok 300 tys obiegów pętli while na sekundę. W czasie transferu ramki przez DMA, CPU nudzi się i może np. odebrać dane z czujników. Można pokusić się o podwójne buforowanie i płynne przygotowywanie danych dla wyświetlacza.

Wybór obsługi – czy DMA czy bez – oprócz konfiguracji w Cube odbywa się w bibliotece za pomocą definicji #define SSD1306_I2C_DMA_ENABLE

A jak wygląda to wszystko przy pracy z interfejsem SPI?

SPI

Kontroler SSD1306 umożliwia pracę interfejsu SPI przy pomocy trzech lub czterech połączeń. Różnica polega na podłączeniu pinu DC odpowiadającego za informacje dla układu czy wysyłane są dane czy komendy. Komunikacja w trybie 4-przewodowych jest 8-bitowa natomiast 3-przewodowa wymaga przesyłania za każdym razem 9 bitów po SPI(jeden informacyjny zamiast połączenia przewodem). Mój egzemplarz wyświetlacza nie pozwala mi na sprawdzenie trybu 3-przewodowego, dlatego ograniczę się tylko do dostępnych czterech połączeń. Zresztą 9-bitowy SPI wydaje się dosyć nienaturalny… Moduł który posiadam nie ma wyprowadzonego pinu MISO lecz i tak nie ma potrzeby czytania czegokolwiek z wyświetlacza. Tryb SPI w takim wypadku ustawię na Half-Duplex. SPI + DC to 4 piny, a jeszcze jest taki pin jak RESET. Nie jest on obligatoryjny, ale jeśli mamy wolne piny w układzie to czemu by go nie użyć. Załącza się go w bibliotece wygodnie jednym ‘difajnem’ – #define SSD1306_RESET_USE. Należy wtedy pin ten podłączyć do 3,3 V.

Ja podłącze wszystkie możliwe piny do MCU. Schemat przedstawia się następująco.

Pin CS trochę odleciał od reszty grupy, ale ma to swoje uzasadnienie o czym za chwilę.

Nota SSD1306 mówi, że maksymalną częstotliwością zegara na SPI może być 10 MHz. STM32L476RG taktowany jest zegarem 80 MHz, więc prescaler powinien być ustawiony na wartość 8. Podobnie jak przy I²C jestem w stanie podkręcić komunikację. W wypadku SPI mogę podnieść częstotliwość linii SCK aż do 40 MHz. Sprawdzę to oczywiście 🙂

Początkowo jednak ustawię wartość w miarę bezpieczną. Preskaler wielkości 64 da mi zegar ~1,25 MHz. Później wystarczy jedynie zmniejszać tą wartość. Piny I²C są pozostałością po poprzednim trybie i nie są niezbędne w tej chwili.

Czas zobaczyć co słychać w analizatorze. Przy 1,25 MHz już otrzymujemy dobry wynik. Przesłanie całej ramki to 8,25 ms. Zdecydowanie lepiej niż maksymalna częstotliwość po I²C a to dopiero 12% możliwości kontrolera na interfejsie SPI.

Dla porównania jeszcze 5 MHz – 2,07 ms.

Oraz 10 MHz – 1,04 ms. Bardzo szybko!

No dobra, ale co z overclockingiem? 🙂 Sprawdziłem to co umożliwia mi preskaler, czyli 20 MHz i 40 MHz. Podobnie jak przy I²C kontroler nawet nie zająknął. Żadnych randomowych czy zgubionych pixeli na ekranie nie zobaczyłem. Wszystko idzie gładko. A czasy? Nie patrząc na to co interpretuje analizator na MOSI i SCK (16 MHz próbkowanie), test jednak pokazuje prawdę.

Transfer ramki przy 20 MHz trwa zaledwie 0,53 ms.

A przy 40 MHz… 0,27 ms! Co za prędkość. Przy takiej prędkości można nie używać DMA 🙂

Jednak należy pamiętać, że jest to 4-krotnie przekroczona maksymalna wartość podana przez producenta. To, że mi się udało w warunkach biurkowych nie oznacza, że wyświetlacz taki będzie działał bez problemu na 40 MHz w środowisku roboczym! Lepiej trzymać się maksymalnie 10 MHz i użyć DMA.

SPI DMA

Oczywiście przewidziałem taką możliwość w bibiotece. DMA włącz w ten sam sposób jak dla I²C i użyj definicji #define SSD1306_SPI_DMA_ENABLE aby przełączyć kod na nadawanie DMA. Niestety to nie wszystko. Jak wiesz w SPI trzeba jeszcze kontrolować pin CS. Należy wrócić nim do stanu wysokiego po zakończeniu nadawania po DMA. Przecież nie będę czekał na zakończenie transferu, aby przestawić pin. Nie po to konfiguruję DMA, aby czekać jak przy zwykłym transferze… Są dwie drogi, które zaimplementowałem w bibliotece:

  1. Przerwanie void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) czyli od zakończenia transferu DMA. Jest to dobre rozwiązanie, ale należy pamiętać o tym, aby aktywować przerwanie w Cube oraz wstawić funkcję void SSD1306_DmaEndCallback(SPI_HandleTypeDef *hspi) w jego kod obsługi. Ma to taką zaletę, że dowolny pin może być CS’em
  2. Drugim sposobem jest ustawienie pinu CS w tryb sprzętowej kontroli. MCU sam może nim machać wtedy, kiedy jest potrzebny. Wtedy można zapomnieć o przerwaniu od końca transferu DMA. Wadą takiego rozwiązania jest to, że musi to być dedykowany pin. Dla SPI1 jest to PA4 stąd od początku “dziwny” wybór padł na niego. Drugą wadą jest to, że na tym SPI nie podłączysz już innego urządzenia bo CS od wyświetlacza będzie aktywował się zawsze, gdy puścisz transfer po użytym SPI.

Sam musisz zadecydować co będzie dla Ciebie odpowiednie. Jak wyglądają czasy zajęcia MCU przy SPI DMA? 1,25 MHz – 38,37 µs.

5 MHz – 28,81µs.

10 MHz – 21,81 µs.


Lepiej już nie będzie. Przy 20 i 40 MHz czas ten jest taki sam. Zapewne operacje oboktransferowe jak powroty z funkcji czy HAL’owa obsługa SPI zajmują wielokrotnie więcej niż sam transfer trzech komend. Jednakże wynik jest imponujący.

Pozostała liczba FPSów oraz ilość obiegów pętli while przy DMA. Pozwól, że pominę zdjęcia wyświetlacza pokazujące wyniki. Przedstawię je tabeli zestawiając razem z I²C.

Więc który interfejs wybrać?

CPU time Poll(ms)FPS PollCPU time DMAFPS DMALoops DMA
I2C 100 kHz109101,08 ms11331655
I2C 400 kHz27,4360,3 ms38317300
I2C 800 kHz12,38800,15 ms81293911
SPI 1,25 MHz8,2510438,37 us1022368545
SPI 5 MHz2,0728528,81 us2721585956
SPI 10 MHz1,0440321,81 us3781098468
SPI 20 MHz0,5350721,81 us477691864
SPI 40 MHz0,2758321,81 us544393303

Jakie wnioski można z tego wyciągnąć? Którego interfejsu użyć w projekcje? Do zabawy lub prostego urządzenia nie będzie miało większego znaczenia jaki interfejs wybierzesz. Jednak jeśli:

    1. Masz mało pinów, wielkość systemu dowolna – możesz użyć I²C.
    2. Masz mało pinów, a system jest krytyczny czasowo(posiada wiele elementów i tonę wykonującego się kodu na raz) – weź SPI bez resetu w trybie 3-przewodowym. Wymagać to będzie lekkiej przebudowy biblioteki.
  1. Dowolna ilość pinów i dowolna złożoność – SPI jest zdecydowanie lepszym wyborem. Chyba, że już nie jest dostępne.

Czy zwracać uwagę na maksymalną liczbę FPS? Jedynym “zagrożeniem” dla płynności animacji jest I²C 100 kHz. Cała reszta obsłuży 30 klatek dla zapewnienia płynności. Lepiej też programowo ograniczyć klatkaż do 30 czy 60. To nie Counter Strike.

A DMA? Moim zdaniem jest to must have. Szkoda życia na czekanie aż MCU wyśle dane “ręcznie”. Można na prawdę w tym czasie robić inne rzeczy jak akwizycja danych ze wszystkich czujników. Działanie dzięki temu będzie płynne, a wyświetlacz będzie cieszył oko stałą wysoką liczbą klatek.

Wcześniej wspomniałem o podwójnym buforowaniu. Chcąc wysyłać ramki w równych odstępach czasu na przykład przy pomocy przerwania na timerze może zdażyć się tak, że timer wyzwoli transfer DMA w momencie, gdy MCU wpisuje jakieś dane do bufora wyświetlacza. Wtedy co najmniej jedna ramka będzie zepsuta, co wprawne oko może zauważyć. Można zaimplementować podwójne buforowanie, które powinno to rozwiązać. Co to jest? Jak to zrobić? To kiedy indziej 😉

Podsumowanie

OLEDy są świetnymi wyświetlaczami. Używanie ich jest bardzo przyjemne i chętnie je stosuje w swoich urządzeniach. Myślę, że dobrze wykazałem różnicę między interfejsami i bardziej świadomie będziesz wybierał odpowiednie sterowanie dla siebie.

Jeżeli wyświetlacze OLEDowe Ci się spodbały, możesz nabyć je w różnych wariantach u mnie w sklepie.

Projekt z kodem znajduje się 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ś podystkutować w tym temacie, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.

5/5 - (15 votes)

Podobne artykuły

.

23 komentarze

krzychu1995 · 03/07/2022 o 22:32

Ech….
Pobrałem kod z Twojego Githuba. Miałem pewien problem w przeniesieniu tego pod F411RE, ale w końcu się udało. Padło kilka brzydkich wyrazów, nakombinowałem z ustawieniami DMA, musiałem zmienić następującą linijkę w pliku OLED_SSD1306.c (linia numer 383).
Było:
#if defined(SSD1306_SPI_CONTROL) && !defined(SSD1306_SPI_DMA_ENABLE) && !defined(SPI_CS_HARDWARE_CONTROL)
Jest:
#if defined(SSD1306_SPI_CONTROL) && defined(SSD1306_SPI_DMA_ENABLE) && !defined(SPI_CS_HARDWARE_CONTROL)

Bez tej zmiany nie działała funkcja SSD1306_DmaEndCallback, przez co transmisja nie szła.

W tej chwili już mam kod i działający przykład pod F411RE wraz z DMA, zatem do projektów pod audio gdzie ze względu na proceduralne generowanie dźwięku nie mogę pozwolić sobie na blokującą transmisję SPI – mogę w końcu dopiąć OLED-a 🙂

I tutaj malutka wskazówka – OLEDy te lubią siać zakłócenia ze względu na pracującą pompę ładunkową. Rozwiązania są dwa:
1. Nie włączać pompy w inicjalizacji i pod odpowiedni pin podać napięcie między bodajże 7 a 12V
2. Dać na przewód zasilający rdzeń ferrytowy – to stosuję w moim projekcie syntezatora polifonicznego, który akurat co prawda jest na kilku układach STM32, ale jeszcze programowanych w Arduino (PlatformIO). Ale i tam musiałem małą poprawkę do biblioteki zrobić, gdyż było zamieszanie z ścieżkami w include

    Mateusz Salamon · 04/07/2022 o 09:12

    Aaa no mogłem się gdzieś machnąć jak pisałem ten kod… Dzięki!

    Chyba tylko SSD1306 w chińskich modułach wykorzystuje wewnętrzną przetwornicę. Inne kontrolery SSD mają już zrealizowaną zewnętrzną, ale pewnie też coś sieje 🙂 OLED niestety wymaga wyższego napięcia, aby wysterować swoje diodki.

Tomasz · 08/03/2022 o 18:44

Mam uruchomiony projekt zliczania czasu wkładki gramofonowej na Arduino.
Niestety ekran OLED 0,96c mruga co 1 minutę. Jak można to zjawisko wyeliminować ?!
Pozdrawiam
Tomek

    Mateusz Salamon · 08/03/2022 o 18:47

    Nie mam pojęcia…

Bogdan · 16/10/2020 o 08:46

Witam,
Jestem amatorem STM32. Robiłem wczoraj cos na SSD1306 128×64 i zastanawiał mnie długi czas zapełniania pixelami ekranu przy 400k zegarze I2C.
Miałem akurat wbudowany gettick i zmierzyłem czas – wyszło mi ok. 77ms.

Natknałem sie na Pana artykuł i widze, ze powinno byc szybciej.

Popatrzyłem w kod:
void ssd1306SendData(uint8_t data){
uint8_t control = 0x40; // Co = 0, D/C = 1
I2CMasterBuffer[0] = SSD1306_I2C_ADDRESS;
I2CMasterBuffer[1] = control;
I2CMasterBuffer[2] = data;
HAL_I2C_Master_Transmit(&hi2c1, SSD1306_I2C_ADDRESS, I2CMasterBuffer+1, 2, 1000);
}

void ssd1306Refresh(void) {
ssd1306SendCommand(SSD1306_SETLOWCOLUMN | 0x0); // low col = 0
ssd1306SendCommand(SSD1306_SETHIGHCOLUMN | 0x0); // hi col = 0
ssd1306SendCommand(SSD1306_SETSTARTLINE | 0x0); // line #0
uint16_t i;
for (i=0; i<1024; i++) { ssd1306SendData(_ssd1306buffer[i]); }
}

Policzyłem bity i wychodzi mi 29 bitów na każdy wysłany bajt na ekran za pomoca I2C.
Ekran ma rozdzielczosc 128×64 co daje 8192 bity czyli 1024 byes.

Po doliczeniu overheadu I2C i protokołu SSD1306 mamy:
1026x29bitów = 29 696 bitow do przesłania.

Co powinno zając czas: 29 696 / 400 000 = 74.2ms

U mnie jest 77ms w realu wiec jakby sie zgadza.

Zastanawia mnie dla czego u Pana jest szybciej kilka razy…

Pozdrawiam,
Bogdan

    Mateusz Salamon · 16/10/2020 o 08:49

    Używasz DMA czy wysyłasz blokującymi funkcjami? Ja wycisnąłem maks właśnie, gdy puszczam całą ramkę przez DMA.

    Jeśli używasz zwykłej funkcji wysyłającej pojedyncze bajty do OLED, to w koło każdego bajtu będziesz miał albo adresowanie albo inne operacje na mikrokontrolerze.

Grzegorz · 11/04/2020 o 15:52

Witam.
Zrobiłem podobny projekt oparty o stm32f103 oraz wyświetlacz jaki jest opisany w tym projekcie z tym, że mój jest dwu kolorowy.
komunikacja I2C.
Korzystam z biblioteki dostępnej tutaj oraz z kilku innych i nic się nie wyświetla.
Siedzę nad tym już kilka dni i pomysły się wyczerpały.
Dlatego się zastanawiam czy mój wyświetlacz nie potrzebuje czasem innej biblioteki, a może jest po prostu uszkodzony.
Dodam iż kupiłem go jako nowy i oryginalnie zapakowany.
Może masz jakiś pomysł?
Z poważaniem Grzegorz.

    Mateusz Salamon · 11/04/2020 o 18:01

    Nie wiem jaki masz dokładnie wyświetlacz, ale pewnie też 0.96″ na SSD1306 skoro jest dwukolorowy (zółty z niebieskim). Nie wiem tak naprawdę co zrobiłeś oprócz tego, że nad tym siedzisz kilka dni 🙂
    Jak dołączyłeś moją bibliotekę? Czy zadbałeś o to, aby poprawnie skonfigurować I2C? Czy w bibliotece masz zaznaczone, że ma korzystać z I2C? Czy na pewno adres wyświetlacza masz taki sam, jak ja miałem w artykule? Musisz podać więcej szczegółów.

      Grzegorz · 11/04/2020 o 18:36

      Witam.
      Tak wyświetlacz mam SSD1306 0.96″ kolor żółty i niebieski.
      Jeśli chodzi o mój projekt, to piszę w Eclipse C++. Stworzyłem projekt w CUBE z ustawieniami I2C jak sugerujesz. Skopiowałem pliki z rozszerzeniem c oraz h do mojego projektu. Podałem w bibliotece, że korzystam z I2C. SCU i SDA ustawiałem i sprawdzałem na pinach B6 i B7 oraz B8 i B9 (tak jak u ciebie) oczywiście po zmianie konfiguracji dla danych wyjść. Zegar 100kHz. Dodam, iż do wyświetlacza SCL i SDA dochodzi, bo sprawdzam na pinach. Adres zostawiłem bez zmian. Nie wiem jak go sprawdzić.

      Mateusz Salamon · 11/04/2020 o 18:42

      Ok to w pierwszej kolejności przeskanuj I2C, aby być pewnym, że MCU widzi OLED na magistrali 🙂 Przy okazji wyprintuje adres wyświetlacza jesli go widzi https://github.com/ProjectsByJRP/stm32_hal_i2c_bus_scan

      Grzegorz · 11/04/2020 o 19:25

      Z linkiem do STM32 trafiłeś w 10. To jest moje NUCLEO. Widzę, że jeszcze dużo muszę się nauczyć. Wybacz moje pytanie ale gdzie mi się ta wiadomość wyświetli?

      Mateusz Salamon · 11/04/2020 o 19:37

      Na UART, czyli musisz jakąś konsolę odpalić. Polecam RealTerm 🙂

      Grzegorz · 11/04/2020 o 19:47

      Dzięki za program.
      Pojawiają się same krzaki.

magnez · 30/03/2020 o 19:37

Ciekawy artykuł. Widać, że włożyłeś w niego trochę czasu ale więcej czasu poświęciłeś na zdobycie wiedzy, którą tu wykorzystałeś zarówno w temacie mikrokontorlerów jak i opisywanego wyświetlacza SSD1306 oraz komunikacji I2C i SPI. Szacun.

    Mateusz Salamon · 02/04/2020 o 20:07

    Dzięki! Moje doświadczenie w wyświetlaczach na pewno mi się przydało 🙂

SaS · 03/12/2019 o 13:03

CS nie można zdjąć zaraz po zakończeniu transferu DMA bo transfer JESZCZE TRWA! Sprawdź na analizatorze.
To, że transfer trwa wynika z tego, że przerwanie DMA wywołuje przerwanie po wysłaniu danej do SPI a:
– Po pierwsze “primo” skoro wpisano bajt, to dopiero zacznie on być transmitowany.
– Po drugie “primo” SPI ma 2 bajtowe FIFO.
Problem zbyt wczesnego zdejmowania CS 9problem z ostatnimi pikselami na wyświetlaczu) rozwiązałem używając timera systemowego 1ms. W przerwaniu od DMA ustawiam licznik na 2, który w przerwaniu zmniejsza swoją zawartość. W chwili przejścia z 1 na 0 zdejmuje CS. Dlaczego licznik na 2 a nie 1? Proszę się zastanowić.
Ze względu na problem z CS, to że potrzeba 4 linii (MOSI, SCK, C/D, CS) najczęściej używam I2C.

Kamil · 02/09/2019 o 21:22

Cześć
Mam takie dość nietypowe pytanie.Skąd wiesz że adres dla komend to 0x00 a dla danych to 0x40? Przeglądałem dokumentację do SSD1306 i nie mogę znaleźć.
Pytam bo mam do zrobienia wyświetlacz na 1327 i chciałem się trochę wzorować na Twoim kodzie 😉

    Mateusz Salamon · 02/09/2019 o 21:26

    Dokumentacja kontrolera SSD1306 strona 21 🙂 Masz tam Control Byte który wysyłasz zaraz za Slave Address’em, czyli niejako jest to “rejestr” urządzenia.
    https://msalamon.pl/download/515/

Fonak · 04/05/2019 o 11:53

Ok dzięki, będę wyczekiwał na artykuły. Na internecie można znaleźć sporo informacji na temat tych wyświetlaczy. Szczególnie dużo jest odnośnie typu ST7735 (TFT 1.8″ 128×160 SPI) oraz matrycy IPS 1,3″ 240×240 (ST7789) jednak brakuje szybkiego i łatwego w implementacji drivera pod STM32F1 lub STM32F4 z użyciem CUBEMX i SPI z DMA. Niewątpliwą zaletą jest też ich cena, model na matrycy 1,8″ TFT ST7735S (czerwone PCB) można kupić za około 12zł a model na sterowniku ST7789 (IPS 1,3″ 64k kolorów) za 10zł z przesyłką od myfredów 🙂

    SaS · 03/12/2019 o 13:12

    Poszukaj biblioteki dla Arduino, najlepiej sprawdź czy z “Srajduino” zadziała (nie przerażaj się prędkością a raczej wolnością wyświetlania) i przenieś na STM32. Jak zadziała (z pewnością tez wolno jak na Srajduino) to zmodyfikuj biblioteki tak aby “drawPixel” zapisywał do bufora w RAM. Dodaj jeszcze funkcję “display”, która przez DMA wyśle do wyświetlacza (pamiętaj o problemie zdejmowania CS). Od teraz wyświetlacz bedzie potrafił działać z max prędkością.
    W ten sposób zrealizowałem obsługę wielu wyświetlaczy, nawet akceleratora dla Srajduino https://www.youtube.com/playlist?list=PLdtkbzWTUVMnNf_gExmLmiaacjvhzPRdM
    Większa wersja w trakcie realizacji https://www.youtube.com/playlist?list=PLdtkbzWTUVMnNUNH3MiWQ9s86bcULhy33
    Na problem małej ilości RAM przy dużych rozdzielczościach tez jest lekarstwo ale bez RTOS sie raczej nie obejdzie.

      Mateusz Salamon · 08/12/2019 o 15:05

      O tak, wadą Arduinowych bibliotek często jest to, że nie korzystają zbuforowania ramki(lub jej części)

Fonak · 03/05/2019 o 13:37

Hej,
Czy jest w planach podobny artykuł dotyczący wyświetlaczy LCD opartych o kontroler ST7789 i ST7735S. Są to bardzo popularne moduły używane w wielu projektach.

    Mateusz Salamon · 03/05/2019 o 21:23

    Cześć! Dzięki za podrzucenie ciekawego wyświetlacza. Nie znam ich, ale wyglądają ciekawie. Zobaczę co da się zrobić 😉

Dodaj komentarz

Avatar placeholder

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