1 4 Transmisja szeregowa, obsługa wyświetlacza LCD. Zagadnienia do przygotowania: - budowa i działanie interfejsu szeregowego UART, - tryby pracy, - ramka transmisyjna, - przeznaczenie buforów obsługi LCD, - elementy języka C poznane dotychczas na zajęciach. 4.1 Wstęp 4.1.1 Rejestry wykorzystywane w transmisji szeregowej rejestr nadajnika/odbiornika nazwa 7 6 5 4 3 1 0 adres SBUF data 99H rejestr konfiguracyjny nazwa 7 6 5 4 3 1 0 adres SCON SM0 SM1 SM REN TB8 RB8 TI RI 98H rejestr kontroli trybu pracy mikrokontrolera nazwa 7 6 5 4 3 1 0 adres PCON SMOD - - - GF1 GF0 PD IDL 87H Rejestr nadajnika i odbiornika to w rzeczywistości dwa osobne rejestry mapowane w przestrzeni adresowej wewnętrznej pamięci danych pod tym samym adresem tj. 99H. Zapis pod ten adres powoduje zapis do bufora nadajnika, natomiast odczyt z tego adresu powoduje odczyt bufora odbiornika (nie ma możliwości zapisu do bufora odbiornika, ani odczytu bufora nadajnika). Bity SM0, SM1 służą do wyboru trybu pracy portu szeregowego zgodnie z poniższą tabelą: SM0 SM1 tryb opis prędkość transmisji 0 0 0 rejestr przesuwający f osc / 1 0 1 1 8-bit UART zmienna
(wykorzystywany timer T1) 1 0 9-bit UART f osc / 64 lub f osc / 3 1 1 3 9-bit UART zmienna (wykorzystywany timer T1) Bit SM wykorzystywany jest w trybach i 3 do komunikacji wieloprocesorowej (nie omawiana). W trybie 1 jeżeli SM = 1 warunkiem odbioru danych jest prawidłowa detekcja bitu stopu (opis w dalszej części) Bit REN kontroluje pracę odbiornika. Jeżeli REN = 1 odbiornik jest odblokowany, natomiast jeżeli REN = 0 dane przychodzące są ignorowane. Bit TB8 jest 9-tym transmitowanym bitem danych w trybach i 3. Bit RB8 jest 9-tym odbieranym bitem danych w trybach i 3. Bit TI jest wskaźnikiem przerwania z układu nadajnika (TI = 1). Zerowany programowo. Bit RI jest wskaźnikiem przerwania z układu odbiornika (RI = 1). Zerowany programowo. 4.1. Opis działania (bez przerwań) Nadawanie n bajtów (tryb 1): 1. zerujemy flagę TI. zapisujemy daną 8-bitową w rejestrze SBUF (bufor nadajnika) 3. czekamy do momentu, gdy TI = 1 4. operacje (1-3) powtarzamy kolejne n - 1 razy Sygnałem inicjującym rozpoczęcie transmisji jest zapis do rejestru SBUF. Mikrokontroler w tym czasie wystawia na wyprowadzenie TXD wartość L (bit startu), następnie 8 bitów danych a na końcu wartość H (bit stopu). Po zakończeniu transmisji ustawia TI = 1. Odbieranie n bajtów (tryb 1): 1. czekamy do momentu, gdy RI = 1. odczytujemy daną 8-bitową z rejestru SBUF (bufor odbiornika) 3. zerujemy flagę RI 4. operacje (1-3) powtarzamy kolejne n - 1 razy Sygnałem inicjującym rozpoczęcie odbioru danych jest dla mikrokontrolera pojawienie się na wyprowadzeniu RXD wartości L (bit startu). Następnie mikrokontroler próbkuje stan linii RXD (zgodnie z wybraną prędkością transmisji) kolejne 8 razy, a skompletowany bajt danych umieszcza w buforze odbiornika oraz ustawia RI = 1 (wersja gdy SM = 0). Natomiast gdy SM = 1 po próbkowaniu 8 bitów danych próbkuje jeszcze bit stopu. Jeśli jego wartość jest prawidłowa tj. H, skompletowany bajt danych umieszcza w buforze odbiornika oraz ustawia RI = 1. W przeciwnym razie informacja jest ignorowana. W trybie i 3 transmisja jest podobna do schematu przedstawionego powyżej. Jedyna różnica polega na tym, że na samym końcu jest nadawany/odbierany bit TB8/RB8. Należy także
3 pamiętać o tym, że podczas nadawania należy najpierw zapisać informację do TB8 a dopiero potem do SBUF. Najczęściej 9-ty bit danych wykorzystywany jest do kontroli parzystości/nieparzystości. Przy podłączeniu dwóch urządzeń linie TXD i RXD łączy się na krzyż, tzn. wyprowadzenie TXD pierwszego urządzenia podłącza się do RXD drugiego, a TXD drugiego do RXD pierwszego. W ten sposób można zrealizować transmisję na stosunkowo krótkie odległości. Na dłuższe odległości należy dodatkowo skorzystać z układów odbiorników/nadajników linii typu full-duplex np. RS3 / RS4, itp. Dla standardów half-duplex (wspólna linia do nadawania i odbioru) np. RS485 / CAN należy ponadto opracować własny protokół transmisji, uniemożliwiający jednoczesne nadawanie przez obydwa urządzenia. 4.1.3 Komunikacja z komputerem klasy PC a) w katalogu na dysku c:\ znajduje się program obsługi terminala, b) aby go wywołać należy uruchomić ttermpro.exe, c) przy starcie istnieje możliwość wyboru nasłuchu pomiędzy TCP/IP i Serial. Należy wybrać to drugie, d) domyślnie ustawione są parametry transmisji: 9600 bodów, 8-bitów danych, bez kontroli parzystości, 1 bit stopu. Są one identyczne jak w przykładach z punktu poprzedniego, dlatego nie trzeba ich zmieniać. Jeśli jest taka potrzeba, to można to zrobić w Setup => Serial Port..., e) Tuż po starcie nasłuch jest włączony. Aby rozłączyć połączenie należy wybrać File => Disconnect. Ponowne połączenie następuje przez File => New connection..., f) wysyłanie znaków realizowane jest w bardzo prosty sposób. Polega na naciskaniu odpowiednich przycisków na klawiaturze. Należy pamiętać o tym, że nie mogą pracować dwie aplikacje równocześnie na tym samym porcie szeregowym. Dlatego za każdym razem po sprawdzeniu działania programu należy zakończyć na File => Disconnect, inaczej program loadera/debuggera DSM-51 nie będzie działać. 4.1.4 Wyświetlacz LCD Wyświetlacz LCD (ze sterownikiem HD44780) obsługuje się za pomocą następujących buforów: - LCDWC (adres hex: 0xF080): zapis rozkazów do sterownika (polecenia sterujące np. czyszczenie wyświetlacza, ustawianie kursora, wybór linii (DD_RAM) itp., szczegóły: literatura), np. w celu wyboru początku pierwszej linii należy do bufora LCDWC wysłać wartość 0x80, - LCDWD (adres hex: 0xF081): zapis danych do wyświetlenia (do pamięci sterownika), - LCDRC (adres hex: 0xF08): odczyt stanu sterownika (zajętości), - LCDRD (adres hex: 0xF083): odczyt danych przechowywanych aktualnie w pamięci RAM sterownika. Należy pamiętać, że w danej chwili można wyświetlić na wyświetlaczu tylko jeden znak ASCII. Przesunięcie kursora po napisaniu znaku realizowane jest automatycznie przez sterownik.
4 4. Ćwiczenia do wykonania 4..1 Ciągła transmisja małej litery alfabetu (od a do z) prędkość transmisji (baudrate): 9600 bodów kontrola parzystości: brak liczba bitów stopu: 1 Konfiguracja SCON: Jeśli nie ma kontroli parzystości i transmisja z pojedynczym bitem stopu wybieramy tryb 1 (8-bitów danych), a zatem SM0 = 0 i SM1 = 1. Ponieważ odbiornik nie jest wykorzystywany możemy ustawić REN = 0. Wartości pozostałych bitów w rejestrze są bez znaczenia. Konfiguracja prędkości transmisji: W trybie 1 sygnałem taktującym port szeregowy jest przepełnienie z czasomierza timer 1: baudrate SMOD 3 (timer 1overflow rate) Wybieramy tryb pracy czasomierza timer 1 (TL1 przeładowywany z TH1), a zatem: baudrate 3 SMOD 1 f 56 osc TH1 Po prostych przekształceniach mamy (przy założeniu że SMOD = 1 i f osc = 11.059 MHz): 56 TH1 3 56 3 1105900 1 9600 SMOD 1 56 f osc baudrate 6 50 FAh ostatecznie: TH1 <= 1111 1010b (FAh) TMOD <= 0010 xxxxb (xh) // konfiguracja timer 1 (x wartość dowolna) TR1 <= 1b // podłączenie sygnału zegarowego do timer 1 Przykłądowy kod programu: #include <REGX51.H> // program główny void main(void) { // deklaracje zmiennych unsigned char znak = a ; // konfiguracja portu szeregowego SM0 = 0; SM1 = 1; REN = 0; PCON = 0x80; // SMOD = 1 TH1 = 0xfa; TMOD = 0x0; TR1 = 1; // główna pętla programu
5 while(1) { TI = 0; SBUF = znak; while(!ti); if (znak!= 'z ) znak++; else znak = a ; 4.. Transmisja dwukierunkowa (z obsługą przerwań) Przykład ten jest rozszerzeniem poprzedniego. Wysyła stan, w jakim znajduje się dioda (tym razem z użyciem funkcji printf). Ponadto odczytuje dane z portu szeregowego sterujące działaniem diody (kolejny przychodzący znak w na przemian załącza i wyłącza diodę). #include <REGX51.H> #include <STDIO.H> //na potrzeby printf sbit dioda = P1^7; // zmienne globalne unsigned char znak; // przechowuje kod ostatnio odebranego znaku // funkcja obsługi przerwania z odbiornika portu szeregowego void f_uart(void) interrupt 4 { // gdy przerwanie z odbiornika... if (RI) { znak = SBUF; //...zapamiętanie nadesłanego znaku if (znak == w ) dioda = ~dioda; //...włącz/wyłącz diode RI = 0; // kasowanie flagi przerwania printf( dioda: %s\n,!dioda? on : off ); printf( ostatnia komenda: %c\n\n, znak); // program główny void main(void) { // inicjalizacja zmiennych znak = 0; // konfiguracja portu szeregowego SM0 = 0; SM1 = 1; SM = 1; REN = 1; PCON = 0x80; // SMOD = 1 TH1 = 0xfa; TMOD = 0x0; TR1 = 1; // konfiguracja systemu przerwań ES = 1; // odblokowanie przerwania z portu szeregowego EA = 1; // włączenie systemu przerwań TI = 1; // wymagane na potrzeby funkcji printf // główna pętla programu while(1);
6 4..3 Program obsługi wyświetlacza LCD Zmodyfikować poniższy program wyświetlający w górnej linii LCD dowolny napis (ciąg znaków) tak, aby dodatkowo w dolnej linii wyświetlić dowolną dwucyfrową zmienną typu całkowitego. #include <regx51.h> //wybieramy Atmel-AT89C51 #include <absacc.h> //do obsługi pamięci zewnętrznej XDATA #define LCDWC 0xf080 //adres rejestru zapisu rozkazów sterujących do LCD #define LCDWD 0xf081 //adres rejestru zapisu danych do LCD #define LCDRC 0xf08 //adres rejestru odczytu stanu sterownika LCD (zajętości //bitu flagi BF) main() { char* tekst=" Mikroprocesory"; //wskaźnik do łańcucha znaków int j,liczba = 74; //XBYTE[LCDWC]=0x0f; //włączenie LCD, kursora i migotania znaku (ustawienia domyślne) XBYTE[LCDWC]=0x01; //wyzerowanie LCD i ustawienie kursora na początku //pierwszej linii pod adresem 0x00 //XBYTE[LCDWC]=0x0c; //włączenie LCD bez kursora i migotania znaku while( *tekst ) { XBYTE[LCDWD]=*tekst; //bufor zapisu danych-wskazanie na //początek łańcucha znaków while(xbyte[lcdrc]&0x80); //bufor odczytu sterowania-testowanie //zajętości bitu BF (ang. busy flag) for( j=0; j<00; j++ ); //opóźnienie czasowe tekst++; //kolejne znaki łańcucha znaków //dolna linia???