Grzegorz Cygan Krótki kurs programowania w języku C mikrosterowników standardu MCS-51 z wykorzystaniem systemu DSM-51 i kompilatora SDCC wydanie 3
1.Spis treści 1. Wprowadzenie do programowania systemu DSM-51...3 2. Pierwsze programy w język Asemblera i w języku C...5 3. Złożone programy w języku C...6 3.1. Operacje arytmetyczne...6 3.2. Operacje logiczne...7 3.3. Programowania standardowego wyjścia...8 3.4. Specyfikatory formatowania funkcji printf...9 3.5. Funkcja putchar dla sygnalizatora dźwiękowego...9 3.6. Zwłoki czasowe...10 3.7. Zmienne...10 4. Programowanie układów wewnętrznych mikrosterownika...13 4.1. Programowanie portów...13 4.2. Programowanie układu transmisji szeregowej SIO...14 4.3. Programowanie sterownika przerwań...15 4.4. Miernik refleksu...16 5. Programowanie zewnętrznych układów peryferyjnych...17 5.1. Niskopoziomowe programowanie wyświetlacza LCD systemu DSM-51...17 5.2. Przetwornik A/C...18 5.3. Silnik krokowy...19 5.4. Wyświetlacz siedmiosegmentowy...20 6. Wybrane rejestry mikrosterownika 8051...21 6.1. Rejestr konfiguracyjny układów czasowo-licznikowych...21 6.2. TCON - drugi rejestr układów czasowo-licznikowych...21 6.3. Rejestr konfiguracyjny układu transmisji szeregowej...21 6.4. Rejestr sterowania poborem mocy...21 6.5. Rejestr sterownika przerwań...21 Grzegorz Cygan Krótki kurs programowania... 2
1 Wprowadzenie do programowania systemu DSM-51 Do wykonania ćwiczeń niezbędny jest system Operacyjny Windows 1 oraz następujące programy: sdcc (kompilator), sdas8051 (asembler), make, dsm51run (dostarczony z systemem DSM-51), rm, oraz edytor tekstu z kolorowaniem składni (polecam program Far Manager z wtyczką Far Colorer). Do usuwania niepotrzebnych plików można używać polecenia del, ale z pewnych nieomawianych tu powodów, użyjemy Linuksowego polecenia rm. Program w języku C zapiszemy w pliku main.c. Podprogramy odwołujące się do sprzętu (elementów systemy DSM-51) będziemy tworzyć w języku Asemblera i zapisywać w pliku dsm51.a51. Asembler to język procesora (mikrosterownika), tyle że dostosowany dla ludzi, ponieważ liczbowe kody rozkazów zastąpiono nazwami. W języku maszynowym program jest ciągiem liczb, które można zapisać w systemie dwójkowym lub szesnastkowym. Posługując się nazwami rozkazów, zamiast kodami liczbowymi, trudniej jest się pomylić. Rysunek pokazuje proces tworzenia programu wynikowego main.ihx, oraz jego jego uruchomienie w systemie DSM-51. main.c dsm.a51 Kompilacja Asemblacja main.rel dsm.rel Konsolidacja main.ihx Rysunek 1: Proces tworzenia programu dla DSM-51 Aby zautomatyzować proces tworzenia programu, użyjemy programu make. Wystarczy napisać stosowny plik Makefile, którego zawartość dla omawianego procesu może być taka. Listing 1 main.ihx: main.rel dsm.rel sdcc $^ %.rel: %.c sdcc -c $< %.rel: %.a51 sdas8051 -l -o $< DSM-51 Uruchomienie 1 Właściwie prawie wszystkie programy, z wyjątkiem dsm51run, wywodzą się z systemu Unix/Linux. Grzegorz Cygan Krótki kurs programowania... 3
run: main.ihx dsm51run $< clean: rm -f *.asm *.lnk *.lst *.rel *.mem *.map *.sym *.rst *.ihx Pliki źródłowe main.c, dsm.a51 oraz Makefile muszą się znajdować w tym samym katalogu (Folderze). Oto zawartość pliku main.c Listing 2 extern void write_data (unsigned char); extern void write_instr (unsigned char); extern void clrscr (void); void main (void) clrscr (); write_data ('S'); write_instr (0xc4); write_data ('o'); write_instr (0x88); write_data ('s'); while (1); W języku C podprogramy te należy zadeklarować. Słowo extern oznacza, że funkcje te są zdefiniowane innym module (pliku), unsigned char jest typem zmiennej; unsigned char w nawiasie oznacza, że funkcja przyjmuje liczbę typu unsigned char, a unsigned char przed nazwą funkcji informuje, że funkcja zwraca liczbę typu unsigned char. Jeżeli typ zmiennej jest nieokreślony (void) to funkcja odpowiednio nie przyjmuje lub nie zwraca wartości. A oto zawartość pliku dsm.a51 Listing 3.area HOME (CODE) _clrscr:: mov a,#1 lcall 0x8106 ;Wywolanie podprogramu WRITE_INSTR systemu DSM-51 ret _write_data:: mov a,dpl lcall 0x8102 ret _write_instr:: mov a,dpl lcall 0x8106 ret W pliku dsm.a51 są zaimplementowane trzy podprogramy w języku Asemblera. Nazwy podprogramów to etykiety, czyli symboliczne nazwy komórek pamięci. 1. _clrscr czyszczenie wyświetlacza 2. _write_data wypisywanie znaków na wyświetlaczu 3. _write_instr wpisywanie instrukcji do wyświetlacza Aby otrzymać program dla systemu DSM-51 napisz w konsoli: make aby uruchomić na DSM-ie make run Grzegorz Cygan Krótki kurs programowania... 4
a w celu posprzątania make clean 2 Pierwsze programy w język Asemblera i w języku C Ćwiczenie 1 1. Stwórz pliki main.c, dsm.a51 oraz Makefile zgodnie z opisem w rozdziale 1. 2. Dołącz system DSM-51 do komputera. Uruchom program poleceniem make run. Opisz efekt działania programu. Zapoznaj się z komunikatami w plikach z rozszerzeniem lst. 3. Przenieś wszystkie deklaracje funkcji do pliku dsm.h. W pliku main.c dopisz na początku poniższą linię. Sprawdź czy program nadal działa. #include dsm.h 4. Bardzo często posługujemy się typem unsigned char, dlatego proponuję umieścić na początku pliku dsm.h następującą definicję i zastąpić wszystkie wystąpienia unsigned char nazwą U08. typedef unsigned char U08; 5. Zmień położenie litery s. Ustawienie kursora realizujemy za pomocą funkcji write_instr. Argument funkcji write_instr decyduje o pozycji kursora. Przykładowo liczba 88 (szesnastkowo) = 10001000 (dwójkowo) ma następujące znaczenie Tabela 1: Struktura rozkazu Set DD_RAM address Kod instrukcji Set DD_RAM address (zawsze 1) Numer wiersza 0 górny 1 dolny Pozycja w wierszu 1 0 0 0 1 0 0 0 6. Włącz migający kursor. Wszystkie instrukcje wyświetlacza zebrano w tabeli 2. Tabela 2: Rozkazy wyświetlacza LCD ze sterownikiem HD44780 Nazwa oryginalna Opis kod Clear display Kasuj wyświetlacz 0 0 0 0 0 0 0 1 Return home Ustaw kursor na początku 0 0 0 0 0 0 1 X Entry mode set Display ON/OFF control Cursor and display shift Function set Set CG_RAM address Ustawienie tryb wprowadzania danych Sterowanie włączaniem i wyłącz. wyświetlacza i kursora Przesunięcie kursora i (lub) wyświetlacza Ustawienie funkcji wyświetlacza Ustawienie adresu generatora znaków 0 0 0 0 0 1 I/D S 0 0 0 0 1 D C B 0 0 0 1 S/C R/L X X 0 0 1 DL N F X X 0 1 A5 A4 A3 A2 A1 A0 Set DD_RAM address Ustawienie adresu danych 1 A6 A5 A4 A3 A2 A1 A0 7. W pliku dsm.h zadeklaruj funkcję test_on. extern void test_on (void) i zaimplementuj tę funkcję w Asemblerze za pomocą instrukcji clr p1.7 8. Analogicznie, jak w poprzednim punkcie, stwórz funkcję test_off. Wskazówka: musisz znaleźć na liście Grzegorz Cygan Krótki kurs programowania... 5
rozkazów rozkaz, który działa odwrotnie w stosunku do rozkazu clr. 9. Stwórz funkcję, która wczytuje liczbę z zakresu od 0 do 255 (unsigned char) z klawiatury systemu DSM-51. W pamięci stałej ROM systemu pod adresem 0x811e znajduje się się podprogram GET_NUM, który umożliwia wprowadzenie liczby w systemie BCD, jednak proponuję przedstawiony poniżej program, który umożliwi wprowadzenie liczby w systemie dziesiętnym. Listing 4 _read_dec:: mov dpl,#0 sjmp rd loop: mov dph,a add a,#'0 lcall 0x8102 ;Wywolanie podprogramu WRITE_DATA s. DSM-51 mov a,dpl mov b,#10 mul ab add a,dph mov dpl,a rd: lcall 0x811c ;Wywolanie podprogramu WAIT_KEY sys. DSM-51 cjne a,#15,loop ret 10.I jeszcze przyda się podprogram, który drukuje bajt w systemie dziesiętnym. Listing 5 _print_dec:: mov a,dpl mov b,#100 div ab add a,#'0 lcall 0x8102 ;Wywolanie podprogramu WRITE_DATA sys.dsm-51 mov a,b mov b,#10 div ab add a,#'0 lcall 0x8102 mov a,b add a,#'0 lcall 0x8102 ret 3 Złożone programy w języku C 3.1 Operacje arytmetyczne W poniższej tabeli zebrano zaimplementowane do tej pory funkcje w języku Asemblera. Deklaracja funkcji w C (w pliku dsm.h) Odpowiednik w Asemblerze systemu DSM-51 i adres w pamięci DSM-51 Opis void write_data (U08); WRITE_DATA Zapis danych do LCD Grzegorz Cygan Krótki kurs programowania... 6
void write_instr (U08); void clrscr (void); void test_on (void); void print_dec (U08); U08 read_dec (void); (0x8102) WRITE_INSTR (0x8106) Zapis instrukcji do LCD Czyszczenie ekranu LCD Włączenie diody TEST Wyświetlenie bajtu w systemie dziesiętnym na LCD Odczyt bajtu w systemie dziesiętnym z klawiatury U08 wait_key (void); WAIT_KEY (0x811c) Czeka na klawisz i zwraca jego kod Za pomocą tych funkcji możemy wreszcie napisać prosty program obliczeniowy. Listing 6 #include "dsm.h" void main (void) U08 a, b; while (1) clrscr (); a = read_dec (); write_data ('+'); b = read_dec (); write_data ('='); print_dec (a+b); wait_key (); Ćwiczenie 2 1. Sprawdź działanie innych operatorów: arytmetycznych: -, *, /, %, oraz logicznych &,. 3.2 Operacje logiczne Iloczyn logiczny (koniunkcja, AND) Suma logiczna (alternatywa, OR) Operacje logiczne wykonywane są na każdym bicie, niezależnie od pozostałych bitów w danym bajcie. Mikrosterowniki mają dodatkowo rozkazy wykonujące operacje logiczne na pojedynczych bitach. Iloczyn logiczny może być zastosowany do wyzerowania wybranych bitów, a suma do ustawienia, np.: Liczba a 1 0 1 0 0 1 0 1 Maska AND 0 0 0 0 0 1 1 1 --------------- Wynik a 0 0 0 0 0 1 0 1 Jeżeli potraktujemy pierwszą liczbę jako liczbę przetwarzaną drugą liczbę jako tzw. maskę, to jak widać bity, dla których w masce są zera (cztery najmłodsze) zostają wyzerowane. Asembler Listing 7.GLOBL _READ_DEC.GLOBL _PRINT_DEC.AREA CSEG (CODE) Grzegorz Cygan Krótki kurs programowania... 7
POWTARZAJ: LCALL ANL LCALL SJMP _READ_DEC DPL,#0x07 _PRINT_DEC POWTARZAJ Język C Listing 8 extern unsigned char read_dec (void); extern void print_dec (unsigned char); void main (void) unsigned char a; while (1) a = read_dec (); a &= 0x07; print_dec (a); Operacje logiczne mogą niekiedy zastąpić operacje arytmetyczne, powyższy program oblicza resztę z dzielenia przez 8. Ćwiczenie 3 1. Napisz odpowiedni program w języku Asemblera. 2. Sprawdź czy tak samo działa program z normalną operacją arytmetyczną. W programie w języku C trzeba zmienić odpowiednią linię na taką: a %= 8; W języku Asemblera program obliczający resztę z dzielenia przez 8 może wyglądać tak. Listing 9.GLOBL _READ_DEC.GLOBL _PRINT_DEC.AREA CSEG (CODE) POWTARZAJ: LCALL MOV MOV DIV MOV LCALL SJMP _READ_DEC A,DPL B,#8 AB DPL,B _PRINT_DEC POWTARZAJ Ćwiczenie 4 1. Jak widać dzielenie jest dużo bardziej kosztowne. Sprawdź czy kompilator wykorzysta rozkaz dzielenia DIV czy bardziej optymalny rozkaz iloczynu logicznego ANL. 3.3 Programowania standardowego wyjścia Korzystanie z wyświetlacza LCD znacznie ułatwia biblioteka stdio. Poniższy przykład pokazuje użycie najbardziej uniwersalnej funkcji z tej biblioteki printf. Funkcja printf korzysta z funkcji putchar, która jest odpowiedzialna za drukowanie znaków. Wywoływanie poszczególnych funkcji pokazano na poniższym schemacie: Grzegorz Cygan Krótki kurs programowania... 8
printf putchar _write_data 0x8102 Każde wywołanie funkcji związane jest z odłożeniem na stos adresu powrotu. Zaimplementuj funkcję putchar w Asemblerze, a ciąg wywołań będzie krótszy. printf _putchar 0x8102 Listing 10 #include <stdio.h> #include "dsm.h" void putchar (char c) write_data (c); void main (void) char imie[] = "Janek"; U08 wiek; char koncowka; wiek = read_dec(); koncowka = (wiek < 5)? 'a' : ' '; clrscr (); printf ("%s ma %d lat%c.", imie, wiek, koncowka); W programie z p. 10 do zmiennej koncowka zostanie przypisana wartość 'a' (kod ASCII litery a) lub spacja. Można zrezygnować ze zmiennej koncowka i całą konstrukcję warunkową umieścić bezpośrednio jako argument funkcji printf. Ćwiczenie 5 1. Zmodyfikuj program kalkulatora z punktu 9, zamieniając wszystkie funkcję korzystające z wyświetlacza funkcją printf. Porównaj zajętość pamięci FLASH (w pliku.mem). 3.4 Specyfikatory formatowania funkcji printf Siła funkcji prontf 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; Ćwiczenie 6 1. W programie z listingu 7 zamień specyfikator %d na %u, a następnie na %x. Zastanów się dlaczego takie a nie inne wartość są wyświetlane. 3.5 Funkcja putchar dla sygnalizatora dźwiękowego Standardowym wyjściem wcale nie musi być wyświetlacz. Często wyjściem jest łącze szeregowe. W ćwiczeniu poznasz zastosowanie brzęczyka jako elementu wysyłającego komunikaty morse'a. Grzegorz Cygan Krótki kurs programowania... 9
Ćwiczenie 7 1. Napisz program źródłowy w języku C według listingu 8 i zapisz program w pliku hellomors.c. Druga część programu znajduje się w pliku mors.a51. Listing 11 #include <stdio.h> void main () puts ("CENTRUM EDUKACJI ZAWODOWEJ"); 3.6 Zwłoki czasowe W programach często potrzebne są opóźnienia (zwłoki) czasowe, przykładowo aby uzyskać miganie lampki. Algorytm działania takiego programu jest następujący: 1. Zapal lampkę (diodę), 2. Poczekaj, 3. Zgaś, 4. Poczekaj, 5. Wróć do punktu 1. W pamięci DSM-51 znajdują się programy opóźnień czasowych, ale nie będziemy z nich korzystać, ponieważ docelowo chcemy się uniezależnić od gotowych programów. Do implementacji opóźnień zastosujemy pętle for. Oto przykład prostego programu. Listing 12 #include "dsm.h" void main (void) unsigned int i; while (1) test_on(); //1. Zapal diodę test for (i = 0; i < 0xffff; i++); //2. Poczekaj test_off(); //3. Zgaś for (i = 0; i < 0xffff; i++); //4. Poczekaj //5. Wróć do punktu 1 Ćwiczenie 8 1. Napisz funkcję opóźnienia czasowego o nazwie delay z wykorzystaniem pętli for. Wywołaj podprogram delay w miejscach oznaczonych komentarzem Poczekaj (zamiast pętli for). Spróbuj zmniejszyć dwukrotnie opóźnienie. 2. Zmniejsz dwukrotnie opóźnienie. 3. Zwiększ dwukrotnie opóźnienie. 3.7 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. 1.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, 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 3. Dla porównania podano także rozmiary typów w języku Java, Grzegorz Cygan Krótki kurs programowania... 10
w którym są one zdefiniowane. Tabela 3: 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. 1.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. 1.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 wymieniono poniżej: Tabela 4: Przestrzenie adresowe mikrosterowników standardu MCS-51 Przestrzeń Opis rozmiar sfr rejestry specjalnego przeznaczenia Obszar nieciągły (omówienie wybranych rejestrów w dodatku A) 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 zewnętrzna pamięć danych (dostęp za pomocą indeksowania rejestrami Ri) maks. 256 B* w jednym banku Grzegorz Cygan Krótki kurs programowania... 11
xdata zewnętrzna pamięć danych (dostęp za pomocą indeksowania rejestrem DPTR) maks. 64 KiB = 65536 B * code pamięć programu maks. 64 KiB = 65536 B * * - poza mikrosterownikiem, rozmiar zależy od zastosowanych układó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 poszczegó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 9 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 5: 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 Listing 13 #include <stdio.h> #include "dsm.h" char napis[] = "Ala ma kota, a Ola ma psa."; Grzegorz Cygan Krótki kurs programowania... 12
void main (void) clrscr(); puts (napis); 4 Programowanie układów wewnętrznych mikrosterownika Mikrosterowniki zawierają 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. 4.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 14 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 15 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) CSDS = 0x01; CSDB = 0x7f; Grzegorz Cygan Krótki kurs programowania... 13
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++); 4.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 10 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. Rysunek 2: Okno programu Putty Listing 16 #include <stdio.h> #include <8051.h> void delay (void) unsigned int i; for (i = 0; i < 0xffff; i++); void putchar (char c) while (!TI); TI = 0; Grzegorz Cygan Krótki kurs programowania... 14
SBUF = c; void main () unsigned char i; TMOD = 0x20; SCON = 0x52; PCON = 0x80; TH1 = 253; TR1 = 1; for (i = 0; ; i++) delay(); 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 4.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 17 #include <8051.h> void delay (void) unsigned int i; for (i = 0; i < 0xffff; i++); //Program glowny void main (void) TMOD = 0x01; //Konfiguracja ukladow czasowo-licznikowych Grzegorz Cygan Krótki kurs programowania... 15
TR0 = 1; ET0 = 1; EA = 1; while (1) P1_7 =!P1_7; delay (); //Uruchomienie ukladu czasowo-licznikowego T0 //Wlaczenie zezwolenia na przerwania od T0 //Wlaczenie zezwolenia ogolnego //Program obslugi przerwania od ukladu czasowo-licznikowego T0 void t0_isr () interrupt 1 using 1 P1_5 =!P1_5; 4.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 3: Złącze izolowane galwanicznie (rysunek z dokumenntacji DSM-51) Grzegorz Cygan Krótki kurs programowania... 16
5 Programowanie zewnętrznych układów peryferyjnych Jeden z układów zewnętrznych już programowaliśmy, to jest wyświetlacz LCD. To oczywiste ponieważ trudno było by nauczyć się podstaw programowania bez korzystania z wyświetlacza. W tym rozdziale zajmiemy się układami, które występują w systemie DSM-51 i nie są częścią mikrosterownika. Nie będą omawiane wszystkie układy. Przy okazji poznamy także nowe możliwości funkcje niestandardowe funkcję wyjścia printf_fast_f, która została stworzona specjalnie dla mikrosterowników. 5.1 Niskopoziomowe 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. 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. 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 6: Rejestry wyświetlacza LCD Ćwiczenie 11 Adres Nazwa rejestru Kierunek Operacji 80H LCDWC wpis rozkazów 81H LCDWD wpis danych 82H LCDRC odczyt stanu 83H LCDRD odczyt danych 1. Uruchom program przykładowy. 2. Zmodyfikuj go tak, aby wyświetlić więcej znaków. Listing 18 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 7: Instrukcje sterujące sterownika HD44780 (HITACHI) Nazwa oryginalna Opis kod Clear display Kasuj wyświetlacz 0 0 0 0 0 0 0 1 Return home Ustaw kursor na początku 0 0 0 0 0 0 1 X Grzegorz Cygan Krótki kurs programowania... 17
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 19 #include "dsm_io.h" void main () while (LCDRC & 0x80); LCDWC = 1; while (LCDRC & 0x80); LCDWD = 'D'; while (LCDRC & 0x80); LCDWD = '-'; //Pokaz znak - 5.2 Przetwornik A/C //Czekaj na gotowosc wyswietlacza //Clear display //Pokaz litere 'D' Przetwornik A/C przetwarza napięcie na liczbę. W systemie DSM-51 do wejścia przetwornika dołączony jest multiplekser analogowy, umożliwiający podanie na wejście przetwornika napięcia z jednego z ośmiu wejść. Wejścia multipleksera zlokalizowane są w złączu "ANALOG I/O". Multiplekser programujemy za pomocą portu CSMX, a przetwornik - CSAD. Obydwa porty zadeklarowane są w pliku dsm51.h. Ćwiczenie 12 1. Wypróbuj programy z listingów 17 i 18. 2. Zmień specyfikator formatowania (%d) w funkcji printf, aby uzyskać wynik w systemie szesnastkowym (tak jak w programie z listingu 16. 3. Zmień sposób prezentacji wyniku tak, aby na wyświetlaczu pojawiała się wartość napięcia wyrażona w woltach. Zakres przetwarzania wynosi od 0V do 5V. Do wyświetlenia wyniku użyj funkcji printf_fast_f ("Napiecie %01.2f V", (float)csad * 5.0 / 255.0); Listing 20 #include <8051.h> #include "dsm.h" #include "dsm_io.h" void delay (void) unsigned int i; for (i = 0; i < 0xffff; i++); void main (void) Grzegorz Cygan Krótki kurs programowania... 18
while (1) CSMX = 0; //Wejscie 1 przetwornika a/c CSAD = 1; //Zainicjuj pomiar clrscr (); print_dec (CSAD); Listing 21 #include <8051.h> #include <stdio.h> #include "dsm.h" #include "dsm_io.h" void delay (void) unsigned int i; for (i = 0; i < 0xffff; i++); void main (void) while (1) CSMX = 0; CSAD = 1; delay (); clrscr (); printf ("%03d", CSAD); 5.3 Silnik krokowy Silnik dołączony jest do portu A układu 8255 (CS55A). Program główny konfiguruje ten port jako wyjście poprzez wpisanie liczby 0x80 (10000000B) do rejestru konfigurującego układu 8255 (CS55D). Listing 22 #include <8051.h> #include "dsm_io.h" typedef unsigned char U08; //Program glowny void main (void) TMOD = 0x01; TR0 = 1; ET0 = 1; EA = 1; CS55D = 0x80; while (1); //Program obslugi przerwania od ukladu czasowo-licznikowego T0 void t0_isr (void) interrupt 1 using 1 code static U08 sekwencja[]=1, 3, 2, 6, 4, 12, 8, 9; Grzegorz Cygan Krótki kurs programowania... 19
static U08 kat; TH0 = 248; CS55B &= 0xf0; CS55B = sekwencja[kat & 0x7]; kat++; 5.4 Wyświetlacz siedmiosegmentowy W tym programie putchar zapisuje w buforze, z którego napis do wyświetlenia pobiera funkcja obsługi przerwania. Listing 23 #include <stdio.h> #include <8051.h> #include <ctype.h> #include "dsm_io.h" void main (void) TMOD &= 0xf0; TMOD = 0x02; TR0 = 1; ET0 = 1; EA = 1; puts ("1234"); volatile unsigned char _seg7buf[6]; void timer0_isr (void) interrupt 1 using 1 code static unsigned char kody [] = 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f; static unsigned char nrwysw; if (++nrwysw > 5) nrwysw = 0; P1_6 = 1; CSDS = 1 << nrwysw; CSDB = kody[_seg7buf[nrwysw]]; P1_6 = 0; void putchar (char c) if (isdigit(c)) _seg7buf[5] = _seg7buf[4]; _seg7buf[4] = _seg7buf[3]; _seg7buf[3] = _seg7buf[2]; _seg7buf[2] = _seg7buf[1]; _seg7buf[1] = _seg7buf[0]; _seg7buf[0] = c - '0'; Grzegorz Cygan Krótki kurs programowania... 20