LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Obsługa wyświetlacza telefonu Nokia 6100 ze sterownikiem Epson S1D15G00: programowa obsługa wyświetlacza

Sterownik S1D15G00 może się komunikować z hostem za pomocą 8- lub 16-bitowej równoległej magistrali pracującej w przemysłowym standardzie Intel8080 lub Motorola 6800. Takie połączenie zapewnia dużą prędkość przesyłanych danych…

 

 

 

Opis adresowania pamięci przez sterownik Epson S1D15G00 przedstawiono w pierwszej części artykułu.

 

Opis rozkazów obsługiwanych przez sterownik Epson S1D15G00 przedstawiono w drugiej części artykułu.

  

Wyświetlacz z Nokii 6100 zastosowano w module KAmodTFT2,
który znajdzie się w ofercie sklepu
KAMAMI.pl
w najbliższym czasie.

 

 

 

 

 

 

Sterownik S1D15G00 może się komunikować z hostem za pomocą 8- lub 16-bitowej równoległej magistrali pracującej w przemysłowym standardzie Intel8080 lub Motorola 6800. Takie połączenie zapewnia dużą prędkość przesyłanych danych, ale jest kłopotliwe w implementacji (duża liczba linii sterujących). Dlatego sterownik wyposażono też w 3- lub 4-przewodowy interfejs SPI. Do przesyłania danych wykorzystywane są linie danych DATA, zegarowa CLK i wyboru CS. W interfejsie 4-przewodowym dodawana jest jeszcze linia wyboru dane/polecenia D/C. Można wtedy przesyłać interfejsem standardowe dane 8-bitowe i wykorzystać w tym celu sprzętowe interfejsy SPI.
W interfejsie 3-przewodowym wyboru dane/komendy dokonuje się przesyłając dodatkowy dziewiąty bit. Upraszcza to interfejs (jedna linia mniej niż standardowo), ale trochę komplikuje jego programową obsługę. W wyświetlaczu telefonu Nokia 6100 w sterowniku S1D15G00 jest włączony na stałe interfejs SPI w wersji 3-przewodowej.

 

 

Rys. 7. Przesłanie danych 3-przewodową 
magistralą SPI

Rys. 7. Przesłanie danych 3-przewodową magistralą SPI

 

Nietypowy interfejs SPI, w którym się przesyła 9 bitów danych najwygodniej jest zaimplementować programowo. Na list. 1 pokazano procedurę WriteSpi obsługującą wysyłanie przez host 8 bitów danych. Przesyłanie kompletnego 9-bitowego słowa można łatwo zrealizować przez poprzedzenie wywołania WriteSpi wysłaniem zera (zapisanie komendy), lub wysłaniem jedynki (zapisanie 8-bitowej danej). Procedury realizujące wysyłanie komend i danych zostały pokazano na list. 2 i 3.

List. 1. Procedura wysyłająca 8 bitów danych przez SPI

void WriteSpi(unsigned char data){
unsigned char i;
    SetBit(CLK); //linia zegarowa stan wysoki
         for (i=0;i<8;i++) {
    ClrBit(CLK);        //linia zegarowa stan niski 
        if ((data&0x80)==0)
     ClrBit(DATA);    //na linii danych „0”
        else 
     SetBit(DATA);    //na linii danych „1”
    SetBit(CLK);        //linia zegarowa stan wysoki
    data<<=1;        //kolejny bit danych 
  }
    SetBit(CS);        //linia SCF w stan nieaktywny (wysoki)
}

 

List. 2. Wysłanie komendy do sterownika LCD

void WriteSpiCommand(unsigned char cmd){
    ClrBit(CS);    //SCF w stan aktywny niski
    ClrBit(CLK);
    ClrBit(DATA);//pierwszy bit =0 - komendy 
    WriteSpi(cmd);
}

 

List. 3. Wysłanie danej do sterownika LCD

void WriteSpiData(unsigned char data){
    ClrBit(CS);    //SCF w stan aktywny niski
    ClrBit(CLK);
    SetBit(DATA);//pierwszy bit =1 - dane 
    WriteSpi(data);
}

 

Po włączeniu zasilania sterownik wyświetlacza nie jest gotowy do pracy i wymaga zerowania oraz programowego zainicjalizowania. Pierwszą czynnością jaka należy wykonać jest sprzętowe zerowanie sterownika przez podanie na linię zerującą sterownik LCD stanu niskiego, a następnie wymuszenie na tej linii stanu wysokiego – co realizuje program pokazany na list. 4. Programową inicjalizację sterownika pokazano na list. 5.

List. 4. Inicjalizacja interfejsu SPI i zerowanie sterownika

SetBit(CS);    //linie interfejsu SPI inicjowane w stan wysoki 
SetBit(CLK);
SetBit(DATA);
ClrBit(RES);    //reset wyświetlacza aktywny
for(i=0;i<8000;i++)
delay();
SetBit(RES);    //reset nieaktywny
for(i=0;i<8000;i++)
delay();

 

List. 5. Inicjalizacja sterownika S1D15G00

// Display control
    WriteSpiCommand(DISCTL);
    WriteSpiData(0x00); // P1: 0x00 = 2 współczynnik podziału i period=8 
    WriteSpiData(0x20); // P2: 0x20 = 132/4 - 1 = 32 linii 
    WriteSpiData(0x00); // P3: 0x00 = bez inwersji 
// COM scan
    WriteSpiCommand(COMSCN);
    WriteSpiData(1); // P1: 0x01 = skanowanie 1->80, 160<-81
// Właczenie wewnętrznego oscylatora
    WriteSpiCommand(OSCON);
// Wyjście ze stanu uśpienia
    WriteSpiCommand(SLPOUT);
// programowanie układu zasilania (przetwornicy)
    WriteSpiCommand(PWRCTR);
    WriteSpiData(0x0f); // wł. Regulatora nap. ref., wł układu regulatora nap. wyjściowego, wł układu boostera
// Wyświetlanie inwersyjne
    WriteSpiCommand(DISINV);
// Data control
    WriteSpiCommand(DATCTL);
    WriteSpiData(0x01); // P1: 0x01 = dekrementowanie licznika stron, inkrementowanie licznika kolumn, zmiana licznika kolumn
    WriteSpiData(0x00); // P2: 0x00 = sekwencja RGB (wartość domyślna)
    WriteSpiData(0x02); // P3: 0x02 = 16 bitowa skala szarości (12-bitowy kolor A)
// sterowanie kontrastem
    WriteSpiCommand(VOLCTR);
    WriteSpiData(32); // P1 = 32 współczynnik alfa
    WriteSpiData(3); // P2 = 3 współczynnik podziału Ra/Rb
    for(i=0;i<8000;i++) //opóźnienie na ustabilizowanie się napięcia wyjściowego
    delay();;
// włączenie sterowania wyświetlaczem
    WriteSpiCommand(DISON);

 

Inicjalizację sterownika rozpoczynamy od wysłania komendy DisplayControl. Parametr P1 komendy jest wyzerowany, co oznacza współczynnik podziału=2, okres przełączania=8 (wartość domyślna). Parametr P2 ma wartość 0x20, czyli 132 dziesiętnie. Ponieważ nie chcemy żeby jakieś linie były wyświetlane inwersyjnie, to trzeci parametr zerujemy.
Kolejna komenda COMSCAN określa orientacje wysiedlanej informacji. Po wpisaniu do parametru komendy wartości 0x01 będzie to skanowanie 1->80, 160<-81. Następne 2 komendy włączają wewnętrzny oscylator (OSCON) i wybudzają sterownik (drivery) z domyślnego stanu uśpienia (SLPOUT).
W trakcie prób z wyświetlaczem okazało się, że żeby wyświetlane kolory były prawidłowe trzeba wysłać komendę DISINV. W opisie sterownika nie znalazłem informacji o tym w jakiej sytuacji należy używać tej komendy. Być może jej działanie jest związane z konstrukcją samej matrycy LCD.
Pierwszy parametr następnej komendy DACTL określa sposób modyfikacji liczników stron i kolumn. Dokładnie to zostało opisane przy okazji omawiania tej komendy. W inicjalizacji pierwszy parametr ma wartość 0x01. Adres stron jest dekrementowany, adres kolumn inkrementowany. Po każdym zapisaniu danych są modyfikowane liczniki kolumn. Drugi parametr jest wyzerowany do domyślnej wartości sekwencji kolorów RGB.
Trzeci parametr programuje tryb wyświetlania 8- lub 12-bitowy. Po wpisaniu wartości 0x02 wybrany zostaje tryb 16 bitowej skali szarości równoważny 12-bitowemu kolorowi.
Kontrast wyświetlacza jest regulowany parametrami komendy VOLCTR. Pierwszy parametr to opisywany wyżej parametr a. Można go ustawiać w zakresie 0…63. Drugi parametr określa współczynnik podziału dzielnika rezystorów Ra i Rb. Dobierając eksperymentalnie wartości tych parametrów można ustawić optymalny kontrast wyświetlacza. W trakcie prób regulacji kontrastu okazało się, że parametr 2 działa tylko dla wartości 2.
Po włączeniu układu przetwornicy i ustawieniu napięcia wyjściowego trzeba odczekać czas potrzebny na ustabilizowanie się napięcia zasilającego drivery i można włączyć sterowanie matryca wyświetlacza komendą DISON.
Ostatnią czynnością wykonywaną w trakcie inicjalizacji jest czyszczenie pamięci obrazu sterownika, bo po włączeniu zasilania w tej pamięci zapisują się wartości przypadkowe. Czyszczenie będzie polegało na zapisaniu wartości odpowiadającej jednakowemu kolorowi wszystkich pikseli wyświetlacza. Kolor inicjalizacji jest argumentem procedury ClsLCD (list. 6).

List. 6. „Czyszczenie” wyświetlacza

void ClsLCD(short color) {
  int i; 
// ustawienie zakresu zmian licznika adresowego stron pamięci
  WriteSpiCommand(PASET);
  WriteSpiData(0);    //adres początkowy 
  WriteSpiData(131);    //adres końcowy
// ustawienie zakresu zmian licznika adresowego kolumn 
  WriteSpiCommand(CASET);
  WriteSpiData(0);    //adres początkowy 
  WriteSpiData(131);    //adres końcowy
// zapisanie pamięci kolorem z 
  WriteSpiCommand(RAMWR);
  for(i = 0; i < ((131 * 131) / 2); i++) {
    WriteSpiData((color >> 4) & 0xFF);    //kopiowanie 12 bitów dla jednego piksela na 24 bity dla 2 pikseli
    WriteSpiData(((color & 0xF) << 4) | ((color >> 8) & 0xF));
    WriteSpiData(color & 0xFF);
  }   
}

 

Podczas „czyszczenia” komendami PASET i CASET definiujemy zakresy zmian adresów liczników stron i kolumn. Ponieważ zapisywanie będzie dotyczyło całego ekranu, to liczniki muszą się zmieniać w zakresie od 0 do 131 dla stron (wierszy) i kolumn. Zapisywanie danych do pamięci wyświetlacza należy poprzedzić komendą RAMWR. Jej wykonanie powoduje wpisanie do liczników adresowych wartości początkowych ustawionych komendami PASET i CASET. Każde zapisanie danej będzie powodowało modyfikację liczników w sposób określony komendą DATCTL z procedury inicjalizacji.
W argumencie komendy trzeba podać 12-bitową wartość odpowiadającą oczekiwanemu kolorowi. Na list. 7 pokazano definicje 12-bitowych liczb odpowiadającym kolorom w systemie RGB.

List. 7. 12-bitowe definicje kolorów

// definicja kolorów 12-bitowych
#define WHITE 0xFFF
#define BLACK 0x000
#define RED 0xF00
#define GREEN 0x0F0
#define BLUE 0x00F
#define CYAN 0x0FF
#define MAGENTA 0xF0F
#define YELLOW 0xFF0
#define BROWN 0xB22
#define ORANGE 0xFA0
#define PINK 0xF6A

 

Jednemu pikselowi odpowiada 12 bitów w pamięci obrazu. Jednak dane są zapisywane do sterownika bajtowo. Dane najlepiej jest przesyłać w 3- bajtowych „porcjach” (3*8=24 bity)odpowiadających kolejnym 2 pikselom po 12 bitów każdy. Dla zapisania pamięci całego wyświetlacza trzeba zapisać (131*131) 12-bitowych słów, ale lepiej jest zapisać (131*131)/2 24-bitowych słów czyli 3 bajtów. Tak też jest to robione w procedurze ClsLCD pokazanej na list. 7. W pętli, która jest wykonywana (131*131)/2 jest wykonywane kopiowanie 12 bitów argumentu koloru dla jednego piksela na 24 bity dla 2 pikseli.
Na prawidłowo zainicjowanym wyświetlaczu możemy wyświetlać informacje tekstowe lub bitmapy. Wyświetlanie tekstu wymaga zdefiniowania w pamięci mikrokontrolera – hosta tablicy z generatorem znaków. Graficzna natura wyświetlacza powoduje, że można definiować znaki o różnej wielkości elastycznie je dostosowując do potrzeb. Dodatkowo w kolorowym wyświetlaczu można definiować kolor znaku i kolor tła podnosząc czytelność i atrakcyjność wysiedlanej informacji.
Pierwszą bardzo żmudną czynnością jaka należy wykonać jest zdefiniowanie tablicy generatora znaków lub zastosowanie takiej tablicy pracowanej przez kogoś innego. Znalazłem gotową tablicę znaków o trzech rozmiarach znaków: 6x8 pikseli, 8x8 pikseli i 8x16 pikseli autorstwa Jamesa P. Lyncha. Do tablicy została dołączona procedura LCDPutChar (również tego autora) umożliwiająca wyświetlanie pojedynczych znaków . Po testach okazało się, że procedura działa doskonale i jest elastyczna, dlatego zdecydowałem się ja przedstawić (list. 8). Argumentami funkcji LCDPutChar są:

  

  • kod wyświetlanego znaku c,
  • współrzędne x, y początku napisu,
  • zmienna size określająca wielkość znaku,
  • definicja koloru znaku fColor,
  • definicja koloru tła bColor.

Aby wyświetlanie mogło dobrze działać dla różnych rozmiarów znaków, na początku każdej tablicy generatora znaków zostały umieszczone informacje o liczbie kolumn i wierszy w znaku i liczbie bajtów do pobrania z tablicy, niezbędnej do wyświetlenia jednego znaku. Te informacje są pobierane z tablicy i zapisywane do zmiennych nColumns, nRows i nBytes. Na podstawie nColumns i nRows oraz argumentów współrzędnych x i y są wyliczane adresy początku wyświetlania napisu i zapisywane komendami PASET i CASET. Do samego wyświetlania wykorzystany został taki sam mechanizm automatycznej zmiany adresów w obszarze zdefiniowanym przez PASET i CASET. Podobnie jak w procedurze czyszczenia ekranu do pamięci wyświetlacza są zapisywane za każdym razem 3 bajty określające kolor 2 pikseli.
Trzeba pamiętać, że argument c zawiera kod ASCII znaku, a tablica z wzorcami znaków zaczyna się od znaku spacji. Z tego powodu od kodu zwartego w zmiennej c trzeba odjąć 0x20. Jednak pierwsze 8 lub 16 bajtów tablicy są zajęte na informacje o rozmiarze znaku i liczbie bajtów na znak. Dlatego do kodu ASCII odejmuje się wartość 0x1F.

List. 8. Procedura wyświetlania pojedynczego znaku (autor James P. Lynch)

void LCDPutChar(char c, int x, int y, int size, int fColor, int bColor) {
//deklaracje tablic z generatorami znaków 
  extern const unsigned char FONT6x8[97][8]; 
  extern const unsigned char FONT8x8[97][8];
  extern const unsigned char FONT8x16[97][16];
  int i,j;
  unsigned int nCols;
  unsigned int nRows;
  unsigned int nBytes;
  unsigned char PixelRow;
  unsigned char Mask;
  unsigned int Word0;
  unsigned int Word1;
  unsigned char *pFont;
  unsigned char *pChar;
  unsigned char *FontTable[] = {(unsigned char *)FONT6x8, (unsigned char *)FONT8x8,
  (unsigned char *)FONT8x16};
//pobranie wskaźnika wybranej argumentem size jednej z trzech tablicy znaków
    pFont = (unsigned char *)FontTable[size];
// pobranie nColumns, nRows and nBytes – ilość kolumn, wierszy i bajtów na znak 
    nCols = *pFont;
    nRows = *(pFont + 1);
    nBytes = *(pFont + 2);
//pobranie wskaźnika do ostatniego znaku do wyświetlania i 
//korekcja z kodu ASCII do kodów tablicy generatora 
    pChar = pFont + (nBytes * (c - 0x1F)) + nBytes - 1;
// zapisanie zakresu adresowania stron (wierszy) – komenda PASET
    WriteSpiCommand(PASET);
    WriteSpiData(x);
    WriteSpiData(x + nRows - 1);
// zapisanie zakresu adresowania kolumn– komenda CASET
    WriteSpiCommand(CASET);
    WriteSpiData(y);
    WriteSpiData(y + nCols - 1);
// komenda RAMWR
    WriteSpiCommand(RAMWR);
//pętla dla każdego wiersza od góry do dołu 
    for (i = nRows - 1; i >= 0; i--) {
//kopiowanie wiersza pikseli z tablicy generatora i dekrementacja wiersza
    PixelRow = *pChar--;
//pętla dla każdego piksela w wierszu (z lewej do prawej) – każda pętla to 2 piksele
    Mask = 0x80;
    for (j = 0; j < nCols; j += 2) {
//jeżeli bit piksela jest ustawiony, to używamy koloru znaku 
//w przeciwnym przypadku koloru tła 
    if ((PixelRow & Mask) == 0)
     Word0 = bColor;
    else
     Word0 = fColor;
    Mask = Mask >> 1;
    if ((PixelRow & Mask) == 0)
     Word1 = bColor;
    else
     Word1 = fColor;
    Mask = Mask >> 1;
//zapisanie 3 bajtów dla 2 pikseli 
    WriteSpiData((Word0 >> 4) & 0xFF);
    WriteSpiData(((Word0 & 0xF) << 4) | ((Word1 >> 8) & 0xF));
    WriteSpiData(Word1 & 0xFF);
    }
}

 

 

 

Rys. 8. Sposób wyświetlania litery E

Rys. 8. Sposób wyświetlania litery E

 

Na rys. 8 pokazano przykład ilustrujący sposób wyświetlania znaku „E” o rozmiarze 8x8 pikseli od współrzędnej (20, 20). Najpierw definiujemy obszar 8x8 pikseli od współrzędnych początkowych komendami PASET i CASET, za co odpowiada program pokazany na list. 9.

List. 9. Procedura definiująca obszar wyświetlania znaku

WriteSPICommand (PASET);
writeData(20);
WriteData(27);    //limit 20,27
WriteSPICommand (CASET);
WriteData(20);    //limit 20,27
WriteData(27);

 

W czasie wpisywania danych licznik kolumn jest inkrementowany i kiedy osiągnie wartość 27 zostanie wyzerowany, a zwiększy się licznik wierszy. W ten sposób po wpisaniu 64 słów 12-bitowych zostanie wyświetlony znak „E”. Trzeba wpisać 64 słowa, bo każdy bajt z generatora odpowiada 8 pikselom, a bajtów jest 8.
Procedurę wyświetlania jednego znaku wykorzystamy do napisani procedury wyświetlającej napisy (list. 10). Argumentem procedury są: wskaźnik do początku tablicy z napisem, współrzędne x, y, rozmiar znaku i kody koloru znaku oraz tła.

List. 10. Wyświetlenie dłuższych tekstów z wykorzystaniem generatora znaków

void LCDPutStr(char *pString, int x, int y, int Size, int fColor, int bColor) {
// w pętli do napotkania znacznika końca łańcucha znaków 0x00
    while (*pString != 0x00) {
// wyświetlanie znaku 
    LCDPutChar(*pString++, x, y, Size, fColor, bColor);
// wyliczenie pozycji następnego znaku w zależności od jego wielkości 
    if (Size == SMALL)
    y = y + 6;    //dla 6x8
    else if (Size == MEDIUM)
    y = y + 8;    //dla 8x8
    else
    y = y + 8;    //dla 8x16
    // współrzędna poza zakresem 
    if (y > 131) break;
    }
}

 

Wyświetlanie pełnowymiarowych bitmap jest bardzo proste pod warunkiem, że mamy przygotowaną przez odpowiedni program tablicę z zapisanymi 8-bitowymi danymi RGB dla każdego z pikseli, lub tablice w której na 3 bajtach zapisane są 2 piksele. Zapisywanie bitmapy niewiele różni się od „czyszczenia” ekranu. Zamiast wpisywania stałej wartości koloru czyszczenia zapisuje się do pamięci dane z tablicy bitmapy. Na list. 11 pokazano wyświetlanie bitmapy w trybie koloru 8-bitowego.

List. 11. Zapisanie pełnowymiarowej bitmapy w trybie koloru 8-bitowego

void LCDWriteBmp(void) {
  long j;
  
  WriteSpiCommand(DATCTL);
  WriteSpiData(7);    // P1: 0x00 = adres strony dekr., adres kolumn dekr. ,mod. licznika stron
  WriteSpiData(0x00);    // P2: 0x00 = RGB (wartosc domyslna)
  WriteSpiData(0x01);    // P3:tryb 8 bitowy
  
  WriteSpiCommand(CASET);    //zakres licznika kolumn
  WriteSpiData(0);
  WriteSpiData(131);
  
  WriteSpiCommand(PASET);    //zakres licznika stron
  WriteSpiData(0);
  WriteSpiData(131);
  
  WriteSpiCommand(RAMWR);    //zapis do pamieci 17424 (132*132)bajtow
  for(j = 0; j < 17424; j++) {
    WriteSpiData(bmp[j]);
  }
  // powrot do ustawien dla wyswietlania tekstu
  WriteSpiCommand(DATCTL);
  WriteSpiData(0x01); // P1: 0x01 = adres strony dekr., adres kolumn inkr ,mod. licznika stron
  WriteSpiData(0x00); // P2: 0x00 = RGB 
  WriteSpiData(0x02); // P3: 0x02 = tryb 12 bitowy
  // Display On
  WriteSpiCommand(DISON);
}

 

Absolwent Wydziału Elektroniki Politechniki Wrocławskiej, współpracownik miesięcznika Elektronika Praktyczna, autor książek o mikrokontrolerach Microchip i wyświetlaczach graficznych, wydanych nakładem Wydawnictwa BTC. Zawodowo zajmuje się projektowaniem zaawansowanych systemów mikroprocesorowych.
Tagi: Epson