Wyświetlacze graficzne LCD ze sterownikiem KS0108 - sterowanie w języku C od podstaw. cz. 1.pdf

(689 KB) Pobierz
089-092_lcd_cz1.indd
K U R S
Wyświetlacze graficzne LCD ze
sterownikiem KS0108 – sterowanie
w języku C od podstaw, część 1
Najtańsze wyświetlacze graficz-
ne zbudowane są w oparciu o ste-
rownik KS0107/KS0108 (HD61202/
HD61203) bez generatora znaków.
Cena takiego wyświetlacza o roz-
dzielczości 128x64 pikseli jest
w przybliżeniu 3–krotnie większa
od ceny zwykłego wyświetlacza al-
fanumerycznego 16x2, przy o wiele
większych możliwościach prezen-
tacji danych. Wyświetlacz o takiej
rozdzielczości pozwala na wyświe-
tlenie ośmiu linii po 21 znaków
(typowa czcionka 5x7 pikseli), co
daje łączną liczbę 168 znaków,
oczywiście posiada także możliwość
wyświetlenia grafiki. Brak wbudo-
wanego generatora znaków wydaje
się być poważną wadą wyświetla-
cza, jednak w prosty sposób można
sobie z tym poradzić przez progra-
mową generację znaków przez mi-
krokontroler. Wszystkie niezbędne
funkcje realizujące wyświetlanie
tekstu wraz z tablicą znaków zapi-
saną w pamięci programu zajmują
około 1 kB pamięci.
W najróżniejszych urządzeniach zbudowanych w oparciu
o mikrokontrolery, do prezentacji danych wyjściowych wykorzystywane
są wyświetlacze LCD. Najczęściej są to wyświetlacze alfanumeryczne
ze sterownikiem HD44780 ze względu na stosunkowo niską
cenę oraz łatwe sterowanie. Możliwości prezentacji danych na
wyświetlaczach alfanumerycznych są niewielkie w porównaniu
z wyświetlaczami graficznymi. W artykule zajmiemy się obsługą
w języku C wyświetlacza graficznego ze sterownikiem KS0108.
„Na warsztat” weźmie-
my wyświetlacz JM12864A
produkowany przez fir-
mę JHD. Wyświetlacz
JM12864A posiada dwa
kontrolery, aktywowane sy-
gnałami /CS1 oraz /CS2
(aktywny stan niski). Opis
wyprowadzeń tego wy-
świetlacza przedstawiono
w tab. 1 .
Wyświetlacze innych firm
mogą posiadać inny rozkład
wyprowadzeń, tak więc za-
wsze przed podłączeniem
ich należy dokładnie zapo-
znać się z dokumentacją. Do stero-
wania wyświetlaczem wymaganych
jest 13 wyprowadzeń mikrokontro-
lera (można tę liczbę zmniejszyć
stosując ekspandery portów z sze-
regowym wejściem, np. PCF8574,
ale spowolni to wymianę danych
pomiędzy mikrokontrolerem, a wy-
świetlaczem). W przeciwieństwie
do wyświetlaczy ze sterownikiem
HD44780 nie jest możliwa transmi-
sja w trybie 4–bitowym.
Wyświetlacz posiada 1 kB pa-
mięci RAM. Organizację pamięci
i jej odwzorowanie na ekran wy-
świetlacza przedstawia rys. 1 . Ad-
res Y odpowiada współrzędnej x
ekranu (oś pozioma), natomiast ad-
res X określa numer strony (czyli
jest podzieloną przez 8 współrzęd-
ną y ekranu). Adres Z
określa, która linia pamięci obrazu
będzie odwzorowana w najwyższej
linii wyświetlacza. Zapis danych
do pamięci odbywa się po 8 bitów,
najmłodszy bit odpowiada pikselo-
wi położonemu w linii najwyższej
w obrębie danej strony, a najstarszy
bit odpowiada pikselowi położone-
mu w linii najniższej. Pokazano to
na rys. 2 (fragment dwóch pierw-
szych stron ekranu wyświetlacza).
Tab. 1. Opis wyprowadzeń wyświetla-
cza graficznego JM12864A
Numer Oznaczenie Funkcja
1 /CS1
Wybór pierwszego
kontrolera
Rozkazy sterownika KS0108
Sterownik KS0108 obsługuje 7
podstawowych rozkazów:
2 /CS2
Wybór drugiego
kontrolera
3 V ss
Masa
4 V dd
+5V
Display On/Off
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
Zasilanie LCD (regu-
lowane)
6 RS Wybór rejestru (0:
instrukcje, 1:dane)
7 R/W Kierunek przesyłu (0:
zapis, 1: odczyt)
8 EN Wykonanie operacji
9 DB0 Bit danych 0
10 DB1 Bit danych 1
11 DB2 Bit danych 2
12 DB3 Bit danych 3
13 DB4 Bit danych 4
14 DB5 Bit danych 5
15 DB6 Bit danych 6
16 DB7 Bit danych 7
0 0 0 0 1 1 1 1 1 D
D=0 – wyświetlanie zawartości
pamięci RAM na ekranie wyłączone
D=1 – wyświetlanie zawartości
pamięci RAM na ekranie włączone
Instrukcja nie ma wpływu na
zawartość pamięci RAM.
Set Address (Y address)
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
0 0 0 1 AC5 AC4 AC3 AC2 AC1 AC0
Ustawienie adresu Y wyświetlacza
(czyli współrzędnej x ekranu) z zakre-
Elektronika Praktyczna 1/2007
89
Rys. 1. Organizacja pamięci i jej odwzorowa-
nie na ekran wyświetlacza
5 V ee
649301758.044.png 649301758.045.png 649301758.046.png 649301758.047.png 649301758.001.png
K U R S
su 0...63. Adres jest automatycznie
inkrementowany po każdej operacji
odczytu, bądź zapisu danych.
su Y. Po wykonaniu instrukcji
następuje automatyczna inkre-
mentacja adresu Y.
Set Page (X address)
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
Komunikacja
mikrokontrolera
z wyświetlaczem
Szyna danych interfejsu wy-
świetlacza zajmuje cały dowol-
nie wybrany port, natomiast
sygnały sterujące zajmują 5
wyprowadzeń drugiego dowol-
nie wybranego portu. Poszcze-
gólne sygnały sterujące można
przypisać do dowolnych wy-
prowadzeń w ramach wybrane-
go portu. Schemat połączenia
wyświetlacza JM12864A z mi-
krokontrolerem ATMega16 jest
przedstawiony na rys. 3 . Napię-
cie ujemne konieczne do zasilania
wyświetlacza jest wytwarzane przez
układ MAX232 (w przypadku zasto-
sowania wyświetlacza z wbudowa-
nym generatorem napięcia ujemnego
można ten układ pominąć).
Przedstawione w artykule pro-
cedury zostały napisane w języku
C i skompilowane kompilatorem
avr–gcc. „Sterownik” wyświetla-
cza znajduje się w trzech pli-
kach: JM12864A.c (kod procedur),
JM12864A.h (definicje stałych, kon-
figuracja portów itp.) oraz font.h
(tablica czcionek). Pliki te należy
dołączyć do projektu korzystającego
z procedur obsługi wyświetlacza.
W pliku JM12864A.h znajdują
się definicje poszczególnych sygna-
łów sterujących:
// port szyny danych
// można ustawić dowolny
#define LCD_DATA_PORT PORTA
#define LCD_DATA_PIN PINA
#define LCD_DATA_DDR DDRA
// port sygnałów sterujących
// można ustawić dowolny
#define LCD_CTRL_PORT PORTC
#define LCD_CTRL_PIN PINC
#define LCD_CTRL_DDR DDRC
// sygnały sterujące
// wszystkie sygnały należą do portu
CTRL, kolejność dowolna
#define LCD_CS1P PC6
#define LCD_CS2P PC5
#define LCD_EN PC0
#define LCD_RW PC1
#define LCD_RS PC2
Następnie zdefiniowanych jest
kilka podstawowych makroinstrukcji
kontrolujących stan sygnałów steru-
jących:
// makroinstrukcje ustawienia stanu
na linii CS1
#define SET_CS1() (LCD_CTRL_PORT |=
(1 << LCD_CS1P))
#define CLR_CS1() (LCD_CTRL_PORT &=
~(1 << LCD_CS1P))
// makroinstrukcje ustawienia stanu
na linii CS2
#define SET_CS2() (LCD_CTRL_PORT |=
(1 << LCD_CS2P))
#define CLR_CS2() (LCD_CTRL_PORT &=
~(1 << LCD_CS2P))
0 0 1 0 1 1 1 AC2 AC1 AC0
Ustawienie adresu X (wybór
strony) wyświetlacza z zakresu 0...7.
Wszelkie operacje zapisu i odczytu
danych wykonywane są na bieżącej
stronie do momentu wybrania no-
wej strony.
Display Start Line (Z address)
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
0 0 1 1 AC5 AC4 AC3 AC2 AC1 AC0
Rys. 2. Strona pamięci w powiększeniu
Adres Z określa, od której linii
obraz zawarty w pamięci RAM ma
zostać wyświetlony na ekranie.
// makroinstrukcje ustawienia stanu
na linii EN
#define SET_EN() (LCD_CTRL_PORT |= (1
<< LCD_EN))
#define CLR_EN() (LCD_CTRL_PORT &=
~(1 << LCD_EN))
// makroinstrukcje ustawienia stanu
na linii RW
#define SET_RW() (LCD_CTRL_PORT |= (1
<< LCD_RW))
#define CLR_RW() (LCD_CTRL_PORT &=
~(1 << LCD_RW))
// makroinstrukcje ustawienia stanu
na linii RS
#define SET_RS() (LCD_CTRL_PORT |= (1
<< LCD_RS))
#define CLR_RS() (LCD_CTRL_PORT &=
~(1 << LCD_RS))
// makroinstrukcje ustawiające od-
powiednią kombinację sygnałów CS1
i CS2
#define LCD_CS0() CLR_CS1();SET_
CS2();
#define LCD_CS1() SET_CS1();CLR_
CS2();
#define LCD_NOCS() SET_CS1();SET_
CS2();
Status read
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
0 1 BUSY 0 ON/
OFF
RE-
SET 0 0 0 0
Status kontrolera:
BUSY=1 – kontroler wykonuje
operację i nie przyjmuje kolejnych
instrukcji
BUSY=0 – kontroler jest gotowy
do przyjęcia kolejnej instrukcji
ON/OFF=1 – wyświetlacz jest
włączony
ON/OFF=0 – wyświetlacz jest
wyłączony
RESET=1 – trwa inicjalizacja
wyświetlacza, żadne instrukcje za
wyjątkiem instrukcji odczytu statu-
su nie są akceptowane
RESET=0 – inicjalizacja zakoń-
czona, kontroler w stanie normalnej
pracy
Oraz definicje rozkazów sterow-
nika KS0108:
#define DISPLAY_SET_Y 0x40
#define DISPLAY_SET_X 0xB8
#define DISPLAY_START_LINE 0xC0
#define DISPLAY_ON_CMD 0x3E
#define ON 0x01
#define OFF 0x00
#define DISPLAY_STATUS_BUSY0x80
W programie (plik JM12864A.c)
należy zadeklarować globalne
zmienne przechowujące współrzęd-
ne kursora, gdyż nie jest możliwy
odczyt z wyświetlacza aktualnej po-
zycji kursora w:
unsigned char lcd_x, lcd_y;
Write Display Data
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
1 0 D7 D6 D5 D4 D3 D2 D1 D0
Zapis danej do pamięci RAM wy-
świetlacza pod aktualny adres Y. Po
wykonaniu instrukcji następuje auto-
matyczna inkrementacja adresu Y.
Funkcje pomocnicze
Pierwszą funkcją będzie funkcja
konfigurująca porty wykorzystywa-
ne do komunikacji z wyświetlaczem
w tryb wyjściowy. Wyświetlacz
JM12864A nie wymaga dodatkowej
inicjalizacji (poza wyczyszczeniem
ekranu) jak to miało miejsce w wy-
świetlaczu alfanumerycznym.
void lcdInit(void)
{
LCD_DATA_DDR = 0xFF;
Read Display Data
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
1 1 D7 D6 D5 D4 D3 D2 D1 D0
Odczyt danej z pamięci RAM
wyświetlacza spod aktualnego adre-
90
Elektronika Praktyczna 1/2007
649301758.002.png 649301758.003.png 649301758.004.png 649301758.005.png 649301758.006.png 649301758.007.png 649301758.008.png 649301758.009.png 649301758.010.png 649301758.011.png 649301758.012.png 649301758.013.png 649301758.014.png 649301758.015.png
K U R S
else // w przeciwnym ra-
zie
{LCD_CS1()} // zapisuje-
my do drugiego kontrolera
lcdWait(); // oczekiwanie
na gotowość kontrolera
SET_RS(); // wysoki stan
na linii RS –> dane
CLR_RW(); // niski stan
na linii RW –> zapis
LCD_DATA_DDR = 0xFF; //
port danych –> wyjście
LCD_DATA_PORT = data; //
wystawienie na port danej
SET_EN(); // wysoki stan na
linii EN
delay(); // opóźnienie
CLR_EN(); // niski stan na
linii EN
lcd_x++; // zwiększenie
współrzędnej x wyświetlacza
(pomocniczej)
if(lcd_x > 127) // jeśli
koniec ekranu
lcd_x = 0; // to wyzeruj
współrzędną x
LCD_NOCS();
}
Rys. 3. Schemat dołączenia wyświetlacza JM12864 do mikrokontrolera
Odczyt danych
Dane odczytywane są
przy wysokim stanie linii RS i RW.
unsigned char lcdReadData(void)
{
unsigned char data;
if(lcd_x < 64) // jeśli współrzędna
x wyświetlacza < 64
{LCD_CS0()} // to odczytujemy
z pierwszego kontrolera
else // w przeciwnym razie
{LCD_CS1()} // odczytujemy
z drugiego kontrolera
lcdWait(); // oczekiwanie na goto-
wość kontrolera
SET_RS(); // wysoki stan na linii
RS –> dane
SET_RW(); // wysoki stan na linii
RW –> odczyt
SET_EN(); // wysoki stan na linii
EN
delay(); // opóźnienie
LCD_DATA_DDR = 0x00; // ustawienie
portu danych w tryb wejsciowy
data = LCD_DATA_PIN; // odczyt da-
nych z portu
CLR_EN(); // niski stan na linii EN
lcd_x++; // zwiększenie współrzędnej
x wyświetlacza
if(lcd_x > 127) // jesli koniec
ekranu
lcd_x = 0; // to wyzeruj współ-
rzędną x
LCD_NOCS();
return data;
}
Ustawienie współrzędnych
wyświetlania
Funkcja ustawia współrzędne wy-
świetlacza, na których wykonana
zostanie następna operacja (odczyt,
bądź zapis). Ze względu na organiza-
cję pamięci wyświetlacza współrzęd-
na pionowa może przyjmować war-
tości z zakresu 0...7 (numer strony).
Współrzędna pozioma może przyjmo-
wać wartości z zakresu 0...127.
void lcdGoTo(unsigned char x, unsi-
gned char y)
{
lcd_x = x; // przypisanie współrzę-
dym globalnym nowych wartości
lcd_y = y;
if(lcd_x > 63) // jeśli współrzędna
pozioma jest większa od 64 to
{
LCD_CS1(); // uaktywnienie drugie-
go kontrolera
lcdWriteCmd(DISPLAY_SET_X | lcd_
y); // zapis współrzędnej pionowej
lcdWriteCmd(DISPLAY_SET_Y | (lcd_x
LCD_CTRL_DDR |= ((1 << LCD_CS1P) |
(1 << LCD_CS2P) |
(1 << LCD_RS) | (1 << LCD_RW) | (1
<< LCD_EN));
}
CLR_EN(); // wyzeruj linię EN
} while((LCD_DATA_PIN & DISPLAY_
STATUS_BUSY)); // powtarzaj do
// wyzerowania flagi BUSY
}
Zapis instrukcji
Zapis instrukcji odbywa się przy
niskim stanie linii RS i RW. Opada-
jące zbocze na linii E zatrzaskuje
dane w rejestrze wejściowym. Funk-
cja zapisuje instrukcję do aktywne-
go w danej chwili kontrolera. Jeśli
instrukcja ma być zapisana do oby-
dwu kontrolerów należy ją wywołać
dwukrotnie ustawiając przed każ-
dym wywołaniem odpowiedni stan
na liniach CS1 i CS2.
void lcdWriteCmd(unsigned char cmd)
{
lcdWait(); // oczekiwanie na goto-
wość kontrolera
CLR_RS(); // niski stan na linii RS
–> rozkaz
CLR_RW(); // niski stan na linii RW
–> zapis
LCD_DATA_DDR = 0xFF; // port danych
–> wyjście
LCD_DATA_PORT = cmd; // wystawienie
na port instrucji
SET_EN(); // ustawienie linii EN
delay(); // opóźnienie
CLR_EN(); // wyzerowanie linii EN
}
Kolejna funkcja wprowadza
opóźnienie wymagane przez sterow-
nik wyświetlacza podczas operacji
odczytu oraz zapisu danych bądź
instrukcji:
void delay(void)
{
asm("nop");asm("nop");
}
Dokumentacja sterownika określa
minimalny czas trwania stanu na
linii EN na 450 ns. Przeprowadzo-
ne przeze mnie eksperymenty wyka-
zały, że w rzeczywistości dodawanie
opóźnień jest zbędne. Wyświetlacz
poprawnie współpracował z mikro-
kontrolerem taktowanym sygnałem
o częstotliwości 16 MHz bez żad-
nych dodatkowych opóźnień. Nie-
mniej jednak, aby mieć pewność
poprawnego działania należy spełnić
wymogi czasowe transmisji zalecane
przez producenta kontrolera.
Sprawdzanie zajętości
kontrolera
Przed zapisaniem instrukcji do
wyświetlacza należy sprawdzić flagę
zajętości kontrolera. Do czasu za-
kończenia wykonywania poprzedniej
instrukcji kontroler ignoruje kolejne
instrukcje. W tym celu zdefiniujmy
następującą funkcję:
void lcdWait(void)
{
LCD_DATA_DDR = 0x00; // ustawienie
portu danych w tryb wejściowy
CLR_RS(); // niski stan na linii RS
–> odczyt rejestru statusu
SET_RW(); // wysoki stan na linii RW
–> odczyt z wyświetlacza
do { //pętla
delay(); // opóźnienie
SET_EN(); // ustaw linię EN
delay(); // opóźnienie
Zapis danych
Dane są zapisywane przy wy-
sokim stanie linii RS i niskim sta-
nie linii RW. Opadające zbocze na
linii E zatrzaskuje dane w rejestrze
wejściowym. Poniższa funkcja doko-
nuje sprawdzenia aktualnej pozycji
i w zależności od niej odpowiednio
ustawiana jest kombinacja sygnałów
CS1 i CS2.
void lcdWriteData(unsigned char
data)
{
if(lcd_x < 64) // jeśli współrzędna
x wyświetlacza < 64
{LCD_CS0()} // to zapisujemy do
pierwszego kontrolera
Elektronika Praktyczna 1/2007
91
649301758.016.png 649301758.017.png 649301758.018.png 649301758.019.png 649301758.020.png 649301758.021.png 649301758.022.png 649301758.023.png 649301758.024.png
K U R S
– 64)); // zapis współrzędnej pozio-
mej
}
else // w przeciwnym razie
{
LCD_CS0(); // uatywnienie pierw-
szego kontrolera
lcdWriteCmd(DISPLAY_SET_X | lcd_
y); // zapis współrzędnej pionowej
lcdWriteCmd(DISPLAY_SET_Y | lcd_
x); // zapis współrzędnej poziomej
LCD_CS1(); // uaktywnienie drugie-
go kontrolera
lcdWriteCmd(DISPLAY_SET_X | lcd_
y); // zapis współrzędnej pionowej
lcdWriteCmd(DISPLAY_SET_Y | 0); //
wyzerowanie współrzędnej poziomej
}
LCD_CS0(); // uaktywnienie pierwsze-
go kontrolera
lcdWriteCmd(DISPLAY_START_LINE | 0);
//
LCD_CS1(); // uaktywnienie drugiego
kontrolera
lcdWriteCmd(DISPLAY_START_LINE | 0);
LCD_NOCS();
}
ler
lcdWriteCmd(DISPLAY_ON_CMD | ON); //
włączenie wyświetlacza
LCD_NOCS();
}
Wyłączenie wyświetlacza
Wyłączenie wyświetlania zawar-
tości pamięci RAM na ekranie na-
stępuje po wykonaniu instrukcji Di-
splay Off
void lcdOff(void)
{
LCD_CS0(); // aktywny pierwszy kon-
troler
lcdWriteCmd(DISPLAY_ON_CMD | OFF);
// wyłączenie wyświetlacza
LCD_CS1(); // aktywny drugi kontro-
ler
lcdWriteCmd(DISPLAY_ON_CMD | OFF);
// włączenie wyświetlacza
LCD_NOCS();
}
{
unsigned char x, y; // pomocnicze
zmienne
for (y = 0; y < 8; y++) // 8–krotne
powtórzenie petli
{
lcdGoTo(0,y); // ustawienie współ-
rzędnej y wyświetlacza
for (x = 0; x < 128; x++) // 128–
–krotne powtórzenie pętli
lcdWriteData(0); // zapis do pa-
mięci wyświetlacza wzorca
}
lcdGoTo(0,0); // ustawienie począt-
kowych współrzędnych
}
Rozkazy związane z trybem tek-
stowym oraz kilka przykładów
funkcji graficznych przedstawimy
w kolejnym odcinku.
Radosław Kwiecień, EP
radoslaw.kwiecien@ep.com.pl
Włączenie wyświetlacza
Włączenie wyświetlania zawar-
tości pamięci RAM na ekranie na-
stępuje po wykonaniu instrukcji Di-
splay On . Instrukcję należy wysłać
do każdego kontrolera oddzielnie:
void lcdOn(void)
{
LCD_CS0(); // aktywny pierwszy kon-
troler
lcdWriteCmd(DISPLAY_ON_CMD | ON); //
włączenie wyświetlacza
LCD_CS1(); // aktywny drugi kontro-
Czyszczenie wyświetlacza
Czyszczenie zawartości wyświe-
tlacza polega na wypełnieniu pa-
mięci danych odpowiednim wzor-
cem. Jeśli wyświetlacz ma wy-
świetlać obraz pozytywowy pamięć
należy wypełnić zerami, natomiast
gdy wyświetlacz ma wyświetlać
obraz negatywowy pamięć należy
wypełnić bajtami o wartości 255.
void lcdCls(void)
Literatura
1. Bresenham's Integer Only Line
Drawing Algorithm, John Kennedy,
http://homepage.smc.edu/kennedy_
john/BRESENL.PDF
2. A Fast Bresenham Type Algorithm
For Drawing Circles, John Kennedy,
http://homepage.smc.edu/kennedy_
john/BCIRCLE.PDF
3. Driver for graphic LCD display
128x64, Gregor Horvat
92
Elektronika Praktyczna 1/2007
649301758.025.png 649301758.026.png 649301758.027.png 649301758.028.png 649301758.029.png 649301758.030.png 649301758.031.png 649301758.032.png 649301758.033.png 649301758.034.png 649301758.035.png 649301758.036.png 649301758.037.png 649301758.038.png 649301758.039.png 649301758.040.png 649301758.041.png 649301758.042.png 649301758.043.png
Zgłoś jeśli naruszono regulamin