[MBED] STM32 i SSD1306 – sterowanie dwukolorowym OLED [1]
Komendy sterownika SSD1306
Przy okazji omawiania adresowania pamięci RAM obrazu opisałem kilka komend przeznaczonych do wyboru trybu adresowania i ustawiania liczników adresu. Sterownik SSD1306 ma dosyć spory zestaw komend sterujących. Dokładne opisywanie wszystkich znacznie wykracza poza zakres tego artykułu i nie jest tez konieczne, bo dane te można znaleźć w dokumentacji.
Obsługa SSD1306 w mbed
Jak już wspomniałem moduł wyświetlacza jest skonfigurowany do pracy z 4-przewodową magistralą SPI. Jako sterownik – host wykorzystałem moduł Nucleo-F401RE z mikrokontrolerem rodziny STM32F401, produkowany przez firmę STM. Główne powody tego wyboru to wbudowany programator i wsparcie w systemie bezpłatnego środowiska projektowego mbed oferowanego przez firmę ARM.
Wsparcie Nucleo przez mbed zapewnia bardzo prostą konfigurację układów peryferyjnych i pozwala skupić się na głównym problemie, czyli na obsłudze wyświetlacza.
Na początek zajmiemy się interfejsem komunikacyjnym SPI. W środowisku mbed trzeba zainicjalizować SPI podając linie na których będą dostępne sygnały MOSI (dane wyjściowe), MISO (dane wejściowe) i SCK (zegar taktujący przesyłaniem danych). Standardowo te sygnały są dostępne na złączu zgodnym ze standardem Arduino Uno modułu Nucleo. Jeżeli chcemy z nich skorzystać to wystarczy zadeklarować interfejs z predefiniowanymi definicjami (listing 1).
List. 1. Konfiguracja interfejsu SPI i linii dodatkowych
SPI dev(SPI_MOSI, SPI_MISO, SPI_SCK); DigitalOut CS(PB_6); DigitalOut DC(PC_7); DigitalOut RES(PA_9);
Interfejs SPI oprócz linii MOSI, MISO i SCK wymaga linii CS (standardowa linii interfejsu SPI) i linii DC określającej miejsce docelowe przesyłanych danych (rejestry sterujące, lub pamięć obrazu). Poza tym sterownik SSD1306 musi zostać sprzętowo wyzerowany przez wymuszenie stanu niskiego na linii RESET. Linie interfejsu SPI łącznie z linią RESET należy również zdefiniować. Konfiguracja sprzętowa została pokazana na listingu 1.
Wyprowadzenia płytki wyświetlacza nie zawierają linii wyjścia danych ze sterownika, a to oznacza, że żadnych danych nie będziemy mogli odczytywać.
Do sterowania wyświetlaczem będą nam potrzebne dwie użyteczne procedury: zapisania rejestrów stepujących, czyli kodu komendy i ewentualnie jej argumentów, oraz zapisania danej do pamięci obrazu wyświetlacza – listing 2 i listing 3.
List. 2. Wysłanie kodu komendy do sterownika
//*********************************************** //wysłanie danej przez SPI //********************************************** void WriteSpi(unsigned char data) { dev.write(data); } //*********************************************** //zapisanie kodu komendy do sterownika SSD1306 //*********************************************** void WriteCmd(unsigned char cmd) { CS=1; DC=0; //DC=0 przesłanie komendy CS=0; WriteSpi(cmd); //zapis kodu komendy CS=1; }
List. 3. Zapisanie danej do pamięci RAM
//*********************************************** //zapisanie danej do pamięci obrazu sterownika SSD1306 //*********************************************** void WriteData(unsigned char data) { CS=1; DC=1; //DC=1 przesłanie danej do pamięci RAM CS=0; WriteSpi(data); //zapis danej CS=1; }
SSD1306 tak jak większość sterowników po włączeniu zasilania ustawia w rejestrach domyślne wartości. To jakie wartości trzeba wpisać do tych rejestrów zależy od rodzaju użytej matrycy OLED i zazwyczaj program użytkownika musi je zapisać w procesie inicjalizacji. Kompletna procedura inicjalizacji void InitSSD1306 () została pokazana na listingu 4.
List. 4. Inicjalizacja sterownika wyświetlacza
//*********************************************** //inicjalizacja sterownika SSD1306 //*********************************************** void InitSSD1306 (void) { CS=1; DC=0; RES=1; //_reset = 1; wait(0.01); RES=0; //_reset = 0; wait(0.10); RES=1; //_reset = 1; WriteCmd(0xAE);//wyłącz panel OLED WriteCmd(0x00);//adres koluny LOW WriteCmd(0x10);//adres koluny HIGH WriteCmd(0x40);//adres startu linii WriteCmd(0x20);//tryb adresowania strony WriteCmd(0x02); WriteCmd(0x81);//ustaw kontrast WriteCmd(0xCF); WriteCmd(0xA1);//ustaw remapowanie WriteCmd(0xC0);//kierunek skanowania WriteCmd(0xA6);//wyświetlanie bez inwersji WriteCmd(0xA8);//ustaw multiplex ratio WriteCmd(0x3F);//1/64 WriteCmd(0xD3);//ustaw display offset WriteCmd(0x00);//bez offsetu WriteCmd(0xD5);//ustaw divide ratio/częstotliwość oscylatora WriteCmd(0x80);//100ramek/sec WriteCmd(0xD9);//ustaw okres pre charge WriteCmd(0xF1);//pre charge 15 cykli, discharge 1 cykl WriteCmd(0xDA);//konfiguracja wyprowadzeń sterownika WriteCmd(0x12); WriteCmd(0xDB);//ustawienie vcomh WriteCmd(0x40); WriteCmd(0x8D);//ustawienie Charge Pump WriteCmd(0x14); WriteCmd(0xA4);//"podłączenie" zawartości RAM do panelu OLED WriteCmd(0xA6);//wyłączenie inwersji wyświetlania WriteCmd(0xAF);//włącza wyświetlacz DisplayCls(0);
Najpierw wykonywana jest sprzętowa sekwencja wygenerowania impulsu zerującego o długości 10 ms i zainicjowaniu linii CS =1 i DC=0. Właściwa programowa inicjalizacja zaczyna się wysłania komendy AE wyłączającej sterowanie matrycą OLED. Potem są inicjowane(zerowane) liczniki adresowe kolumn i stron i wprowadzany tryb adresowania Page Addressing Mode. Zakładamy, że wyświetlacz będzie pracował w układzie normalnym, to znaczy, że dół wyświetlacza będzie przy krawędzi płytki z wyprowadzeniami. Do tej orientacji mechanicznej jest ustawiane remapowanie, kierunek skanowania i konfiguracja wyprowadzeń sterownika.
Ponieważ sterownik może współpracować z różnymi matrycami, to ma możliwość ustawienia wielu parametrów takich jak divide ratio, częstotliwość odświeżania, oraz napięcie zasilające (prąd segmentów) matrycy OLED. Z tymi ustawieniami jest bardzo często problem. Albo mamy wyświetlacz, o którym tylko wiemy jaki ma sterownik, albo w danych katalogowych nie są podane tego typu istotne informacje. Pozostaje inicjowanie pracy układu sterującego metodą prób i błędów. W przypadku opisywanego wyświetlacza istnieje coś w rodzaju dokumentacji technicznej, ale niestety opisuje ona głównie komunikację ze sterownikiem, a te informacje są doskonale opisane w dokumentacji sterownika SSD1306.
Zacznijmy inicjowanie sterownika od ustawienia kontrastu. Prąd segmentu OLED można wyliczyć z zależności:
Parametr Contrast to wartość argumentu komendy 81h zmieniająca się w zakresie od 0 do 255.
Parametr Scale Factor ma stałą wartość równą 8.
Żeby zasilić matrycę OLED napięciem wyższym od napięcia zasilającego układy cyfrowe w układ SSD1306 wbudowano przetwornicę podwyższająca napięcie działająca na zasadzie pompy ładunkowej charge pump. Do działania takiego układu potrzebne są tylko 2 zewnętrzne kondensatory podłączane do wyprowadzeń C1N, C1P i C2N oraz C2P. Wartość napięcia wyjściowego określa się zewnętrznym rezystorem podłączonym do wyprowadzenia IREF. Napięcie na tym wyjściu jest równe Vcc-2,5, gdzie Vcc jest napięciem zasilającym matrycę. W opisywanym wyświetlaczu rezystor R1 ma wartość 1MΩ i powinien wymuszać prąd o wartości ok 12,5µA +/- 2µA. Powoduje on spadek na R1 równy 12,5V. Zatem matryca jest zasilana napięciem 12,5V+2,5V=15V.
Po włączeniu zasilania sterownika przetwornica jest domyślnie wyłączona. Dlatego w procedurze inicjalizacji należy ją włączyć wysyłając komendę 8D z argumentem 14h (A2=1).
Po wyliczeniu prądu jednego segmentu można optymalnie dobrać wartość kontrastu dla matrycy. Ja wpisałem DFh, ale eksperymentowałem również z innymi wartościami.
Stopień podziału częstotliwości taktującej ustala się w zakresie od 1 do 16 komendą D5 Set Display Clock Divide Ratio/Oscillator Frequency. Najmłodsze 4 bity zawierają wartość D, a DCLK=Fosc/D+1. Jak można zobaczyć w procedurze inicjalizacji argument komendy D5 ma wartość 80h, czyli D+1=1. Sterownik jest taktowany częstotliwością równą częstotliwości Fosc. Cztery starsze bity argumentu zawierają współczynnik K określający ile cykli zegara jest przeznaczonych na wyświetlenie jednego rzędu (linijki). Częstotliwość z jaka są wyświetlane ramki można wyliczyć z zależności:
W przykładzie mamy D=1, K=8.
Współczynnik multipleksowania Mux jest ustawiany komendą A8 i wynosi 64. Zatem FFRM=Fosc/(8*64)=Fosc/512. Częstotliwość oscylatora to ok. 370kHz i FFRM jest równa ~720Hz.
Ostatnią czynnością inicjalizacji jest wyczyszczenie (wyzerowanie) pamięci obrazu RAM tak by po inicjalizacji na ekranie ni wyświetlały się przypadkowe wartości. Procedura czyszczenia wyświetlacza została pokazana na listingu 5. Działa ona poprawnie dla trybu adresowania Page Addressing Mode ustawionego w czasie inicjalizacji sterownika.
List. 5. Czyszczenie zawartości wyświetlacza
//********************************************** //zeruj bufor wyświetlacza //i zapisz jego zwartość do RAM obrazu //********************************************** void DisplayCls(unsigned char fill) { unsigned char i, j; for (i = 0; i < 8; i ++) { for (j = 0; j < 128; j ++) { DispBuff[j][i] = fill; } } RefreshRAM();//zawartość bufora do RAM obrazu } //**************************************************** //zapisanie zawartości bufora do RAM wyświetlacza //**************************************************** void RefreshRAM(void) { uint8_t i, j; for (i = 0; i < 8; i ++) { WriteCmd(0xB0 + i); SetColStart(); for (j = 0; j < 128; j ++) { WriteData(DispBuff[j][i]); } } }