mikrokontrolery.net

 

   

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 naszego programu :

// dołączenie pliku nagłówkowego
// zawierajacego definicje rejestrow
// wewnetrznych procesora
#include <8051.h>
// zdefiniowanie alternatywnej nazwy portu P2
#define klawiatura P2
// zdefiniowanie alternatywnej nazwy portu P0
#define diody P0

// główna funkcja programu
void main(void)
{
// 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;
}
}

}

Jak zapewne zauważyłeś, pojawiła się nowa dyrektywa 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 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ę 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 :

// dołączenie pliku nagłówkowego
// zawierajacego definicje rejestrow
// wewnetrznych procesora
#include <8051.h>
// 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
// główna funkcja programu
void main(void)
{
// 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;
}
}

}

Jako "nazwy jasno mówiące 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.

// dołączenie pliku nagłówkowego
// zawierajacego definicje rejestrow
// wewnetrznych procesora
#include <8051.h>
// 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

// główna funkcja programu
void main(void)
{
// 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;
}
}

}

Teraz nasz program wygląda 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).

 

 

 

 

 

 

 

 

 

 

 

 
 
 
 

(c) 2004-2008 Radosław Kwiecień
Polityka prywatności