PROGRAMOWANIE MIKROKONTROLERÓW 8051 W JĘZYKU C



Podobne dokumenty
PROGRAMOWANIE MIKROKONTROLERÓW

1 Podstawy c++ w pigułce.

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

1 Podstawy c++ w pigułce.

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

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

Systemy wbudowane. Uniwersytet Łódzki Wydział Fizyki i Informatyki Stosowanej. Witold Kozłowski

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

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

Podstawy Programowania C++

Podstawy programowania w języku C

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

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

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

Podstawy programowania w języku C i C++

Programowanie komputerowe. Zajęcia 4

Poradnik programowania procesorów AVR na przykładzie ATMEGA8

Część 4 życie programu

Mikrokontroler ATmega32. Język symboliczny

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

W przeciwnym wypadku wykonaj instrukcję z bloku drugiego. Ćwiczenie 1 utworzyć program dzielący przez siebie dwie liczby

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

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

Programowanie strukturalne i obiektowe

Programowanie w języku C++ Grażyna Koba

2 Przygotował: mgr inż. Maciej Lasota

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

7. Pętle for. Przykłady

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

Programowanie w języku Python. Grażyna Koba

4. Funkcje. Przykłady

Pętla for. Wynik działania programu:

Lab 9 Podstawy Programowania

Pętle. Dodał Administrator niedziela, 14 marzec :27

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

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

Operacje wykonywane są na operandach (argumentach operatorów). Przy operacji dodawania: argumentami operatora dodawania + są dwa operandy 2 i 5.

Wprowadzenie do podstaw programowania AVR (na przykładzie mikrokontrolera ATmega 16 / 32)

Język ludzki kod maszynowy

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

Klawiatura matrycowa

ze względu na jego zaokrąglony kształt musimy go umieścić w innych bloczkach np. ze zmienną: lub jeśli chcemy sprawdzić jaki właśnie znak odczytujemy:

Warsztaty dla nauczycieli

Przedrostkowa i przyrostkowa inkrementacja i dekrementacja

Język C : programowanie dla początkujących : przewodnik dla adeptów programowania / Greg Perry, Dean Miller. Gliwice, cop

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

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

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

1. Operacje logiczne A B A OR B

Pytania sprawdzające wiedzę z programowania C++

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

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

Zestaw Edukacyjny Atmega-8 (AJAWe-0711) Porty wejścia-wyjścia.

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

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Ćwiczenie 1. Wprowadzenie do programu Octave

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

Język C, tablice i funkcje (laboratorium)

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Lekcja : Tablice + pętle

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Zadanie 04 Ktory z ponizszych typow danych w jezyku ANSI C jest typem zmiennoprzecinkowym pojedynczej precyzji?

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

Mikrooperacje. Mikrooperacje arytmetyczne

Języki skryptowe w programie Plans

Algorytmy i struktury danych

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

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Podstawy programowania - 1

1 Wielokrotne powtarzanie tych samych operacji

Wstęp do informatyki- wykład 9 Funkcje

I - Microsoft Visual Studio C++

Instrukcja do ćwiczeń

Jak zawsze wyjdziemy od terminologii. While oznacza dopóki, podczas gdy. Pętla while jest

do instrukcja while (wyrażenie);

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

Pascal - wprowadzenie

Elżbieta Kula - wprowadzenie do Turbo Pascala i algorytmiki

PODSTAWY INFORMATYKI 1 PRACOWNIA NR 6

JAVAScript w dokumentach HTML (1) JavaScript jest to interpretowany, zorientowany obiektowo, skryptowy język programowania.

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

Pętle i tablice. Spotkanie 3. Pętle: for, while, do while. Tablice. Przykłady

Wskaźniki w C. Anna Gogolińska

Ćwiczenie 1. Wprowadzenie do programu Octave

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

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

Języki programowania zasady ich tworzenia

Odczyt danych z klawiatury Operatory w Javie

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

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

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

Programowanie komputerowe. Zajęcia 3

Zmienne, stałe i operatory

Bloki anonimowe w PL/SQL

Wykład 2 Składnia języka C# (cz. 1)

Język C zajęcia nr 11. Funkcje

Szkolenia specjalistyczne

Transkrypt:

PROGRAMOWANIE MIKROKONTROLERÓW 8051 W JĘZYKU C KŁ ZSP4 2012

Programowanie mikrokontrolerów 8051 w jezyku C - część 1 Gdy już skompletujemy nasz warsztat programistyczny i sprzętowy, pora na napisanie pierwszego programu w języku C. Najbardziej efektowne są programy, których działanie mozna odrazu zobaczyc na własne oczy. Ja zwykle, gdy zaczynam pracę z nowym mikrokontrolerm, piszę program, który zapala diode LED. W ten sposób można najszybciej przekonać się o poprawnym działaniu programu. Do mikrokontrolera należy podłączyc 8 diod LED w sposób pokazany na rysunku : Wartości rezystorów należy dobrać odpwiednio do posiadanych diod LED. Jesli są to diody standardowe, to rezystancja rezystorów powinna mieć wartość ok. 330 Ohm. Natomiast gdy dysponujemy diodami niskopądowymi, to rezystancja rezystorów może mieć wartośc ponad 1 kohm (należy zwrócić także uwagę na wydajność prądową portów mikrokontrolera). Operator przypisania. W tym podtemacie zapozamy się z najczęściej używanym operatorem - operatorem przypisania. Służy on, jak jego nazwa wskazuje, do przypisania do danej zmiennej wartości innej zmiennej, badź stałej. // zapisanie do portu P0 liczby 0x55 P0 = 0x55; // pusta pętla nieskonczona - zatrzymanie porgramu ; Na początku musimy dołączyć plik nagłówkowy z definicjami rejestrów procesora. Gdybyśmy tego nie zrobili, to nie moglibyśmy odwoływac się do rejestrów procesora za pomocą nazw symbolicznych, tylko przez podawanie adresu danego rejestru. Takie rozwiązanie byłoby bardzo niewygodne. Ja w naszym przykładzie dołączyłem plik 8051.h - "bezpieczny" dla większości mikrokontrolerów z rodziny 8051. W przypadku, gdybyśmy korzystali z rejestrów specyficznych dla danego mikrokontrolera, nie występujących w standartowym 8051, to musimy dołaczyć odpowiedni dla danego mikrokontrolera plik nagłówkowy. W sytuacji, gdy używamy tylko typowych rejestrów można spokojnie zastosowac plik 8051.h. Każdy program pisany w języku C musi się składać z głównej funkcji main. Funkcja ta nie zwraca żadnej wartości ani do funkcji nie jest przekazywana żadna wartość, więc funkcję tą

deklarujemy jako. Słowo void przed nazwą fukcji mówi kompilatorowi, że funkcja nie zwraca wartości, albo inaczej mówiąc, że zwracana wartośc jest typu void (pusty, brak typu). Słowo void w nawiasach okrągłych po nazwie fukcji mówi kompilatorowi, że do funkcji nie jest przekazywana żadna wartość. W zależności od tego, jaką funkcę chcemy napisac i jakie parametry ma ona przyjmować oraz jaką wartość może zwracać, deklaracja funkcji może przyjmować najróżniejsze postaci : void funkcja(char x) - funkcja przyjmująca jeden parametr typu char (znak), niezwracająca wartości void funkcja(char x, char y) - funkca przyjmująca dwa parametry typu char, niezwracająca wartości char funkcja(void) - funkcja nieprzyjmująca żadnych parametrów, zwracająca wartość typu char Oczywiście mozliwych kombinacji jest bardzo dużo i zależą one od tego, jakie zadania ma spełniać dana funkcja. Gdy już mamy szkielet programu, to nalezy wpisac właściwy kod programu. W naszym pierwszym programie w zasadzie decydujące dznaczenia ma jeden wiersz programu : P0 = 0x55;. Jest to instrukcja przypisująca do portu P0 wartość 55h. Objawi się to zapaleniem co drugiej diody LED podłączonej do portu P0. Liczby w systemie szesnastowym w języku C zapisujemy właśnie w podany sposób : liczbę szesnastkową (bez znaku 'h' na końcu) nalezy poprzedzic ciągiem znaków '0x'. Po zapisaniu do portu właściwej wartości należy zatrzymać wykonywanie programu. Najłatwiej dokonać tego wykorzystując pętlę while. Pętla ta jest wykonywana tak długo, aż jej warunek jest prawdziwy. Poniewaz w naszym programie warunek pętli jest wartością stałą, reprezentującą prawdę logiczną, to nasza pętla będzie się wykonywała bez końca. Funkcje logiczne. Wyzerowanie odpowiednich linii portu możemy zrealizować także w inny sposób, przez wykonanie iloczynu logicznego portu ze stałą. Przedstawia to tabela prawdy funkcji AND : Jeśli potraktujemy zmienną A jako nasz port, a zmienną B jako maskę określającą które bity należy wyzerować, to będą nas interesować dwa ostatnie wiersze tej tabeli. Jak wynika z tabeli odpowieni bit rejestru zostanie wyzerowany, gdy odpowiadający mu bit maski będzie miał wartość 0. W przeciwnym przypadku stan bitu rejestru sie nie zmieni. Rzeczą ważną jest aby pamietać, że odpowiednie bity rejestru, na których chcemy przerowadzić iloczyn musza być w stanie 1. Kod programu realizującego zerowanie linii portu P0 za pomocą iloczynu logicznego jest przedstawiony poniżej : // iloczyn logiczny portu P0 ze stałą 55h P0 &= 0x55; // pusta pętla nieskonczona - zatrzymanie porgramu

; // iloczyn logiczny portu P0 ze stałą 55h P0 &= 0x55; // pusta pętla nieskonczona - zatrzymanie porgramu ; Wiekszość kodu jest taka sama jak w programie poprzednim. Wyjaśnienia wymaga jeden wiersz kodu : P0 &= 0x55; Jest to skrócony zapis nastepującego wyrażenia : P0 = P0 & 0x55; Język C umozliwia stosowanie skróconych wyrażeń, będących połączeniem operatora przypisania z operatorami arytmetycznymi, badź logicznymi. Możliwe są następujące skrócone formy zapisu wyrażeń : Funkcja Zapis skrócony Zapis normalny dodawanie a += b a = a + b odejmowanie a -= b a = a - b mnożenie a *= b a = a * b dzielenie a /= b a = a / b iloczyn logiczny a &= b a = a & b suma lgiczna a = b a = a b przesunięcie w lewo przesunięcie w prawo alternatywa logiczna a <<= b a >>= b a ^= b a = a << b a = a >> b a = a ^ b Po zerowaniu linii portu nadszedł czas na ich ustawianie. Służy do tego funkcja logiczna OR. Tabela prawdy funkcji OR jest przedstawiona poniżej :

Tym razem interesują nasz dwa pierwsze wiersze tabeli. Wynika z nich, że aby ustawić odpowniedni bit rejestru, to odpowiadający mu bit maski musi mieć wartość 1 no i oczywiście bity do ustawienia muszą mieć wartość 0. Program przedstawiony jest poniżej : // ustawienie wszystkoch linii portu P0 w stan niski P0 = 0; // suma logiczna portu P0 ze stałą F0h P0 = 0xF0; // pusta pętla nieskończona - zatrzymanie porgramu ; W naszym programie pojawił się dodatkowy wiersz kodu : P0 = 0; Ustawia on wszystkie linie portu P0 w stan niski. Ponieważ chcemy linie portu P0 ustawić więc muszą być w stanie niskim. Jednak zaraz po zresetowaniu procesora wszystkie pory są ustawiane w stan niski. Musimy więczaraz przed ich ustawieniem je wyzerować. Samo ustawnienie wybranuch linii w stan wysoki realizuje poniższy kod : P0 = 0xF0; Ponownie zastosowałem skrócony zapis łaczący operator przypisania z operatorem sumy logicznej. Programowanie mikrokontrolerów 8051 w języku C - część 2 W drugiej części kursu programowania zapoznamy się z obsługą klawiatury.w tym celu musimy podłączyć do układu z poprzedniej części kursu, do portu P3, 8 przycisków w sposób pokazany na ponizszym rysunku : Klawiatura ta działa w następujący sposób : po naciśnięciu przycisku wyprowadzenie portu, do którego jest podłączony przycisk, jest zwierane do masy, co wymysza na wyprowadzeniu portu stan niski. Port musi zawierać wewnętrzne rezystory podciągajęce linie portu do szyny zasilającej. W przeciwnym razie będziumy musieli dołączyć zewnętrzne rezystory podciągające. W typowych mikrokontrolerach rodziny 8051 rezystory te są wbudowane w każdy port, za wyjątkiem portu P0 a także w bardzo popularnym AT89C2051 za wyjątkiem linii P1.0 i P1.1. Tak wiec przed podłączeniem klawiatury nalezy się upewnić, że port do którego chcemy podłaczyc klawiaturę posada rezystory podciągające.

Pierwszy program wykorzystujący klawiaturę jest bardzo prosty i zamieszczony poniżej : // pętla nieskończona // przepisanie do portu P0 stanu linii portu P3 P0 = P3; Cały program opiera się w zasadzie na nieustannym przepisywaniu stanu linii portu P3 do portu P0. Efektem tego jest zapalanie odpowiedniej diody LED po naciśnięciu dowolnego przycisku. Zauważmy, że naciśniecie przycisku wywołuje wymuszenie na odpowiednim wyprowadzeniu portu P3 stanu niskiego, a zapalenie diody LED jest spowodowane ustawieniem na odpowiednim wyprowadzeniu portu P0 również stanu niskiego. Dzięki temu możemy po prostu przepisać stan linii portu P3 do portu P0, bez zadnych dodatkowych zabiegów. Jest to zrealizowane przez instrukcję : P0 = P3; Nie są wymagane żadne dodatkowe zmienne pośredniczące w przesyłaniu danych z portu P3 do portu P0. Instrukcja warunkowa if Teraz poznamy bardzo ważną instrukcję warunkową if. Służy ona do warunkowego wykonania fragmentu programu. Najprostsza wersja instrukcji if wygląda następująco : if(warunek) instrukcja; Jesli warunek ma wartość true (prawda) to wykonywana jest instrukcja, w przeciwnym razie instrukcja nie będzie wykonana. Przykład zastosowania instrukcji warunkowe if jest pokazany poniżej : // Pętla nieskończona // jeśli P3.0 jest w stanie niskim if(p3_0 == 0) // to ustaw na P0.0 stan niski P0_0 = 0; // jesli P3.1 jest w stanie niskim if(p3_1 == 0) // to ustaw na P0.0 stan wysoki P0_0 = 1;

zapisowi P3.0, czyli określa pin 0 portu P3. Przyjrzyjmy sie teraz dokładniej instrukcji if : if(p3_0 == 0) P0_0 = 0; W nawiasach okrągłych po słowie if umieszczono warunek. Warunkiem musi być wyrażenie zwracające wartość logiczną, czyli prawda lub fałsz. W naszym przykładzie dokonujemy sprawdzenia, czy wyprowadzenie P3.0 jest w stanie niskim. Jeśli tak, to oznacza to, że naciśnięty został przycisk podłączony do tego wyprowadzenia. Należy podjąć wtedy odpowiednie działanie, czyli ustawić wyprowadzenie P0.0 w stan niski. Początkujących adeptów programowania w jezyku C może zadziwić znak "==", czyli operator porównania. Jest on inny niż w językach Basic lub Pascal i z początku bardzo łatwo się myli z operatorem przypisania "=". Instrukcja if może także wyglądać następująco: if(warunek) instrukcja1; else instrukcja2; Słowo else (w przeciwnym przypadku) umieszczone przed instrukcja2 mówi, że instrukcja2 zostanie wykonana tylko wtedy, gdy warunek instrukcji if będzie niespełniony, czyli będzie wartości false. W sytuacji, gdy w przypadku spełnienia danego warunku wykonanych ma być kilka instrukcji, to należy blok tych instrukcji ująć w nawiasy klamrowe : if(warunek) instrukcja1; instrukcja2; instrukcja3; Instrukcja iteracyjna for Zapoznamy się teraz z kolejną niezwykle użyteczna instrukcją - z instrukcją for. Służy ona do realizowania wszelkiego rodzaju pętli. Ogólna postać instrukcji for wygląda następujaco : for(inicjalizacja zmiennej licznikowej; warunek; modyfikacja zminnej licznikowej) inicjalizacja zmiennej licznikowej - jest to przypisanie do zmiennej licznikowej jej wartości początkowej warunek - warunek, który określa kiedy pętla ma być wykonywana modyfikacja zmiennej licznikowej - odpowiednie zmodyfikowanie zmiennej licznikowej (inkrementacja, dekrementacja lub cokolwiek innego) W przykładowym programie wykorzystamy pętlę for do wygenerowania pewnego opóźnienia. Funkcja generująca opóźnienie (a raczej przerwę w wykonywaniu programu) jest bardzo przydatna przy współpracy mikrokontrolera z wolniejszymi układami peryferyjnymi, gdzie trzeba czekać np. na zakończenie pomiaru, itp. W naszym programie wykorzystamy funkcję opóźniającą do generowania prostego efektu świetlnego na diodach LED. Kod programu przedstawiony jest poniżej : // definicja funkcji opóźniającej void czekaj(unsigned char x)

// deklaracja dwóch zmiennych pomocniczych unsigned char a, b; // potrójnie zagnieżdzona pętla for // ta pętla zostanie wykonana x-razy for( ; x > 0; x--) // ta 10 razy for(a = 0; a < 10; ++a) // a ta 100 razy for(b = 0; b < 25; ++b); // pętla nieskończona // zapal odpowiednią kombinace diod LED P0 = 0x55; // odczekaj pewien okres czasu // zapal inną kombinację diod LED P0 = 0xAA; // odczekaj pewien okres czasu Po raz pierwszy stworzyliśmy własną funkcję. Zgodnie z tym, co napisałęm w pierwszej części kursu, funkcja czekaj nie zwraca zadnej wartości (void) i wymaga jednego parametru typu unsigned char (liczba 8-bitowa bez znaku). Parametrem tym będzie żadana przez nas długość opóźnienia (mniej-więcej w milisekundach). Przyjrzyjmy sie pierwszej pętli for : for( ; x > 0; x--) Brakuje tutaj części inicjalizacji zmiennej licznikowej, ponieważ tą zmienną jest parametr przekazywany do funkcji. Gdybyśmy w tym miejscy zainicjalizowali zmienną x, to przekazywany parametr zostałby zamazany. W pętli for może brakować dowolnego elementu - może nawet pętla for wyglądać następująco : for( ; ; ; ) W takim przypadku pętla ta będzie wykonywana bez końca. Nasza funkcja opóźniająca składa się z trzech zagnieżdzonych pętli for. Wwyniku tego łączny czas wykonywania tych pętli jest iloczynem powtórzeń każdej pętli. Dokłądny czas opóźnienia trudno jest określić, ponieważ ze wzgledu na rózne techniki optymalizacji kodu przez kompilator nie jest znany dokłądny czas wykonywania jednej pętli. Można co prawda odczytać z pliku *.asm generowanego przez kompilator jakie instrukcje zostały uzyte do realizacji tych pętli i określić dokładny czas ich wykonania, ale nie mamy gwarancji, że po zmianie bądź warunku pętli, badź wartości początkowych oraz końcowych kompilator nie zastosuje innego kodu. Tak więc generowanie opóźnień za pomocą pętli jest przydatne tylko przy generowniu przyblizonych opóźnień. Do odmierzania dokładnych odcinków czasu należy zastosować wewnętrzne timery mikrokontrolera. Programowanie mikrokontrolerów 8051 w jezyku C - część 3 Instrukcja switch i preprocesor. W sytuacji, gdy chcemy sprawdzić jedną zmienną na okoliczność różnych jej wartości, zamiast użycia rozbudwanego bloku instrukcji if-else wygodniej jest zastosować instrukcję switch. Ogólna postac instrukcji switch wygląda następująco :

switch(zmienna) case jakaswartosc1: instrukcja; break; case jakaswartosc2: instrukcja; break;... case jakaswartoscn: instrukcja; break; default : instrukcja; break; zmienna może byc dowolnym wyrażeniem bądź zmienną, pod warukiem że wartość tej zmiennej lub wyrażenia jest typu całkowitego. Nasz przykładowy program niech realizuje następującą funkcję : po nacisnięciu przycisku zapal przeciwną diodę LED (tzn "od drugiej strony"). Przyjrzyjmy sie kodowi na // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // pętla nieskończona // zgaszenie diod LED diody = 0xFF; // w zależnosci od wciśniętego przycisku // zapal odpowiednią diodę LED switch(klawiatura) case 254 : diody = 127; break; case 253 : diody = 191; break; case 251 : diody = 223; break; case 247 : diody = 239; break; case 239 : diody = 247; break; case 223 : diody = 251; break; case 191 : diody = 253; break; case 127 : diody = 254; break; szego programu : preprocesora - #define - jak nazwa wskazuje służy ona do definiowania. W rzeczywistości ma ona dwojakie zastosowanie. Po pierwsze służy do prostego przypisania do ciągu znaków bądź stałęj wartości liczbowej lub, jak w naszym przypadku, do określenia "wygodniejszej" nazwy jakiejś zmiennej. Po drugie do definiowania symboli kompilacji warunkowej. Z drugim zagadnieniem spotkamy się w dalszej części kursu, więc teraz nie zaprzątajmy nim sobie uwagi. Tak więc za pomocą dyrektywy #define przypisaliśmy do napisu klawiatura napis P2. W tym miejscu należy wyjaśnić co to takiego jest preprocesor. Tak więc słowo "preproesor" jest połączeniem słów "pre" - przed oraz "procesor" - w tym przypadku kompilator. Tak więc preprocesor jest programem uruchamianym przed uruchomieniem właściwego kompilatora. Preprocesor służy do wstępnej obróbki pliku źródłowego. Gdy preprocesor przegląda plik źródłowy i natrafi na ciąg znaków zdefiniowany przez dyrektywe #define, to zastąpi ten ciąg, ciągiem do niego przypisanym. Dzięki temu mozemy zamiest niewiele znaczących nazw portu uzywać w programie jasnych i jednoznacznie mówiących o ich przeznaczeniu nazw zmiennych i stałych. Po nadaniu portom naszego mikrokontrolera wygodnych i przejrzystych nazw nadchodzi czas na właściwy program. I znowu będzie on wykonywany w pętli nieskończonej. Na początku tej pętli przypiszemy do portu diody liczbę 0xFF czyli 255. Spowoduje to wygaszenie diod po

zwolnieniu przycisku, a także w sytuacji gdy naciśniemy więcej niż jeden przycisk. Następnie pojawia się instrucka switch. Jako zmienną tej instrukcji wykorzysatmy naszą klawiaturę. Teraz należy sprawdzić przypadek naciśnięcia każdego z przycisków osobno. Ponieważ naciśnięcie przycisku jest sygnalizowane wymuszeniem na linii, do której jest podłączony stanu niskiego, to po naciśnięciu przycisku S1 klawiatura przyjmie wartość 11111110 binarnie, czyli 254 dziesiętnie. Jeżeli naciśnięcie tego przycisku zostanie stwierdzone, to naley zapalić diodę D8 - przez przypisanie do portu diody liczby 01111111 dwójkowo, czyli 127 dziesiętnie. Po wykonaniu założonego zadania należy opuścić isntrukcję switch za pomocą słowa kluczowego break. W podobny sposób sprawdzamy pozostałe siedem przypadków. W powyższym przykładzie niezbyt elegancko wygląda zarówno sprawdzanie który klawisz został naciśnięty, jak i zapalanie odpowiedniej diody LED. Podczas pisania programu nie należy podawać stałych liczbowych (ani żadnych innych) bezpośrednio, tylko nalezy wcześniej zdefioniować stałą o nazwie jasno mówiącej o jej przeznaczeniu. Pozatym, w sytuacji gdy będziemy musieli zmienić stałą (oczywiście na etapie pisania programu, a nie w czasie jego działania) to w bardziej rozbudowanych programach zmienienie tej liczby w miescach w których nalezy ją zmienić będzie bardzo kłopotiwe. Tym bardziej, że nie będziemy mogli użyć machanizmu "znajdż/zamień", ponieważ łatwo zmienimy nie ten znak co trzeba. Bardziej elekgancka (oczywiście nie najbardziej - ta będzie za chwile) wersja programu jest przedstawiona poniżej : // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // #define poz1 254 #define poz2 253 #define poz3 251 #define poz4 247 #define poz5 239 #define poz6 223 #define poz7 191 #define poz8 127 // pętla nieskończona // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // #define poz1 254 #define poz2 253 #define poz3 251 #define poz4 247 #define poz5 239 #define poz6 223 #define poz7 191 #define poz8 127

// pętla nieskończona // zgaszenie diod LED diody = 0xFF; // w zależnosci od wciśniętego przycisku // zapal odpowiednią diodę LED switch(klawiatura) case poz1 : diody = poz8; break; case poz2 : diody = poz7; break; case poz3 : diody = poz6; break; case poz4 : diody = poz5; break; case poz5 : diody = poz4; break; case poz6 : diody = poz3; break; case poz7 : diody = poz2; break; case poz8 : diody = poz1; break; e o przenaczeniu stałej" wybrałem pozx, gdzie x = 1..8. Jak łatwo można sie domysleć "poz" to skrót od "pozycja". Ponieważ stałe liczbowe odpowiadające przyciskom, jaki diodm LED sa identyczne nie zastosowałem rozróżnienia czy chodzi o pozycję przycisku czy diody LED. Jednak już naprawde elegancko będzie, gdy użyjemy odpowiednich stałych dla diod i przycisków osobno // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // #define S1 254 #define S2 253 #define S3 251no. // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // #define S1 254 #define S2 253 #define S3 251 #define S4 247 #define S5 239 #define S6 223 #define S7 191 #define S8 127 // #define D1 254 #define D2 253 #define D3 251

#define D4 247 #define D5 239 #define D6 223 #define D7 191 #define D8 127 // pętla nieskończona // zgaszenie diod LED diody = 0xFF; // w zależnosci od wciśniętego przycisku // zapal odpowiednią diodę LED switch(klawiatura) case S1 : diody = D8; break; case S2 : diody = D7; break; case S3 : diody = D6; break; case S4 : diody = D5; break; case S5 : diody = D4; break; case S6 : diody = D3; break; case S7 : diody = D2; break; case S8 : diody = D1; break; // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // #define S1 254 #define S2 253 #define S3 251 #define S4 247 #define S5 239 #define S6 223 #define S7 191 #define S8 127 // #define D1 254 #define D2 253 #define D3 251 #define D4 247 #define D5 239 #define D6 223

#define D7 191 #define D8 127 // pętla nieskończona // zgaszenie diod LED diody = 0xFF; // w zależnosci od wciśniętego przycisku // zapal odpowiednią diodę LED switch(klawiatura) case S1 : diody = D8; break; case S2 : diody = D7; break; case S3 : diody = D6; break; case S4 : diody = D5; break; case S5 : diody = D4; break; case S6 : diody = D3; break; case S7 : diody = D2; break; case S8 : diody = D1; break; juz bardzo przejrzyście. Ja wybrałem nazwy identyczne z numerami elementów na mojej płytce uruchomieniowej, Ty możesz je dowolnie zmienić. Gdyby porównać kod wynikowy powyższych trzech programów to dla każdego z nich byłby identyczny. Dzieje się tak, że z punktu widzenia kompilatora te trzy programy są identyczne, ponieważ w "miejscach strategiczych" występują te same dane. Jest to kolejnym objawem preprocesora - kod źródłowy programu przed kompilacją został doprowadzony do postaci zrozumiałej przez kompilator (gdyby pominąć proces przetwarzania pliku przez preprocesor, to kompilator zgłosiłby mnóstwo błedów). Programowanie mikrokontrolerów 8051 w języku C - część 4 Tablice danych. Tablica jest miejscem przechowywania danych o tym samym typie. Tablicę deklarujemy podając typ elementów w niej przechowywanych, jej nazwę oraz rozmiar. Rozmiar podaje się w nawiasach kwadratowych []. Elementy tablicy są przechowywane w kolejno następujących po sobie komórkach pamięci. Przykładowa deklaracja tablicy może wyglądać następująco : int tablica[5]; Powyższy zapis deklaruje pięcioelementową tablicę danych typu int. Dostęp do poszczególnych elementów tablicy uzyskujemy przez podanie nazwy tablicy, oraz numeru elementu tablicy, do którego dostęp chcemy uzyskać. Przykładowo, zapisanie do drugiego elementu tablicy jakiejś wartości może wyglądac nastepująco : tablica[1] = 0; W tym miejscu ważna uwaga : elementy tablicy sa numerowane od 0 a nie od 1. Tak więc pierwszy element ma numer 0, drugi 1 itd. Aby w miejscu deklaracji tablicy od razu umieścić w niej jakies dane należy zastosować poniższy zapis : int tablica[5] = 5, 2, 31, 55, 40;

Gdy już mamy jako takie pojęcie na temat tablic przyjrzyjmy się pierwszemu przykładowemu programowi w tej części kursu: char code tablica[4] = 0x55,0xAA,0x0F,0xF0; // definicja funkcji opóźniającej void czekaj(unsigned char x) // deklaracja dwóch zmiennych pomocniczych unsigned char a, b; // potrójnie zagnieżdzona pętla for // ta pętla zostanie wykonana x-razy for( ; x > 0; x--) // ta 10 razy for(a = 0; a < 10; ++a) // a ta 100 razy for(b = 0; b < 25; ++b); P0 = tablica[0]; P0 = tablica[1]; P0 = tablica[2]; P0 = tablica[3]; Program ten ma za zadanie genereować prostą sekwencję (przechowywaną właśnie w tablicy) odpowiednio zapalanych diod LED, podłączonychdo portu P0. Ponieważ poszczególne elementy tej tablicy nie będą się nigdy zmieniać (są to dane stałe), możemy ją umieścić w pamięci programu. Określa to słowo code przed nazwą tablicy. Po deklaracji tablicy pojawia się znajoma już nam funkcja opóźniająca czekaj, służąca do generowania opóźnień w wykonaniu programu. Program główny opiera się na wysyłaniu na port, do którego podłączone sa diody LED, kolejnych elementów tablicy zawierającej dane sterujace diodami. Gdy tablica składa się z niewielu elementów, to powyższy program jeszcze może zostać uznany za poprawny, ale w sytuacji gdy tablica będzie się składać z kilkunastu, lub nawet kilkudziesięciu elementów, to przepisywanie elementów z tablicy do portu należy już zrealizować w pętli. Przykładowa realizacja z użyciem pętli for przedstawiona jest poniżej : char code tablica[4] = 0x55,0xAA,0x0F,0xF0; // definicja funkcji opóźniającej void czekaj(unsigned char x) // deklaracja dwóch zmiennych pomocniczych unsigned char a, b; // potrójnie zagnieżdzona pętla for // ta pętla zostanie wykonana x-razy for( ; x > 0; x--)

// ta 10 razy for(a = 0; a < 10; ++a) // a ta 100 razy for(b = 0; b < 25; ++b); char i; for(i = 0; i < 4; i++) P0 = tablica[i]; Licznik pętli for jest jednocześnie indeksem elementu w tablicy. Wskaźniki Wskaźniki są bardzo ważnym elementem języka C. Ogólnie mówiąc wskaźnik jest zmienną przechowująca adres innej zmiennej. Wskaźniki deklarujemy w następujacy sposób : typ * nazwa; Aby uzyskac dostęp do zmiennej wskazywanej przez wskaźnik nalezy użyć operatora wyłuskania *, na przykład poniższa instrikcja : *wskaznik = 0; spowoduje zapisanie do zmiennej (a racezj do komórki pamieci) wskazywanej przez wskaznik liczby 0. Można sobie zadać pytanie jaki jest cel stosowania wskaźników, skoro wskazują ona na inną zmienną, jakby nie można było się posługiwać tylko tą zmienną. Wskaźniki odgrywają dużą rolę w przekazywaniu do funkcji parametrów, a ściślej mówiąc pozwalają na modyfikowanie parametru przekazanego do funkcji. Jednak na razie użyjemy wskaźików do innego celu, a mianowicie do dostępu do poznanej wcześniej tablicy z danymi. Przyjrzyjmy się poniższemu programowi : char code tablica[] = 0x55,0xAA,0x0F,0xF0; char code * wskaznik; // definicja funkcji opóźniającej void czekaj(unsigned char x) // deklaracja dwóch zmiennych pomocniczych unsigned char a, b; // potrójnie zagnieżdzona pętla for // ta pętla zostanie wykonana x-razy for( ; x > 0; x--) // ta 10 razy for(a = 0; a < 10; ++a) // a ta 100 razy for(b = 0; b < 25; ++b);

wskaznik = tablica; P0 = *wskaznik++; P0 = *wskaznik++; P0 = *wskaznik++; P0 = *wskaznik++; Realizuje on dokładnie taką samą funkcję jak pierwszy program z tej części kursu. Pierwszą zmianą w stosunku do poprzedniego programu jest deklaracja wskaźnika : char code * wskaznik; Zgodnie z tym, co pisałem o deklarowaniu wskaźnika wskazuje on na typ char umieszczony w pamięci programu code. Jednak samo zadeklarowanie wskaźnika nie pozwola nam na jego używanie. Wtym momecie nasz wskaźnik nie przechowuje żadnego adresu, a zwłaszcza adresu naszej tablicy. Przed jego użyciem, nalezy zapisać do niego adres tablicy. Dokonujemy tego w poniższy sposób : wskaznik = tablica; I tu powstanie małe zamieszanie, ponieważ tablica również jest wskaźnikiem! Tak więc powyższy zapis przepisuje do jednego wskaźnika zawartość innego wskaźnika. Aby w jawny sposób do wskaźnika przypisac adres jakiejś zmiennej należy użyć operator adresu &, tak jak pokazano poniżej : wskaznik = &tablica[0]; I tu znów małe zamieszanie, ponieważ chcemy do wskaźnika zapisać adres pierwszego elementu tablicy, do którego dostęp uzyskamy przez podanie w nawiasach kwadratowych jedo numeru. Gdybyśmy użyli takiego zapisu : wskaznik = &tablica; to kompilator zgłosi błąd, gdyż zapis ten ozancza przypisanie do wskaźnika adresu innego wskaźnika, a nie adresu danej typu char. Wiem, że to na początku jest bardzo skomplikowane, bowiem wskaźniki należą do najtrudniejszych zagadnień w języku C i bardzo często sprawiają problemy początkujacym programistom.