mikrokontrolery.net

 

   

Część 1 - Sterowanie diodami LED

W przykładzie tym poznamy sposób sterowania diodami LED. Zalecam użycie mikrokontrolera AT89S8252, lub innego w obudowie 40-końcówkowej. W tej sytuacji dostępnych jest 32 linie I/O, co pozwoli nam na swobodne poznawanie mikrokontrolera, bez martwienia się o brakujące porty.

Aby przeprowadzić ćwiczenie należy do portu P0 naszego mikrokontrolera podłączyć 8 diod LED w sposób pokazany na rysunku. Aby włączyć dowolną diodę LED należy na odpowiednim wyprowadzeniu portu ustawić stan niski.

Przykład 1.1.ASM

NAME P01_01_ASM_SRC
$INCLUDE (ATMEL/REG8252.INC)
P01_01_ASM SEGMENT CODE
RSEG P01_01_ASM
CLR P0.0 ; USTAWIENIE WYPROWADZENIA P0.0 W STAN NISKI
; POWODUJE ZAŚWIECENIE SIĘ DIODY LED D1
SJMP $ ; PĘTLA NIESKOŃCZONA
END

Przykład 1.1.C

#include <ATMEL/REG8252.H>
sbit LED1 = 0x80; // Deklaracja zmiennej o nazwie LED1
// Znajdujacej sie pod adresem 80h
// Co odpowiada pinowi 0 portu 0
void main(void)
{
LED1 = 0; // ustawienie pinu P0.0 w stan niski
while(1); // petla nieskonczona
}

Przykład 1.1.BAS

Reset P0.0
Do : Loop : End


Omówienie:


W przykładzie 1.1 włączenie diody LED D1 następuje przez ustawienie na wyprowadzeniu P0.0 stanu niskiego.

- W języku asemblera jest to zrealizowane przez użycie instrukcji "CLR P0.0". Instrukcja CLR (ang. Clear) służy wyłącznie do zerowania wskaĄnika przeniesienia lub dowolnego bitu adresowanego bezpośrednio, tzn. bitu w obszarze pamięci RAM adresowanym bitowo lub przestrzeni rejestrów SFR dostępnych bitowo (o adresie podzielnym przez 8).
- W języku C najpierw trzeba zadeklarować zmienną o wybranej nazwie (w podanym przykładzie "LED1"). Następnie za pomocą zwykłej instrukcji przypisania do zmiennej odpowiadającej danemu bitowi przypisujemy "0".
- W języku BASIC do zerowania bitu portu służy instrukcja "RESET".

W każdym z języków do zrealizowania danego celu (ustawienie wyprowadzenia P0.0 w stan niski) użyto jednej instrukcji tego języka. Zobaczymy teraz na ile instrukcji tłumaczone jest to polecenie i jak dużo miejsca w pamięci zajmuje każdy program. Pliki wynikowe programu (*.HEX) zostały zdiasemblowane przy użyciu programu D51.


Przykład 1.1.ASM.D51

;
; D51 V2.6 8051 Disassembly of asm.hex
; 10/11/2003 16:58
;
org 0
;
clr p0.0
X0002: sjmp X0002
;
end
;


Przykład 1.1.C.D51

;
; D51 V2.6 8051 Disassembly of c.hex
; 10/11/2003 16:58
;
org 0
;
ljmp X0003
;
X0003: mov sp,#8
clr a
mov r0,#0ffh
X0009: mov @r0,a
djnz r0,X0009
mov p2,#0ffh
ljmp X0012
;
X0012: clr p0.0
X0014: sjmp X0014
;
end
;


Przykład 1.1.BAS.D51

;
; D51 V2.6 8051 Disassembly of bas.hex
; 10/11/2003 16:58
;
org 0
;
ljmp X002e
;
reti
;
org 0bh
;
reti
;
org 13h
;
reti
;
org 1bh
;
reti
;
org 23h
;
reti
;
org 2bh
;
reti
;
org 2eh
;
X002e: mov r0,#0ffh
clr a
X0031: mov @r0,a
djnz r0,X0031
mov sp,#21h
mov 20h,#0
clr p0.0
X003c: ljmp X003c
;
clr ea
X0041: sjmp X0041
;
end
;


Prrogram napisany w asemblerze po zdiasemblowaniu składa się z dokładnie tych samych instrukcji, jakie zostały użyte w kodzie Ąródłowym.
Nieco gorzej jest z programem napisanym w C. Pierwszą instrukcją jest instrukcja "LJMP X0003", która w naszym przykładzie powoduje "przeskok" do następnej instrukcji z kolei, czyli ta instrukcja jest zbędna! Wynika z tego fakt, że kompilator nie sprawdza zakresu skoku i wstawia tą instrukcję zupełnie niepotrzebnie. Jeżeli już musi być ona użyta to lepszym rozwiązaniem byłoby użycie dwubajtowej "AJMP" lub "SJMP" zamiast trzybajtowej "LJMP". Z kolejnych siedmiu instrukcji składa się procedura inicjalizacji mikrokontrolera. Instrukcja "MOV SP, #8" ustawia wskaˇnik stosu na 8. Cztery kolejne instrukcje:

clr a
mov r0,#0ffh
X0009:
mov @r0,a
djnz r0,X0009

służą do wyzerowania wewnętrznej pamięci RAM mikrokontrolera, której zawartość po włączeniu zasilania może być nieokreślona. Kolejna instrukcja "MOV P2,#0FFh" powoduje ustawienie wszystkich wyprowadzeń portu P2 w stan wysoki. Listing programu inicjującego mikrokontroler znajduje się w katalogiu kompilatora i bardziej dociekliwi czytelnicy tego kursu mogą się z nim zapoznać. Następnie występuje instrukcja "LJMP X0012". I tutaj znowu powoduje ona przejście do następnej z kolei instrukcji, więc jest niepotrzebna. Petla nieskończona jest zrealizowana przy użyciu dwubajtowej instrukcji "SJMP X0014", czyli najodpowiedniejszej do tego celu.


Program napisany w Bascomie już wygląda dużo gorzej. Na początku występuje instrukcja skoku do procedury inicjalizującej procesor. Po tej instrukcji następuje kilka instrukcji powrotu z procedur obsługi przerwań. Jeżeli w naszym programie nie wykorzystujemy przerwań, to te instrukcje spokojnie można by było pominąć. Zaoszczędzilibyśmy kilkadziesiąt bajtów pamięci programu, co w niektórych przypadkach może być zbawienne. Procedura inicjalizująca procesor jest podobna do tej z C. Po procedurze inicjalizującej jest w końcu nasza instrukcja powodująca włączenie diody LED. Pętla nieskończona jest w tym przypadku zrealizowana przy użyciu trzybajtowej instrukcji "LJMP", chociaż dwubajtowa "SJMP" w zupełności by wystarczyła. Znowu kompilator nie sprawdza zakresu skoku. Następnie występuje instrukcja blokująca przerwania, pomimo niewykorzystywania układu przerwań w programie. Ta ostatnia isntrukcja trochę mnie dziwi, ponieważ nie zostanie ona nigdy wykonana! Program zapętli się na poprzedniej instrukcji realizującej pętlę nieskończoną , więc dwie ostatnie instrukcje w kodzie są zupełnie zbędne. Znowu marnotrawienie pamięci w przypadku BASCOMA.

Jak widać z powyższego porównania, najwięcej niepotrzebnych instrukcji generuje Bascom. W C jest o wiele lepiej niż w Bascomie, ale i tak dużo gorzej niż w asemblerze. Tylko ten jeden przykład jest w stanie uzmysłowić nam, jaka jest zaleta programowania w asemblerze - całkowita kontrola rozmiaru kodu wynikowego.

Ponieważ w pakiecie RIDE 51 występuje bardzo pożyteczna moim zdaniem opcja kompilowania programu poprzez program asemblera ( i generowania jednocześnie pliku *.SRC do każdego pliku *.C) w następnych przykładach wykorzystamy tą funkcję zamiast diasemblowania pliku *HEX (chyba, że zajdzie taka potrzeba).

Przykład 1.2
W tym przykładzie nauczymy się wykorzystywać aliasy nazw (tzn. będziemy danemu pinowi portu przypisywać inną nazwę i w programie odwoływać się do tego pinu poprzez tą nazwę).


Przykład 1.2.ASM

NAME P01_02_ASM_SRC
$INCLUDE (ATMEL/REG8252.INC)
LED1 EQU P0.0
P01_02_ASM SEGMENT CODE
RSEG P01_02_ASM
CLR LED1 ; USTAWIENIE WYPROWADZENIA P0.0 W STAN NISKI
; POWODUJE ZAŚWIECENIE SIĘ DIODY LED D1
SJMP $ ; PĘTLA NIESKOŃCZONA
END

Przykład 1.2.C
Ponieważ w C alias nadaliśmy już w pierwszym przykładzie nie ma wersji przykładu 1.2 w języku C.

Przykład 1.2.BAS

Led1 Alias P0.0
Reset Led1
Do : Loop : End

 

Ze względu na to, że przykład 1.2 nie różni się pod względem kodu wynikowego od przykładu 1.1 nie przeprowadzono diasemblacji i analizy kodu wynikowego. Przykałd 1.2 miał na celu przedstawienie wygodniejszego posługiwania się pinami. Polecam ten sposób odwoływania się do pinów ze względu na łatwiejsze wprowadzanie zmian do programu. Wyobraˇmy sobie sytację, gdy w programie liczącym kilkadziesiąt, lub nawet kilkaset instrukcji odwoływaliśmy się do pinów bezpośrednio przez ich nazwę i nagle musimy zmienić pin pełniący daną funkcję. Zamiast męczyć sie z tymi kilkuset instrukcjami i przeoczyć połowę z nich lub popełnić wiele innych błedów, zmianiamy tylko deklarację aliasu. Jest to rozwiązanie duzo prostsze, a pozatym daje ono bardziej czytelne programy. Bardziej wymowne jest stosowanie nazw typu: "LED1" czy "KLAWISZ1" zamiast P0.0 itp.

Przykład 1.3

Ten przykład prezentuje metodę ustawiania stanu niskiego na wyprowadzeniu za pomocą instrukcji iloczynu logicznego „ANL”.
Przyjrzyjmy się tabeli prawdy dla funkcji AND.

A
B
Y
0
0
0
0
1
0
1
0
0
1
1
1

Jak widać, wynikiem funkcji AND jest logiczna jedynka tylko wtedy, gdy obydwa parametry funkcji są logicznymi jedynkami. W pozostałych przypadkach wynikiem jest "0". Możemy to wykorzystać do włączenia diody, a nawet kilku diod za pomocą jednej instrukcji asemblera.

Przykład 1.3.ASM

NAME P01_03_ASM_SRC
$INCLUDE (ATMEL/REG8252.INC)
P01_03_ASM SEGMENT CODE
RSEG P01_03_ASM
ANL P0,#0FEh
SJMP $ ; PĘTLA NIESKOŃCZONA
END

 

Przykład 1.3.C

#include <ATMEL/REG8252.H>
void main(void)
{
P0 &= 0xFE;
while(1);
}


Przykład 1.3.BAS

P0 = P0 And &B11111110
Do : Loop : End

Ponieważ iloczyn logiczny jest wykonywany na całym porcie, a nie na pojedynczym bicie, możemy to wykorzystać do zmiany stanu kilku bitów portu jednocześnie. W ten sposób zaoszczędzimy miejsce w pamięci programu, bo zamiast użycia np. czterech dwubajtowych instrukcji "CLR bit" użyjemy jedną trzybajtową "ANL drect, #data", co przedstawia przykład 1.4.

Przykład 1.4

Przykład 1.4.ASM
NAME P01_03_ASM_SRC
$INCLUDE (ATMEL/REG8252.INC)
P01_03_ASM SEGMENT CODE
RSEG P01_03_ASM
ANL P0,#0AAh
SJMP $ ; PĘTLA NIESKOŃCZONA
END

 

Przykład 1.4.C

#include <ATMEL/REG8252.H>
void main(void)
{
P0 &= 0xAA;
while(1);
}

Przykład 1.4.BAS

P0 = P0 And &B10101010
Do : Loop : End


Jak widać, za pomocą tylko jednej instrukcji włączyliśmy cztery diody jednocześnie. Takie rozwiązanie jest bardzo estetyczne i użyteczne, chociaż z drugiej strony bardziej czytelne jest użycie pojedynczych instrukcji z poszczególnymi nazwami ustawianych pinów.

Ostatnio na forum:

 

 

 

 

 
 
 
 
 

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