mikrokontrolery.net

 

   

Cześć 2 - obsługa klawiatury

W ćwiczeniu tym zapoznamy się z obsługą prostej klawiatury. Aby przeprowadzić to ćwiczenie musimy do portu P2 podłączyć przełączniki zwierające wyprowadzenia portu do masy w momencie naciśnięcia przycisku, tak jak pokazano na rysunku . Podłączenie diod LED jest identyczne jak w przykłądzie poprzednim. Nie nadaje się do tego celu port P0 ze względu na brak wewnętrznych rezystorów podciągających. Jeżeli chcemy do portu P0 podłączyć klawiaturę to musimy "podciągnąć" piny porto P0 do plusa zasilania poprzez dolączenie pomiędzy wyprowadzenia portu a szynę zasilającą rezystorów o wartości kilku- do kilkudziesięciu kiloomów.

Przykład 2.1


Program przedstawiony w tym przykładzie kopiuje stan linii portu P2 do portu P0, czyli naciśnięcie przycisku będzie sygnalizowane zaświeceniem się odpowiedniej diody LED. Dioda będzie się świeciła tylko czasie przyciśnięcia przycisku. Po jego puszczeniu diada zgaśnie.

Przykład 2.1.ASM


NAME P02_01_ASM_SRC

$INCLUDE (ATMEL/REG8252.INC)

P02_01_ASM SEGMENT CODE

RSEG P02_01_ASM
PETLA:
MOV P0,P2
SJMP PETLA
END

Przykład 2.1.C

#include <ATMEL/REG8252.h>

void main(void)
{
while(1)
P0 = P2;
}

Przykład 2.1.BAS

Do
P0 = P2
Loop
End

Omówienie

W powyższym przykładzie stan linii portu P2 jest kopiowany do portu P0.

  • W języku asemblera zostało to zrealizowane przy pomocy instrukcji "MOV direct1, direct2" , kopiującej zawartośćˇ komórki wewnętrznej pamięci RAM z obszaru adresowanego bezpośrednio lub z obszaru rejestrów SFR do komórki pamięci, lub rejestru SFR w tym samym obszarze. W celu nieskończonego powtarzania tej instrukcji zastosowano instrukcję skoku krótkiego "SJMP adr" wykonującą skok do etykiety "PETLA"
  • W języku C zadanie to zostało zrealizowane poprzez przypisanie do portu P0 wartości portu P2. Pętla nieskończona została zrealizowana poprzez użycie instrukcji "while(1)"
  • W języku Basic instrukcja przypisania wartości portu P2 do portu P0 została ujęta pomiędzy instrukcje "DO" i "LOOP", które realizują pętlę nieskończoną.

Przeprowadˇmy teraz analizę kodu wynikowego powyższego programu wygenerowanego poprzez każdy z użytych kompilatorów.

Przykład 2.1.ASM.D51
;
; D51 V2.6 8051 Disassembly of asm.hex
; 10/28/2003 19:06
;
org 0
;
X0000: mov p0,p2
sjmp X0000
;
end
;

Przykład 2.1.C.D51
main:
?WHILE1:
; SOURCE LINE # 6
;$@$C_SOURCE_LINE(6)
MOV P0,P2
SJMP ?WHILE1

; END OF main

Przykład 2.1.BAS.D51
;
; D51 V2.6 8051 Disassembly of bas.hex
; 10/28/2003 19:06
;

. ; Pominięte zostały instrukcje powrotu
. ; z podprogramów obsługi przerwań
.


X002e: mov r0,#0ffh
clr a
X0031: mov @r0,a
djnz r0,X0031
mov sp,#21h
mov 20h,#0

; Tutaj rozpoczyna się właściwy program

X003a: mov p0,p2
ljmp X003a
;
clr ea
X0042: sjmp X0042
;
end

Kompilator każdego z języków użył do zrealizowania podanego zadania jednej instrukcji "MOV direct1, direct2", a więc zrealizował to zadanie optymalnie. Jedyną wątpliwość budzi użycie przez Bascoma trzybajtowej instrukcji "LJMP" do zrealizowania pętli nieskończonej. Ponieważ skok następuje do poprzedniej instrukcji wystarczyło użyć dwubajtową instrukcję "SJMP" , ewentualnie także dwubajtową instrukcję "AJMP". Świadczy to o tym, że Bascom nie sprawdza zakresu skoku i zawsze wstawia instrukcję obejmującą największy obszar pamięci i niepotrzebnie zajmującą jeden bajt pamięci prgramu więcej. Natomiast kompilator jezyka C użył do zrealizowania pętli najbardziej optymalnej instrukcji "SJMP".

Przykład 2.2


Program przedstawiony w tym przykładzie po naciśnięciu przycisku S1 zapala diodę D1, a po naciśnięciu przycisku S8 gasi tę diodę.

Przykład 2.2.ASM


NAME P02_02_ASM_SRC

$INCLUDE (ATMEL/REG8252.INC)

P02_01_ASM SEGMENT CODE

RSEG P02_01_ASM

S1 EQU P2.0
S8 EQU P2.7
LED EQU P0.0

START:
JB S1, DALEJ
CLR LED

DALEJ:
JB S8, START
SETB LED

SJMP START

END

Przykład 2.2.C


#include <ATMEL\REG8252.H>

sbit S1 = P2^0;
sbit S2 = P2^7;
sbit LED1 = P0^0;

void main(void)
{
while(1)
{
if (S1 == 0) LED1 = 0;
if (S2 == 0) LED1 = 1;
}
}

Przykład 2.2.BAS

S1 Alias P2.0
S2 Alias P2.7
LED Alias P0.0
Do
If S1 = 0 Then Reset LED
If S2 = 0 Then Set LED
Loop
End

W tym przykładzie doskonale widać, który z języków oferuje najlepszą czytelność kodu. Asembler oczywiście nie zapewnia tej jakże ważnej cechy. Moim zdaniem najbardziej czytelny jest program napisany w języku C, choć zwolennicy Bascoma zapewne nie przyznają mi racji.

Przykład 2.3

Program w tym przykładzie po naciśnięciu przycisku S1 zapala diodę D1 a po ponownym naciśnięciu przycisku S1 gasi tą diodę.

Przykład 2.3.ASM
NAME P02_02_ASM_SRC

$INCLUDE (ATMEL/REG8252.INC)

P02_01_ASM SEGMENT CODE

RSEG P02_01_ASM

S1 EQU P2.0
LED EQU P0.0

START:
JB S1, START
CPL LED
SJMP START

END

Przykład 2.3.C
#include <ATMEL\REG8252.H>

sbit S1 = P2^0;
sbit LED1 = P0^0;

void main(void)
{
while(1)
{
if (S1 == 0)
LED1 = !LED1;
}
}

Przykład 2.3.BAS
S1 Alias P2.0
Led1 Alias P0.0
Do
If S1 = 0 Then Led1 = Not Led1
Loop
End

Opis:
Nasz program nie zachowuje się tak, jak powinien. Po naciśnięciu przycisku dioda się zapala w sposób przypadkowy. Gdy przycisk przytrzymamy wciśnięty trochę dłużej, to jasność świecenia diody spada, co świadczy o tym, że dioda jest na przemian zapalana i gaszona. Przyczyną tego jest ogromna szybkość wykonywania programu przez mikrokontroler, wynosząca ok. 1 milion instrukcji na sekundę. Aby temu zapobiec należy wstawić pętlę opóˇniającą.

Przykład 2.4.ASM
NAME P02_04_ASM_SRC
$INCLUDE (ATMEL/REG8252.INC)
P02_04_ASM SEGMENT CODE
RSEG P02_04_ASM
S1 EQU P2.0
LED EQU P0.0

START:
JB S1, START
CPL LED
ACALL CZEKAJ
SJMP START

CZEKAJ:
MOV R1,#255
L2:
MOV R0,#255
L1:
DJNZ R0,L1
DJNZ R1,L2
RET
END

Przykład 2.4.C
#include <ATMEL\REG8252.H>
sbit S1 = P2^0;
sbit LED = P0^0;

void czekaj(void)
{
char x,y;
for (x = 255; x > 0; --x)
for (y = 255; y > 0; --y);
}

void main(void)
{
while(1)
{
if (S1 == 0)
{
LED = !LED;
czekaj();
}
}
}

Przykład 2.4.BAS
S1 Alias P2.0
Led1 Alias P0.0
Do
If S1 = 0 Then Led1 = Not Led1
waitms 200
Loop
End

Opis:
Przyjrzyjmy się procedurze opóˇniającej napisanej w języku asemblera:
CZEKAJ:
MOV R1,#255
L2:
MOV R0,#255
L1:
DJNZ R0,L1
DJNZ R1,L2
RET

Instrukcja "MOV R1, #255" służy do ustawienia licznika pętli L2. Instrukcja "MOV R0, #255" służy do ustawienia licznika pętli L1. Użycie dwóch pętli zagnieżdżonych podyktowane jest małym zakresem powtórzeń realizowanych przez instrukcję "DJNZ" (maksymalnie 256 razy, gdy licznik pętli ustawimy na "0"). Do zrealizowania pętli służy instrukcja "DJNZ Rn, rel". W naszym przykładzie w pierwszej kolejności jest realizowana pętla L1 (255 powtórzeń, co daje ok. 510 us, przy zegarze 12 MHz). Pętla ta jest z kolei powtarzana 255 razy w pętli L2. W sumie daje to opóˇnienie ok. 130 ms.

W języku C procedura opóˇniająca wygląda następująco:

void czekaj(void)
{
char x,y;
for (x = 255; x > 0; --x)
for (y = 255; y > 0; --y);
}

W pierwszym wierszu procedury deklarujemy dwie zmienne, które posłużą nam jako liczniki pętli. Instrukcja "for(x = 255; x >0; --x)" realizuje pętlę, w której jest zagnieżdżona druga pętla, podobna do pierwszej. Skonstruowanie tych pętli, jako liczących "w dół" narzucone zostało przez architekturę mikrokontrolera i występowanie w języku asemblera instrukcji pętli wykorzystujących dekrementację licznika pętli. Daje to bardziej zwięzły kod wynikowy niż z realizująca tą samą liczbę powtórzeń pętla "for(x = 0; x < 255, ++x)".

W języku BASCOM do opóˇnienia wykorzystana została specjalnie do tego celu przeznaczona instrukcja "WAITMS x". Jest ona najprostsza w użyciu, bowiem podajemy liczbowo czas opóˇnienia w milisekundach nie musimy mozolić się z obliczaniem ilości powtórzeń i czasu realizowania pętli, jak w przypadku C i asemblera.

Porównajmy teraz kod wynikowy tych trzech programów. Już samo porównanie rozmiaru plików *.BIN daje nam jasny obraz efektywności każdego z trzech języków:
- plik generowany przez BASCOM - 101 B
- plik generowany przez RC51 - 37 B
- plik generowany przez RA51 - 18 B
Program napisany w BASCOM’ie jest ponad 5,6 razy większy od programu napisanego w asemblerze spełniającego tą samą funkcję. Dokładnie tyle samo te programy zajmują pamięci w mikrokontrolerze.

Przykład 2.4.BAS.D51
;
; D51 V2.6 8051 Disassembly of bas.hex
; 2/16/2004 13:07
;

. ; Pominięte zostały instrukcje powrotu
. ; z podprogramów obsługi przerwań
.

X002e: mov r0,a
X002f: mov a,#0adh
X0031: inc a
nop ; data truncated
;
org 3ah
;
jnz X0031
nop
djnz r0,X002f
ret
;
X0040: mov r0,#0ffh
clr a
X0043: mov @r0,a
djnz r0,X0043
mov sp,#21h
mov 20h,#0
X004c: mov c,p2.0
mov 20h.4,c
jnc X0055
ljmp X005a
;
X0055: mov c,p0.0
cpl c
mov p0.0,c
X005a: mov a,#0c8h
acall X002e
ljmp X004c
;
clr ea
X0063: sjmp X0063
;
end
;


Jak widać kod wynikowy programu napisanego w Bascomie jest zagmatwany i mało czytelny.

 

 

 

 

 

 

 

 

 

 

 
 
 
 

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