13 4 Transmisja szeregowa na przykładzie komunikacji dwukierunkowej z komputerem PC, 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 2 1 0 adres SBUF data 99H rejestr konfiguracyjny nazwa 7 6 5 4 3 2 1 0 adres SCON SM0 SM1 SM2 REN TB8 RB8 TI RI 98H rejestr kontroli trybu pracy mikrokontrolera nazwa 7 6 5 4 3 2 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 / 12
14 0 1 1 8-bit UART zmienna (wykorzystywany timer T1) 1 0 2 9-bit UART f osc / 64 lub f osc / 32 1 1 3 9-bit UART zmienna (wykorzystywany timer T1) Bit SM2 wykorzystywany jest w trybach 2 i 3 do komunikacji wieloprocesorowej (nie omawiana). W trybie 1 jeżeli SM2 = 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 2 i 3. Bit RB8 jest 9-tym odbieranym bitem danych w trybach 2 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.2 Opis działania (bez przerwań) Nadawanie n bajtów (tryb 1): 1. zerujemy flagę TI 2. 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 2. 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 SM2 = 0). Natomiast gdy SM2 = 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.
15 W trybie 2 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 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. RS232 / RS422, 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 c:\ptm\teraterm 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: 0xF082): 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.
16 4.2. Ćwiczenia do wykonania 4.2.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 = 2 32 SMOD (timer 1overflow rate) Wybieramy tryb 2 pracy czasomierza timer 1 (TL1 przeładowywany z TH1), a zatem: baudrate = 2 32 SMOD 12 f osc ( 256 TH1) Po prostych przekształceniach mamy (przy założeniu że SMOD = 1 i f osc = 11.0592 MHz): SMOD 2 f osc TH1 = 256 32 12 baudrate 2 11059200 = 256 = 256 6 = 250 = FAh 32 12 9600 ostatecznie: TH1 <= 1111 1010b (FAh) TMOD <= 0010 xxxxb (2xh) // konfiguracja timer 1 (x wartość dowolna) TR1 <= 1b // podłączenie sygnału zegarowego do timer 1 Kod programu: #include <REG51.H> // program główny void main(void) { // deklaracje zmiennych unsigned char v_znak = a ; // konfiguracja portu szeregowego SM0 = 0; SM1 = 1; REN = 0; PCON = 0x80; // SMOD = 1 TH1 = 0xfa; TMOD = 0x20; TR1 = 1; // główna pętla programu
17 while (1) { TI = 0; SBUF = v_znak; while (!TI); if (v_znak!= z ) v_znak++; else v_znak = a ; 4.2.2 Transmisja dwukierunkowa (z obsługą przerwań) Przykład ten jest rozszerzeniem poprzedniego. Wysyła stan w jakim znajduje się głośnik (tym razem z użyciem funkcji printf). Ponadto odczytuje dane z portu szeregowego sterujące działaniem głośnika (kolejny przychodzący znak w na przemian załącza i wyłącza głośnik). #include <REG51.H> #include <STDIO.H> sbit glosnik = P1^5; // zmienne globalne unsigned char v_char; // przechowuje kod ostatnio odebranego znaku // funkcja obsługi przerwania z portu szeregowego void f_uart(void) interrupt 4 { // gdy przerwanie z odbiornika... if (RI) { v_char = SBUF; //...zapamiętanie nadesłanego znaku if (v_char == w ) glosnik = ~glosnik; //...włącz/wyłącz głośnik RI = 0; // kasowanie flagi przerwania // program główny void main(void) { // inicjalizacja zmiennych v_char = 0; // konfiguracja portu szeregowego SM0 = 0; SM1 = 1; SM2 = 1; REN = 1; PCON = 0x80; // SMOD = 1 TH1 = 0xfa; TMOD = 0x20; 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) { printf( glosnik: %s\n,!glosnik? on : off ); if (v_char) printf( ostatnia komenda: %c\n\n, v_char);
18 4.2.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 <reg51.h> //wybieramy Atmel-AT89C51 #include <absacc.h> //do obslugi pamieci zewnetrznej XDATA main () { char* tekst=" PTM"; //wskaznik do lancucha znakow int j,liczba=12; //XBYTE[0xf080]=0x0f; //wlaczenie LCD, kursora i migotania znaku (ustawienia domyslne) XBYTE[0xf080]=0x01; //bufor zapisu sterowania-wyzerowanie LCD i ustawienie kursora na poczatku pierwszej linii //XBYTE[0xf080]=0x0c; //wlaczenie LCD bez kursora i migotania znaku while( *tekst ) { XBYTE[0xf081]=*tekst; //bufor zapisu danych-wskazanie na poczatek lancucha znakow while(!(xbyte[0xf082] & 0x80) ); //bufor odczytu sterowania-testowanie zajetosci bitu BF //(negacja iloczynu bitowego) for( j=0; j<200; j++ ); //opóznienie czasowe tekst++; //kolejne znaki lancucha znakow //dolna linia???