RC-51-2.pdf

(160 KB) Pobierz
Microsoft Word - RC-51-2-modyfikacje.doc
http://www.easy-soft.tsnet.pl
Sterowanie wyświetlaczem LCD w trybie 4-bitowym
Zacznijmy od jakiegoś użytecznego programu. Czegoś, co przyda nam się w przyszłości tak, aby nie
marnować cennego czasu. Proponuję na początek podłączenie wyświetlacza LCD w trybie 4 bity. Rozbierzemy
na kawałeczki poszczególne fragmenty programu analizując jego kod. To najlepsza droga do zrozumienia
całości.
Początek to tradycyjnie już deklaracje. Wybieramy model pamięci (#pragma SMALL) , dołączamy
właściwą definicję rejestrów mikroprocesora (#include <reg51.h>), określamy, które bity sterują
wyświetlaczem (sbit), jakie definicje znaków zapiszemy do wyświetlacza itp. Zwróćmy uwagę na troszkę inny
niż w innych językach programowania, opis bitu portu. Separatorem pomiędzy nazwą portu a numerem bitu nie
jest znak kropki, lecz umowny symbol potęgi. Słowo kluczowe sbit umieszcza naszą definicję w rejestrze funkcji
specjalnych SFR, w obszarze adresowania bitowego.
Przyjrzyjmy się zawartości tablicy z definicjami znaków użytkownika CGRom. Zauważmy, że jej
deklaracja zawiera wiele istotnych informacji dla naszego programu praktycznie w jednej linijce:
- określony zostaje typ elementów tablicy, w tym przypadku char (czyli element jednobajtowy),
- tablicę zostaje umieszczona w pamięci programu mikrokontrolera (słowo code ),
- tablica otrzymuje nazwę symboliczną ( CGRom ) i określona zostaje liczba jej elementów ( 65 ),
- elementom tablicy nadawana jest wartość stała,
Pierwszą z funkcji, którą napotkamy analizując program, jest Delay . Jest to zwykła pętla, która absorbuje
mikrokontroler na około 1 milisekundę. Tak stanie się tylko wtedy, gdy użyjemy rezonatora kwarcowego
7,3728MHz. Jeśli stosujemy inny kwarc, procedura wymaga zmiany. Zmieni się bowiem czas potrzebny na
wykonanie pętli. Słowo void przed definicją funkcji mówi nam, że funkcja nie zwraca sobą żadnych parametrów
jako rezultat działania. I tutaj słowo wyjaśnienia. Funkcja w języku C może zwracać tylko jedną wartość. Nie
może zwrócić ich kilku tak, jak procedura języka Pascal poprzez „var”. Jeśli zachodzi potrzeba aby funkcja
zwracała więcej niż jeden wynik działania, można to zrobić na przykład przekazując wskaźnik do parametru.
Wówczas obliczenia wykonywane są bezpośrednio na zmiennych źródłowych. Inną metodą jest przekazanie
wskaźnika do listy parametrów lub samej listy. Działania wewnątrz funkcji, o ile nie są wykonywane na
wskaźnikach, wykonywane są lokalnie i dotyczą wyłącznie zmiennych zawartych pomiędzy nawiasami
klamrowymi {}, oznaczającymi jej początek i koniec.
Parametr unsigned int k , to po prostu liczba milisekund do „odczekania”. Słowo unsigned oznacza,
że liczba k jest dwubajtową liczbą bez znaku, czyli przyjmuje wartości tylko i wyłącznie dodatnie. Ciało funkcji
zawiera również deklaracje zmiennych pomocniczych j oraz k . Posłużą nam one do budowy pętli for . Ciekawy
jest w języku C jej zapis: for (j = 0; j < k; j++) . Co oznacza: wstaw do zmiennej j liczbę 0 (j = 0;), następnie
dopóki j jest mniejsze od k (j < k;) zwiększaj wartość j o 1 (j++) wykonując działanie opisane za nawiasem, w
tym przypadku jest to następna pętla for.
Zwróćmy uwagę, że o ile w RC-51, przed deklaracją typu int , musimy użyć słowa unsigned , o ile ma to
być liczba bez znaku, o tyle słowo to jest zbędne w przypadku typu char . Domyślnie kompilator zakłada, że
mamy do czynienia z liczbą unsigned char . Uwaga: jest to cecha kompilatora RC-51 firmy Raisonance. Używając
innego, można się spotkać z innymi rozwiązaniami (np. firma Keil stosuje typ uchar ). Oczywiście można przed
typem char dopisać słowo unsigned . Wówczas jest to zgodne ze specyfikacją ANSI C i prawdopodobnie będzie
działać tak samo, bez względu na typ użytego kompilatora.
Po Delay znajduje się funkcja WriteByteToLcd . Tak samo, jak Delay jest to funkcja typu void. Jej zadaniem
jest zapis jednobajtowej liczby X do rejestru wyświetlacza. Funkcja dokonuje podziału bajtu na połówki i
zapisuje je w bezpieczny sposób – wykorzystując tylko bity b4..b7 i nie uszkadzając zawartości b0..b3 - do
portu PORT. Być może podział bajtu to nie jest właściwe słowo - przyjrzyjmy się metodzie.
Dzięki funkcji sumie bitowej OR, cztery najstarsze bity portu PORT, w tym przypadku zadeklarowanego jako P2,
ustawiane są na „1”. Zapis PORT |= 0xF0 można bowiem przekształcić na równoważny mu PORT = PORT |
0xF0. Właściwy zapis zmiennej do portu następuje poprzez funkcję AND, jednak po uprzednim ustawieniu dolnej
połówki zapisywanego bajtu, na wartość „1” (X | 0x0F). Zapis PORT &= (X | 0x0F) można rozłożyć na
następujący szereg działań:
-
X = X | 0x0F
//dolne 4 bity portu X przyjmują wartość „1”
J.Bogusz, Obsługa wyświetlacza LCD pracującego w trybie 4-bitowej szyny danych, Strona 1 z 8
305448379.003.png 305448379.004.png
http://www.easy-soft.tsnet.pl
-
PORT = PORT & temp //dolne 4 bity portu PORT pozostają nienaruszone
//górne przyjmują wartość X
Nawias jest konieczny, ponieważ suma bitowa OR musi być wykonana przed iloczynem zapisującym połówkę
bajtu do portu mikrokontrolera. W identyczny sposób postępujemy z dolną połówką bajtu z tym, że
wykonywane jest przesunięcie w lewo o 4 pozycje, bitów zmiennej X (X <<= 4, to znaczy X = X << 4).
Funkcja zapisując dane do LCD, nie testuje stanu flagi busy wyświetlacza zakładając, że po 1 milisekundzie
wszystkie operacje wykonywane przez kontroler wyświetlacza, zostaną zakończone. Wywołanie Delay(1)
wprowadza konieczne opóźnienie.
Kolejnymi są funkcje o nieco przydługich nazwach WriteToLcdCtrlRegister i LcdWrite . Ich zadaniem
jest zapis parametru X do LCD przy odpowiedniej kombinacji sygnałów sterujących. Obie funkcje korzystają z
omówionej wcześniej WriteByteToLcd . Ponieważ określiliśmy w deklaracjach, że zmienne LcdReg, LcdRead i
LcdEnable to są bity portu wyjściowego mikrokontrolera, więc podstawienie wartości „1” lub „0” jest
odpowiednikiem rozkazów asemblera SETB i CLR. Prawidłowy (i szczególnie użyteczny, gdy mamy do czynienia
z dużą liczbą zmiennych) jest również zapis LcdReg = LcdRead = 0; Kompilator podzieli go na pojedyncze
operacje SETB i CLR. Zapis w takiej postaci nie ma więc wpływu na wielkość generowanego kodu wynikowego,
poprawia jednak czytelność programu.
Dalsze funkcje są bardzo podobne. Na pewną nowość natkniemy się dopiero w GotoXY. Jej rolą jest
obliczenie i ustawienie właściwego dla pozycji kursora x , y adresu zapisu bajtów do wyświetlacza. Wykorzystuje
ona polecenie switch do rozpatrzenia poszczególnych możliwych wartości y i wyboru odpowiedniej akcji.
switch (y)
{
case 0:
x += 0x80;
case 1:
x += 0xC0;
break ;
case 2:
x += 0x94;
break ;
case 3:
x += 0xD4;
}
Ze switch ściśle związane są case i break . Case funkcjonuje tak, jak etykieta danego warunku, break przerywa
rozpatrywanie warunków. Jeśli nie użyjemy polecenia break , wówczas nastąpi przejście do następnego warunku
i jego rozpatrzenie. W naszym przypadku jest to tylko strata czasu. Jedynym zadaniem funkcji GotoXY jest
bowiem zwiększenie wartości x tak, aby wskazywała pożądany adres – nie ma potrzeby dalszego analizowania
zmiennej y . Jeśli nasz y będzie miał wartość 0, wówczas do wartości x zostanie dodana liczba 80H. Jeśli 1, to
C0H, jeśli 2 to 94H i tak dalej.
Kilka słów komentarza. Przechodzenie od jednego przypadku do drugiego budzi trochę wątpliwości.
Niewątpliwie zaletą jest to, że można definiować wiele różnych warunków dla jednej akcji. Jednak taka
konstrukcja programu, która umożliwia przechodzenie od warunku do warunku, jest bardzo podatna na
„rozsypanie” się podczas jej modyfikacji. Z wyjątkiem wielu etykiet dla pojedynczej akcji, przechodzenie przez
przypadki, powinno być stosowane bardzo oszczędnie i zawsze opatrzone komentarzem. Do dobrego stylu
programowania należy wstawianie break po ostatniej instrukcji ostatniego przypadku, mimo że nie jest to
konieczne. Pewnego dnia, gdy dopiszesz na końcu jakiś inny przypadek, ta odrobina zapobiegliwości może cię
uratować.
Funkcja WriteTextXY wykorzystuje wskaźnik. Jest to wskaźnik do elementu typu char . Jego definicję
możemy bardzo łatwo odróżnić po symbolu * . Zauważmy, że do funkcji jako parametr nie jest przekazywany
tekst do wyświetlenia, a jedynie wskazanie (adres) do miejsca w pamięci RAM (lub ROM), gdzie ten tekst został
umieszczony. Wskaźnik ma rozmiar tylko 2 bajtów nie tak jak tekst, który może zająć znacznie więcej. Operacja
S++; przesuwa wskazanie na następny znak w łańcuchu. Kompilator sam dab o to, aby zwiększanie wskaźnika
powodowało wskazanie na następny znak. Nie musisz przejmować się liczbą bajtów inkrementacji o ile wskaźnik
ma przypisane wskazanie do określonego typu elementu. Pętla while kończy się, gdy wskaźnik S pokaże znak o
kodzie 0. Znak ten umieszczany jest przez kompilator zawsze na końcu tekstu. Równocześnie z S zwiększana
jest współrzędna x . Jeśli wartość x przekroczy maksymalną ilość znaków w wierszu, następuje zwiększenie y i
przejście do następnej linii na wyświetlaczu LCD. Podobnie funkcjonuje DefineSpecialCharacters jest jednak
prostsza, bo nie oblicza żadnych wartości x i y a jedynie przesuwa wskazania na następny bajt tablicy. Również i
tutaj w pętli while napisać można warunek while (*ptr), ponieważ pojawienie się znaku o kodzie 0, while
traktuje jako warunek końca (0 jest równoważne false ). Jednak dla większej czytelności programu i wyraźnego
zaakcentowania w jaki sposób kończy się tablica definicji, został użyty zapis while (*ptr != 0) . W funkcji
DefineSpecialCharacters znaleźć możemy jeszcze dwie nowe konstrukcje, których nie używaliśmy wcześniej.
Pierwsza z nich to słowo kluczowe void objęte nawiasami jako parametr funkcji. Oznacza to tylko tyle, że lista
parametrów funkcji jest pusta. Druga, to przypisanie w wywołaniu funkcji, w programie głównym main(),
wskaźnikowi ptr adresu tablicy CGRom . Jednoargumentowy operator & tym razem nie oznacza iloczynu
logicznego, czy bitowego – podaje nam adres tablicy CGRom w pamięci mikrokontrolera.
Jak już wspomniałem przy okazji omawiania przerwań, program główny w C jest to funkcja o nazwie
main . W programach pisanych dla mikrokontrolerów najczęściej jest ona typu void z pustą listą parametrów
(również void). Należy jednak pamiętać o tym, że program napisany dla mikrokontrolera nigdy nie może się
J.Bogusz, Obsługa wyświetlacza LCD pracującego w trybie 4-bitowej szyny danych, Strona 2 z 8
break ;
305448379.005.png
http://www.easy-soft.tsnet.pl
skończyć. Nawet jeśli zrobił on już swoje i nie ma żadnych dalszych funkcji do realizacji, to funkcję main należy
zakończyć pętlą nieskończoną taką, jak: while(1) albo for(;;). Mikrokontroler nie posiada bowiem żadnego
systemu operacyjnego takiego jak DOS, który po zakończeniu pracy programu przejmie kontrolę.
Najprawdopodobniej nasz program po zakończeniu pracy napotka na przykład tablicę danych umieszczoną za
kodem, którą potraktuje jak rozkazy języka asembler i zrobi coś zupełnie nieprzewidywalnego.
Pliki nagłówkowe (.h)
Rozważmy teraz pewną możliwość. Mamy już napisany program w języku C, mamy w nim pewne
funkcje, o których wiadomo, że przydadzą nam się również w wielu innych przypadkach. Chociażby nasz
pierwszy program sterujący wyświetlaczem – czy użyjemy go tylko do wyświetlania symbolu „piłeczki”
odbijającej się na ekranie LCD? Z całą pewnością przyda się na również do innych zastosowań. Pojawia się więc
pytanie : czy można z takiego programu zrobić swego rodzaju bibliotekę funkcji, którą będzie można dołączyć
do własnego programu i używać zawsze wtedy, gdy jest potrzebna? A co z modyfikacją pewnych parametrów
procedur, czy zawsze trzeba mieć dostęp do źródła programu i szukać nierzadko wśród kilkuset linii programu
tego parametru, który ma być zmieniony? Odpowiedzią na tak postawione pytania są tak zwane pliki
nagłówkowe (z angielskiego header files), które umożliwiają podzielenie programu na mniejsze fragmenty oraz
mogą zawierać definicje stałych i zmiennych używanych zarówno przez program główny, jak i przez biblioteki
funkcji. W języku C, zbiory nagłówkowe, wyróżnia rozszerzenie nazwy .H (na przykład lcd4bit.h). Tak może
wyglądać plik nagłówkowy utworzony dla biblioteki funkcji wyświetlacza LCD z poprzedniego przykładu.
// port,do którego podłączono wyświetlacz LCD
#define PORT
P2
// bity sterujące LCD
sbit LcdEnable =
PORT^0;
sbit LcdRead =
PORT^3;
sbit LcdReg =
PORT^2;
// opóźnienie około k* 1 milisekundy dla kwarcu 7,3728MHz
void Delay (unsigned int k);
// zapis bajtu do lcd
void WriteByteToLcd(char X);
// zapis bajtu do rejestru kontrolnego LCD
void WriteToLcdCtrlRegister(char X);
// zapis bajtu do pamięci obrazu
void LcdWrite(char X);
// czyszczenie ekranu LCD
void LcdClrScr(void);
// inicjalizacja wyświetlacza LCD w trybie 4 bity
void LcdInitialize(void);
// ustawia kursor na współrzędnych x,y
void GotoXY(char x, char y);
// wyświetla tekst na współrzędnych x, y
void WriteTextXY(char x, char y, char *S)
// wyświetla tekst od miejsca, w którym znajduje się kursor
void WriteText(char *S);
// definiowanie znaków z tablicy wskazywanej przez ptr
void DefineSpecialCharacters(char *ptr);
Jak widać jest to bardzo krótki zbiór tekstowy zawierający podstawowe definicje zmiennych i funkcji.
Zbiór ten dołączamy do programu głównego przy pomocy dyrektywy #include , podobnie jak postępowaliśmy z
definicją rejestrów mikrokontrolera (notabene też jest to zbiór nagłówkowy). W takiej sytuacji, program
źródłowy naszej biblioteki nie może zawierać funkcji main(). Funkcja ta zostanie zdefiniowana w naszym
programie głównym.
Tworząc pliki nagłówkowe trzeba zachować ostrożność. Można bowiem samemu stworzyć pewien
„bałagan” polegający na tym, że maleńki nawet program będzie miał dostęp do wielu danych, których w
praktyce nie potrzebuje. Będzie to rzutować również na rozmiar ostatecznego kodu wynikowego oraz utrudni
swego rodzaju zachowanie porządku w deklaracjach zmiennych i funkcji. Więcej o plikach nagłówkowych w
następnym opisywanym przykładzie programu w języku C. Aby wykorzystać bowiem mechanizm tworzenia
plików nagłówkowych, musimy się nauczyć jak tworzyć project files i do czego one służą.
Jacek Bogusz
jacek.bogusz@easy-soft.tsnet.pl
J.Bogusz, Obsługa wyświetlacza LCD pracującego w trybie 4-bitowej szyny danych, Strona 3 z 8
305448379.006.png
http://www.easy-soft.tsnet.pl
/************************************************
Obsluga wyswietlacza LCD w trybie 4 bity
RC-51 Raisonance
Przykladowy program w jezyku C dla EP
-------------------------------------------------
jacek.bogusz@ep.com.pl
*************************************************/
// wybór modelu pamieci
#pragma SMALL
// dolaczenie definicji rejestrów '51
#include <reg51.h>
// definicje znaków specjalnych dla wyswietlacza LCD
char code CGRom[65] = {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
// "kratka"
0x00
0xC0,0xC0,0xFF,0xF1,0xF1,0xF1,0xFF,0xC0,
// pusty kwadrat 0x01
0xC0,0xC0,0xFF,0xFF,0xFF,0xFF,0xFF,0xC0,
// kwadrat zacz. 0x02
0xE0,0xFF,0xFF,0xFF,0xE0,0xE0,0xE0,0xE0,
// linia
0x03
0xFF,0xFF,0xF9,0xF3,0xE7,0xF3,0xF9,0xFF,
// znak w lewo 0x04
0xFF,0xFF,0xF3,0xF9,0xFC,0xF9,0xF3,0xFF,
// znak w prawo 0x05
0xFF,0xFF,0xFB,0xF1,0xE4,0xEE,0xFF,0xFF,
// znak w góre 0x06
0xFF,0xFF,0xFF,0xEE,0xE4,0xF1,0xFB,0xFF,
// znak w dól 0x07
0x00};
// port,do którego podlaczono wyswietlacz LCD
#define PORT P2
// 4 najstarsze bity, to bity danych, 4 mlodsze moga byc w dowolny sposób skonfigurowane jako sterujace
// po zmianie definicji moga to byc równiez 2 rózne porty,jednak w programie przykladowym zakladam
// wykorzystanie tylko jednego z portów mikrokontrolera
// bity sterujace LCD
sbit LcdEnable =
PORT^0;
sbit LcdRead =
PORT^3;
sbit LcdReg =
PORT^1;
// opóznienie okolo 1 milisekundy dla kwarcu 7,3728MHz
void Delay (unsigned int k)
{
for (j = 0; j < k; j++)
for (i = 0; i <= 296; i++);
}
// zapis bajtu do lcd, osobna procedura dla zmniejszenia objetosci kodu wynikowego
J.Bogusz, Obsługa wyświetlacza LCD pracującego w trybie 4-bitowej szyny danych, Strona 4 z 8
unsigned int i,j;
305448379.001.png
http://www.easy-soft.tsnet.pl
void WriteByteToLcd(char X)
{
LcdEnable = 1;
PORT |= 0xF0;
// ustawienie górnej polówki portu PORT na "1"
PORT &= (X | 0x0F);
// "bezkolizyjny" zapis 1-szej polówki bajtu (przez funkcje logiczna)
LcdEnable = 1;
// zapis 2-giej polówki bajtu
PORT |= 0xF0;
// ustawienie górnej polówki portu PORT na "1"
PORT &= (X | 0x0F);
// zapis 2-giej polówki bajtu i maskowanie 4 mlodszych bitów
LcdEnable = 0;
// opadajace zbocze E - zapis do LCD
Delay(1);
}
// zapis bajtu do rejestru kontrolnego LCD
void WriteToLcdCtrlRegister(char X)
{
LcdReg = LcdRead = 0;
// ustawienie sygnalów sterujacych
WriteByteToLcd(X);
}
// zapis bajtu do pamieci obrazu
void LcdWrite(char X)
{
LcdRead = 0;
// ustawienie sygnalów sterujacych
LcdEnable = 1;
WriteByteToLcd(X);
}
// czyszczenie ekranu LCD
void LcdClrScr(void)
{
WriteToLcdCtrlRegister(0x01);
}
// inicjalizacja wyswietlacza LCD w trybie 4 bity
void LcdInitialize(void)
{
char i;
J.Bogusz, Obsługa wyświetlacza LCD pracującego w trybie 4-bitowej szyny danych, Strona 5 z 8
LcdEnable = 0;
// zapis do wyswietlacza (opadajace zbocze sygnalu E)
X <<= 4;
// przesuniecie 4x w lewo
LcdReg = 1;
305448379.002.png
Zgłoś jeśli naruszono regulamin