Grzegorz Cygan Krótki kurs programowania w języku C mikrosterowników standardu MCS-51 z wykorzystaniem systemu DSM-51, kompilatora SDCC i środowiska MIDE-51 wydanie 2
1.Spis treści 1. Przygotowanie komputera do pracy z DSM-51...3 2. Pierwszy program dla systemu DSM-51 z wykorzystaniem MIDE-51...3 3. Zmienne...4 3.1. Typy proste...4 3.2. Typy złożone...5 3.3. Organizacja pamięci mikrosterownika...5 4. Instrukcje zmieniające przebieg wykonywania programu...7 4.1. Pętle for i while...7 4.2. Typowe zastosowania pętli for...8 5. Wybrane funkcje z biblioteki stdio...9 5.1. Funkcja putchar dla wyświetlacza LCD...9 5.2. Funkcja putchar dla sygnalizatora dźwiękowego...10 5.3. Funkcja printf...11 6. Programowanie wyświetlacza LCD systemu DSM-51...11 6.1. Wyświetlanie znaków bez korzystania z podprogramów systemu DSM-51...12 6.2. Korzystanie z instrukcji sterujących sterownika HD44780...13 7. Programowanie układów wewnętrznych mikrosterownika...13 7.1. Programowanie portów...14 7.2. Programowanie układu transmisji szeregowej SIO...15 7.3. Programowanie sterownika przerwań...16 7.4. Miernik refleksu...16 8. Programowanie zewnętrznych układów peryferyjnych...18 8.1. Przetwornik A/C...18 8.2. Silnik krokowy...19 8.3. Wyświetlacz siedmiosegmentowy...19 9. Dodatak A - Wybrane rejestry mikrosterownika 8051...21 9.1. Rejestr konfiguracyjny układów czasowo-licznikowych...21 9.2. TCON - drugi rejestr układów czasowo-licznikowych...21 9.3. Rejestr konfiguracyjny układu transmisji szeregowej...21 9.4. Rejestr sterowania poborem mocy...21 9.5. Rejestr sterownika przerwań...21 10. Dodatek B - Niezbędne pliki...22 10.1. Plik nagłówkowy DSM-51...22 10.2. Koder morse'a dla MCS-51...22 10.3. Miernik refleksu...25 Grzegorz Cygan Krótki kurs programowania... 2
1 Przygotowanie komputera do pracy z DSM-51 Do wykonania ćwiczeń potrzebny będzie komputer z kompilatorem języka C generujący kod w języku maszynowym mikrosterowników standardu MCS-51. Wszystkie programy zostały sprawdzone kompilatorem sdcc, który jest rozpowszechniany na licencji Open Source. Niezbędne jest także oprogramowanie systemu DSM-51. W systemie operacyjnym musi być zdefiniowana zmienna Path ze ścieżkami dostępu do programów wykonywalnych kompilatora, aby możliwe było korzystanie z kompilatora z wiersza poleceń. Ćwiczenie 1 1. Połącz komputer z systemem DSM-51 poprzez port COM1 i włącz zasilanie systemu DSM-51. 2. Uruchom systemu Windows i zaloguj się na swoje konto. 3. Sprawdź czy jest zainstalowane oprogramowanie systemu DSM-51 oraz środowisko programistyczne MIDE-51. 4. Sprawdź, czy w katalogu C:\MIDE\sdcc\bin znajduje się plik dsm51run.exe. 5. Uruchom wiersz poleceń cmd i wydaj w nim polecenie sdcc. Komunikat podobny do takiego: Nazwa 'sdcc' nie jest rozpoznawana jako polecenie wewnętrzne lub zewnętrzne, program wykonywalny lub plik wsadowy. świadczy, że albo nie zainstalowano wymienionych programów, albo nie ma ustawionej zmiennej środowiskowej Path. Zmienna ta powinna zawierać ścieżkę do kompilatora C:\MIDE\sdcc\bin. 6. W swoim folderze domowym utwórz folder KursC przeznaczony na pliki tworzone podczas zajęć. Skopiuj do tego folderu pliki: dms51.h i mors.asm. 2 Pierwszy program dla systemu DSM-51 z wykorzystaniem MIDE-51 MIDE-51 to środowisko programistyczne, które posiada edytor tekstu z kolorowaniem składni. Środowisko MIDE-51 musi zostać skonfigurowane. W tym celu należy: wcisnąć F12 i w zakładce Programmer, w polu Execute file wpisać lub wskazać C:\MIDE\sdcc\bin\dsm51run.exe należy także zaznaczyć opcje ShortPathname 8.3 format oraz Output with.hex Extension. Rysunek 1: Okno konfiguracji środowiska MIDE-51 Grzegorz Cygan Krótki kurs programowania... 3
Po napisaniu programu wystarczy nacisnąć Ctrl+F9, aby program został skompilowany i uruchomiony w systemie DSM-51. Program źródłowy zostaje poddany kompilacji, w wyniku której otrzymujemy plik wynikowy (z rozszerzeniem ihx lub hex). Plik wynikowy jest przesyłany do systemu DSM-51 za pomocą programu dsm51run.exe. Rysunek 2 pokazuje, co się dzieje po wciśnięciu Ctrl+F9. Program źródłowy Kompilacja (sdcc) Program wynikowy Uruchomienie (dsm51run) DSM-51 Ćwiczenie 2 1. Uruchom MIDE-51. Rysunek 2: Proces tworzenia programu dla systemu DSM-51 2. Przepisz program z listingu 1 i zapisz go w pliku z rozszerzeniem c. Pamiętaj o zapisywaniu w folderze KursC. Listing 1 #include <8051.h> P1_7 = 0; 3. Uruchom program skrótem Ctrl+F9. Program składa się tylko z funkcji main (program główny), która ustawia stan niski na porcie P1.7. Dyrektywa #include dołącza plik nagłówkowy zawierający deklarację portu P1_7. 3 Zmienne Zmienne służą do przechowywania danych (liczb, tekstu). Każda zmienna ma określony typ. Do kodowania zmiennych stałoprzecinkowych używa się najczęściej naturalnego kodu dwójkowego NBC lub kodu uzupełnień do dwóch U2. Liczby zmiennoprzecinkowe kodowane są zgodnie ze standardem IEEE754. Do kodowania znaków korzysta się z kodu ASCII. Każda zmienna musi zostać zadeklarowana. Podczas deklaracji podaje się typ i nazwę zmiennej. 3.1 Typy proste Niestety w C nie określono rozmiaru poszczególnych typów. Wiadomo jedynie, że rozmiar typu char jest mniejszy lub równy rozmiarowi typu short, short int jest mniejszy lub równy int, itp. Można to zapisać, za pomocą operatora sizeof i operatorów relacji włąściwych dla języka C, Grzegorz Cygan Krótki kurs programowania... 4
następująco: sizeof char <= sizeof short <= sizeof int <= sizeof long Rozmiary poszczególnych typów używane w kompilatorze sdcc, którego będziemy używać na zajęciach, przedstawiono w tabeli. Dla porównania podano także rozmiary typów w języku Java, w którym są one zdefiniowane. Tabela 1: Porównanie typów prostych w języku C i Java i i ich rozmiar Nazwa typu bit Rodzaj Język C (kompilator sdcc) Język Java 1 Nie występuje char 8 16 byte Nie występuje 8 stałoprzecinkowy short 16 16 int 16 32 long 32 64 float 32 32 zmiennoprzecinkowy double 64 64 Liczba bitów decyduje oczywiście o zakresie dopuszczalnych wartości. Jeżeli np. typ jest 16 bitowy liczby mogą być z zakresu od (- 2 15 ) do (2 15-1), czyli od -32768 do 32767. Modyfikator unsigned zmienia zakres typu int na liczby dodatnie z zakresu od 0 do 2 16 2 (65535). W języku Java modyfikator unsigned nie występuje. Typ char w języku C ma najczęściej 8 bitów więc jest używany do przechowywania bajtów (odpowiednik typu byte z języka Java). Warto wiedzieć, że procesor 8-bitowy będzie pracował najefektywniej na liczbach 8- bitowych. Dlatego pisząc programy dla systemu DSM-51 do zapisu liczb będzie często używany typ char, jeśli zakres od -128 do 127 jest wystarczający. Do przechowywania liczb dodatnich będziemy korzystali z typu unsigned char, którego zakres wartości wynosi od 0 do 255. 3.2 Typy złożone Typy złożone składają się z typów prostych, są to struktury, unie, tablice, a także pola bitowe. Tych ostatnich nie polecam! Ze względu na to, że niniejsza publikacja to tylko "krótki kurs programowania", nie będę omawiał struktur oraz unii. Tak deklarujemy tablicę o nazwie napis, w której można przechowywać 10 zmiennych typu char. char napis[10]; Tablice przechowujące zmienne typu char pełnią w języku C ważną rolę, ponieważ służą do przechowywania napisów (stringów). W tablicy 10-elementowej można przechowywać napis 9- literowy, bo na końcu napisu musi się znaleźć element o wartości 0 (NUL) oznaczający koniec napisu. 3.3 Organizacja pamięci mikrosterownika W komputerach PC do przechowywania zmiennych służy pamięć operacyjna. W systemach z mikrosterownikami MCS-51 nie ma pamięci operacyjnej. Wyróżnia się tu oddzielną pamięć dla danych i dla programów. Ponadto pamięć danych składa się z kilku niezależnych przestrzeni adresowych, które wynieniono poniżej: Tabela 2: Przestrzenie adresowe mikrosterowników standardu MCS-51 Przestrzeń Opis rozmiar sfr rejestry specjalnego przeznaczenia Obszar nieciągły (omówienie wybranych Grzegorz Cygan Krótki kurs programowania... 5
data wewnętrzna pamięć danych o dostępie bezpośrednim 128 B idata wewnętrzna pamięć danych o dostępie pośrednim 128 B pdata xdata zewnętrzna pamięć danych (dostęp za pomocą indeksowania rejestrami Ri) zewnętrzna pamięć danych (dostęp za pomocą indeksowania rejestrem DPTR) rejestrów w dodatku A) maks. 256 B* w jednym banku maks. 64 KiB = 65536 B * code pamięć programu maks. 64 KiB = 65536 B * * - poza mikrosterownikiem, rozmiar zależy od zastosowanych ukłądów pamięciowych Zmienne mogą być przechowywane we wszystkich wymienionych przestrzeniach, kompilator decyduje o przeznaczeniu poszczególnych przestrzeni adresowych. Programista może wpływać na decyzje kompilaltora umieszczając przed deklaracjami zmiennych słowa kluczowe: sfr, data, idata, pdata, xdata, code. Zauważ, że nazwy zaczynają się od dwóch znaków podkreślenia, dlatego że nie są to standardowe słowa języka C. Kompilator w miarę możliwości będzie korzystał rejestrów roboczych zlokalizowanych w przestrzeni data. Przestrzeń ta jest najbardziej cenna, ponieważ dostęp do niej jest najszybszy. Jeżeli w programie występują tzw stałe, czyli "zmienne", których wartość nie zmienia się, należy korzystać z modyfikatora const. Dla zilustrowania tego zagadnienia przedstawiam program, w którym jedna linia decyduje o wykorzystaniu poszcególnych obszarów pamięci. Zadaniem programu jest wyświetlenie tekstu, którego treść się nie zmienia w czasie działania programu. Przy nieumiejętnym zadeklarowaniu zmiennej napis: char napis[] = "Ala ma kota, a Ola ma psa."; treść napisu (łącznie 27 bajtów) zostanie umieszczona w cennej pamięci wewnętrznej. Wystarczy użyć modyfikatora const, aby systuację poprawić. const char napis[] = "Ala ma kota, a Ola ma psa."; Ćwiczenie 3 1. Uruchom program przykładowy. 2. Po każdej zmianie deklaracji zmiennej napis sprawdzaj wykorzystanie poszcególnych przestrzeni adresowych, zaglądając do odpowiednich plików mem. Wypróbuj kolejno: 1. char napis[] = "Ala ma kota, a Ola ma psa."; 2. const char napis[] = "Ala ma kota, a Ola ma psa."; 3. const char * napis = "Ala ma kota, a Ola ma psa."; 4. const char * const napis = "Ala ma kota, a Ola ma psa."; 3. Odpowiedź znajduje się w tabeli 2. Tabela 3: Zajętość pamięci dla programu z listingu 2 w zależności od sposobu delaracji zmiennej (informacje uzyskane z plku z rozszerzeniem mem) Deklaracja stringu Przestrzeń data (Internal RAM) Zajętość pamięci Przestrzeń code (ROM/EPROM/ FLASH) char napis[] = "Ala ma kota, a Ola ma psa."; 27 345 const char napis[] = "Ala ma kota, a Ola ma psa."; 0 291 const char * napis = "Ala ma kota, a Ola ma psa."; 3 303 const char * const napis = "Ala ma kota, a Ola ma psa."; 0 308 Grzegorz Cygan Krótki kurs programowania... 6
Listing 2 #include <stdio.h> #include <ctype.h> char napis[] = "Ala ma kota, a Ola ma psa."; void putchar (char c) if (isprint (c)) WRITE_DATA (c); LCD_CLR; puts (napis); 4 Instrukcje zmieniające przebieg wykonywania programu Instrukcje if, else, switch, while, do, for, continue, break i goto umożliwiają wykonanie fragmentu programu w zależności od spełnienia określonego warunku, np wciśnięcie klawisza. Możliwe jest także wielokrotne powtórzenie fragmentu, itp. Rysunek 3a pokazuje działanie instrukcji if. Rysunek 3b przedstawia działanie pętli. Do budowy pętli wykorzystujemy instrukcje for i while. W algorytmie przedstawionym na rysunku 3b sprawdzanie warunku odbywa się na początku (przed wykonaniem pętli), ale możliwe jest także sprawdzenie warunku na końcu pętli za pomocą pętla do... while. Fragment poprzedzający warunek Fragment poprzedzający pętlę Warunek Blok instrukcji wykonane warunkowo Warunek Blok instrukcji wykonane warunkowo Dalsza część programu Dalsza część programu a) b) Rysunek 3: Algorytm programu a) z instrukcją warunkową, b) z pętlą. Grzegorz Cygan Krótki kurs programowania... 7
4.1 Pętle for i while Dla omówienia poszczególnych instrukcji warunkowych wykonajmy zadanie polegające na wydrukowaniu kolejny liczb. Najlepiej do tego zadania nadaje się pętla for, ale dla porównania zastosojemy także pętlę while. Po słowie kluczowym for w nawiasie podaje się 3 wyrażenia oddzielone średnikami. Listing 3 unsigned char i; WRITE_INSTR (1); for (i = 0; i < 5; i++) WRITE_DATA (i + '0'); Pierwsze wyrażenie (i = 0) służy do inicjalizacji zmiennej, czyli nadania zmiennej początkowej wartości. Drugie wyrażenie (i < 5), to warunek wykonania pętli. Trzecie wyrażenie mówi co należy po każdym wykonaniu pętli. W tym przykładzie i++ to postinkrementacja, czyli zwiększenie wartości zmiennej o jeden. Pętla while, podobnie jak for, umożliwia wielokrotne powtórzenie fragmentu programu. Po słowie kluczowym while w nawiasie podaje się jedno wyrażenie - warunek wykonania pętli. Można osiągnąć taki sam efekt, jak w przypadku pętli for, ale inicjalizację zmiennej trzeba wykonać przed pętlą, a inkrementację zmiennej wewnątrz pętli. Listing 4 unsigned char i = 0; WRITE_INSTR (1); while (i < 5) WRITE_DATA (i + '0'); i++; 4.2 Typowe zastosowania pętli for W ćwiczeniu pokazano dwa typowe zastosowania pętli for: do zbudowania pętli nieskończonej i do wygenerowania zwłoki czasowej. Listing 5 #include <8051.h> unsigned int i; for ( ; ; ) for (i = 0; i < 65535; i++) P1_7 =!P1_7; Grzegorz Cygan Krótki kurs programowania... 8
Pętla nieskończona (for ( ; ; )) nie zawiera ani inicjalizacji zmiennej, ani warunku, ani inkrementacji zmiennej. Pętlę nieskończoną można zrealizować także za pomocą instrukcji while (1). W języku C każda wartość niezerowa, np. 1, oznacza prawdę, dlatego pętla nigdy się nie kończy. Pętla generująca opóźnienie (for (i = 0; i < 65535; i++)) nie zawiera żadnych instrukcji, ponieważ chodzi tylko o zajęcie procesorowi czasu. Oczywiście jest to swoiste marnowanie mocy obliczeniowej, ale jest to zarazem najprostszy sposób osiągnięcia zwłoki czasowej. Ponieważ, jak już wspomniałem, pętla nie zawiera instrukcji (nawiasy klamrowe są puste) można pętlę zapisać krócej tak: for (i = 0; i < 65535; i++); Ćwiczenie 4 1. Przetestuj program przykładowy. 2. Spróbuj zwiększyć dziesięciokrotnie wartość zwłoki. 3. Zastąp zęwnętrzną pętlę for pętlą while. 5 Wybrane funkcje z biblioteki stdio Program w języku C składa się z pewnej (praktycznie nieograniczonej) liczby funkcji. Funkcja main jest uruchamiana jako pierwsza, czyli jest programem głównym. Z funkcji main można wywołać inne funkcje. Można samodzielnie pisać funkcje, albo korzystać z funkcji bibliotecznych. Aby skorzystać z funkcji bibliotecznych należy dołaczyć odpowiedni plik nagłówkowy. Aby korzystać z biblioteki stdio dołączamy plik stdio.h w taki sposób: #include <stdio.h> Funkcję puts można wykorzystywać do drukowania napisów na różnego typu urządzeniach. W komputerze PC jest to zwykle monitor, a w mikrosterownikach wyświetlacz i interfejs szeregowe wystarczy napisać odpowiednią funkcję putchar. 5.1 Funkcja putchar dla wyświetlacza LCD Ćwiczenie 5 1. Uruchom następujący program. 2. Poeksperymentuj z programem usuwając linie, których działanie chcesz sprawdzić. Zamiast usuwania lepiej te linie zakomentować, czyli poprzedzić je dwoma ukośnikami. W zasadzie taki typ komentarza pojawił się dopiero w języku C++, ale występują we wszystkich nowych kompilatorach języka C. W środowisku MIDE komentarze są oznaczone kolorem zielonym. Poniżej przedstawiono przykład zastosowania komentarza. // WRITE_DATA (c); Listing 6 #include <stdio.h> #include <ctype.h> void putchar (char c) if (isprint (c)) WRITE_DATA (c); Grzegorz Cygan Krótki kurs programowania... 9
LCD_CLR; puts ("CENTRUM EDUKACJI ZAWODOWEJ"); Dyrektywy include powodują dołączenie plików nagłówkowych (z rozszerzeniem h). W plikach nagłówkowych znajdują się deklaracje funkcji bibliotecznych, np.: w pliku stdio.h są zadeklarowane funkcje putchar i puts, w ctype.h funkcja isprint. W pliku dsm51.h zdefiniowano makroinstrukcje LCD_CLR i LCD_DATA. Zauważ, że nazwy makroinstrukcji, w odróżnieniu od funkcji, piszemy wielkimi literami. Nagłówek funkcji putchar informuje, że funkcja zwraca typ void, oraz że funkcja przyjmuje jeden argument typu char. Funkcja putchar wywołuje funkcję isprint, aby sprawdzić, czy znak da się wydrukować. Do drukowania poszczególnych znaków wykorzystuje się makroinstrukcję WRITE_DATA, która z kolei wywołuje odpowiedni podprogram systemu DSM-51. Nagłówek funkcji main informuje, że funkcja ta, ani nie przyjmuje, ani nie zwraca argumentów. Do drukowania napisu korzystamy z funkcji puts, ta korzysta z putchar, aby drukować poszczególne litery. 5.2 Funkcja putchar dla sygnalizatora dźwiękowego Ćwiczenie 6 1. Podobnie jak poprzednio utwórz następujący plik z programem w języku C. Możesz skorzystać z edytora środowiska MIDE-51, notatnika lub innego. Zauważ, że program jest podobny do poprzedniego. Zapisz program w pliku hellomors.c. 2. Druga część programu napisana jest w języku Asemblera w pliku mors.asm. 3. Teraz należy skompilować program, który jest napisany w języku C, zasemblować program w języku Asemblera, połączyć obie części w cały program za pomocą konsolidatora i wysłać do systemu DSM-51, wykonując poniższe polecenia: sdcc -c hellomors.c sdas8051 -l -o mors.asm sdcc hellomors.rel mors.rel dsm51run hellomors.ihx 4. Zapoznaj się z plikami wygenerowanymi w poszczególnych etapach tworzenia pliku wynikowego. Listing 7 /* Ten program sklada sie z dwoch plikow: hellomors.c i mors.a51 Program nalezy kompilowac z wiersza polecen: sdcc -c hellomors.c sdas8051 -l -o mors.asm sdcc hellomors.rel mors.rel dsm51run hellomors.ihx */ #include <stdio.h> #include <ctype.h> #include <8051.h> extern void mors_putchar (char); void putchar (char c) if (isprint (c)) WRITE_DATA (c); Grzegorz Cygan Krótki kurs programowania... 10
mors_putchar (c); void main () for (;;) puts ("CENTRUM EDUKACJI ZAWODOWEJ"); 5.3 Automatyzacja procesu tworzenia programu przy użyciu make Zarządzanie budową programu składającego się z kilku plików omówimy na przykładzie programu składającego sie z dwóch plików źródłowych, napisanych w dwóch językach: w języku C hellomors.c i w Asemblerze mors.asm. Proces tworzenia pliku wynikowego hellomors.ihx przedstawia rysunek.na rysunku wymieniono polecenia, które należy wydać z wiersza poleceń. hellomors.c mors.asm Kompilacja sdcc -c hellomors.c Asemblacja sdas8051 -l -o mors.asm hellomors.rel mors.rel Konsolidacja sdcc hellomors.rel mors.rel hellomors.ihx Uruchomienie dsm51run hellomors.ihx Po dokonaniu zmian w pliku hellomors.c należy ponownie skompilować ten plik, następnie skonsolidować i uruchomić program. Polecenia można zapisać w pliku z rozszerzeniem cmd. Oto zawartość pliku: sdcc -c hellomors.c if errorlevel 1 goto stop sdas8051 -l -o mors.asm if errorlevel 1 goto stop sdcc hellomors.rel mors.rel dsm51run hellomors.ihx :stop pause Pracę znacznie usprawnia program make, który sprawdza, w których plikach dokonano zmian i wykonuje odpowiednie polecenia. Program czyta plik Makefile, którego zawartosć może być następująca: hellomors.ihx : hellomors.rel mors.rel sdcc hellomors.rel mors.rel hellomors.rel : hellomors.c sdcc -c hellomors.c Grzegorz Cygan Krótki kurs programowania... 11
mors.rel : mors.asm sdas8051 -l -o mors.asm run : hellomors.ihx dsm51run hellomors.ihx W pliku zapisane są nastęujące cele: hellomors.ihx, hellomors.rel, mors.rel oraz sztuczny cel run. Po nazwie celu umieszczony jest dwukropek, po którym wymienione są warunki budowy celu. Czyli, aby zbudować hellomors.ihx musi być plik hellomors.rel i mors.rel. W następnych liniach znajdują sie polecenia budowy celu poprzedzone tabulatorem. Uruchomienie programu make następuje przez wydania polecenia make. Sztuczny cel run umożliwia uruchomienie programu w systemie DSM-51. Należy po prostu wywołać program make z nazwą tego celu, czyli napisać w wierszu poleceń, czyli: make run Ćwiczenie 7 1. Zbuduj program "koder morse'a" korzystając z make. 2. Dodaj sztuczny cel o nazwie clean, który usunie wszystkie niepotrzebne pliki powstałe podczas tworzenia programu wynikowego. 5.4 Funkcja printf Standardową funkcja wyjścia printf jest dużo bardziej uniwersalna od funkcji puts. Oprócz statycznych napisów można za jej pomocą drukować wartości zmiennych w różnych systemach liczbowych. Podobnie jak puts funkcja printf korzysta z funkcji putchar do drukowania poszczególnych znaków. Siła funkcji polega na zastosowaniu specyfikatorów formatowania (z prefiksem %), które można umieszczać w dowolnym miejscu stringu formatującego. Każdemu specyfikatorowi odpowiada jedna zmienna lub wyrażenie na liście argumentów funkcji printf. Poniżej przedstawiono wybrane specyfikatory przydatne zwłaszcza w programach dla mikrosterowników. %d wartość zmiennej w postaci dziesiętnej ze znakiem; %u wartość zmiennej w postaci dziesiętnej bez znaku; %x wartość zmiennej w postaci szesnastkowej; %c znak o kodzie ASCII równym wartość zmiennej ośmiobitowej; %04.3f wartość liczby zmiennoprzecinkowej typu float w polu o szerokości 4 znaków części całkowitej i 3 - części ułamkowej. Wolne pola poprzedzające będą wypełnione zerami. %s string; Listing 8 #include <stdio.h> #include <ctype.h> void putchar (char c) if (isprint (c)) WRITE_DATA (c); char wiek = 25; LCD_CLR; printf ("Mam %d lat%c", wiek, (wiek < 5)? 'a' : ' '); W przykładowym programie %d formatuje sposób wyświetlania wartości zmiennej wiek, %c pokazuje wartość wyrażenia (wiek < 5)? 'a' : ' ', którego wynikiem jest zmienna typu char przyjmująca wartości a (kod ASCII litery a ) lub kod spacji. Grzegorz Cygan Krótki kurs programowania... 12
Ćwiczenie 8 1. Zamień specyfikator %d na %u, a następnie na %x. Zastanów się dlaczego takie a nie ine wartość są wyświetlane. 2. Napisz program korzystający i innych specyfikatorów. 6 Programowanie wyświetlacza LCD systemu DSM-51 Na pierwszych zajęciach korzystaliśmy z wyświetlacza LCD, ale używaliśmy do tego gotowego podprogramu z pamięci stałej systemu DSM-51. W systemie DSM-51 zastosowano wyświetlacz z popularnym sterownikiem HD44780. Sterownik ten ma cztery rejestry, które w systemie DSM-51 są widziane w przestrzeni adresowej PDATA pod następującymi adresami. Tabela 4: Rejestry wyświetlacza LCD Adres Nazwa rejestru Kierunek Operacji 80H LCDWC wpis rozkazów 81H LCDWD wpis danych 82H LCDRC odczyt stanu 83H LCDRD odczyt danych 6.1 Wyświetlanie znaków bez korzystania z podprogramów systemu DSM-51 Użycie makroinstrukcji WRITE_DATA, WRITE_INSTR oznacza wykorzystanie podrogramów z systemu DSM-51. Jeśli piszemy program dla własnego systemu, to nie mamy żadnych gotowych podprogramów. W tym rozdziale nauczysz się jak obsłużyć wyświetlacz LCD bezpośrednio przez rejestry wyświetlacza. Ćwiczenie 9 1. Uruchom program przykładowy. 2. Zmodyfikuj go tak, aby wyświetlić więcej znaków. Listing 9 pdata at(0x81) unsigned char LCDWD; void main () LCDWD = 'a'; W zadaniu pierwszym pojawi się problem gubienia znaków. Wynika to z tego, że znaki będą drukowane poza obszarem widocznym na wyświetlaczu. Po prostu program LCD_DATA automatycznie przenosi kursor na początek. Zatem trzeba skasować wyświetlacz wpisując rozkaz o kodzie 1 (Clear display) do rejestru rozkazów. Pewnie zauważysz, że nadal nie są drukowane wszystkie znaki wyświetlacz nie nadąża. Najprostszym rozwiązaniem będzie zastosowanie pętli opóźniającej for. 3. Zamiast pętli opóźniającej dokonaj odczytu rejestru stanu. Najstarszy bit w tym rejestrze informuje, że wyświetlacz jest zajęty. Tabela 5: Instrukcje sterujące sterownika HD44780 (HITACHI) Nazwa oryginalna Opis kod Clear display Kasuj wyświetlacz 0 0 0 0 0 0 0 1 Grzegorz Cygan Krótki kurs programowania... 13
Return home Ustaw kursor na początku 0 0 0 0 0 0 1 X Entry mode set Ustawienie tryb wprowadzania danych 0 0 0 0 0 1 I/D S Display ON/OFF control Cursor and display shift Sterowanie włączaniem i wyłączaniem 0 0 0 0 1 D C B Przesunięcie kursora i (lub) wyświetlacza 0 0 0 1 S/C R/L X X Function set Ustawienie funkcji wyświetlacza 0 0 1 DL N F X X Set CG_RAM address Ustawienie adresu generatora znaków 0 1 A5 A4 A3 A2 A1 A0 Set DD_RAM address Ustawienie adresu danych 1 A6 A5 A4 A3 A2 A1 A0 Przed każdym wpisaniem danych LCDWD lub rozkazów LCDWC należy sprawdzać bit BYSY, jest to najstarszy bit w rejestrze LCDRC. Jeśli ten bit jest ustawiony, to oznacza, że wyświetlacz jest zajęty. Listing 10 void main () while (LCDRC & 0x80); LCDWC = 1; while (LCDRC & 0x80); LCDWD = 'D'; while (LCDRC & 0x80); LCDWD = '-'; //Pokaz znak - //Czekaj na gotowosc wyswietlacza //Clear display //Pokaz litere 'D' 6.2 Korzystanie z instrukcji sterujących sterownika HD44780 W poprzednim ćwiczeniu korzystaliśmy tylko z instrukcji sterującej Clear Display. Program przykładowy z listingu??? pokazuje zastosowanie rozkazu Cursor and display shift. Program jest zmodyfikowaną wersją programu z listingu 9. Na końcu dodano pętlę w której następuje przesuwanie napisu. Ćwiczenie 10 1. Uruchom program przykładowy. 2. Zamień rozkaz Cursor and display shift na rozkaz Display ON/OFF control. Wartość bitu D powinna się zmianiać tak, aby napis migał. Listing 11 #include <stdio.h> #include <ctype.h> void putchar (char c) if (isprint (c)) WRITE_DATA (c); unsigned int i; LCD_CLR; Grzegorz Cygan Krótki kurs programowania... 14
puts ("CENTRUM EDUKACJI ZAWODOWEJ"); for (;;) for (i = 0; i < 50000; i++); //Zwloka czasowa WRITE_INSTR (0x18); //Cursor and display shift 7 Programowanie układów wewnętrznych mikrosterownika Mikrosterownik to coć więcej niż procesor (inna nazwa mikrosterownika to komputer jednoukładowy). Każdy mikrosterownik zawiera szereg układów sprzętowych, które wykonują określone zadania niezależnie od procesora. Wyjaśnię to na przykładzie licznika. Po zaprogramowaniu trybu pracy licznika i uruchomieniu, licznik zlicza impulsy doprowadzone do wejścia, na przykład zlicza impulsy z czujnika położenia wału korbowego w samochodzie. Praca licznika jest niezależna od pracy procesora, który może w tym czasie wykonywać na przykład obliczenie prędkości. 7.1 Programowanie portów Mikrosterowniki posiadają porty umożliwiające bezpośrednie sterowania elementów wykonawczymi, np. włączenia żarówki, przekaźnika, diody LED. Mikrosterownik 8051 posiada cztery porty ośmiobitowe, więc można sterować na przykład 32 diodami LED. Włączanie elementu sterowanego z portu odbywa się przez podanie zera na wymagany bit w porcie, co w języku Asemblera może wyglądać tak CRL P1.7 Po ludzku rozkaz ten mówi wyzeruj (clear) bit 7 portu P1. W systemie DSM-51 do tego portu dołączona jest dioda TEST. Niestety w języku C rozwiązanie nie jest równie proste. Asembler to język procesora, dlatego Asembler wie co co to P1.7. Programowanie portów nie jest zdefiniowane w standardzie języka C. Oczywiście kompilator SDCC posiada odpowiednie instrukcje umożliwiające wykonanie zadania. Instrukcje takie zaczynają się dwoma znakami '_'. Program przykładowy pokazuje jak zadeklarować zmienną specjalnego typu sbit. Po słowie at w nawiasie podaje się adres portu. Podobny program uruchamiałeś na pierwszym ćwiczeniu, ale zamiast deklaracji bitu dołączaliśmy plik nagłówkowy 8051.h, w którym ta deklaracja występuje. Listing 12 sbit at (0x97) P1_7; void main () P1_7 = 0; Sześciopozycyjny wyświetlacz siedmiosegmentowy sterowany jest z portów zewnętrznych znajdujących się w przestrzeni adresowej PDATA mikrosterownika, pod adresami 30 oraz 38. Włączeniem całego wyświetlacza steruje port P1.6. Listing 13 sbit at (0x96) P1_6; pdata at(0x30) unsigned char CSDS; pdata at(0x38) unsigned char CSDB; void main () unsigned char i; P1_6 = 0; while (1) Grzegorz Cygan Krótki kurs programowania... 15
CSDS = 0x01; CSDB = 0x7f; for(i = 0; i < 200; i++); CSDS = 0x02; CSDB = 0x06; for(i = 0; i < 200; i++); CSDS = 0x04; CSDB = 0x07; for(i = 0; i < 200; i++); 7.2 Programowanie układu transmisji szeregowej SIO Układ transmisji szeregowej umożliwia współpracę systemu DSM-51 z komputerem PC poprzez fizyczny port szeregowy RS-232 lub poprzez wirtualny port szeregowy, połączony fizycznie, np łączem USB, LAN, bluetooth. Zastosowania potru są praktycznie nieograniczone. Warto jeszcze wspomnieć o interfejsie MIDI, który jest stosowany w elektronicznych instrumentach muzycznych. Ćwiczenie 11 1. Uruchom program przykładowy. 2. Uruchom program program Putty i ustaw następujące parametry transmisji: prędkość v BR = 19200 b/s, 8 bitów danych, 1 bit stopu. 3. Zmień ustawienia szybkości transmisji i wypróbuj działanie programu. Listing 14 #include <stdio.h> #include <8051.h> void putchar (char c) while (!TI); TI = 0; SBUF = c; Grzegorz Cygan Krótki kurs programowania... 16
void main () unsigned char i; TMOD = 0x20; SCON = 0x52; PCON = 0x80; TH1 = 253; TR1 = 1; for (i = 0; ; i++) DELAY_100MS (20); printf ("Minela wlasnie %u\r", i); Instrukcja Omówienie TMOD = 0x20 T1: 8 bitowy czasomierz bez bramkowania. SCON = 0x52 REN=1, SM0=1, TI=1. Tryb 1 asynchroniczny, 8 bitów danych, szybkość regulowana układem T1 PCON = 0x80 SMOD = 1 TH1 = 253 TH1=256 f OSC 2 SMOD ( 1 ) 384v BR Częstotliwość zegarową f OSC można odczytać z rezonatora kwarcowego systemu DSM-51lub z dokumentacji. TR1 = 1 Uruchom układ czasowo-licznikowy T1 7.3 Programowanie sterownika przerwań Systemu przerwań umożliwia zrealizowanie systemu wielozadaniowego. W programie przykładowym w funkcji main w pętli while (1) następuje naprzemienne zapalanie i gaszenie diody TEST. Drugie (niezależne) zadanie włączanie i wyłączanie brzęczyka, wykonywane jest w programie obsługi przerwania od układu czasowo licznikowego. Dla przypomnienia program główny to funkcja main. Programy obsługi przerwania (ang. interrupt service routine, isr) mają w nagłówku funkcji słowo interrupt. Po nim podaje się numer układu, np. T0 ma numer 1. Nazwa ISR może być dowolna, ale dobrze jeśli coś mówi. Słowo using określa numer banku rejestrów. Ze wzoru można obliczyć czas, co jaki będzie się uruchamiał program obsługi przerwania od układu czasowego. bitow 12 2liczba T = = 12 216 f OSC 11,0592MHz =71,1ms ( 2 ) Listing 15 #include <8051.h> //Program glowny TMOD = 0x01; //Konfiguracja ukladow czasowo-licznikowych TR0 = 1; //Uruchomienie ukladu czasowo-licznikowego T0 ET0 = 1; //Wlaczenie zezwolenia na przerwania od T0 EA = 1; //Wlaczenie zezwolenia ogolnego while (1) P1_7 =!P1_7; DELAY_100MS(5) //Program z pamieci EPROM systemu DSM-51 Grzegorz Cygan Krótki kurs programowania... 17
//Program obslugi przerwania od ukladu czasowo-licznikowego T0 void t0_isr () interrupt 1 using 1 P1_5 =!P1_5; 7.4 Miernik refleksu Na podsumowanie przedstawiam gotowy program (listing znajduje się w dodatku B), w którym wykorzystano wszystkie układy wewnętrzne mikrosterownika omawiane w tym rozdziale. Miernik wykorzystuje złącze ISOLATED I/O, do którego dołączone są niektóre porty mikrosterownika. Port P3.4 do dołączenia "pedału hamulca" i port P1.2 do dołączenia "świateł stop". Rysunek 4: Złącze izolowane galwanicznie (rysunek z dokumenntacji DSM-51) Grzegorz Cygan Krótki kurs programowania... 18