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

(742 KB) Pobierz
099-102-lcd_cz2.indd
KURS
Wyświetlacze graficzne LCD ze
sterownikiem KS0108 – sterowanie
w języku C od podstaw, część 2
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. Wartykule zajmiemy się obsługą
w języku C wyświetlacza graficznego ze sterownikiem KS0108.
Wyświetlanie ciągu znaków
z pamięci programu
Ponieważ przechowywanie w pa-
mięci RAM stałych napisów powo-
duje znaczne zużycie stosunkowo
niewielkiej pamięci, wygodniej jest
niezmienne ciągi znaków przecho-
wywać w pamięci programu. Ze
względu na specyficzny odczyt pa-
mięci programu, w kompilatorze
avr–gcc do tego celu służy oddziel-
na funkcja.
void lcdWriteStringPgm(prog_char *
s)
{
char c; // pomocnicza zmienna
while(c = pgm_read_byte(s++)) //
wykonuj dopóki znak wskazywany przez
s jest
// rożny od zera
lcdWriteChar(c); // zapis znaku
}
W pierwszej części artykułu za-
poznaliśmy się z ogólnymi zasadami
sterowania wyświetlaczem graficz-
nym i sposobami dołączenia go do
mikrokontrolera. Poniżej pokażemy,
w jaki sposób na ekranie wyświetla-
cza graficznego można wyświetlać
teksty, a także – co nas może na-
wet bardziej interesuje – grafikę.
bezpośrednio do plików źródłowych
języka C oraz asemblera.
Wyświetlenie znaku
Wyświetlenie znaku polega na
zapisaniu do wyświetlacza 5 kolej-
nych bajtów tworzących dany znak.
Pierwszy znak w tablicy (spacja)
ma indeks 0, podczas gdy w tabli-
cy ASCII ten znak posiada kod 32.
Przyjęto tak, żeby nie marnować
miejsca na niewyświetlane znaki
w pamięci programu (pierwsze 32
znaki tablicy ASCII). Należy więc
od kodu znaku przekazanego do
funkcji jako parametr odjąć liczbę
32.
void lcdWriteChar(char x)
{
char i;
x–= 32; // konwersja kodu znaku
for(i = 0; i < 5; i++)
lcdWriteData(pgm_read_byte(font5x7
+ (5 * x) + i)); // zapis do wyświe-
tlacza
// 5 kolejnych bajtów tworzących
znak
lcdWriteData(0); // odstęp między
znakami
}
Ustawienie współrzędnych
tekstowych
W trybie tekstowym wygodniej-
sze jest posługiwanie się współrzęd-
nymi odpowiadającymi położeniu
znaków na wyświetlaczu. Jak wie-
my każdy znak składa się z sześciu
pionowych części, zamiana współ-
rzędnych tekstowych na graficzne
sprowadza się więc do pomnożenia
przez 6 poziomej współrzędnej tek-
stowej.
void lcdLocate(unsigned char x,
unsigned char y)
{
lcdGoTo(x * 6, y);
}
Tryb tekstowy
Wyświetlacze zbudowane w opar-
ciu o kontroler KS0108 nie posiada-
ją generatora znaków, wyświetlanie
tekstu wymaga więc zdefiniowania
własnej tablicy czcionek, która bę-
dzie przechowywana w pamięci pro-
gramu mikrokontrolera. Tablica ta
znajduje się w pliku font.h.
Każdy znak jest określony pię-
cioma bajtami danych (typowa
czcionka 5x8 pikseli). Kolejność baj-
tów w tablicy odpowiada kolejności
poszczególnych pionowych fragmen-
tów znaku. Przykładowy wygląd li-
tery A przedstawiono na rys. 4 . Do
projektowania własnych czcionek
można wykorzystać program ze
strony http://radzio.dxp.pl/font/. Pro-
gram ten umożliwia eksport danych
Wyświetlanie
ciągu znaków
Parametrem
funkcji jest wskaź-
nik do typowego
dla języka C ciągu
znaków zakończo-
nego zerem (znaj-
dującego się w pa-
mięci danych).
void lcdWriteStrin-
g(char * s)
{
while(*s) // wy-
konuj dopóki znak
wskazywany przez s
jest różny od zera
lcdWriteCha-
r(*s++); // zapis
znaku
}
Rys. 4. Przykładowy kształt litery A
Fot. 5. Przykładowy tekst umieszczony na wyświetlaczu
graficznym
Elektronika Praktyczna 2/2007
99
649301873.043.png 649301873.044.png 649301873.045.png 649301873.046.png 649301873.001.png 649301873.002.png 649301873.003.png 649301873.004.png 649301873.005.png
KURS
Przykładowy tekst umieszczony
na wyświetlaczu za pomocą zapre-
zentowanych funkcji przedstawia
fot. 5 .
void lcdClrPixe-
l(unsigned char x,
unsigned char y)
{
unsigned char temp;
// zmienna pomoc-
nicza
lcdGoTo(x, y/8); //
ustawienie współ-
rzędnych
temp = lcdReadDa-
ta(); //
lcdGoTo(x, y/8); //
ustawienie współ-
rzędnych
temp = lcdReadDa-
ta(); // odczyt
aktualnego stanu
pikseli
lcdGoTo(x, y/8); //
ponowne ustawienie
współrzędnych
lcdWriteData(temp &
~(1 << (y % 8)));
// zapis odpowied-
nio zmodyfikowanej
wartości
}
Tryb graficzny
Głównym zadaniem wyświetla-
cza graficznego jest (jak sama na-
zwa wskazuje) wyświetlanie grafiki.
Przedstawię teraz kilka podstawo-
wych procedur graficznych.
Zapalanie piksela
Włączenie piksela polega na
ustawieniu odpowiadającego mu
bitu w pamięci wyświetlacza. Nie-
stety możemy odczytywać i zapisy-
wać tylko cały bajt, dlatego pro-
cedura włączenia piksela przebiega
następująco:
– ustawiamy współrzędne: pozio-
mą oraz podzieloną przez 8
pionową (ponieważ dostęp do
pamięci odbywa się całymi baj-
tami),
– odczytujemy z wyświetlacza ak-
tualny stan pikseli (operację od-
czytu należy wykonać dwukrot-
nie),
– modyfikujemy odczytany bajt
poprzez wykonanie na nim
operacji sumy logicznej z bitem
o wartości 1 przesuniętym w le-
wo o resztę z dzielenia współ-
rzędnej pionowej przez 8 (po-
łożenie bitu w obrębie wybranej
strony),
– zapisujemy tak zmodyfikowany
bajt danych pod odpowiedni
adres pamięci wyświetlacza.
void lcdSetPixel(unsigned char x,
unsigned char y)
{
unsigned char temp; // zmienna po-
mocnicza
lcdGoTo(x, y/8); // ustawienie
współrzędnych
temp = lcdReadData(); // odczyt da-
nych z wyświetlacza
lcdGoTo(x, y/8); // ustawienie
współrzędnych
temp = lcdReadData(); // odczyt ak-
tualnego stanu pikseli
lcdGoTo(x, y/8); // ponowne ustawie-
nie współrzędnych
lcdWriteData(temp | (1 << (y % 8)));
// zapis odpowiedno zmodyfikowanej
wartości
}
Gaszenie piksela
Procedura gaszenia piksela prze-
biega podobnie jak jego zapalanie.
Jedyną różnicą jest sposób mody-
fikacji odczytanego aktualnego sta-
nu pikseli: zamiast sumy logicznej
wykonywany jest iloczyn logiczny
z zanegowaną wartością przesunię-
cia w lewo bitu o wartości 1 o resz-
tę z dzielenia przez 8 współrzędnej
pionowej.
Fot. 6. Efekt działania funkcji lcdLine
Rysowanie linii
Funkcja przyjmuje 4 argumen-
ty: współrzędne x i y początku
oraz współrzędne x i y końca linii.
Funkcja rysowania linii bazuje na
algorytmie Brensenham’a. Szczegó-
łowy opis algorytmu Brensenham’a
do rysowania linii można znaleźć
w [1].
void lcdLine(int X1, int Y1,int
X2,int Y2)
{
int CurrentX, CurrentY, Xinc, Yinc,
Dx, Dy, TwoDx, TwoDy,
TwoDxAccumulatedError, TwoDyAccu-
mulatedError;
Dx = (X2–X1); // obliczenie składo-
wej poziomej
Dy = (Y2–Y1); // obliczenie składo-
wej pionowej
TwoDx = Dx + Dx; // podwojona skła-
dowa pozioma
TwoDy = Dy + Dy; // podwojona skła-
dowa pionowa
CurrentX = X1; // zaczynamy od X1
CurrentY = Y1; // oraz Y1
Xinc = 1; // ustalamy krok zwiększa-
nia pozycji w poziomie
Yinc = 1; // ustalamy krok zwiększa-
nia pozycji w pionie
if(Dx < 0) // jesli składowa pozioma
jest ujemna
{
Xinc = –1; // to będziemy się "co-
fać" (krok ujemny)
Dx = –Dx; // zmieniamy znak skła-
dowej na dodatni
TwoDx = –TwoDx; // jak również po-
dwojonej składowej
}
if (Dy < 0) // jeśli składowa piono-
wa jest ujemna
{
Yinc = –1; // to będziemy się "co-
fać" (krok ujemny)
Dy = –Dy; // zmieniamy znak skła-
dowej na dodatki
TwoDy = –TwoDy; // jak równiez po-
dwojonej składowej
}
lcdSetPixel(X1,Y1); // stawiamy
pierwszy krok (zapalamy pierwszy
piksel)
if ((Dx != 0) || (Dy != 0)) //
sprawdzamy czy linia składa się
z więcej niż jednego
// punktu ;)
{
// sprawdzamy czy składowa pionowa
jest mniejsza lub równa składowej
poziomej
if (Dy <= Dx) // jeśli tak, to
idziemy "po iksach"
{
TwoDxAccumulatedError = 0; //
zerujemy zmienną
do // ruszamy w drogę
{
CurrentX += Xinc; // do aktu-
alnej pozycji dodajemy krok
TwoDxAccumulatedError += Two-
Dy; // a tu dodajemy podwojoną skła-
dową //pionową
if(TwoDxAccumulatedError > Dx)
// jeśli TwoDxAccumulatedError jest
większy //od Dx
{
CurrentY += Yinc; // zwięk-
szamy aktualną pozycję w pionie
TwoDxAccumulatedError –=
TwoDx; // i odejmujemy TwoDx
}
lcdSetPixel(CurrentX,Curren-
tY);// stawiamy następny krok (zapa-
lamy piksel)
}while (CurrentX != X2); //
idziemy tak długo, aż osiągniemy
punkt docelowy
}
else // w przeciwnym razie idzie-
my "po igrekach"
{
TwoDyAccumulatedError = 0;
do
{
CurrentY += Yinc;
TwoDyAccumulatedError +=
TwoDx;
if(TwoDyAccumulatedError>Dy)
{
CurrentX += Xinc;
TwoDyAccumulatedError –=
TwoDy;
}
lcdSetPixel(CurrentX,Cur-
rentY);
}while (CurrentY != Y2);
}
}
}
Efekt działania funkcji lcdLine
przedstawiono na fot. 6 .
Rysowanie prostokąta
Funkcja przyjmuje 4 argumen-
ty: współrzędne x oraz y lewego
górnego rogu prostokąta oraz wy-
sokość i szerokość prostokąta. Po-
nieważ prostokąt może się składać
tylko z linii pionowych i poziomych
do jego narysowania nie wykorzy-
100
Elektronika Praktyczna 2/2007
649301873.006.png 649301873.007.png
KURS
Fot. 7. Efekt działania funkcji lcdRectangle
{
lcdGoTo(x,j); //
ustawienie współ-
rzędnych
// pętla wykona
się tyle razy,
ile punktów jest
w linii
for(i = 0; i <=
b; i++)
{
lcdWriteData-
(0xFF); // zapis
strony
}
}
// dorysowujemy
pozostałe linie
for(i = 0; i <=
mod8; i++)
{
// pętla wykona
się tyle razy,
ile punktów jest
w linii
for(j = 0; j <=
b; j++)
lcdSetPixel(x +
j, y + i + (a – mod8));
}
}
jącą wyświetlenie na wyświetlaczu
obrazu w postaci bitmapy. Pozwoli
to na przygotowanie obrazu w do-
wolnym edytorze graficznym na
komputerze i jego łatwe wyświetlenie
na wyświetlaczu. Bitmapa w pamięci
mikrokontrolera musi zajmować ko-
lejne komórki pamięci, zgodnie z ko-
lejnością ich wyświetlania na wy-
świetlaczu. Format komputerowego
pliku BMP posiada nieco inną or-
ganizację danych, utrudniającą pro-
ste wyświetlenie obrazu na wyświe-
tlaczu LCD. Przygotowanie bitmapy
do wyświetlenia będzie wymagało
wykorzystania prostego program dla
komputera PC, dokonującego kon-
wersji pliku BMP na postać źródło-
wą języka C (wygenerowana zosta-
nie tablica zawierająca kolejne bajty
przeznaczone do wyświetlenia). Pro-
gram ten można pobrać ze strony
http://radzio.dxp.pl/asystentlcd/. Do-
datkowo w programie tym można
zaprojektować ikony o rozmiarze 8x8
oraz 16x16 pikseli (oczywiście pro-
gram generuje tablicę z danymi go-
tową do wklejenia do programu dla
mikrokontrolera).
Funkcja wyświetlająca bitmapę
przyjmuje 5 argumentów: wskaźnik
do tablicy z bitmapą znajdującą się
w pamięci Flash, współrzędne pod
którymi bitmapa ma zostać wyświe-
tlona oraz jej rozmiar. Współrzędna
pionowa przyjmuje wartości 0...7
(numer strony). Wysokość bitmapy
może przyjmować wartości będące
wielokrotnością liczby 8. Natomiast
współrzędna pozioma i szerokość
bitmapy mogą przyjmować dowolne
wartości (mieszczące się oczywiście
na wyświetlaczu).
void lcdBitmap(char * bmp, unsigned
char x, unsigned char y, unsigned
char dx, unsigned char dy)
stamy funkcji rysowania linii przed-
stawionej powyżej, tylko zrobimy to
w znacznie prostszy sposób.
void lcdRectangle(unsigned char x,
unsigned char y, unsigned char b,
unsigned char a)
{
unsigned char j; // zmienna pomoc-
nicza
// rysowanie linii pionowych
(boki)
for (j = 0; j < a; j++) {
lcdSetPixel(x, y + j); // linia
lewa
lcdSetPixel(x + b – 1, y + j);
// linia prawa
}
// rysowanie linii poziomych (pod-
stawy)
for (j = 0; j < b; j++) {
lcdSetPixel(x + j, y); // linia
górna
lcdSetPixel(x + j, y + a – 1);
// linia dolna
}
}
Rysowanie okręgu
Funkcja przyjmuje 3 argumenty:
współrzędne x i y środka okręgu oraz
jego promień. Funkcja bazuje na al-
gorytmie Brensenham’a. Szczegółowy
opis algorytmu zawarty jest w [2].
void lcdCircle(unsigned char cx,
unsigned char cy ,unsigned char ra-
dius)
{
int x, y, xchange, ychange, radiu-
sError;
x = radius;
y = 0;
xchange = 1 – 2*radius;
ychange = 1;
radiusError = 0;
while(x >= y)
{
lcdSetPixel(cx+x, cy+y);
lcdSetPixel(cx–x, cy+y);
lcdSetPixel(cx–x, cy–y);
lcdSetPixel(cx+x, cy–y);
lcdSetPixel(cx+y, cy+x);
lcdSetPixel(cx–y, cy+x);
lcdSetPixel(cx–y, cy–x);
lcdSetPixel(cx+y, cy–x);
y++;
radiusError += ychange;
ychange += 2;
if (2*radiusError + xchange > 0)
{
x––;
radiusError += xchange;
xchange += 2;
}
}
}
Efekt działania funkcji lcdRectan-
gle przedstawiono na fot. 7 . Obraz
jest rozciągnięty w pionie, ponieważ
wyświetlacz JM12864A posiada pro-
stokątne piksele.
Rysowanie wypełnionego
prostokąta
Funkcja przyjmuje 4 parametry
podobnie jak funkcja poprzednia.
Rysowany jest wypełniony prosto-
kąt. W celu przyśpieszenia funkcji
prostokąt nie jest rysowany piksel
po pikselu. Całkowicie zajęte stro-
ny są wypełniane bajtami o warto-
ści 255, natomiast pozostałe linie,
które nie zajmują pełnej strony są
dorysowywane w tradycyjny sposób.
void lcdFillRectangle(unsigned char
x, unsigned char y, unsigned char b,
unsigned char a)
{
unsigned char i, j;
// zmienna przechowująca ilość stron
zajętych przez prostokąt
unsigned char div8 = a / 8;
// zmienna przechowujaća ilość pozo-
stałych linii (nie tworzących pełnej
strony)
unsigned char mod8 = a % 8;
// pętla wykona się tyle razy, ile
stron zajmuje prostokąt
for(j = 0; j < div8; j++)
Efekt działania
funkcji lcdCircle
przedstawiono na
fot. 8 .
Wyświetlanie
bitmapy
Rysowanie więk-
szych obrazków
piksel po pikselu
byłoby zadaniem
bardzo czasochłon-
nym i skompliko-
wanym, dlatego też
zajmiemy się teraz
funkcją umożliwia-
Fot. 8. Efekt działania funkcji lcdCircle
Elektronika Praktyczna 2/2007
101
649301873.008.png 649301873.009.png 649301873.010.png
KURS
Fot. 9. Efekt działania funkcji lcdBitmap
Fot. 10. Przykład praktycznego wykorzystania wy-
świetlacza graficznego do obsługi analizatora stanów
logicznych
{
unsigned char i, j;
// pętlawykona się tyle razy, ile
stron zajmuje bitmapa
for(j = 0; j < dy / 8; j++)
{
// ustawienie współrzędnych
lcdGoTo(x,y + j);
// pętla wykona się tyle razy, ile
pikseli szerokości ma bitmapa
for(i = 0; i < dx; i++)
{
lcdWriteData(pgm_read_byte(bm-
p++)); // zapis danych
}
}
}
świetlacza w prostym
analizatorze stanów
logicznych.
Mam nadzieję, że artykuł przy-
bliżył Czytelnikom sposób obsługi
wyświetlacza graficznego ze sterow-
nikiem KS0108. Dzięki stosunko-
wo dużym możliwościom prezen-
tacji danych takiego wyświetlacza
oraz niskiej cenie może on znaleźć
szerokie zastosowanie w najróżniej-
szych urządzeniach elektronicznych,
gdzie wymagane jest duże pole
odczytowe. Zaadaptowanie zapre-
zentowanych tu procedur na inny
mikrokontroler nie powinno sprawić
większych trudności, jako że użyto
najbardziej uniwersalnego języka
programowania mikrokontrolerów
(język C).
Radosław Kwiecień
radoslaw.kwiecien@ep.com.pl
Efekt działania funkcji lcdBitmap
przedstawiono na fot. 9 .
Rysowanie kółek i linii jest mało
efektownym przykładem zastosowa-
nia graficznego wyświetlacza LCD,
spójrzmy więc na fot. 10 przedsta-
wiającą praktyczne zastosowanie wy-
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
102
Elektronika Praktyczna 2/2007
649301873.011.png 649301873.012.png 649301873.013.png 649301873.014.png 649301873.015.png 649301873.016.png 649301873.017.png 649301873.018.png 649301873.019.png 649301873.020.png 649301873.021.png 649301873.022.png 649301873.023.png 649301873.024.png 649301873.025.png 649301873.026.png 649301873.027.png 649301873.028.png 649301873.029.png 649301873.030.png 649301873.031.png 649301873.032.png 649301873.033.png 649301873.034.png 649301873.035.png 649301873.036.png 649301873.037.png 649301873.038.png 649301873.039.png 649301873.040.png 649301873.041.png 649301873.042.png
Zgłoś jeśli naruszono regulamin