PROGRAMOWANIE MIKROKONTROLERÓW

Podobne dokumenty
PROGRAMOWANIE MIKROKONTROLERÓW 8051 W JĘZYKU C

1 Podstawy c++ w pigułce.

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

1 Podstawy c++ w pigułce.

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.

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

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

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

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

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

Podstawy Programowania C++

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

Podstawy programowania w języku C

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

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

Mikrokontroler ATmega32. Język symboliczny

Podstawy programowania w języku C i C++

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Poradnik programowania procesorów AVR na przykładzie ATMEGA8

Część 4 życie programu

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?

Programowanie strukturalne i obiektowe

2 Przygotował: mgr inż. Maciej Lasota

Programowanie komputerowe. Zajęcia 4

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

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

Pętla for. Wynik działania programu:

Programowanie w języku Python. Grażyna Koba

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

Lab 9 Podstawy Programowania

4. Funkcje. Przykłady

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

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

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

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

7. Pętle for. Przykłady

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

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

Klawiatura matrycowa

Przedrostkowa i przyrostkowa inkrementacja i dekrementacja

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

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

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

Pytania sprawdzające wiedzę z programowania C++

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

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

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

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:

Informatyka I: Instrukcja 4.2

Warsztaty dla nauczycieli

Ćwiczenie 1. Wprowadzenie do programu Octave

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

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

Odczyt danych z klawiatury Operatory w Javie

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

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

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

Lekcja : Tablice + pętle

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

Mikrooperacje. Mikrooperacje arytmetyczne

Język ludzki kod maszynowy

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

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

1. Operacje logiczne A B A OR B

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

Algorytmy i struktury danych

I - Microsoft Visual Studio C++

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

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

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

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

PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH. KL IV TI 6 godziny tygodniowo (6x15 tygodni =90 godzin ),

Ćwiczenie 1. Wprowadzenie do programu Octave

Języki skryptowe w programie Plans

Język C++ zajęcia nr 2

Języki programowania zasady ich tworzenia

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

Wprowadzenie do programowania w języku C

Pascal - wprowadzenie

Zmienne, stałe i operatory

Podstawy programowania - 1

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

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

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

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

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

Bloki anonimowe w PL/SQL

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

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

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

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

Wstęp do informatyki- wykład 9 Funkcje

Język C, tablice i funkcje (laboratorium)

Wskaźniki w C. Anna Gogolińska

1 Wielokrotne powtarzanie tych samych operacji

PODSTAWY INFORMATYKI 1 PRACOWNIA NR 6

Pętle. for, while, do... while, foreach. Materiał pomocniczy do kursu Podstawy programowania Autor: Grzegorz Góralski ggoralski.

Instrukcja do ćwiczeń

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

Transkrypt:

PROGRAMOWANIE MIKROKONTROLERÓW

Wprowadzenie 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 można od razu zobaczyć na własne oczy. Ja zwykle, gdy zaczynam pracę z nowym mikrokontrolerem, piszę program, który zapala diodę LED. W ten sposób można najszybciej przekonać się o poprawnym działaniu programu. Do mikrokontrolera należy podłączyć 8 diod LED w sposób pokazany na rysunku : Wartości rezystorów należy dobrać odpowiednio do posiadanych diod LED. Jeśli są to diody standardowe, to rezystancja rezystorów powinna mieć wartość ok. 330 Ohm. Natomiast gdy dysponujemy diodami niskoprądowymi, to rezystancja rezystorów może mieć wartość ponad 1 kohm (należy zwrócić także uwagę na wydajność prądową portów mikrokontrolera).

Operator przypisania. W tym podtemacie zapoznamy 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, bądź stałej. // zawierającego definicje rejestrów // wewnętrznych procesora // zapisanie do portu P0 liczby 0x55 P0 = 0x55; // pusta pętla nieskończona - zatrzymanie programu while(1); Na początku musimy dołączyć plik nagłówkowy z definicjami rejestrów procesora. Gdybyśmy tego nie zrobili, to nie moglibyśmy odwoływać 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 standardowym 8051, to musimy dołączyć odpowiedni dla danego mikrokontrolera plik nagłówkowy. W sytuacji, gdy używamy tylko typowych rejestrów można spokojnie zastosować 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ą funkcji mówi kompilatorowi, że funkcja nie zwraca wartości, albo inaczej mówiąc, że zwracana wartość jest typu void (pusty, brak typu). Słowo void w nawiasach okrągłych po nazwie funkcji mówi kompilatorowi, że do funkcji nie jest przekazywana żadna wartość. W zależności od tego, jaką funkcję chcemy napisać 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) - funkcja 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 możliwych kombinacji jest bardzo dużo i zależą one od tego, jakie zadania ma spełniać dana funkcja. Gdy już mamy szkielet programu, to należy wpisać właściwy kod programu. W naszym pierwszym programie w zasadzie decydujące znaczenia 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 szesnastkowym w języku C zapisujemy właśnie w podany sposób : liczbę szesnastkową (bez znaku 'h' na końcu) należy poprzedzić 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. Ponieważ 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 : // zawierajacego definicje rejestrow // wewnetrznych procesora // iloczyn logiczny portu P0 ze stałą 55h P0 &= 0x55; // pusta pętla nieskończona - zatrzymanie programu while(1); // zawierającego definicje rejestrów // wewnętrznych procesora // iloczyn logiczny portu P0 ze stałą 55h P0 &= 0x55; // pusta pętla nieskończona - zatrzymanie programu while(1);

Funkcje arytmetyczne i logiczne Funkcja Zapis skrócony Zapis normalny Język C umożliwia stosowanie skróconych wyrażeń, będących połączeniem operatora przypisania z operatorami arytmetycznymi, bądź logicznymi. Możliwe są następujące skrócone formy zapisu wyrażeń : 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 a <<= b a = a << b przesunięcie w prawo a >>= b a = a >> b alternatywa logiczna a ^= b a = a ^ b Do ustawiania linii portów służy 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ć odpowiedni 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 : W naszym programie pojawił się dodatkowy wiersz kodu : P0 = 0xF0; Ponownie zastosowałem skrócony zapis łączący operator przypisania z operatorem sumy logicznej. 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ęc zaraz przed ich ustawieniem je wyzerować. Samo ustawienie wybranych linii w stan wysoki realizuje poniższy kod :

// zawierającego definicje rejestrów // wewnętrznych procesora // ustawienie wszystkich linii portu P0 w stan niski P0 = 0; // suma logiczna portu P0 ze stałą F0h P0 = 0xF0; // pusta pętla nieskończona - zatrzymanie programu while(1); 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ęc zaraz przed ich ustawieniem je wyzerować. Samo ustawienie wybranych linii w stan wysoki realizuje poniższy kod : P0 = 0xF0; Ponownie zastosowałem skrócony zapis łączący operator przypisania z operatorem sumy logicznej. Nie są wymagane żadne dodatkowe zmienne pośredniczące w przesyłaniu danych z portu P3 do portu P0.

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 poniższym 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 wymusza na wyprowadzeniu portu stan niski. Port musi zawierać wewnętrzne rezystory podciągające linie portu do szyny zasilającej. W przeciwnym razie będziemy 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 należy się upewnić, że port do którego chcemy podłączyć klawiaturę posada rezystory podciągające.

Pierwszy program wykorzystujący klawiaturę jest bardzo prosty i zamieszczony poniżej : // zawierającego definicje rejestrów // wewnętrznych procesora // pętla nieskończona while(1) // 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 LEDpo naciśnięciu dowolnego przycisku. zauważmy, że naciśniecie przycisku wywołuje wymuszenie na odpowiednim wyprowadzeniu portu p3 stanu niskiego, a zapalenie diody LEDjest 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 żadnych dodatkowych zabiegów. jest to zrealizowane przez instrukcję : p0 = p3;

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 : // zawierajacego definicje rejestrow // wewnetrznych procesora // Pętla nieskończona while(1) // 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 się 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 języku 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ępująco : for(inicjalizacja zmiennej licznikowej; warunek; modyfikacja zmiennej 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 // zawierającego definicje rejestrów // wewnętrznych procesora // 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 while(1) // zapal odpowiednią kombinacje diod LED P0 = 0x55; // odczekaj pewien okres czasu czekaj(250); // zapal inną kombinację diod LED P0 = 0xAA; // odczekaj pewien okres czasu czekaj(250);

Po raz pierwszy stworzyliśmy własną funkcję. Zgodnie z tym, co napisałem w pierwszej części kursu, funkcja czekaj nie zwraca żadnej wartości (void) i wymaga jednego parametru typu unsigned char (liczba 8-bitowa bez znaku). Parametrem tym będzie żądana przez nas długość opóźnienia (mniej-więcej w milisekundach). Przyjrzyjmy się 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żdżonych pętli for. W wyniku tego łączny czas wykonywania tych pętli jest iloczynem powtórzeń każdej pętli. Dokładny czas opóźnienia trudno jest określić, ponieważ ze względu na różne techniki optymalizacji kodu przez kompilator nie jest znany dokładny czas wykonywania jednej pętli. Można co prawda odczytać z pliku *.asm generowanego przez kompilator jakie instrukcje zostały użyte 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, bądź 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 generowaniu przybliżonych 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 rozbudowanego bloku instrukcji if-else wygodniej jest zastosować instrukcję switch. Ogólna postać 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 być dowolnym wyrażeniem bądź zmienną, pod warunkiem że wartość tej zmiennej lub wyrażenia jest typu całkowitego. Nasz przykładowy program niech realizuje następującą funkcję : po naciśnięciu przycisku zapal przeciwną diodę LED (tzn. "od drugiej strony").

Przyjrzyjmy się kodowi: // zawierajacego definicje rejestrow // wewnetrznych procesora // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 // pętla nieskończona while(1) // 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; - #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 dyrektywę #define, to zastąpi ten ciąg, ciągiem do niego przypisanym. Dzięki temu możemy zamiast niewiele znaczących nazw portu używać 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 while(1). 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ę instrukcja switch. Jako zmienną tej instrukcji wykorzystamy 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 należy 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ć instrukcję 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 należy wcześniej zdefiniować stałą o nazwie jasno mówiącej o jej przeznaczeniu. Poza tym, 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 miejscach w których należy ją zmienić będzie bardzo kłopotliwe. Tym bardziej, że nie będziemy mogli użyć mechanizmu "znajdź/zamień", ponieważ łatwo zmienimy nie ten znak co trzeba. Bardziej elegancka (oczywiście nie najbardziej - ta będzie za chwile) wersja programu jest przedstawiona obok : // zawierającego definicje rejestrów // wewnętrznych procesora // 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 // zawierającego definicje rejestrów // wewnętrznych procesora // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zdefiniowanie alternatywnej nazwy portu P0 #define diody P0 //

o przeznaczeniu stałej" wybrałem pozx, gdzie x = 1..8. Jak łatwo można się domyśleć "poz" to skrót od "pozycja". Ponieważ stałe liczbowe odpowiadające przyciskom, jaki diodom LED sa identyczne nie zastosowałem rozróżnienia czy chodzi o pozycję przycisku czy diody LED. Jednak już naprawdę elegancko będzie, gdy użyjemy odpowiednich stałych dla diod i przycisków osobno #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 while(1) // 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;

// zawierajacego definicje rejestrow // wewnetrznych procesora // 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. // zawierajacego definicje rejestrow // wewnetrznych procesora // 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 while(1) // 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;

cd z poprzedniej strony // zawierającego definicje rejestrów // wewnętrznych procesora // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zawierającego definicje rejestrów // wewnętrznych procesora // 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 while(1) // zgaszenie diod LED diody = 0xFF; // w zależności 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; Z 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 strategicznych" 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łędów).

cd z poprzedniej strony // zawierającego definicje rejestrów // wewnętrznych procesora // zdefiniowanie alternatywnej nazwy portu P2 #define klawiatura P2 // zawierającego definicje rejestrów // wewnętrznych procesora // 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 while(1) // zgaszenie diod LED diody = 0xFF; // w zależności 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; Z 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 strategicznych" 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łędó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 jakieś dane należy zastosować poniższy zapis : int tablica[5] = 5, 2, 31, 55, 40; 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żdżona 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); while(1) P0 = tablica[0]; czekaj(250); P0 = tablica[1]; czekaj(250); P0 = tablica[2]; czekaj(250); P0 = tablica[3]; czekaj(250);

Program ten ma za zadanie generować prostą sekwencję (przechowywaną właśnie w tablicy) odpowiednio zapalanych diod LED, podłączonych do 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 sterujące 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 pętli: 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żdżona 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; while(1) for(i = 0; i < 4; i++) P0 = tablica[i]; czekaj(250);

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ępujący sposób : typ*wskaźnik = 0; Aby uzyskać dostęp do zmiennej wskazywanej przez wskaźnik należy użyć operatora wyłuskania *, na przykład poniższa instrukcja : typ * nazwa; spowoduje zapisanie do zmiennej (a raczej do komórki pamięci) wskazywanej przez wskaźnik 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źnikó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); while(1) wskaznik = tablica; P0 = *wskaznik++; czekaj(250); P0 = *wskaznik++; czekaj(250); P0 = *wskaznik++; czekaj(250); P0 = *wskaznik++; czekaj(250);

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 pozwolą nam na jego używanie. W tym momencie nasz wskaźnik nie przechowuje żadnego adresu, a zwłaszcza adresu naszej tablicy. Przed jego użyciem, należy 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 przypisać adres jakiejś zmiennej należy użyć operator adresu &, tak jak pokazano poniżej : 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 jego numeru. Gdybyśmy użyli takiego zapisu : wskaznik = &tablica; często sprawiają problemy początkującym programistom. to kompilator zgłosi błąd, gdyż zapis ten oznacza 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