POLITECHNIKA WARSZAWSKA WYDZIAŁ ELEKTRYCZNY INSTYTUT ELEKTROENERGETYKI

Podobne dokumenty
METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

1 Podstawy c++ w pigułce.

1 Podstawy c++ w pigułce.

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Zmienne, stałe i operatory

Programowanie strukturalne i obiektowe

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Podstawy programowania skrót z wykładów:

Mikrokontroler ATmega32. Język symboliczny

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

2 Przygotował: mgr inż. Maciej Lasota

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Systemy zapisu liczb.

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Podstawy programowania w języku C

Systemy liczbowe. 1. Przedstawić w postaci sumy wag poszczególnych cyfr liczbę rzeczywistą R = (10).

Podstawy Programowania C++

ARCHITEKTURA SYSTEMÓW KOMPUTEROWYCH

Arytmetyka komputera. Na podstawie podręcznika Urządzenia techniki komputerowej Tomasza Marciniuka. Opracował: Kamil Kowalski klasa III TI

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Informatyka, Ćwiczenie Uruchomienie Microsoft Visual C++ Politechnika Rzeszowska, Wojciech Szydełko. I. ZałoŜenie nowego projektu

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

1. Wprowadzanie danych z klawiatury funkcja scanf

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

Część 4 życie programu

Przedmiot: Urządzenia techniki komputerowej Nauczyciel: Mirosław Ruciński

Język ludzki kod maszynowy

Podstawy Programowania Podstawowa składnia języka C++

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Języki i metodyka programowania. Wprowadzenie do języka C

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Program w C. wer. 10 z drobnymi modyfikacjami! Wojciech Myszka :28:

Systemy liczbowe używane w technice komputerowej

Języki C i C++ Wykład: 2. Wstęp Instrukcje sterujące. dr Artur Bartoszewski - Języki C i C++, sem. 1I- WYKŁAD

Program w C. wer. 12 z drobnymi modyfikacjami! Wojciech Myszka :59:

Pracownia Komputerowa wykład VI

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

Podstawowe operacje arytmetyczne i logiczne dla liczb binarnych

Podstawy programowania. Wykład: 5. Instrukcje sterujące c.d. Stałe, Typy zmiennych c.d. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Spis treści WSTĘP CZĘŚĆ I. PASCAL WPROWADZENIE DO PROGRAMOWANIA STRUKTURALNEGO. Rozdział 1. Wybór i instalacja kompilatora języka Pascal

Programowanie strukturalne i obiektowe : podręcznik do nauki zawodu technik informatyk / Adam Majczak. Gliwice, cop

Metody numeryczne Technika obliczeniowa i symulacyjna Sem. 2, EiT, 2014/2015

Kompilator języka C na procesor 8051 RC51 implementacja

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

Podstawy Informatyki

1.1. Pozycyjne systemy liczbowe

Metoda znak-moduł (ZM)

Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Ćwiczenie 1. Podstawy. Wprowadzenie do programowania w języku C. Katedra Metrologii AGH

Język C zajęcia nr 11. Funkcje

Język C, tablice i funkcje (laboratorium, EE1-DI)

ARYTMETYKA BINARNA. Dziesiątkowy system pozycyjny nie jest jedynym sposobem kodowania liczb z jakim mamy na co dzień do czynienia.

Samodzielnie wykonaj następujące operacje: 13 / 2 = 30 / 5 = 73 / 15 = 15 / 23 = 13 % 2 = 30 % 5 = 73 % 15 = 15 % 23 =

Pętla for. Wynik działania programu:

Wprowadzenie do architektury komputerów systemy liczbowe, operacje arytmetyczne i logiczne

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 5 Liczby w komputerze

Podstawy Informatyki. Metalurgia, I rok. Wykład 3 Liczby w komputerze

12. Wprowadzenie Sygnały techniki cyfrowej Systemy liczbowe. Matematyka: Elektronika:

Zapis liczb binarnych ze znakiem

Instrukcje warunkowe i skoku. Spotkanie 2. Wyrażenia i operatory logiczne. Instrukcje warunkowe: if else, switch.

SYSTEMY LICZBOWE. Zapis w systemie dziesiętnym

Programowanie strukturalne. Opis ogólny programu w Turbo Pascalu

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Podstawy programowania. Wykład: 4. Instrukcje sterujące, operatory. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Podstawy programowania. Wykład Co jeszcze... Przypomnienia, uzupełnienia. Krzysztof Banaś Podstawy programowania 1

Powtórka algorytmów. Wprowadzenie do języka Java.

Języki i paradygmaty programowania

Arytmetyka liczb binarnych

Operatory AND, OR, NOT, XOR Opracował: Andrzej Nowak Bibliografia:

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

PODSTAWY INFORMATYKI 1 PRACOWNIA NR 6

Podstawy programowania w języku C i C++

Laboratorium Wstawianie skryptu na stroną: 2. Komentarze: 3. Deklaracja zmiennych

Znaki w tym systemie odpowiadają następującym liczbom: I=1, V=5, X=10, L=50, C=100, D=500, M=1000

Kod uzupełnień do dwóch jest najczęściej stosowanym systemem zapisu liczb ujemnych wśród systemów binarnych.

Urządzenia Techniki. Klasa I TI. System dwójkowy (binarny) -> BIN. Przykład zamiany liczby dziesiętnej na binarną (DEC -> BIN):

SYSTEMY LICZBOWE 275,538 =

4 Standardy reprezentacji znaków. 5 Przechowywanie danych w pamięci. 6 Literatura

Dla człowieka naturalnym sposobem liczenia jest korzystanie z systemu dziesiętnego, dla komputera natomiast korzystanie z zapisu dwójkowego

Wstęp do Programowania, laboratorium 02

Podstawy Informatyki. Kompilacja. Historia. Metalurgia, I rok. Kompilatory C++ Pierwszy program. Dyrektywy preprocesora. Darmowe:

Ogólny schemat prostego formularza: A może lepiej zamiast przycisku opartego o input tak:

RODZAJE INFORMACJI. Informacje analogowe. Informacje cyfrowe. U(t) U(t) Umax. Umax. R=(0,Umax) nieskończony zbiór możliwych wartości. Umax.

Lab 9 Podstawy Programowania

Programowanie strukturalne język C - wprowadzenie

Język C - podstawowe informacje

Wiadomości wstępne Środowisko programistyczne Najważniejsze różnice C/C++ vs Java

Podstawy Informatyki. Metalurgia, I rok. Wykład 6 Krótki kurs C++

JAVA. Platforma JSE: Środowiska programistyczne dla języka Java. Wstęp do programowania w języku obiektowym. Opracował: Andrzej Nowak

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Podstawy Informatyki dla Nauczyciela

Podstawy programowania. Wykład Pętle. Tablice. Krzysztof Banaś Podstawy programowania 1

Języki i metody programowania. Omówienie języków C, C++ i Java

Operatory. Operatory bitowe i uzupełnienie informacji o pozostałych operatorach. Programowanie Proceduralne 1

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 2 WSTĘP DO INFORMATYKI

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

INFORMATYKA Studia Niestacjonarne Elektrotechnika

Techniki multimedialne

Transkrypt:

POLITECHNIKA WARSZAWSKA WYDZIAŁ ELEKTRYCZNY INSTYTUT ELEKTROENERGETYKI LABORATORIUM PODSTAW TECHNIKI MIKROPROCESOROWEJ IMPLEMENTACJA JĘZYKA C W SYSTEMIE MIKROPROCESOROWYM DSM-51 Instrukcje laboratoryjne dla studiów dziennych magisterskich Ćwiczenia: M21, M22, M23, M24, M25 0 Warszawa, 2004

1. Wstęp W instrukcjach pokazano przykłady wykorzystania języka C do programowania mikrokontrolerów z rodziny 8051. Przedstawione przykłady dotyczą podstaw wykorzystania, osobom zainteresowanym dokładniejszym zapoznaniem się z językiem C oraz budową mikrokontrolerów sugeruje się sięgnięcie do literatury [1,2,3,4,5]. Do wykonania ćwiczeń z niniejszego rozdziału konieczne jest korzystanie z systemu dydaktycznego DSM-51. 1.1. Zwięzły opis języka C W technikach programowania od lat widoczny jest trend upraszczania języków programowania, które ponadto stają się coraz łatwiejsze do opanowania, oferując wiele gotowych i zrozumiałych procedur. Ułatwienie programowania powoduje jednak, że tworzony w trakcie kompilacji kod wynikowy stawia dużo większe wymagania w stosunku do urządzenia na którym ma być uruchomiony, niż kod powstający z programu napisanego w języku Asembler. Asembler jest językiem bardzo dobrym, uczy programowania praktycznie od podstaw. Jest przy tym bardzo szybki i umożliwia wieloraką optymalizację kodu programu. Jednak każdy, kto kiedykolwiek napisał program w tym języku wie, że bardzo trudno szuka się w nim błędów, a rzeczy nawet pozornie proste, trzeba wykonać używając wielu rozkazów. Jednym słowem programy pisane w asemblerze pochłaniają mnóstwo czasu, który nie zawsze jest do dyspozycji. Większości wad asemblera pozbawiony jest język C: język o zwartej i czytelnej składni oraz kodzie źródłowym, bardzo oszczędny kod wynikowy oraz łatwa lokalizacja błędów umożliwiają zaoszczędzenie dużej ilości czasu podczas uruchamiania programu. Istnieje wiele odmian języka C. Niniejszy rozdział został napisany z myślą o wykorzystaniu tej wersji języka C która została zaimplementowana w pakiecie programów opracowanych przez firmę Keil Gmbh dla procesora 8051. W pakiecie tym program napisany w języku C jest tłumaczony do postaci języka asemblera i następnie do postaci kodu maszynowego. Ogólnie program źródłowy napisany w języku C składa się z wielu elementów w tym z: identyfikatorów dyrektyw preprocesora komentarzy deklaracji instrukcji 1.1.1. Identyfikatory Identyfikatory to słowa, którymi oznaczamy obiekty programu źródłowego, takie jak nazwy zmiennych, funkcje oraz definicje struktur itp. W języku C identyfikator składa się z liter, cyfr oraz znaku podkreślenia: _ (underline), przy czym musi zaczynać się od litery lub znaku podkreślenia. Niektóre kompilatory dopuszczają stosowanie znaku dolara (dollar sign) w nazwach identyfikatorów, ale nigdy na pierwszym miejscu. Jednak nie jest to powszechnie stosowane przez programistów (kod programu nie jest kompatybilny z innymi kompilatorami: non-portable TT), toteż w przykładach nie będzie używany. Przy konstruowaniu identyfikatorów musimy pamiętać o dwóch podstawowych zasadach: Litery małe i wielkie uważa się za różne z tego powodu identyfikatory INDEX, index, InDeX nie są zatem tożsame. Możemy ich użyć w jednym programie do nazwania trzech różnych zmiennych (nie jest to jednak zalecane ponieważ nazwy powinny różnić się od siebie znaczeniem adekwatnym do sytuacji). Standardowo za znaczące uważa się pierwsze 32 znaki nazwy identyfikatora. Większość kompilatorów dopuszcza stosowanie dłuższych nazw, lecz znaki po 32 mogą być 1

2 zignorowane przez kompilator. Warto dodać, że nigdy nie ma potrzeby używania tak długich identyfikatorów 1.1.2. Dyrektywy preprocesora Dyrektywy preprocesora zajmują pełne wiersze i zaczynają się od znaku # (hash). Najczęściej spotykaną dyrektywą jest linia postaci: #include <nazwa_pliku>, gdzie nazwa_pliku jest nazwą pliku np.: #include <reg51.h> Wymieniony plik zostaje przed kompilacją w całości włączony do programu i najczęściej zawiera deklaracje funkcji. 1.1.3. Komentarze Komentarz jest opisem tekstu źródłowego programu ignorowanym przez kompilator. Istnieją dwa rodzaje identyfikatorów: /* Komentarz */ lub // Komentarz W pierwszym przypadku tekst komentarza może być jedno- lub wielowierszowym napisem nie zawierającym dwu znaku */ (gwiazdka, ukośnik) np.: /*************************************************************************** Program Przykład, zerowanie linii mikrokontrolera ***************************************************************************/ Natomiast po dwóch ukośnikach komentarz jest napisem mieszczącym się w jednym wierszu np.: void write_control(unsigned char a_1){ //zapis rozkazu Ta ostatnia metoda jest bardzo powszechna i wygodna (większość kompilatorów ją akceptuje), mimo że nie jest częścią standardu ANSI C. 1.1.4. Przykładowy program w języku C Poniżej przedstawiono jeden z najprostszych przykładowych programów w języku C. Po skompilowaniu i uruchomieniu ustawia on bit o adresie 97h na wartość 0, a następnie wykonuje niekończącą się pętlę pustych instrukcji. 1. *************************************************************************** 2. Program Przykład, zerowanie linii mikrokontrolera 3. ***************************************************************************/ 4. sbit led = 0x97; /*definicja adresu portu P1_7 */ 5. /*****************Funkcja główna ****************************************/ 6. int main (void) 7. { 8. led=0; 9. while (1){ 10. 11. //Koniec programu W pierwszych trzech liniach umieszczono komentarz zaczynający się dwoma znakami /* i kończący się dwoma znakami */. W linii 4 znajduje się definicja zmiennej o nazwie led. Przy pomocy kwalifikatora pamięci sbit identyfikatorowi zmiennej zostaje przyporządkowany adres miejsca w pamięci w postaci 1 bitu o adresie 97h. W linii 6 znajduje się bardzo ważne słowo main. Jest to główna funkcja programu, która musi wystąpić w programie dokładnie jeden raz. Od tej funkcji rozpoczyna się wykonywanie programu. Oczywiście nie musi ona

wystąpić zaraz na początku kodu, lecz należy pamiętać, że jest to punkt początkowy dla całego programu. Deklaracja funkcji zamieszczonej w przykładzie nazywana jest definicją funkcji. Wewnątrz funkcji głównej przy pomocy wskaźnika o nazwie led zostaje przypisany stan niski do bitu o adresie 97h. W linii 9 wywołana zostaje pętla while(1). Pętla while sprawdza na początku warunek i jeśli jest on prawdziwy =1 pętla się wykonuje. W tym przypadku warunek zawsze jest równy 1, dlatego pętla będzie się wykonywała w nieskończoność. 1.1.5. Funkcje Funkcje to podprogramy mające wykonać pewne określone zadanie. Funkcja zaczyna się i kończy nawiasem klamrowym. Znajdujący się wewnątrz zestaw poleceń nazywany jest ciałem funkcji. Znajdują się tam deklaracje zmiennych oraz instrukcje języka C. Definicja funkcji wygląda następująco: <typ rezultatu> <nazwa funkcji>( ) { <deklaracja typów, definicja zmiennych, deklaracje> <wyrażenia> Deklaracja funkcji ma następującą formę: <typ rezultatu> <nazwa funkcji>(<definicja parametrów>); Wywołanie funkcji jest następujące: <nazwa funkcji>(<argumenty>) W przykładowym programie nagłówek może mieć postać: int main(void), lecz można w nim pominąć zarówno słowo int, jak i słowo void. Jeśli przed nazwą funkcji nie ma określenia jej typu, funkcja jest domyślnie typu int, tzn. jej rezultat jest liczbą całkowitą (integer). Funkcja może wymagać pewnych parametrów w celu właściwego wykonania. Listę tych parametrów umieszcza się w nawiasach okrągłych (parentheses) za nazwą funkcji. Puste nawiasy oznaczają, że funkcja jest bezparametrowa, co można podkreślić słowem kluczowym void. Z tego powodu np. cztery poniższe nagłówki są sobie równoważne: int main(void), int main(), main(void), main(). Ciało funkcji Ciało funkcji jest ujęte w klamry {braces. Całość kodu wewnątrz klamr odnosi się do funkcji (w programie przykładowym delay). W zamieszczonym poniżej przykładzie przedstawiono fragment programu zawierający deklarację oraz wywołanie przykładowej funkcji nie będącej funkcja main. Każda linia polecenia w ciele funkcji jest zakończona średnikiem. Wewnątrz funkcji deklarowane są zmienne pomocnicze oraz wywoływane są dwie pętle for(). void delay (unsigned char del) { unsigned int i,j; for (j=0;j<del;j++) for (i=0;i<3000;i++); //deklaracja funkcji innej niż main delay(5); //wywołanie funkcji 1.1.6. Systemy liczbowe W procesie programowani mikrokontrolerów należy sprawnie przeliczać wartości pomiędzy systemami liczbowymi takimi jak: 3

System dwójkowy System heksadecymalny System dziesiętny System binarny (dwójkowy) Jest to system pozycyjny o postawie 2, używający cyfr 0 i 1. Wszelkie dane w układach cyfrowych reprezentowane są w tym systemie. Podobnie jak w systemie dziesiętnym, cyfra zapisana po prawej stronie ma najmniejszą wartość, a kolejne mają wartości będące iloczynem danej cyfry i kolejnej potęgi podstawy systemu (dwójki). Liczby zapisane w tym systemie oznacza się dodając za liczbą cyfrę 2 w indeksie dolnym lub literę b. Poszczególne cyfry każdej liczby w systemie binarnym, nazywane są bitami. Liczby ujemne można zapisać na kilka sposobów: zapis znak-moduł - w tym zapisie najstarszy bit (zapisywany po lewej stronie) decyduje o znaku liczby (0 - dodatnia, 1 - ujemna), natomiast pozostałe bity decydują bezpośrednio w wartości liczby, np. -4 zapisujemy jako 1100. Zapis w uzupełnieniu do jedności (tzw. U1). Najstarszy bit pełni podobną rolę jak w przypadku zapisu znak-moduł. Aby otrzymać liczbę ujemną w tym zapisie należy zanegować (zamienić na przeciwne) wszystkie bity liczby dodatniej, np. 4 = 0100b, -4 = 1011b. Ponieważ występuje tu problem zera dodatniego i ujemnego (zero można przedstawić w postaci: +0 = 0000b lub -0 = 1111b) system ten jest rzadko stosowany. Zapis w uzupełnieniu do dwóch (tzw. U2). Liczby ujemne w tym systemie otrzymujemy poprzez zanegowanie odpowiedniej liczby dodatniej i dodanie 1. Ponieważ nie występują tu dwie reprezentacje zera i działania na liczbach ujemnych są znacznie łatwiejsze niż w systemie znak-moduł, U2 jest powszechnie stosowanym w urządzeniach cyfrowych systemem zapisu liczb ujemnych. System heksadecymalny (kod HEX) Jest to system pozycyjny o postawie 16, używający cyfr 0..9 i liter A..F. Podobnie jak w systemie dziesiętnym, cyfra zapisana po prawej stronie ma najmniejszą wartość, a kolejne mają wartości będące iloczynem danej cyfry i kolejnej potęgi podstawy systemu (16). Liczby zapisane w systemie heksadecymalnym oznacza się dodając za liczbą cyfrę 16 w indeksie dolnym lub literę h. Przekształcenie liczby w systemie binarnym na liczbę heksadecymalną Przekształcając liczbę binarną na liczbę heksadecymalną należy ja najpierw podzielić na 4 cyfrowe grupy, zaczynając od prawej strony. Gdy w efekcie takiego podziału ostatnia grupa nie ma 4 cyfr należy uzupełnić ją zerami. Następnie dla każdej takiej grupy przyporządkowuje się wartości heksadecymalne według tabl. 7.1. Tablica 7.1. Tablica zamiany kodów binarnych na heksadecymalne. Grupa cyfra heksadecymalna grupa cyfra heksadecymalna 0000 0 1000 8 0001 1 1001 9 0010 2 1010 A 0011 3 1011 B 0100 4 1100 C 0101 5 1101 D 0110 6 1110 E 0111 7 1111 F Następnie tak uzyskane kody grup utworzą liczbę w zapisie heksadecymalnym. 4

Przykład: Liczba binarna: 1101101101b. Na wstępie dokonywany jest podział na cztero cyfrowe grupy, zaczynając od prawej strony, co w efekcie daje: 11 0110 1101. Ponieważ pierwsza grupa nie jest pełna, uzupełniamy ją do 4 cyfr zerami dostając: 0011 0110 1101. Następnie każdej grupie zostaje przyporządkowany kod heksadecymalny (cyfra szesnastkowa) wg tabl.7.1. 0011 0110 1101 3 6 D W związku z powyższym po przekształceniach liczba przybierze postać 36Dh. Przekształcenie liczby w systemie szesnastkowym na liczbę binarną Przekształcając liczbę heksadecymalną na liczbę binarna należy każdej z cyfr liczby szesnastkowej przyporządkować cztery cyfry liczby binarnej wg tabl.7.1. Przykład: Liczba szesnastkowa: 4C26Fh. Każdej z cyfr liczby szesnastkowej należy przyporządkować cztery cyfry liczby binarnej wg tabeli 8.2.: 4 C 2 6 F 0100 1100 0010 0110 1111 Następnie należy łączyć otrzymane grupy cyfr i pominąć zera na początku liczby nie mające wpływu na jej wartość. Przekształcana liczba przybierze więc postać: 1001100001001101111b. Przekształcenie liczby w systemie binarnym na liczbę dziesiętną Przekształcając liczbę binarna na liczbę dziesiętną należy posłużyć się pojęciem wagi pozycji. Każda cyfra zależnie od pozycji posiada określoną wagę wg poniższego zestawienia: 6 5 4 3 2 1... 2, 2, 2, 2, 2, 2 Jedynka na danej pozycji oznacza, że wagę dla danej pozycji należy uwzględnić w sumie będącej liczbą wynikową, 0 oznacza, że nie należy jej uwzględniać. Przykład: Liczba binarna: 10110101b. Ponieważ występuje 8 cyfr, kolejne (od lewej) będą miały 7 6 5 4 3 2 1 0 następujące wagi: 2 =128, 2 =64, 2 =32, 2 =16, 2 =8, 2 =4, 2 =2, 2 =1 (potęgi liczby 2). Z tego względu wartość dziesiętna przedstawionej liczby binarnej wyniesie: 1*128 + 0*64 + 1*32 + 1*16 + 0*8 + 1*4 + 0*2 + 1*1 = 128+32+16+4+1 = 181 1.1.7. Zmienne, stałe, tablice i ich deklaracje oraz reprezentacja Nazwa zmienna określany jest pewien obszar w pamięci mikrokontrolera, w którym mogą być przechowywane dane. Z punktu widzenia osoby piszącej program, zmienna posiada następujące cechy podstawowe: nazwa, identyfikator, typ, wartość. Nazwa zmiennej pozwala wskazać w programie, fragment pamięci, w którym przechowywana jest zmienna. Łatwiej jest posługiwać się nazwą niż adresem liczbowym (łatwiej zrozumieć napis imię niż *0x12342. Kompilator dokonując tłumaczenia napisanego programu zamienia wszystkie nazwy zmiennych na odpowiadające im adresy w pamięci. Wszystkie nazwy zmiennych przed użyciem muszą być zadeklarowane. Wartość zmiennej jest tym, co przechowujemy w obszarze pamięci określanym przez nazwę. Wartość może się zmieniać w dowolnym momencie w czasie wykonania programu. Wartością może być liczba całkowita, zmiennoprzecinkowa (ułamek dziesiętny), adres w pamięci komputera (tzw. wskaźnik), tekst itp. W momencie deklaracji wartość zmiennej lokalnej (zadeklarowanej wewnątrz funkcji) jest nieokreślona tzn. jej wartość jest przypadkowa, natomiast zmienne globalne (deklarowane poza funkcjami) są inicjowane na zero. 5

Typ zmiennej określa jaką wartość można wpisać do obszaru wskazywanego przez nazwę (czy będzie to liczba całkowita, zmienno-przecinkowa itp., czy też inny rodzaj danej). W zależności od rodzaju wartości (typu zmiennej), inny będzie rozmiar pamięci potrzebny do jej zapamiętania. Poniżej w tabl. 7.2 pokazano typy zmiennych występujące w języku C wraz z liczbą bitów jaką zajmują (Rozmiar) oraz zakresem zmienności liczby jak może zostać w nie wpisana (Zakres). Tablica 7.2. Tablica zmiennych języka C 6 Typ słowo kluczowe Rozmiar bit Bajt Zakres char 8 1-127 to 127 unsigned char 8 1 0 to 255 int 16 2-32767 to 32767 Unsigned int 16 2 0 to 65535 Signed short 16 2-32767 to 32767 Unsigned short 16 2 0 to 65535 long int 32 4-2147483647 to 2147483647 unsigned long int 32 4 0 to 4294967295 *sbit 1 --- 0 lub 1 *sfr 8 1 0 do 255 float 32 4 6 cyfrowa precyzja double 64 8 10 cyfrowa precyzja long double 80 10 16 cyfrowa precyzja * Typy charakterystyczne dla mikrokontrolera 8051 i kompilatora Keil. Kompilator na podstawie typu określa jaką ilość pamięci należy przydzielić zmiennej i jakie operacje są na niej dopuszczalne, np.: int x; oznacza deklarację zmiennej całkowitej (typu int) o nazwie x. Słowo int jest w języku C słowem zarezerwowanym (reserved word) słowem kluczowym. Słowa kluczowe mają ściśle określone funkcje programowe i nie mogą być używane w innych celach; np. nie można ich użyć jako identyfikatora zmiennej. Rodzaj operacji dopuszczalny dla danej zmiennej oraz przyjęty sposób reprezentacji danej zależą od przyjętego dla niej systemu liczbowego. System liczbowy to sposób reprezentacji wartości liczbowej. Podstawowymi cechami każdego systemu są: pozycyjność (lub nie) - w systemach pozycyjnych wartość cyfry zależy od jej pozycji w liczbie ilość cyfr podstawa systemu - liczba, której kolejne potęgi wyznaczają wartości (wagi) kolejnych cyfr w liczbie Powszechnie używany system dziesiętny jest systemem pozycyjnym o podstawie 10, wykorzystującym 10 cyfr. Jednakże w układach cyfrowych znacznie wygodniejszy jest system binarny oparty na liczbie 2. System ten ma jednak zasadniczą wadę - do zapisu dużych liczb potrzebna jest bardzo duża ilość cyfr, a ponadto takie długie ciągi zer i jedynek są bardzo trudne do zapamiętania. Stąd człowiek używa systemu o podstawie 2, głównie w postaci zapisu heksadecymalnego (szesnastkowego), rzadko oktalnego (ósemkowego). System szesnastkowy ma tę cechę, że zapis dużych liczb nie stanowi problemu poza tym zamiana danej na system binarny i odwrotnie jest bardzo prosta. 1.1.8. Typy zmiennoprzecinkowe Specjalnego opisu wymagają niezwykle ważne zmienne umożliwiające wykorzystywanie obliczeń zmiennoprzecinkowych. Procesor mikrokontrolera wykorzystuje liczby całkowite z tego względu pojawia się duży problem w przypadku konieczności użycia liczb

rzeczywistych tj. liczb o postaci: 243.75 lub większych np. 12300055446684316545.65468166546. W takich przypadkach pomocny jest zmiennopozycyjny zapis liczb. Liczby zapisane w ten sposób charakteryzują się tym, że niezależnie od wielkości liczby jej zapis zajmuje taką samą, stałą liczbę komórek pamięci. Obiekty typu float (ang. floating point number) to liczby zmiennoprzecinkowe (z pływającym przecinkiem), np.: 5.21 czy -3.0. Zajmują one 4 bajty pamięci, co oznacza, że tego rodzaju w liczba może mieć co najmniej 6 cyfr po przecinku. Tworzenie liczb zmiennoprzecinkowych: Rozpatrując strukturę każdej liczby należy stwierdzić, że można ją przedstawić w postaci: m*10 c, gdzie: m mantysa, c cecha (całkowita)) 1 < c < 10. np. liczba 12 341 220 000 000 000 000 = 1.234122*10 19. Analogicznie dowolna liczba może być sprowadzona do postaci m*2 c, gdzie: 1 < m < 2, c jest całkowite), co umożliwia już prowadzenie działań na liczbach binarnych. W tabl.7.3 przedstawiono sposób zamiany liczby 243.75d = 11110011.11b na liczbę zmiennoprzecinkową typu float (4 bajtową). Tablica 7.3. Przedstawienie liczby 243.75d w formacie zmiennoprzecinkowym float 7 2 6 2 5 2 4 2 3 2 2 2 1 2 0 2. 1 2 2 2 128 64 32 16 8 4 2 1. 0,5 0,25 1 1 1 1 0 0 1 1. 1 1 Aby otrzymać mantysę tej liczby, należy ją odpowiednio przesuwać w lewo lub w prawo (pomnożyć lub podzielić przez dwa) do momentu, w którym po lewej stronie przecinka otrzymana zostanie jedna jedynka. W przedstawionym przykładzie mantysa wynosi więc: m = 1.111001111b. Liczba przesunięć stanowi wtedy cechę liczby. Cecha posiada znak zależny od kierunku przesuwania: + w przypadku przesuwania w lewo, - w przypadku przesuwania w prawo. W analizowanym przykładzie c wynosi +7, ponieważ w celu uzyskania mantysy m z zakresu 1<m<2 należało dokonać siedem przesunięć liczby w lewo. Tak więc liczba 243.75d = m*2 c, gdzie: c = 111b, zaś m = 1.11100111b. Zapis liczby rzeczywistej typu float zajmuje 4 bajty (32 bity) i podzielony jest na trzy części. Pierwsza z nich jest to jeden bit znaku. Przyjmuje się, że przy ujemnych liczbach bit ten jest równy 1, w przeciwnym przypadku jest on równy zero. Kolejną częścią jest osiem bitów przeznaczonych na cechę liczby (cecha zajmuje 7 najmłodszych bitów z pierwszego bajtu i jeden najstarszy bit drugiego bajtu). Trzecią część stanowią pozostałe bity przeznaczone na mantysę (jest ich 23). W sumie daje to 1+8+23 = 32 bity. Położenie opisanych części liczby float pokazuje rys.7.1. Bajt 4 Bajt 3 Bajt 2 Bajt 1 Znak 1bit Cecha 8 bitów Mantysa 23 bity Rys. 7.1. Budowa zmiennej typu Float 7

Po wyznaczeniu wtedy cechy i mantysę oraz znając znak liczby, możemy ją zakodować. W trakcie kodowania przyjmuje się, że: Jeżeli Gdy liczba jest dodatnia, wtedy bit znaku będzie ustawiony na 0. Zapisując cechę, przyjęto, że zwiększa się ją o 127 w celu uwzględnienia znaku (nie stosuje się kodu U2). Ponieważ każda mantysa zaczyna się od 1, zapisywanie jej byłoby marnotrawstwem, dlatego też przyjęto, że mantysa zostaje zapisana bez tej jedynki. Tak więc dla naszej liczby 243.75d: w miejsce bitu znaku należy wstawić 0; w miejsce cechy należy wstawić wartość: 7+127=10000110b. w miejsce mantysy należy wstawić 1110 0111 1000 0000 0000 000b = 23 bity; Ostatecznie zapis liczby zmiennopozycyjnej 243.75d przedstawia się następująco: 01000011 01110011 11000000 00000000 43h 73h C0h 0 Dla liczba 243.75d różni się od analizowanej jedynie znakiem, czyli jednym bitem, w związku z tym jej zapis wygląda następująco: 11000011 01110011 11000000 00000000 C3h 73h C0h 0 1.1.9. Stałe Poza zmiennymi w języku C istnieją również stałe, każda stała posiada typ, określony przez jej wartość i sposób zapisu. Stałe mogą być kilku typów: numeryczne, znakowe, tekstowe. Stałe numeryczne Stałe numeryczne dzielą się na całkowite i zmiennoprzecinkowe. Stała całkowita składa się z dowolnej liczby cyfr. Na początku może znajdować się znak '-'. W języku C na początku liczby może znajdować się również znak + oznaczający liczbę dodatnią. Stałe całkowite, jeśli mieszczą się w zakresie zmiennych typu int, są traktowane jako int. W przypadku, gdy stała nie mieści się w zakresie typu int, a mieści się w zakresie typu long lub na końcu znajduje się litera 'l' - jest traktowana jako long. Jeśli stała nie mieści się w zakresie typu long - jest traktowana jako stała zmiennoprzecinkowa typu double. Stałe bez znaku definiuje się dopisując na końcu literę 'u'. Poniżej pokazano kilka przykładów stałych numerycznych: 8 1234-198 12lu 123u -1956l Stała zaczynająca się od litery 'O' oznacza stałą ósemkową, np.: O12 0777 Stała zaczynająca się od znaków 0x oznacza stałą heksadecymalną (szesnastkową). W przypadku stałej szesnastkowej liczby 10-15 są zastępowane literami 'a'-'f', np.: 0xffff 0x12fe Stała zmiennoprzecinkowa składa się z opcjonalnej części całkowitej, znaku '.', części ułamkowej oraz opcjonalnej definicji wykładnika. Część ułamkowa jest stałą całkowitą nie zawierająca znaków '+' i '-'. Część określająca wykładnik jest poprzedzona znakiem 'e', po którym występuje liczba całkowita, np.: 1.23.23 0.23 1. 1.0 1.2e10 = 1.2 * 10 10.23e-15 = 0.23 * 10-15 Stałe znakowe Stałe znakowe w języku C składają się z pojedynczych znaków zamkniętych w apostrofy; np.: a, 0. Stałe znakowe są w rzeczywistości stałymi całkowitymi. Ich wartość jest równa kodowi znaku na maszynie, na której kompilowany jest program. Jeśli program jest

kompilowany na maszynie pracującej w kodzie ASCII, to wartość stałej 0 jest równa 48, a wartość stałej A - 65. Użycie stałych znakowych zamiast kodów powoduje, że program jest bardziej przenośny. Niektóre kody nie mają drukowalnych odpowiedników, dlatego wprowadzono konstrukcję zaczynającą się od znaku '\'. Znak znajdujący się po znaku '\' jest traktowany w sposób specjalny. W tabl. 7.4 przedstawiono stałe znakowe występujące w języku C. Tablica 7.4 Stałe znakowe języka C Zapis Symbol Opis \n NL(LF) nowa linia (new line) \t HT tabulacja pozioma (horizontal tab) \v VT tabulacja pionowa (vertical tab) \b BS skasowanie znaku na lewo (backspace) \r CR powrót karetki (carriage return) \f FF wysunięcie strony (form feed) \a BEL sygnał dźwiękowy (alert) \\ \ Backslash \?? znak zapytania \' ' Apostrof \" " Cudzysłów \0 NUL znak o kodzie 0 \ooo ooo znak w kodzie ósemkowym \xhh hh znak w kodzie szesnastkowym Poniżej w tabl. 7.5 przedstawiono tabelę zawierającą kody ASCII. Tabela 7.5. Tabela kodów ASCII 0 1 2 3 4 5 6 7 8 9 30! # $ % & 40 ( ) * +, -. / 0 1 50 2 3 4 5 6 7 8 9 : ; 60 < = >? @ A B C D E 70 F G H I J K L M N O 80 P Q R S T U V W X Y 90 Z [ \ ] ^ _ ` a b c 100 d e f g h I j k l M 110 n o p q r s t u v w 120 x y z { ~ Stałe tekstowe Stała tekstowa jest ciągiem znaków zamkniętych w cudzysłowy, np.: "To jest stała tekstowa". Każda stała tekstowa kończy się znakiem o kodzie 0 (zawiera zawsze o jeden znak więcej). Stała tekstowa jest tablicą znaków zawierającą odpowiednią liczbę elementów. Np. "asdf" jest typu char[5]. Zapis ze znakiem \ może być również używany wewnątrz stałych tekstowych. Stała tekstowa może zawierać znak: \0, ale większość programów i funkcji bibliotecznych nie będzie jej poprawnie obsługiwać. 1.1.10. Tablice Tablica jest zbiorem elementów tego samego typu. Każdy element tablicy ma numer. Numer pierwszego elementu w tablicy jest zawsze równy zero. W języku C nie można deklarować tablic wielowymiarowych, jest jednak możliwa deklaracja tablic zawierających tablice, co odpowiada tablicom wielowymiarowym w innych językach. Deklaracja tablicy ma postać: <typ_elementu><nazwa_zmiennej>[rozmiar]; Przykład: 9

int arr[10]; Każdy element deklarowanej tablicy będzie typu typ_elementu, pierwszy element będzie miał numer 0, drugi - 1,..., ostatni - rozmiar-1. Tablicę można inicjować podając w deklaracji po jej nazwie i znaku równości listę wartości oddzielonych przecinkami i zamkniętych w nawiasach klamrowych. Jeśli tablica jest inicjowana w deklaracji, to nie jest konieczne podawanie jej rozmiaru. Możliwość ta jest dostępna we wszystkich kompilatorach ANSI C. Przykład: int a1[5] = {1,5,3,4,2; int a2[] = {1,5,6,3,4,5,6; Tablic używa się w programie podając nazwę zmiennej tablicowej oraz numer elementu, którego operacja ma dotyczyć ujęty w nawiasy kwadratowe. Jako numer elementu może służyć stała całkowita, zmienna typu całkowitego lub dowolne wyrażenie, którego wynikiem jest liczba całkowita. Nawiasy kwadratowe zawierające numer elementu tablicy nazywane są operatorem indeksowania, np.: int a[10]; int i; i = 5; a[5] = 10; a[a[5] - 5] = 4; Możliwe jest zadeklarowanie tablicy tablic (odpowiadającej tablicy dwu- lub więcej wymiarowej), np.: int a[10][15]; Powyższa instrukcja deklaruje 10-cio elementową tablicę a, której polami są 15-sto elementowe tablice zmiennych typu int. Odwołanie do elementów tablicy następuje w sposób naturalny - najpierw podaje się numer tablicy, potem numer elementu wewnątrz tej tablicy: a[4][5] = 10; 1.1.11. Wskaźniki Wskaźnik to zmienna, która zawiera adres innej zmiennej. Użycie wskaźników prowadzi do bardziej efektywnego kodu niż otrzymywany innymi metodami. Jedną z niepożądanych cech wskaźników jest to, że w przypadku nieostrożnego ich stosowania przyczyniają cię do tworzenia niezrozumiałych programów. Łatwo jest utworzyć wskaźnik, który będzie wskazywał na bliżej nieokreślone miejsca. Kompilator języka C dedykowany dla mikrokontrolera 8051 umożliwia użycie 2 typów wskaźników. Pierwszy to wskaźnik do obszaru pamięci deklarowanego przy pomocy kwalifikatorów (takich jak CODE czy DATA) opisanych w rozdziale 7.2.1. dotyczącym kompilatora języka C dla mikrokontrolera 8051. Drugi umożliwia wskazanie dowolnej danej w dowolnym obszarze adresowania mikrokontrolera. Ważne jest to, że jeśli wskaźnik przypisany jest do obiektu zakwalifikowanego do określonego obszaru pamięci, to również zostaje przypisany do tego właśnie obszaru pamięci, w którym umieszczony jest wskazywany obiekt. Drugi wskaźnik nosi nazwę generic. Wskaźnik typu generic zawiera dodatkowo 1 bajt przeznaczony na kod wskazywanego obszaru pamięci. W związku z tym wskaźnik kwalifikowany do pewnego obszaru pamięci zajmuje 2 bajty a wskaźnik generic zajmuje 3 bajty. Poniżej przedstawiono przykłady deklaracji różnych wskaźników. 10 data char[10] Tablica = {0,1,2,3,4,5,6,7,8,9 char generic *Tablica; //wskaźnik do obszaru DATA, wskazuje elementy //zmiennej Tablica char generic *p1; / //wskaźnik do dowolnego obszaru danych pdata char *klawiatura; //wskaźnik do 8 bit. adresu urządzenia zewnętrznego xdata char *pamiec_danych; //wskaźnik do zewnętrznej pamięci

W powyższym przykładzie użyto operatora adresowania pośredniego * zastosowany do wskaźnika podaje zawartość wskazywanego obiektu y=*wskaźnik. W języku C istnieje jeszcze inna metoda przypisania wskazań do określonej zmiennej. Użycie jednoargumentowego operatora & zwraca adres zmiennej (Uwaga: można go stosować tylko do obiektów zajmujących pamięć zmiennych oraz tablic). Char x=1, y=2, z=[5]; Char *wskaźnik; //deklaracja wskaźnika wskaźnik=&x; //zmienna wskaźnik wskazuje na x Y=*wskaźnik; //teraz zmienna y ma wartość 1 wwskaźnik=0; //teraz zmienna x ma wartość 0 wskaźnik=&z[0]; //zmienna wskaźnik wskazuje na 1 element tablicy z 1.1.12. Operatory Kolejnym elementem języka C są operatory, np.: dodawanie, odejmowanie, dzielenia itd. Kompilator języka C może przebudować wyrażenia. W szczególności możliwa jest zmiana łączności operatorów dodawania, odejmowania, mnożenia i dzielenia. W przypadkach wątpliwych lepiej jest odpowiednie wyrażenia ująć w nawiasy. W poniższej tabl. 7.5 przez wartość należy rozumieć wyrażenie, które mogłoby wystąpić po lewej stronie operatora przypisania (najczęściej zmienna lub wyłuskanie wskaźnika). L-wartość musi posiadać adres. Nawiasy nie mają wpływu na to czy wyrażenie jest l-wartością. Uwaga: kompilator oblicza wyrażenia stałe na etapie kompilacji i w ich miejsce wstawia obliczoną wartość. Tablica 7.5. Operatory arytmetyczne Operator Operacja Przykład Opis Operand Typ rezultatu * X*Y Mnożenie arytmetyczny arytmetyczny / X/Y Dzielenie arytmetyczny arytmetyczny % X%Y Reszta z dzielenia liczba całkowita liczba całkowita + X+Y Dodawanie Arytmetyczny lub ptr liczba całkowita lub ptr - X-Y Odejmowanie Arytmetyczny lub ptr liczba całkowita lub ptr ++ X++ Inkrementacja Arytmetyczny lub ptr liczba całkowita lub ptr -- X-- Dekrementacja Arytmetyczny lub ptr liczba całkowita lub ptr Tablica 7.6. Operatory porównania Operator Operacja Przykład Opis Operand Typ rezultatu < If(X<Y) Mniejszy niż arytmetyczny lub ptr liczba całkowita > If(X>Y) Większy niż arytmetyczny lub ptr liczba całkowita <= If(X<=Y) Mniejszy lub równy arytmetyczny lub ptr liczba całkowita >= If(X>=Y) Większy lub równy arytmetyczny lub ptr liczba całkowita == If(X==Y) Równy arytmetyczny lub ptr liczba całkowita!= If(X!=Y) Różny od arytmetyczny lub ptr liczba całkowita Tablica 7.7. Operatory bitowe Operator Operacja Przykład Opis Operand Typ rezultatu << X=X<<2 Przesunięcie w lewo 2 razy liczba całkowita taki sam jak operand >> X=X>>4 Przesunięcie w prawo 4 razy liczba całkowita taki sam jak operand & P1=P1&0xF0 Bitowe and liczba całkowita liczba całkowita ^ P1=P1^0xAA Bitowe xor liczba całkowita liczba całkowita P1=P1 0xF0 Bitowe or liczba całkowita liczba całkowita ~ ~0x77 Dopełnienie jedynkowe liczba całkowita liczba całkowita Uwaga: 0 jest wstawiane do nowych bitów (po przesunięciu) chyba, że wystąpiła operacja przesunięcia w prawo liczby ze znakiem (SIGNED) - wtedy na nowe miejsce kopiowany jest znak. 11

Tablica 7.8. Operatory logiczne 12 Operator Operacja Przykład Opis Operand Typ rezultatu && If(x&&0x80=0) Iloczyn logiczny and arytmetyczny lub ptr liczba całkowita If(x 0x11=0x11) Suma logiczna or arytmetyczny lub ptr liczba całkowita Tablica 7.9. Wywoływanie funkcji, operatory selekcji Operator Składnia Opis () funcname() Wywołanie funkcji [] arrayptr[] Wywołanie tablicy. struct.member Bezpośrednia selekcja członka -> structptr-> Pośrednia selekcja członka 1.1.13. Operator przypisania Operatorem przypisania w języku C jest znak = lub w przypadkach złożonych operatorów znak = oraz znak działania. Wszystkie operatory przypisania zwracają wartość, która została przypisana do danej zmiennej. Ponadto operatory przypisania są prawostronnie łączne tzn. wykonują się one od prawej do lewej. Możliwe są więc zapisy: int a, b, c; a = b = c = 5; Poza prostym operatorem przypisania, w języku C można wykorzystać złożone operatory przypisania przedstawione w tabl. 7.10. Tablica 7.10. Złożone operatory przypisania Operator Operacja Zapis Obraz Opis Operatory += X+=10 X=X+10 Dodawanie arytmetyczny -= X-=10 X=X-10 Odejmowanie arytmetyczny *= X*=5 X=X*5 Mnożenie Arytmetyczny /= X/=2 X=X/2 dzielenie by Arytmetyczny %= X%=3 X=X%3 modulo liczba całkowita >>= X>>=2 X=X>>2 przesunięcie bitów w prawo liczba całkowita <<= X<<=4 X=X<<4 przesunięcie bitów w lewo liczba całkowita &= X&=0xF0 X=X&0xF0 bitowe AND liczba całkowita ^= X^= 0xF0 X=X^0xf0 bitowe XOR liczba całkowita = X = 0xAA X=X 0xAA bitowe lub liczba całkowita 1.1.14. Operacja rzutowania Kolejną ważną operacją jest operacja rzutowania czyli zmiany typu danych. Operator rzutowania służy do zmiany typu danych na inny, np.: (typ_danych)x; gdzie typ_danych oznacza ten typ danych, na który ma zostać zamieniona wartość zmiennej x. Np. deklaracja: y=(float)a; W przypadku gdy a jest liczba całkowitą (typu int) o wartości 5 spowoduje jej zmianę na liczbę zmiennoprzecinkową 5.0 (typu float). 1.1.15. Instrukcje warunkowe Instrukcja warunkowa umożliwia wykonanie pewnej instrukcji w zależności od wartości wyrażenia. Wszystkie wartości różne od 0 są w języku C traktowane jako prawda, równe 0 jako fałsz. Wyrażenia logiczne są liczone tylko do momentu, w którym można określić jego wartość. if (wyrażenie) instrukcja

lub if (wyrażenie) instrukcja1 [else instrukcja2] W obu rozkazach instrukcja może być instrukcją złożoną. Wyrażenie musi być typu skalarnego (wartość wyrażenia równa fałsz 0 lub prawda 1 ). W pierwszej kolejności wyznaczana jest wartość wyrażenia. W pierwszym przypadku instrukcja wykonuje się, jeśli wartość wyrażenia jest prawdą. Jeżeli wartość wyrażenia jest fałszem instrukcja jest pomijana. W drugim wypadku instrukcja1 wykonuje się, jeśli wartość wyrażenia jest prawdą. Jeżeli wartość wyrażenia jest fałszem wykonuje się instrukcja2. W tym przypadku wykonuje się instrukcja1 lub instrukcja 2, nigdy obie. Przykład: if (a > 5) printf("a jest większe od 5\n"); else printf("a jest mniejsze lub równe 5\n"); 1.1.16. Instrukcja wyboru - switch Instrukcja switch służy do wybierania jednego przypadku z wielu. Wartość wyrażenia instrukcji switch jest porównywana ze stałymi związanymi z etykietami case. Jeżeli wynikiem tego wyrażenia jest stała związana z którąś z etykiet case, wykonywanie programu jest kontynuowane począwszy od tej właśnie etykiety. Jeżeli nie jest dostępna żadna pasująca etykieta, wykonywanie programu przechodzi do etykiety domyślnej - default (o ile jest ona dostępna). Chcąc wyjść z instrukcji switch należy użyć rozkazu break - napotkanie kolejnego case lub default nie powoduje wyjścia z instrukcji switch. Składnia instrukcji wyboru jest następująca: switch (wyrażenie){ etykieta1: wyrażenia-1 etykieta2: wyrażenia-2 etykietan: wyrażenia-n default-case: wyrażenia-defaultowe Na przykład: enum dni {pon, wt, sr, czw, pt, sob, ndz; { switch(dzien){ case pon: case wt: printf("nie lubię początku tygodnia \n"); break; case sob: case ndz: printf("lubię weekend!\n"); default: printf("środek tygodnia jest taki sobie, ale weekend jest świetny \n"); break; 1.1.17. Instrukcje pętli programowych Pętle programowe służą do powtarzania tych samych instrukcji wielokrotnie, aż do chwili gdy zostaną spełnione określone warunki. Język C umożliwia wykorzystanie dwu rodzajów pętli: while, for. 13

Instrukcja pętli while Instrukcja while jest pętlą sterowaną z góry co oznacza, że najpierw wyznaczana jest wartość warunku pętli (czyli jej wyrażenia sterującego) Jeżeli jest on prawdziwy czyli równy 1, to następnie wykonywane jest ciało funkcji, po czym wyrażenie sterujące obliczane jest ponownie. Rozkaz umieszczony w pętli while (może to być instrukcja złożona!) jest powtarzany aż do momentu, gdy wartość wyrażenia będzie fałszem czyli równa 0. W przypadku, gdy wartość wyrażenia od razu będzie równa 0, instrukcja nie wykona się ani razu. Jeśli wyrażenie nie przyjmie nigdy wartości 0, instrukcja będzie się wykonywać nieskończoną ilość razy. Jeżeli wartość warunku jest fałszywa czyli równa 0, to wykonywanie programu jest kontynuowane od pierwszej instrukcji następującej po ciele pętli. Składnia instrukcji while jest następująca: while(wyrażenie)instrukcja Instrukcja pętli do...while Pętla do jest podobna do pętli while, z tą różnicą, że najpierw wykonywane jest instrukcja, a następnie wyznaczana jest wartość wyrażenia sterującego (instrukcja sterowana z dołu). Oznacza to, że instrukcja wykona się przynajmniej jeden raz. Ciało pętli (instrukcja lub instrukcje) jest powtarzane aż do chwili, gdy wyrażenie sterujące będzie fałszywe. Składnia instrukcji do...while jest następująca: do instrukcja while(wyrażenie); Przykład: { do{ printf( Zakończyć program? \n"); while (getchar()!= 't'); Podstawową różnicą w stosunku do instrukcji while jest to, że ciało pętli do...while jest wykonywane przynajmniej jednokrotnie. Ciało pętli while może nie zostać w ogóle wykonane, ponieważ jego wyrażenie sterujące już na samym początki może być fałszywe. Instrukcja pętli for Instrukcja for jest pętlą sterowaną z dołu co oznacza, że najpierw wykonywana jest instrukcja, a następnie wyznaczana jest wartość wyrażenia sterującego. Składnia instrukcji jest następująca: for (wyrażenie1;wyrażenie2;wyrażenie3) instrukcja Wszystkie pokazane wyrażenia są opcjonalne. Wyrażenie1 jest obliczane przed wejściem do pętli (UWAGA: tylko raz). Wyrażenie1 może być również deklaracją, a zasięg tak zadeklarowanej zmiennej ograniczony jest tylko do pętli for. Następnie obliczane jest wyrażenie2 (wyrażenie sterujące które musi być typu skalarnego) i sprawdzany jest warunek czy jest ono różne od 0 (prawdziwe). Jeśli tak, wykonywana jest instrukcja i obliczane jest wyrażenie3. Następnie sprawdzana jest wartość wyrażenia2. Pętla jest wykonywana aż do momentu, gdy wartość wyrażenia2 będzie równa 0 (wyrażenie będzie fałszywe). Wyrażenie3 jest zawsze obliczane po wykonaniu instrukcji. Jeśli wszystkie trzy wyrażenia w pętli for są puste (pętla postaci: for(;;) instrukcja), to jest to bezwarunkowa pętla nieskończona. Instrukcja w pętli for może nie wykonać się ani razu, jeśli wyrażenie2 będzie od razu równe 0. Pętla for może być pętlą nieskończoną, jeśli wyrażenie2 nigdy nie przyjmie wartości 0. Wyrażenie pierwsze będzie zawsze obliczone (dokładnie jeden raz). Pętla for umożliwia 14

zgrupowanie instrukcji inicjującej pętlę, warunku kontynuacji i instrukcji wykonanej po zakończeniu pętli w jednym miejscu w programie. Poniżej pokazano przykład instrukcji for: { int i; char txt[10]; for (i = 0; i < 10; i ++) txt[i] = 'A'; 1.1.18. Skoki bezwarunkowe Instrukcje skoku służą do bezwarunkowego przejścia w określone miejsce programu. W języku C występują poniższe rodzaje skoków bezwarunkowych: goto, break, continue, return. Instrukcja skoku do etykiety goto Instrukcja skoku goto powoduje bezwarunkowe przekazanie sterowania do instrukcji opatrzonej etykietą. Etykieta musi znajdować się w tej samej funkcji, z której została wykonana instrukcja skoku. Składnia instrukcji: goto etykieta; Etykietę definiuje się w dowolnym miejscu programu wewnątrz funkcji, w następujący sposób: etykieta; Etykiet nie trzeba deklarować. Poniżej pokazano przykład wykorzystania instrukcji skoku do etykiety: int f(){ int l; scanf("%d", &l"); if (l < 0) goto err; printf("%d! = %d\n", l, silnia(l)); return 1; err: printf("nie mozna obliczyc silni liczby mniejszej od 0\n"); return 0; Instrukcja break Instrukcja break mogąca wystąpić tylko wewnątrz pętli lub instrukcji switch powoduje wyjście z najbardziej zagnieżdżonej pętli lub instrukcji switch. Składnia instrukcji jest następująca: break; Poniżej pokazano dwa przykłady wykorzystania instrukcji break: Przykład 1: int i;... switch (i){ case 1: case 2: printf("1 lub 2\n"); break; default : break;... 15

Przykład 2: int i, l; for (i = 0; i < 10; i ++){ scanf("%d", &l); if (l < 0) break; printf("%d! = %d\n", l, silnia(l));... Instrukcja continue Instrukcja continue może wystąpić tylko wewnątrz instrukcji pętli i powoduje przejście do instrukcji położonej za ostatnią instrukcją w pętli (czyli do instrukcji sprawdzającej warunek kontynuacji pętli). Jej składnia jest następująca: continue; Poniżej pokazano przykład wykorzystania instrukcji continue: int i, l; for (i = 0; i < 10; i ++) { scanf("%d", &l); if (l < 0) continue; printf("%d! = %d\n", l, silnia(l));... Instrukcja return Instrukcja return powoduje wyjście z aktualnie wykonywanej funkcji. Instrukcja return może wystąpić w dowolnym miejscu w ciele funkcji. Opisywany rozkaz może być wywołany z podaniem wyrażenia lub bez. Jeśli wyrażenie zostanie podane, to jego wartość zostanie obliczona przed wyjściem z funkcji i zwrócona na zewnątrz. Składnia instrukcji return może być następująca: return; lub return wyrażenie; Poniżej przedstawiono przykład wykorzystania instrukcji return: long silnia(int n){ long wynik; int i; if (n <= 0) return 1; wynik = 1; for (i = 1; i <= n; i ++) wynik *= i; return wynik; 1.1.19. Instrukcja pusta Instrukcja pusta jest stosowana tam, gdzie składnia języka wymaga wystąpienia instrukcji, a osoba pisząca program nie chce wprowadzać w tym miejscu jakichkolwiek poleceń. Instrukcja pusta ma następująca formę: ; 16

Takim miejscem są często instrukcje pętli, np.: { for(;;;); 1.1.20. Preprocesor i dyrektywy preprocesora Przed kompilacją tekst programu poddawany jest preprocessingowi. W wyniku działania preprocesora otrzymuje się zmodyfikowany tekst programu, który stanowi źródło dla kompilatora. Polecenia preprocesora są nazywane dyrektywami. Wiersze programu rozpoczynające się znakiem '#' oznaczają dyrektywy preprocesora. Składnia dyrektyw preprocesora jest niezależna od składni reszty języka. Wiersze zawierające dyrektywy preprocesora mogą wystąpić w dowolnym miejscu w programie. Oprócz wspomnianych wcześniej dyrektyw, preprocesor dokonuje pewnych standardowych konwersji tekstu programu, np.: Wszystkie wystąpienia znaków '\' (lewy ukośnik, backslash) i bezpośrednio po nim nowej linii są usuwane - tzn. następny wiersz jest łączony z tym, w którym znajdował się znak '\'. Tekst programu jest dzielony na symbole leksykalne i spacje, oraz usuwane są wszystkie komentarze (komentarz jest zamieniany na pojedynczą spację). Sekwencje specjalne w stałych znakowych i tekstowych są zastępowane ich równoważnikami (np. znaki "\n" zamieniane są na znak o kodzie ASCII 13). Sąsiednie stałe tekstowe łączone są w jedną stałą tekstową (tzn. napisy "Ala " "ma kota" po preprocessingu zostaną połączone w jeden napis: "Ala ma kota"). Makrodefinicja - #define Do tworzenia makrodefinicji służy dyrektywa #define o następującej składni: #define identyfikator ciąg_symboli lub #define identyfikator(identyfikator,..., identyfikator) ciąg_symboli Instrukcja w pierwszej postaci zleca preprocesorowi zastępowanie dalszych wystąpień identyfikatora wskazanym ciągiem symboli. Spacje otaczające ciąg symboli są usuwane, np. w przypadku: #define BOK 8... char txt[bok][bok]; deklaracja tablicy txt zostanie zamieniona w następujący sposób: char txt[8][8]; Druga postać dyrektywy #define służy do definicji tzw. makra funkcyjnego. W tej dyrektywie pomiędzy identyfikatorem i nawiasem otwierającym ( nie może być spacji. Dalsze wystąpienie pierwszego identyfikatora, po którym następuje nawias oraz ciągi symboli oddzielone przecinkami i zakończone nawiasem zamykającym są makrowywołaniami. Makrowywołanie zastępuje się ciągiem symboli podanym w makrodefinicji. Spacje otaczające ciąg symboli są usuwane. W podanym ciągu każde wystąpienie identyfikatora z listy parametrów formalnych makrodefinicji (umieszczonego w nawiasach) zastępuje się symbolami reprezentującymi odpowiadający mu argument aktualny makrowywołania. Liczba parametrów w makrodefinicji musi być taka sama jak liczba argumentów w makrowywołaniu. 17

Dla przykładu: #define min(x, y) (((x) < (y))? (x) : (y))... a = min(i, j); ostatnie przypisanie zostanie zastąpione przez: a = (((i) < (j))? (i) : (j); Przy definiowaniu makrodefinicji funkcyjnych należy wszystkie argumenty ujmować w nawiasy ponieważ makrodefinicje są rozwijane tekstowo przed kompilacją, co może spowodować nieoczekiwaną zmianę znaczenia pewnych zapisów, np. przypadku: #define sqr(x) x*x... res = sqr(a+4); przypisanie zostanie rozwinięte do postaci: res = a+4*a+4; pomimo tego, że oczekujemy: res = (a+4)*(a+4); Przyjęło się, że identyfikatory w makrodefinicjach są pisane dużymi literami. Po rozwinięciu makrowywołania preprocesor przegląda powstały w ten sposób tekst w poszukiwaniu kolejnych identyfikatorów do rozwinięcia. Nie są jednak możliwe rozwinięcia rekursywne. Nie jest również możliwe potraktowanie rozwiniętego tekstu jako nowej dyrektywy preprocesora. Dyrektywa #undef Dyrektywa #undef służy do unieważniania poprzedniej definicji makra. Jej składnia jest następująca: #undef identyfikator Dyrektywa #include Dyrektywa #include służy do włączania pliku o podanej nazwie do tekstu źródłowego poddawanego kompilacji. Dyrektywa ta ma jedną z dwóch postaci: #include <nazwa_pliku> lub #include nazwa_pliku W pierwszej postaci plik o podanej nazwie jest poszukiwany w katalogach zależnych od kompilatora. W drugiej postaci plik jest najpierw poszukiwany w katalogu aktualnym i być może innych zdefiniowanych katalogach. Jeśli tam nie zostanie znaleziony to poszukiwanie jest kontynuowane tak samo jak w przypadku pierwszej postaci, czyli w katalogach systemowych kompilatora. 1.1.21. Zastrzeżone słowa kluczowe Niektóre identyfikatory zostały zastrzeżone przez twórców języka C. Służą one do zapisu konstrukcji jakie są dopuszczalne w tym języku. Dlatego nazywa się je słowami kluczowymi. Słowa kluczowe można podzielić na grupy: instrukcje typy danych i ich specyfikatory specyfikatory rodzaju pamięci 18

Do instrukcji zaliczane są następujące słowa kluczowe: break - przerwij, if case - wariant, return continue - kontynuuj, switch do - wykonuj, typedef else - jeśli nie to, while for - dla, default goto - idź do, sizeof - jeśli, - zwróć, - przełącz, - definiuj typ, - dopóki, - domyślna, - wielkość Poniżej zostały wymienione typy danych i ich specyfikatory: char - znak, signed - ze znakiem, double - podwójna, struct - struktura, enum - wyliczeniowa, union - unia, float - zmiennoprzecinkowa, unsigned - bez znaku, int - całkowita, void - nieokreślony, long - długa, const - stała, short - krótki, volatile - ulotna, Specyfikatory klas pamięci są następujące: auto - automatyczna, register - rejestrowa, static - statyczna, extern - zewnętrzne, Należy zauważyć, że słowa kluczowe nie mogą być użyte jako nazwy zmiennych, typów lub funkcji i nie są poprawnymi identyfikatorami w sensie składni języka C. 1.1.22. Funkcja printf Funkcja printf umożliwia formatowane wyprowadzanie danych: W tym celu analizuje najpierw przekazany jako pierwszy argument tekst, a następnie na podstawie informacji zawartych w tym tekście, wypisuje kolejne wartości. Wypisywanie wartości prowadzone jest na standardowe wyjście którym w przypadku mikrokontrolera 8051 jest zwykle port szeregowy. Liczba wyprowadzonych wartości musi być taka jak wynika z przekazanego formatu. W szczególnym przypadku do funkcji printf może zostać przekazany tylko format. Tekst przekazywany jako format, składa się z tekstu, który zostanie wypisany tak jak został przekazany oraz informacji o koniecznych konwersjach. Informacja o konwersji rozpoczyna się znakiem '%'. Każda informacja tego rodzaju odpowiada jednej wartości przekazanej jako kolejny argument. W wypisywanym tekście, kolejne wartości pojawiają się w miejscu odpowiednich konwersji '%'. Same znaki '%' nie są wypisywane. W przypadku, gdy chcemy wypisać na ekranie znak '%' w podanym tekście należy wpisać "%%". W tabl. 7.11 pokazano wszystkie specyfikatory funkcji printf(). Tablica 7.11. Specyfikatory funkcji printf Opis Typ operandu Typ %d,i Dziesiętna notacja int %ld długa liczba całkowita dziesiętna int %o ósemkowa bez znaku int %x,x szesnastkowa bez znaku int %u dziesiętna bez znaku unsigned int %c Znak int lub char %s Ciąg char[] char * %f Dziesiętna notacja double lub float %e,e Notacja naukowa double lub float %g,g Krótkie %e lub %f double lub float 19

%p Wskaźnik void * %% Znak % n/a Poniżej przedstawiono przykłady użycia funkcji printf(): printf("dzisiaj jest wtorek!\n"); wynik: Dzisiaj jest wtorek printf("120 %% 10 = 0\n"); wynik: 120 % 10 = 0 Przykłady specyfikatorów użycia funkcji printf(): %3d wyświetli 3 cyfry wyrównane do prawej %3.0f nie wyświetli przecinka i liczby po nim %3.1f wyświetli jedną cyfrę po przecinku %.1f wyświetli tylko cyfrę po przecinku 1.2. Implementacja języka C na mikroprocesor rodziny 51 1.2.1. Podział pamięci mikrokontrolera 8051 Mikrokontroler 8051 posiada kilka różnych obszarów pamięci zaczynających się od tego samego adresu. Dostęp do tych segmentów pamięci możliwy jest różnymi technikami. Poniżej na rys.7.2 pokazano podział obszaru pamięci mikrokontrolera 8051. 0xFFFF Pamięć programu CODE Zewnętrzna pamięć danych XDATA PDATA 256b PDATA 256b 0xFF Wewnetrzna pamięć danych SFR 0x7F IDATA 0x0000 Sygnał zewnetrzny PSEN MOVC A,@DPTR Sygnał zewnetrzny RD WR MOVC A,@DPTR MOVC @DPTR,A PDATA 256b MOVX A,@Ri MOVX @Ri,A 0x00 Instrukcje Adresowania Pośredniego MOV A,@Ri MOV @Ri,A DATA Instrukcje Adresowania Bezpośredniego MOV A,direct MOV direct,a 0x1F REG 0x00 Instrukcje Adresowania Rejestrowego MOV A,Rr MOV Rr,A BDATA 0x20 0x2F 20 Rys. 7.2 Podział obszaru pamięci mikrokontrolera 8051 W celu zrozumienia zasad użycia poszczególnych rodzajów pamięci oraz modeli kompilacji, należy przyjrzeć się bliżej podziałowi pamięci w mikrokontrolerze 8051. Od adresu 0x00, do adresu 0xFF, rozciąga się obszar pamięci wewnętrznej RAM o nazwie IDATA, która może być adresowana wyłącznie pośrednio, tzn. przy wykorzystaniu adresów zawartych w rejestrach R0 i R1 z aktualnie wybranego banku. Do tego są wykorzystywane instrukcje asemblera: MOV A,@Ri oraz MOV @Ri,A. Obszar wewnętrznej pamięci danych może być zaadresowany w inny sposób (bezpośrednio), ale jest on wtedy kompilatorze języka C traktuje go jako dwa niezależne obszary o nazwach DATA i SFR. Przestrzeń o nazwie DATA zawierająca dane użytkownika leży pomiędzy adresami 0x00 a 0x7Fi jest on adresowany bezpośrednio rozkazami MOV A,direct oraz MOV direct,a, gdzie direct oznacza adres leżący wewnątrz wewnętrznego rozpatrywanego obszaru pamięci. Główne przeznaczenie tego segmentu pamięci to przechowywanie zmiennych wykorzystywanych przez program w czasie pracy. Od adresu 0x80 do 0xFF umieszczony został rejestr funkcji specjalnych (SFR) mikrokontrolera, który może być adresowany bezpośrednio. W obrębie obszaru (segmentu) DATA znajdują się obszary o nazwach BDATA i REG. Obszar BDATA

to szesnaście bajtów (128 bitów) zajmujących przestrzeń adresową od 0x20 do 0x2F w obszarze adresowania bezpośredniego. Specjalną cechę tego obszaru stanowi fakt, że oprócz instrukcji MOV mają tutaj zastosowanie instrukcje operujące na pojedynczych bitach (SETB B_ADRES, gdzie B_ADRES to adres bitu) i wykorzystujące specjalny tryb adresowania pojedynczych bitów (warto zaznaczyć że obszar SFR jest również obszarem dostępnym bitowo). Kolejnym obszarem pamięci mikrokontrolera jest obszar REG składający się z 4 banków po 8 rejestrów (R0-R7) zajmujący przestrzeń adresowa od adresu 0x00 do 0x1F. Jest on dostępny przez adresowanie rejestrowe wykorzystując instrukcje asemblera MOV A,Rr oraz MOV Rr,A (gdzie Rr to rejestry R0 R7). Pozostałe obszary danych kompilatora języka C dotyczą pamięci zewnętrznych, do których adres liczy 16 bitów. Pierwszym z takich segmentów jest segment o nazwie CODE. Przeznaczony na pamięć programu. Obszar ten zajmuje adresy od 0x0000 do 0xFFFF (65536 bajtów). Segment pamięci programu dostępny jest przez instrukcje wykorzystujące do adresowania licznik rozkazów PC oraz 16-bitowy rejestr DPTR. W segmencie CODE poza instrukcjami programu mogą być przechowywane dane, ale o wyłącznie stałych wartościach takie, jak tablice współczynników, stałe itp. Obszar ten dostępny jest przy wykorzystaniu instrukcji asemblera MOVC A,@DPTR. Kolejnym obszarem danych jest obszar zewnętrznej pamięci danych nazywany XDATA lub XRAM. Zaczyna się on od adresu 0x0000 i kończy pod adresem 0xFFFF. Dostępny jest przy wykorzystaniu instrukcji asemblera MOVX A,@DPTR oraz MOVX @DPTR,A. Ten sam obszar zewnętrznej pamięci danych może być adresowany przy wykorzystaniu dwu innych instrukcji : MOVX A,@Ri oraz MOVX @Ri,A, gdzie Ri to R0 lub R1. Wtedy jednak w języku C nazywany jest on PDATA. W takim przypadku rejestr Ri wskazuje 8 bitowa dolną część 16 bitowego adresu, podczas gdy górna cześć (8 bitów) jest wskazywana przez zawartość rejestru P2. Z tego powodu adresowanie to można nazwać stronicowym, ponieważ w rejestrze P2 umieszczony jest numer strony pamięci, a w rejestrze Ri jedynie 8 bitowy adres danej znajdującej się na wskazanej stronie. Uwaga: omawiając przestrzeń adresową pamięci CODE czy XDATA opisano możliwość jej fizycznego rozszerzenia a nie przymus zajmowania całego dostępnego obszaru przez dane urządzenie podłączone w tej przestrzeni adresowej. Zewnętrzna pamięć ROM (segment CODE) jest sterowana sygnałem odczytu PSEN (Program Store Enable). Zmiana poziomu tego sygnału na niski uaktywnia pamięć programu. Dostępem do obszaru XDATA sterują zewnętrzne sygnały RD (odczyt) i WR (zapis), które pozycjonują w czasie odczyt lub zapis danych. Reasumując kompilator C używa następujących adresowanych obszarów pamięci oznaczonych kolejno: 00-Data, 01-Idata, 02-Xdata, 03-Pdata, 04-Bdata, 05-Code DATA bezpośrednio adresowany obszar wewnętrznej pamięci RAM (128 bajtów), IDATA pośrednio adresowany obszar wewnętrznej pamięci RAM mikrokontrolera (128 lub 256 bajtów), XDATA zewnętrzna pamięć danych, adres dostępu 16 bitowy (64kB), PDATA tak samo, jak XDATA z tym, że adres dostępu do pamięci zewnętrznej jest 8 bitowy, BDATA obszar pamięci RAM, w którym możliwe jest adresowanie pojedynczych bitów rejestrów specjalnych SFR, na przykład sbit OV = PSW^2, CODE pamięć programu; może być albo wewnętrzny, albo zewnętrzny ROM mikrokontrolera, SFR obszar rejestrów specjalnych położony w pamięci RAM, SBIT obszar pojedynczego bitu położony w obszarze bitowym wewnętrznej pamięci RAM. 21

Wyżej wymienione słowa kluczowe języka C noszą nazwę kwalifikatorów obszaru pamięci. 1.2.2. Modele kompilacji Język C opracowany przez firmę Keil dla mikrokontrolera 8051 umożliwia dokonanie kompilacji dostosowującej wyjściowy kod maszynowy do 3 różnych modeli pamięci. Krótkiego wyjaśnienia wymaga sam problem modeli pamięci. Rolą każdego kompilatora języka programowania tak zwanego wysokiego poziomu, jest przetłumaczenie kodu programu z postaci zrozumiałej dla człowieka, do postaci zrozumiałej dla mikroprocesora, czyli w uproszczeniu z postaci języka C do kodu maszynowego. Niektóre rozkazy asemblera dla mikrokontrolera 8051 (który jest językiem najbardziej zbliżonym do kodu maszynowego) o zbliżonym działaniu, zajmują różne wielkości pamięci. Na przykład rozkazy skoków mogą zajmować w pamięci 2 lub 3 bajty (AJMP i LJMP). Podobnie z rozkazami wywołania podprogramów LCALL i ACALL. Rozkazy 2-bajtowe wykorzystywane są wówczas, gdy adres docelowy znajduje się w obrębie tego samego 2kB bloku pamięci programu, natomiast 3- bajtowe gdy leży w obrębie 64kB. Można stąd wysnuć wniosek, że jeśli pamięć jest mała, to rozkazy skoków mogą być krótsze, co pozwala zmniejszyć wielkość programu wynikowego. Dodatkowo mikrokontroler może wykorzystywać do przechowywania zmiennych obszar pamięci wewnętrznej oraz zewnętrznej. W przypadku wykorzystywania pamięci wewnętrznej dostęp do zmiennych jest szybki a kod potrzebny do zapisu lub odczytu danej mały. Tego rodzaju rozumowanie leży u podstaw stworzenia różnych modeli kompilacji i różnych modeli pamięci. Dzięki nim kompilator orientuje się jaki kod tworzyć, w jaki sposób go optymalizować, gdzie umieszczać zmienne globalne i lokalne. Pisząc program dla mikrokontrolera 8051 pierwszą decyzją, którą trzeba podjąć jest decyzja dotycząca wyboru modelu pamięci. Kompilator języka C firmy Keil umożliwia wybór trzech modeli pamięci: Small, Compact, Large. W modelu SMALL pamięć RAM służąca do przechowywania zmiennych ma jedynie 128 bajtów umieszczonych w obszarze pamięci mikrokontrolera, dostępnych jako obszar IDATA. Używając modelu SMALL, należy więc zredukować do minimum liczbę zmiennych globalnych używanych w programie. Pozwoli to programowi linkera na kompilowanie funkcji w taki sposób, aby aplikacja pracowała efektywnie. Model SMALL można także stosować przy kompilacji nawet bardzo dużych programów, umieszczając obiekty duże i takie, do których nie jest wymagany bardzo szybki dostęp, w zewnętrznej pamięci RAM. Ten model najlepszy jest również dla aplikacji o krytycznym czasie wykonywania, jako że gwarantuje on najszybszy dostęp do zmiennych i parametrów przez funkcje, podczas gdy duże obszary danych mogą zostać umieszczone poza układem mikrokontrolera. W modelu COMPACT kompilator przygotowuje kod programu w taki sposób aby do przechowywania zmiennych oprócz 128 bajtów pamięci wewnętrznej RAM wykorzystywanych było 256 bajtów zewnętrznej pamięci RAM dostępnej jako obszar PDATA. COMPACT to model pamięci dostosowany do programów, gdzie dla przykładu wewnętrzny RAM mikrokontrolera przeznaczony jest na zmienne systemu operacyjnego. Model ten jest rzadko używany dla całego programu. Najbardziej użyteczna kombinacja to jego połączenie z modelem SMALL używanym lokalnie dla procedur obsługi przerwań. COMPACT stosuje się przede wszystkim do programów zawierających dużą liczbę zmiennych, które nie wymagają krótkiego czasu dostępu, ponieważ odwołanie się do zmiennych odbywa za pomocą instrukcji MOVX A,@Ri lub MOVX @Ri,A wykorzystującej tryb adresowania pośredniego przy pomocy rejestru R0 lub R1. COMPACT może być również bardzo użyteczny dla aplikacji wymagających stosu o dużym rozmiarze, co może oznaczać konieczność umieszczenia go w zewnętrznej pamięci RAM. W modelu LARGE kompilator umożliwia dostęp do całej zewnętrznej pamięci RAM (64kB). Poza tym w dalszym ciągu można wykorzystywać 128 bajtów pamięci wewnętrznej mikrokontrolera. Model LARGE pozwala na niezbyt szybki dostęp do bardzo dużego obszaru 22

pamięci RAM. Dostęp do tej pamięci odbywa się za pomocą instrukcji MOVX @DPTR,A lub MOVX A,@DPTR. Podobnie jak poprzednio, niezbyt często używa się go w odosobnieniu raczej w połączeniu z modelem SMALL. Należy pamiętać, że niezależnie od wybranego modelu pamięci użytkownik ma możliwość bezpośredniego przydziału zmiennych do różnych obszarów pamięci wymuszając ich przypisanie. Przykładowe sposoby wymuszania przypisania zmiennych do obszarów pamięci pokazano poniżej: data int x; idata int x,y; xdata floata x; code int con; pdata int x; bdata char y; //umieść zmienna i w obszarze data //umieść zmienne x oraz y w obszarze idata //umieść zmienna x w obszarze xdata //umieść zmienna con w obszarze code //umieść zmienna x w obszarze stronicowanym przez port //P, a dane w zakresie 0...255 bajtów na stronie //wskazanej przez P2 // umieść zmienna y w obszarze adresowanym bitowo 1.2.3. Porty mikrokontrolera Korzystając z portów wejścia wyjścia mikrokontrolera w programach napisanych w języku C, należy kierować się tymi samymi zasadami, co w języku asembler, co oznacza, że porty, które wymagają ustawienia w stan wysoki przed odczytem z nich danych, muszą być w ten stan ustawione. Mając to na uwadze, korzystanie z portów jest bardzo proste. Można testować stany bitów, przypisywać zmienne i stałe, wykonywać różne inne operacje. Podobnie jak w asemblerze, jeśli operacja wymaga zmian stanu portu (przesunięć bitów i podobnych), lepiej jest utworzyć zmienną będącą kopią stanu portu i na niej wykonywać działania. Potem wystarczy tylko przypisać do danego portu stan zmiennej. W ten sposób można uniknąć zakłóceń mogących się pojawić na wyprowadzeniach portu, w momencie bezpośrednio wykonywania na nim działań. Poniżej przedstawiono przykładowy program wykonujący tego rodzaju zmianę stanu portu P1. zmienna = P1 //(0..3 w zależności od mikrokontrolera), P1 = P1 0xFF; //gdy port pracuje jako wejściowy, dobrze jest jego bity //ustawić na 1 P1 = 0xAA; // numer portu zależny od aplikacji P1 = P1 0x01; // ustawienie bitu P1.0 P1 = P1 & 0xFE; // wyzerowanie bitu P1.0 P1^0 = 1; // odpowiednik rozkazu SETB P1.0 P1^0 = 0; // odpowiednik rozkazu CLR P1.0 P1 = P1 0x01 // ustawienie P1.0 na 1 if (P1^0 = 1... // jeden ze sposobów testowania stan bitu P1.0 1.2.4. Przerwania Słowo kluczowe interrupt powoduje, że funkcja traktowana jest przez kompilator jako obsługująca przerwanie. Przy jej definiowaniu wymagana jest znajomość numeru przerwania kolejności w tablicy wektorów przerwań. Na podstawie tego kompilator wylicza adres obsługi. Odpowiedni wektor przerwania obliczany jest jako 3 + (numer przerwania x 8). Wartości 3 i 8 są przyjmowane jako domyślne dla rodziny 8051. Poniżej pokazano fragment programu zawierający przykładową definicję obsługi przerwania generowanego przez Timer 0. void Przerwanie_Timera0 (void) interrupt 1: { TR0 = 0; // zatrzymanie timera 0 TH0 = 0; // odświeżenie zawarto ś ci rejestrów TL0 = 0x1F; licznik++; // zwiększenie zmiennej licznik o 1 TR0 = 1; // ponowne uruchomienie timera 0 23

Funkcja, która wykonywana jest po stanie RESET procesora nosi nazwę main i na nią zawsze wskazuje wektor przerwania o numerze 0. Treść tej funkcji to program główny. Kompilator sam dba o to, aby wektor przerwania numer 0 zawsze wskazywał na miejsce w pamięci programu, gdzie umieszczony jest kod wynikowy funkcji main. Jaki skutek dla funkcji z przykładu ma umieszczenie słowa kluczowego interrupt? Po pierwsze instrukcja LJMP Przerwanie_Timera0 umieszczana jest w tablicy wektorów przerwań pod adresem 0BH. Po drugie, w przypadku wywołania tej funkcji, przed wykonaniem pierwszej znajdującej się instrukcji na stos odkładane są zawartości rejestrów A, B, PSW, DPH, DPL. Po trzecie, po odłożeniu danych następuje wykonanie instrukcji zawartych w ciele funkcji. Realizowane jest to przez skok pod wspomniany adres obsługi przerwania leżący w obszarze CODE wskazywany przez nazwę funkcji (w tym przypadku przerwanie od Timera0). Z tego względu deklarując podprogram przerwania musimy poinformować kompilator że podprogram jest typu interrupt i że zaczyna się od określonego miejsca w pamięci, miejsce w pamięci zostanie określone dzięki zadeklarowaniu źródła przerwania. Poniżej w tabl. 7.12 pokazano wszystkie źródła przerwań mikrokontrolera 8051 wraz z ich nazwami w języku C oraz adresami obsługi. Tablica 7.12. Źródła przerwań w mikrokontrolerze 8051 24 Źródło Nazwa przerwania w języku C Adres obsługi przerwania IT0 Interrupt 0 0x0003 Timer T0 Interrupt 1 0x000B IT1 Interrupt 2 0x0013 Timer T1 Interrupt 3 0x001B SIO Interrupt 4 0x0023 1.3. Podstawy posługiwania się pakietem Keil C-51 Pakiet µvision2 firmy Keil Elektronik Gmbh to zbiór narzędzi umożliwiających pisanie i uruchamianie programów w języku C i asemblera dla mikrokontrolerów rodziny 51. Składa się on z wielu programów narzędziowych w tym między innymi: C51 Optimizing C Cross Compiler kompilator języka C, A51 Macro Assembler- asembler, 8051 Utilities (linker, object file converter, library manager) zbiór programów dodatkowych w tym: linker łączący plik wynikowy asemblera z bibliotekami oraz konwerter tak powstałego pliku na kod wynikowy w formacie HEX. dscope for Windows Source-Level Debugger/Simulator- program symulatora mikrokontrolera 8051, Keil C51 jest kompilatorem języka C dla procesorów rodziny 8051 zgodnych ze specyfikacją ANSIC. W skład µvision 2 wchodzi edytor tekstu umożliwiający pisanie programu z wcięciami tekstu zgodnie ze standardami przyjętymi dla języka C. Kod źródłowy programu jest wyświetlany w różnych kolorach, co pozwala na wyróżnienie słów kluczowych od opisu oraz od pozostałej części programu. Program dla mikrokontrolera nie różni się zasadniczo od zwykłego programu w języku C, co umożliwia przy znajomości języka oraz wiedzy o budowie mikrokontrolera pisanie programów. Pakiet µvision 2 firmy Keil Gmbh znacznie ułatwia prace programisty i umożliwia przeprowadzenie dokładnej analizy programu. Wersję demonstracyjna pakietu dostępna jest na stronie internetowej producenta www.keil.com. 1.3.1. Instalacja i uruchomienie pakietu Pakiet programów zainstaluje się automatycznie. Po zainstalowaniu jest gotowy do pracy bez żadnych dodatkowych ustawień, czy też restartowania komputera. Aby łatwiej czytać pewne informacje zalecane jest ustawienie rozdzielczości ekranu na 800x600. Przy pierwszym uruchomieniu widoczne jest tylko menu główne które zostało pokazane na rys.7.3.

Rys. 7.3. Widok okna głównego Okno główne programu zostało podzielone na trzy mniejsze okna: Okno projektu - służy do zarządzania projektem, Okno edytora - służy do wpisywania programu, Okno wyjściowe - służy do odczytywania komunikatów kompilatora. Z poziomu okna głównego widoczne są paski narzędziowe pozwalające na szybki dostęp do wybranych, kluczowych opcji programu. Pasek edytora również widoczny w oknie głównym ma formę przejętą z klasycznych edytorów systemu Windows. Pozostałe paski narzędziowe zawierają funkcje charakterystyczne dla programu kompilatora. 1.3.2. Korzystanie z pakietu Korzystanie z pakietu Keil należy rozpocząć od jego uruchomienia, czyli uruchomienia pliku o nazwie Uv2.exe znajdującego się zwykle w katalogu \...\keil\uv2\uv2.exe. Po jego uruchomieniu pojawi się menu główne pokazane na rys.7.3. Do napisania pierwszego programu w języku C należy wybrać z menu głównego opcję Project >New Project, wskazać katalog docelowy dla plików danego projektu oraz wpisać nazwę utworzonego projektu. Po wykonaniu tych czynności pokaże się okno wyboru mikrokontrolera pokazane na rys. 7.4. Wyboru mikrokontrolera dokonuje się na podstawie znajomości producenta oraz typu. Niezwykle przyjaznym i pomocnym elementem tej opcji są opisy pojawiające się po zaznaczeniu wybranego mikrokontrolera. Zawierają one podstawowe informacje dotyczące budowy wewnętrznej mikrokontrolera. Każdy utworzony projekt jest obiektem, dla którego indywidualnie można określić właściwości. Późniejsze otwarcie zapamiętanego projektu powoduje przywrócenie cech środowiska, dla którego program został napisany. 25

Rys. 7.4. Wybór mikrokontrolera W celu rozpoczęcia pisania programu w edytorze pakietu Keil C51 wystarczy wybrać z menu File opcje New File lub też z paska narzędziowego edytora pokazanego na rys. 7.5 wybrać ikonę New File (Nowy plik). Co pozwala na otwarcie nowego pliku w edytorze i zapisanie tam tworzonego programu. Zapisując plik otwarty w edytorze zawierający program należy pamiętać o zapisaniu go z rozszerzeniem *.c co pozwoli na automatyczne rozpoznanie tego pliku przez edytor, jako pliku zawierającego program w języku C. W takim przypadku użyte zostaną opcje wyświetlania tekstowej zawartości pliku z wyróżnieniem podstawowych elementów programu takich jak: słowa kluczowe, instrukcje, komentarze, nazwy plików itd. Rys. 7.5. Widok paska narzędziowego edytora Utworzenie nowego pliku i zapamiętanie go na dysku nie powoduje dodania go do projektu. Aby dodać plik do projektu należy posłużyć się poleceniem z menu głównego wywołanym w następujący sposób: opcja Project> Targets, Groups, Files.. wtedy na ekranie pojawi się okno dialogowe pokazane na rys. 7.6. 26

Rys. 7.6. Dodawanie plików do projektu Rys. 7.7. Ikony kompilacji i opcji projektu Po wybraniu zakładki Groups/Add Files w oknie o nazwie Available Groups należy wskazać grupę, do której należy przyłączyć plik. Jeżeli opcje środowiska nie były zmieniane, to jedyną grupą do wyboru będzie grupa o nazwie Source Group. Po wskazaniu grupy należy użyć przycisku: Add Files to Group w celu wywołania okna wyboru plików. Po dodaniu pliku możliwe jest już kompilowanie tak przygotowanego projektu. Dla użytkownika pakietu DSM-51 ważne jest by kod wyjściowy programu zapisany był w pliku wyjściowym o formacie *.hex, ustawienie tego formatu pliku można wykonać naciskając na pasku narzędziowym ikonę Opcje Projektu pokazana na rys. 7.7. Na rys. 7.8 pokazano okno pojawiające się po kliknięciu wspomnianej ikony. W tym oknie należy wybrać zakładkę Output i w niej zaznaczyć opcje o nazwie Create HEX File oraz wybrać HEX-80. 27

Rys. 7.8. Zakładka Output okna Options for Target (opcje projektu) Ważną opcją z punktu widzenia analizy utworzonego kodu, jest możliwość podglądu pliku zawierającego listing kodu powstającego w wyniku kompilacji kodu źródłowego do zapisu w języku asembler. Można tego dokonać wybierając w tej samej Opcji Projektu zakładkę Listing. Wygląd pojawiającego się w tym przypadku okna pokazano na rys.7.9. Rys. 7.9. Zakładka Listing okna Options for Target Tak przygotowany kompilator umożliwia przygotowanie i analizę programu na poziomie wystarczającym dla celów edukacyjnych. W celu zapoznania się z pozostałymi funkcjami należy zapoznać się z podręcznikiem użytkownika, który jest dołączony do kompilatora i 28