Kurs Asemblera na AVR, AVR Assembler


Przykład 1 - Przerzutnik RS
Najprostszym przykładem wkorzystania portów wejścia/wyjścia jest układ, który pełniłby funkcję przerzutnika RS. Naciskając przycisk S1 zapalamy kropkę dziesiętną wyświetlacza, a naciskając S2 gasimy ją. Algorytm działania programu jest przedstawiony na poniższym rysunku :

0x01 graphic

Kod Ľrdłowy programu przedstawiony jest poniżej :

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 1 - przerzutnik RS

.include "2313def.inc"
.def acc = r16 ; nadanie rejestrowi r16 nazwy symbolicznej
.cseg
.org 0x00

ldi acc, 0b11111111 ; załaduj do acc liczbę 255 (0xff)
out DDRB, acc ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjść
out PORTB, acc

ldi acc, 0b1111100 ;
out DDRD, acc ; piny D0 i D1 są ustawione jako wejścia
; pozostałe jako wyjścia
ldi acc, 0b1110111
out PORTD, acc ; włączony pierwszy wyświetlacz, wejścia
; podciągnięte do zasilania

sprawdz_klawisze:
sbis PIND, 0 ; jeśli pin D0 = 1, to pomiń następną instrukcję
cbi PORTB, 7
sbis PIND, 1 ; jeśli pin D1 = 1, to pomiń następną instrukcję
sbi PORTB, 7
rjmp sprawdz_klawisze ; skocz do początku sprawdzania klawiszy

 


Instrukcja LDI (Load Immediate) powoduje załadowanie do rejestru roboczego (r16-r31) ośmiobitowej stałej.

Składnia:

Operandy:

Licznik rozkazów:

(i) LDI Rd

16 <= d <=31, 0 <= K <= 255

PC <- PC + 1

Kod instrukcji:
1110 KKKK dddd KKKK


Komentarz:
W naszym przypadku ładujemy do rejestru r16 liczbę 255 (wpisujemy same jedynki).

Instrukcja OUT (Store Register to I/O Location) powoduje zapamiętanie zawartości rejestru roboczego (r0-r31) w rejestrze z obszaru rejestrów wejścia-wyjścia.

Składnia

Operandy

Licznik rozkazów

(i) OUT A,Rr

0 <= r<= 31 0 <= A<= 63

PC = PC + 1

Kod instrukcji:
1011 1Aar rrrr AAAA


Komentarz:
Liczbę uprzednio wpisaną do rejestru r16 wpisujemy do rejestru DDRB, w celu określenia funkcji, jaką mają pełnić poszczególne piny portu B. Niestety nie można bezpośrednio załadować liczby do rejestrów wejścia-wyjścia. Następnie wpisujemy tą samą liczbę do rejestru PORTB, co spowoduje ustawienie na wszystkich pinach stanu wysokiego. Wszystkie segmenty wyświetlacza będą zgaszone.

Instrukcja SBIS (Skip if Bit in I/O Register is Set) powoduje pominięcie następnej instrukcji, jeżeli bit w rejestrze wejścia-wyjścia jest ustawiony.

Składnia

Operandy

Licznik rozkazów

(i)SBIS A,b

0 <=A <= 31, 0 <= b <= 7

PC = PC + 1, Warunek nie spełniony
PC = PC + 2, Pominięcie instrukcji jednosłowowej
PC = PC + 3, Pominięcie instrukcji dwusłowowej


Kod instrukcji:
1001 1011 AAAA Abbb


Komentarz:
Piny D0 i D1 są skonfigurowane jako wejścia i podciągnięte do plusa zasilania przez wewnętrzne rezystory. Uaktywnienie wejścia jest interpretowane jako zwarcie go do masy, tak więc stan niski na danej końcówce wejściowej oznacza naciśnięcie przycisku. Jeżeli pin wejściowy jest w stanie wysokim, oznacza to, że przycisk nie został naciśnięty. Tak więc nie powinna zostać podjęta żadna akcja, czyli mikrokontroler pomija kolejną instrukcję. W naszym przypadku reakcja na naciśnięcie przycisku jest obsłużona przez jedną instrukcję. W przypadku, gdy konieczne jest zastosowanie większej liczby instrukcji, należy zorganizować fragment obsługi w trochę inny sposób. Mianowicie za instrukcją SBIS należy umieścić instrukcję wywołującą procedurę, np. RCALL, i cały fragment kodu obsługujący sposób reakcji na naciśnięcie przycisku należy umiescić w tej procedurze.

Instrukcja CBI (Clear Bit in I/O Register) powoduje wyzerowanie bitu w rejestrze wejścia-wyjścia.

Składnia

Operandy

Licznik rozkazów

CBI A, b

0 <= A <= 31, 0 <= b <= 7

PC <= PC + 1


Kod instrukcji:
1001 1000 AAAA Abbb

Komentarz:
Chcemy, aby po naciśnięciu przycisku S1 zapalona została kropka dziesiętna na wyświetlaczu. Aby to osiągnąć należy ustawić na pinie B7 stan niski.

Instrukcja SBI (Set Bit in I/O Register) powoduje ustawienie bitu w rejestrze wejścia-wyjścia.

Składnia

Operandy

Licznik rozkazów

SBI A, b

0 <= A <= 31, 0 <= b <= 7

PC <= PC + 1

Kod instrukcji:
1001 1010 AAAA Abbb

Komentarz:
Po naciśnięciu przycisku S2 należy zgasić wcześniej zapaloną kropkę. Realizujemy to poprzez ustawienie pinu B7 w stan wysoki.

Instrukcja RJMP (Relative Jump) jest instrukcją bezwarunkowego skoku względnego pod adres z zakresu od PC - 2K + 1 do PC + 2K.

Składnia

Operand

Licznik rozkazów

Stos

(i) RJMP k

2K <=k < 2K

PC =PC + k + 1

Niezmieniony

Kod instrukcji:
1100 kkkk kkkk kkkk

Czas wykonania tej instrukcji - 2 cykle zegarowe.

Program zajmuje 24 bajty w pamięci programu. Dla porównania program realizujący identyczną funkcję napisany w Bascomie zajmuje 138 bajtów, czyli prawie sześciokrotnie więcej! Ukazuje to ogromną przewagę asemblera nad Bascomem, czy w ogóle nad językami wyższego poziomu. Poza tym progam napisany w asemblerze wykonuje się szybciej, a już na pewno mamy pełną kontrolę nad czasem wykonania poszczególnych fragmentów programu, czego nie można powiedzieć o Bascomie.

Przykład 2 - Układ czasowy
Po naciśnięciu przycisku S1 kropka wyświetlacza zapala się na ok. 1 sekundę. Algorytm działania programu przedstawiono na poniższym rysunku :

0x01 graphic

Kod źródłowy zamieszczony jest poniżej :

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 2 - Układ czasowy

.include "2313def.inc"
.def acc = r16 ; nadanie rejestrowi r16 nazwy symbolicznej
.cseg
.org 0x00

ldi acc, 0b11111111 ; załaduj do acc liczbę 255 (0xff)
out DDRB, acc ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjście
out PORTB, acc

ldi acc, 0b1111100 ;
out DDRD, acc ; piny D0 i D1 są ustawione jako wejścia
; pozostałe jako wyjścia
ldi r16, 0b1110111
out PORTD, acc

ldi acc, 127 ;
out SPL, acc ; ustawienie wskaˇnika stosu na 127

sprawdz_klawisze:
sbis PIND, 0 ; jeśli przycisk S1 nie jest wciśnięty
; to pomiń następną instrukcję
rcall led ; w przeciwnym wypadku wywołaj procedurę
; zapalającą diodę
rjmp sprawdz_klawisze ; powróć na początek sprawdzania klawiszy

led:
cbi PORTB, 7 ; zapal kropkę wyświetlacza
ldi acc, 250 ; załaduj do acc czas opóˇnienia
rcall waitms ; i wywołaj procedurę opóˇniającą
rcall waitms ; czterokrotne wywołanie opóˇnienia
rcall waitms ; 250ms da w sumie opóˇnienie ok 1s
rcall waitms ;
sbi PORTB, 7
ret

; Przybliżone czasy opóźnień są podane dla
; rezonatora 4MHz
.def licz1 = r20
.def licz2 = r21
.def licz3 = r22
waitms:
mov licz3, acc ; ustaw czas opóĽnienia
; powtórz n razy pętlę L,
; co da opóˇnienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóĽnienie ok 1ms
ldi licz2, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóĽnienie ok. 100 us
ldi licz1, 100
L1:
nop ; 1 cykl
dec licz1 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec licz2
brne l0
; koniec pętli L0
dec licz3
brne L
; koniec pętli L
ret

Pierwsza część programu składa się z instrukcji poznanych w przykładzie wcześniejszym, więc nie będę ich tutaj ponownie opisywał.

Instrukcja MOV (Copy registers) kopiuje zawartość jednego rejestru do drugiego. Zawartość rejestru ˇródłowego nie ulega zmianie.

Składnia rozkazu

Operandy

Licznik rozkazów:

(i) MOV Rd,Rr

0 <= d <=31, 0 <= r <= 31

PC <- PC + 1

Kod operacji :

0010 11rd dddd rrrr

Komentarz:
Wartość opóˇnienia przekazujemy przez rejestr r16 i kopiujemy jego zawartość do r22, czyli do licznika pętli. Takie rozwiązanie zostało zastosowane, w celu zachowania zawartości rejestru r16. Pozwoli to na kilkukrotne wywołąnie procedury opóˇniającej o tą samą wartość opóˇnienia bez konieczności każdorazowego ładowania czasu opóˇnienia.

Instrukcja NOP (No operation) nie wykonuję żadnych czynności poza zwiększeniem zawartości licznika rozkazów o 1. Instrukcja jest wykorzystywana do generowania opóˇnień czasowych.

Składnia rozkazu

Operandy

Licznik rozkazów:

(i) NOP

brak

PC <- PC + 1

Kod operacji :

0000 0000 0000 0000

Komentarz:
Pętla L1 ma wnosić opóˇnienie 100us. Ponieważ cykl rozkazowy przy częstotliwości taktowania mikrokontrolera równej 4MHz wynosi 250ns jeden przebieg pętli musi wnieść opóˇnienie 1us, czyli 4 cykli. Ponieważ instrukcje DEC r20 i BRNE L1 razem trwają 3 cykle (przy niespełnieniu waruknu instrukcji BRNE) to za pomocą intrukcji NOP wstawiamy ten jeden brakujący cykl.

Instrukcja BRNE (Branch if not equal) wykonuje skok warunkowy, jeśli flaga zera (Z) jest wyzerowana.

Składnia rozkazu

Operandy

Licznik rozkazów:

BRNE k

-64 <= k <= +63

PC <- PC + k + 1
PC <- PC + 1, jeśli warunek niespełniony

Kod operacji:

1111 01kk kkkk k001

Instrukcja DEC (Decrement) zmniejsza o jeden zawartość rejestru Rd.

Składnia rozkazu

Operandy

Licznik rozkazów:

DEC Rd

0 <= d <= 31

PC <- PC + 1



Kod operacji:

1001 010d dddd 1010

Komentarz:
Instrukcja ta jest wykorzystana do zmniejszania zawartości licznika pętli. Ponieważ znienia stan flagi Z możliwe jest wykonanie po niej instrukcji BRNE


Program ten napisany w asemblerze zajmuje 62 bajty (31 słów) pamięci programu. Program napisany w Bascomie realizujący tą samą funkcję zajmuje 174 bajty (87 słów).

Przykład 3 - przerzutnik T
Naciśnięcie przycisku S1 zmienia stan kropki wyświetlacza na przeciwny. Algorytm jest przedstawiony na poniższym rysunku :

0x01 graphic

Kod źródłowy programu przedstawiony jest poniżej :

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 3 - Przerzutnik T

.include "2313def.inc"
.cseg
.def acc = r16 ; nadanie rejestrom r16 i
r17 nazw symbolicznych
.def maska = r17
.org 0x00

ldi acc, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, acc ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjście
out PORTB, acc

ldi acc, 0b1111100 ;
out DDRD, acc ; piny D0 i D1 są ustawione jako wejścia
; pozostałe jako wyjścia
ldi acc, 0b1110111
out PORTD, acc

ldi acc, 127 ;
out SPL, acc ; ustawienie wskaznika stosu na 127

cbi PORTD, 7

start:
sbis PIND, 0 ; Jeśli S1 nie jest wciśnięty to
; pomiń następną instrukcję
rcall cpl ; wywołanie procedury cpl
rjmp start ; pętla nieskończona

cpl:
in acc, PORTB ; załaduj do r16 stan przerzutników
; portu B
ldi maska, 0b10000000 ; załaduj do r17 maskę negacji
eor acc, maska ; zmień na przeciwne bity w r16 w/g maski
out PORTB, acc ; zapisz zmodyfikowaną wartość do PORTB
ldi acc, 200 ; czekaj ok. 200ms
rcall waitms ;
ret

; Przybliżone czasy opóznień są podane dla
; rezonatora 4MHz
.def licz1 = r20
.def licz2 = r21
.def licz3 = r22
waitms:
mov licz3, acc ; ustaw czas opóznienia
; powtórz n razy pętlę L,
; co da opóznienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóznienie ok 1ms
ldi licz2, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóznienie ok. 100 us
ldi licz1, 100
L1:
nop ; 1 cykl
dec licz1 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec licz2
brne l0
; koniec pętli L0
dec licz3
brne L
; koniec pętli L
ret

 

Instrukcja EOR (Exclusive OR) - suma modulo 2 zawartości dwóch rejestrów. Wykonanie operacji EOR na rejestrze z drugim argumentem o wartości logicznej jedynki powoduje zanegowanie bitów w rejestrze.

Składnia rozkazu

Operandy

Licznik rozkazów:

(i) EOR Rd,Rr

0 <= d <=31, 0 <= r <= 31

PC <- PC + 1

Kod operacji :

0010 01rd dddd rrrr

Nieco komentarza wymaga procedura cpl. Ponieważ na liście instrukcji mikrokontrolerów AVR nie ma instrukcji służącej bezpośrednio do zanegowania bitu w przestrzeni I/O, tak, jak ma to miejsce w 8051 konieczne jest zrealizowanie tego zadania okrężną drogą. Sposób, w jaki to zrealizowałem nie jest, być może najprostszy, ale spełnia swoje zadanie. Aby zmienić stan lini portu wyjściowego, należy wczytać ten stan do rejestru, np. r16. Jednak nie odczytujemy rejestru PINB, jak mogłoby się wydawać, ale rejestr PORTB, gdyż właśnie ten rejestr ma wpływ na stan linii portu jako wyjścia. Następnie ładujemy do r17 maskę, na podstawie której zanegujemy odpowiedni bit. Ponieważ chcemy zanegować stan kropki wyświetlacza, która jest połączona z pinem 7 portu B, ładujemy wartość 128 (dziesiętnie). Po wykonaniu instrukcji EOR ładujemy z powrotem do PORTB zawartość rejestru r16. Jak widać, to co w 8051 możemy zrealizować za pomocą jednej instrukcji, w AVR musimy użyć trzech instrukcji.

Przykład 4 - Generator impulsów
Kropka wyświetlacza jest na przemian zapalana i gaszona. Algorytm działania programu przedstawiony jest na poniższym rysunku :

0x01 graphic

Kod Ľródłowy przedstawiony jest poniżej :

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 4 - Generator impulsów

.include "2313def.inc"
.def acc= r16
.def maska = r17
.cseg
.org 0x00

ldi acc, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, acc ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjście
out PORTB, acc

ldi acc, 0b1111100 ;
out DDRD, acc ; piny D0 i D1 są ustawione jako wejścia
; pozostałe jako wyjścia
ldi acc, 0b1110111
out PORTD, acc

ldi acc, 127 ;
out SPL, acc ; ustawienie wskaˇnika stosu na 127

cbi PORTD, 7

start:
rcall cpl ; wywołanie procedury cpl
rjmp start ; pętla nieskońc
zona

cpl:
in acc, PORTB ; załaduj do r16 stan przerzutników
; portu B
ldi maska, 0b10000000 ; załaduj do r17 maskę negacji
eor acc, maska ; zmień na przeciwne bity w r16 w/g maski
out PORTB, acc ; zapisz zmodyfikowaną wartość do PORTB
ldi acc, 200 ; czeka
j ok. 200ms
rcall waitms ;
ret

; Przybliżone czasy opóznień są podane dla
; rezonatora 4MHz
.def licz1 = r20
.def licz2 = r21
.def licz3 = r22
waitms:
mov licz3, acc ; ustaw czas opóznienia
; powtórz n razy pętlę L,
; co da opóznienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóznienie ok 1ms
ldi licz2, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóznienie ok. 100 us
ldi licz1, 100
L1:
nop ; 1 cykl
dec licz1 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec licz2
brne l0
; koniec pętli L0
dec licz3
brne L
; koniec pętli L
ret

Powyższy program jest zmodyfikowaną wersją progrmu poprzedniego. Modyfikacja sprowadza się do usunięcia instrukcji "sbis PIND, 0" powodującej pominięcie instrukcji "rcall cpl". Dzięki temu procedura "cpl" jest wykonywana w pętli nieskończonej niezależnie od stanu przycisku, co objawia się miganiem kropki wyświetlacza.

Przykład 5 - wyświetlacz 7-segmentowy
Efektem działania programu jest wyświetlanie kolejno cyfr kodu szesnastkowego na pierwszym wyświetlaczu.

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 5 - Wyświetlacz 7-segmentowy

.include "2313def.inc"
.cseg
.org 0x00

ldi r16, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, r16 ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjś
cie
out PORTB, r16

ldi r16, 0b1111100 ;
out DDRD, r16 ; piny D0 i D1 są ustawione jako wejścia
; pozostałe jako wyjścia
ldi r16, 0b1110111
out PORTD, r16

ldi r16, 127 ;
out SPL, r16 ; ustawienie wskaˇnika stosu na 127

start:
ldi ZH, high(znaki << 1) ; załadowanie do rejestru Z adresu
ldi ZL, low(znaki << 1) ; tablicy z kodami znaków
petla:
lpm ; załaduj do r0 bajt z pamięci programu
mov r16, r0 ; przenieś zawartość r0 do r16
cpi r16, 0 ; sprawdˇ, czy równe zero
breq start ; jeśli tak, skocz na początek programu
adiw ZH:ZL, 1 ; zwiększ zawartość rejestru Z o 1
out PORTB, r0 ; załadowany bajt z pamięci wyświetl na wyświetlaczu
ldi r16, 250 ; załaduj wartość opóˇnienia
rcall waitms ; wywołanie procedury waitms
rjmp petla ; pętla nieskończona


; Przybliżone czasy opóˇnień są podane dla
; rezonatora 4MHz
waitms:
mov r22, r16 ; ustaw czas opóˇnienia
; powtórz n razy pętlę L,
; co da opóˇnienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóˇnienie ok 1ms
ldi r21, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóˇnienie ok. 100 us
ldi r20, 100
L1:
nop ; 1 cykl
dec r20 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec r21
brne l0
; koniec pętli L0
dec r22
brne L
; koniec pętli L
ret

; Tablica z kodami znaków
znaki: .db 192,249,164,176,153,146,130
,248,128,144,136,131,198,161,134,142,0

Instrukcja LPM (Load Program Memory) - ładuj bajt pamięci progrmu do rejestru

Składnia:

Operandy:

Licznik rozkazów:

(i) LPM

brak, r0 domyślnie

PC <- PC + 1

(ii) LPM Rd, Z

0 <= d <= 31

PC <- PC + 1

(iii) LPM Rd, Z+

0 <= d <= 31

PC <- PC + 1

Kod instrukcji:
(i) 1001 0101 1100 1000
(ii) 1001 000d dddd 0100
(iii) 1001 000d dddd 0101

Komentarz :
Aby "wydobyć" bajt z pamięci programu, należy użyć instrukcji, LPM. W naszym przypadku korzystamy z pierwszego wariantu tej instrukcji, ponieważ w mikrokontrolerze AT90S2313 nie zaimplementowano pozostałych dwóch wariantów.

Instrukcja ADIW (Add Immediate to Word) - dodaj bezpośrednio stałą do słowa.

Składnia:

Operandy:

Licznik rozkazów:

(i)ADIW Rd+1:Rd,K

d E {24,26,28,30}, 0 <= K <= 63

PC <- PC + 1

Kod instrukcji:
1001 0110 KKdd KKKK

Komentarz:
Aby pobrać następny bajt z pamięci, konieczne jest zwiększenie adresu zawartego w 16-bitowym rejestrze Z. Jedyną instrukcją realizującą dodanie do słowa 16-bitowego stałej bezpośredniej jest instrukcja ADIW.

Przykład 6 - Licznik TC0 i przerwania
Program w tym przykładzie wyświetla na wyświetlaczu cztery cyfry. Obsługa wyświetlacza odbywa się w ramach przerwania od licznika TC0.

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 6 - Lcz
nik TC0 i przerwania

.include "2313def.inc"
.cseg
.org 0x00
rjmp start
.org 0x06
rjmp timer0
.org 0x0B
start:
ldi r16, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, r16 ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjście

out PORTB, r16 ; ustaw wszystkie piny portu B w stan wysoki
out PORTD, r16 ; ustaw wszystkie piny portu D w stan wysoki

ldi r16, 0b1111100 ; piny D0 i D1 są ustawione jako wejścia
out DDRD, r16 ; pozostałe jako wyjścia

ldi r16, 127 ; ustaw wskaˇnik stosu n
a 127
out SPL, r16

ldi r16, 3; ustaw preskaler timera 0 na 64
out TCCR0, r16

ldi r16, 128 ; wpisz wartosć początkową licznika
out TCNT0, r16

ldi r16, 2 ; odblokuj przerwanie od timera 0
out TIMSK, r16

sei ; ustaw wskaˇnik globalnego zezwolenia na
; prze
rwania


ldi ZH, high(znaki << 1) ; załadowanie do rejestru Z adresu
ldi ZL, low(znaki << 1) ; tablicy z kodami znaków

lpm ; załaduj do r0 bajt z tablicy
mov r10, r0 ; przenieś r0 do r10
adiw ZH:ZL, 1 ; zwiększ rejestr Z o 1
lpm ; załaduj do r0 bajt z tablicy
mov r11, r0 ; przenieś r0 do r11
adiw ZH:ZL, 1 ; zwiększ rejestr Z o 1
lpm ; załaduj do r0 bajt z tablicy
mov r12, r0 ; przenieś r0 do r12
adiw ZH:ZL, 1 ; zwiększ rejestr Z o 1
lpm ; załaduj do r0 bajt z tablicy
mov r13, r0 ; przenieś r0 do r12
petla:
rjmp petla ; pętla nieskończona

timer0:
ldi r16, 250 ; ustaw wartość początkową licznika
out TCNT0, r16 ;

ldi r16, 0xFF ; zgaś wszystkie cyfry wyświetlacza
out PORTD, r16 ;

inc r17 ; zwiększ r17 o 1
cpi r17, 1 ; jeśli r17 != 1 to
brne t1 ; skocz do t1, w przeciwnym razie
cbi PORTD, 6 ; włącz pierwszą cyfrę wyświetlacza
out PORTB, r10 ; wyświetl na wyświetlaczu zawartość r10
rjmp end1 ; skocz do end1
t1:
cpi r17, 2 ; jeśli r17 != 2 to
brne t2 ; skocz do t2, w przeciwnym razie
cbi PORTD, 5 ; włącz drugą cyfrę wyświetlacza
out PORTB, r11 ; wyświetl na wyświetlaczu zawartość r11
rjmp end1 ; skocz do end1
t2:
cpi r17, 3 ; jeśli r17 != 3 to
brne t3 ; skocz do t3, w przeciwnym razie
cbi PORTD, 4 ; włącz trzecią cyfrę wyświetlacza
out PORTB, r12 ; wyświetl na wyświetlaczu zawartość r12
rjmp end1 ; skocz do end1
t3:
cpi r17, 4 ; jeśli r17 != 3 to
brne t4 ; skocz do t4, w przeciwnym razie
cbi PORTD, 3 ; włącz trzecią cyfrę wyświetlacza
out PORTB, r13 ; wyświetl na wyświetlaczu zawartość r13
rjmp end1 ; skocz do end1
t4:
clr r17 ; zeruj r17
end1:
reti ; powrót z procedury obsługi przerwania od T0

; Tablica z kodami znaków
znaki: .db 192,249,164,176,153,146,130,248,128,144,136,131,198,161,134,142,0,0

Instrukcja SEI (Set Global Interrupt Flag) - ustaw flagę globalnego sterowania przerwaniami

Składnia:

Operandy:

Licznik rozkazów:

(i) SEI

brak

PC <- PC + 1

Kod instrukcji:
1001 0100 0111 1000

Komentarz:
Aby możliwe było korzystanie z przerwań, musimy ustawić flagę globalnbego sterowania przerwaniami, która znajduje się w rejestrze SREG.

Instrukcja RETI (Return from Interrupt) - Powrót z procedury obsługi przerwania

Składnia:

Operandy:

Licznik rozkazów:

(i) RETI

brak

(i)PC(15:0) � (SPH:SPL) dla 16-bitowego PC

Uwaga: W mikrokontrolerze AT90S2313 występuje tylko rejestr SPL, gdyż wskaĽnik stosu jest 8-bitowy.

Kod instrukcji:
1001 0101 0001 1000

Komentarz:
Po zakończeniu procedury obsługi przerwania, procesor musi powrócić do miejsca, w którym przerwanie zostało zgłoszone. Przy wywołaniu procedury obsługi przerwana procesor odkłada na stosie stan licznika rozkazów. Instrukcja RETI pobiera ze stosu stan licznika rozkazów, a ponadto ustawia, wyzerowaną na czas obsługi przerwania, flagę globalnego sterowania przerwaniami.

Przykład 7 - prosty zegar
Program w tym przykładzie pełni funkcję prostego zegara.

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 7 - Prosty zegar

.include "2313def.inc"
.cseg
.org 0x00
rjmp start
.org 0x05
rjmp timer1
.org 0x06
rjmp timer0
.org 0x0B
start:
ldi r16, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, r16 ; wpisanie do DDRB samych jedynek
; powoduje ustawienie pinów jako wyjście

out PORTB, r16 ; ustaw wszystkie piny portu B w stan wysoki
out PORTD, r16 ; ustaw wszystkie piny portu D w stan wysoki

ldi r16, 0b1111100 ; piny D0 i D1 są ustawione jako wejścia
out DDRD, r16 ; pozostałe jako wyjścia

ldi r16, 127 ; ustaw wskaˇnik stosu na 127
out SPL, r16

ldi r16, 4 ; ustaw preskaler timera 0 na 64
out TCCR0, r16 ;

ldi r16, 250; wpisz wartosć początkową licznika
out TCNT0, r16 ;

ldi r16, 3 ;
out TCCR1B, r16 ; ustaw preskaler timera 1 na 64

ldi r16, 130 ; odblokuj przerwanie od timera 0 i 1
out TIMSK, r16

sei ; ustaw wskaˇnik globalnego zezwolenia na przerwania

ldi r20, 9 ; ustawienie początkowej wartości
ldi r21, 5 ; liczników czasu
ldi r22, 9 ; dane o czasie są przechowywane
ldi r23, 5 ; w rejestrach roboczych w celu
ldi r24, 3 ; uproszczenia programu
ldi r25, 2

petla:
rjmp petla ; pętla nieskończona
; ponieważ cały program oparty jest o przerwania
; program główny jest pustą pętlą nieskopńczoną

; procedura obsługi przerwania od licznika TC0
; w ramach tej procedury realizowana jest obsługa wyświetlacza
; siedmiosegmentowego sterowanego dynamicznie
timer0:
ldi r16, 250 ; ustaw wartość początkową licznika
out TCNT0, r16

ldi ZH, high(znaki << 1) ; załadowanie do rejestru Z adresu
ldi ZL, low(znaki << 1) ; tablicy z kodami znaków

ldi r16, 0xFF ; zgaś wszystkie cyfry wyświetlacza
out PORTD, r16

inc r17 ; zwiększ r17 o 1
; jednostki minut
cpi r17, 1 ; jsprawdˇ, czy r17 == 2
brne t1 ; jeśli n
ie, to skocz do t1, w przeciwnym razie

add ZL, r22 ; do adresu początku tablicy dodaj przesunięcie zawarte w odpowiednim rejestrze
clr r16 ; ponieważ rejestr Z jest 16-bitowy to dodawanie musi być 16-bitowe, mimo że przesunięcie jest 8-bitowe
adc ZH, r16 ; aby uwzględnić przeniesienie z 8 bitu należy doać z przeniesieniem liczbę 0

lpm ; załaduj z pamięci programu bajt określony adresem zawartym w rejestrze Z
out PORTB, r0 ; prześlij załadowany uprzednio bajt do portu B
cbi PORTD, 3 ; włącz pierwszą cyfrę wyświetlacza
rjmp end1 ; skocz do end1
; dziesiątki minut
t1:
cpi r17, 2 ; sprawdˇ, czy r17 == 2
brne t2 ; jeśli nie, toskocz do t2, w przeciwnym razie

add ZL, r23 ; do adresu początku tablicy dodaj przesunięcie zawarte w odpowiednim rejestrze
clr r16 ; ponieważ rejestr Z jest 16-bitowy to dodawanie musi być 16-bitowe, mimo że przesunięcie jest 8-bitowe
adc ZH, r16 ; aby uwzględnić przeniesienie z 8 bitu należy doać z przeniesieniem liczbę 0

lpm ; załaduj z pamięci programu bajt określony adresem zawartym w rejestrze Z
out PORTB, r0 ; prześlij załadowany uprzednio bajt do portu B
cbi PORTD, 4 ; włącz drugą cyfrę wyświetlacza
rjmp end1 ; skocz do end1
; jednostki godzin
t2:
cpi r17, 3 ; sprawdˇ, czy r17 == 3
brne t3 ; jeśli nie, to skocz do t4, w przeciwnym ra
zie

in r16, PORTB ; realizacja funkcji cosekundowego negowania stanu kropki dziesiętnej wyświetlacza
eor r16, r18
out PORTB, r16

add ZL, r24 ; do adresu początku tablicy dodaj przesunięcie zawarte w odpowiednim rejestrze
clr r16 ; ponieważ rejestr Z jest 16-bitowy to dodawanie musi być 16-bitowe, mimo że przesunięcie jest 8-bitowe
adc ZH, r16 ; aby uwzględnić przeniesienie z 8 bitu należy doać z przeniesieniem liczbę 0

lpm ; załaduj z pamięci programu bajt określony adresem zawartym w rejestrze Z
out PORTB, r0 ; prześlij załadowany uprzednio bajt do portu B
cbi PORTD, 5 ; włącz trzecią cyfrę wyświetlacza
rjmp end1 ; skocz do end1
; dziesiątki godzin
t3:
cpi r17, 4 ; sprawdˇ, czy r17 == 4
brne t4 ; jeśli nie, to skocz do t4, w przeciwnym razie

add ZL, r25 ; do adresu początku tablicy dodaj przesunięcie zawarte w odpowiednim rejestrze
clr r16 ; ponieważ rejestr Z jest 16-bitowy to dodawanie musi być 16-bitowe, mimo że przesunięcie jest 8-bitowe
adc ZH, r16 ; aby uwzględnić przeniesienie z 8 bitu należy doać z przeniesieniem liczbę 0

lpm ; załaduj z pamięci programu bajt określony adresem zawartym w rejestrze Z
out PORTB, r0 ; prześlij załadowany uprzednio bajt do portu B
cbi PORTD, 6 ; włącz czwartą cyfrę wyświetlacza
rjmp end1 ; skocz do end1

t4:
clr r17 ; zeruj r17
end1:
rcall klawiatura ; wywołaj procedurę obsługi klawiatury
reti ; powrót z procedury obsługi przerwania od TC0

; procedura obsługi przerwania od timera TC1
; w ramach tej procedury realizowana jest aktualizacja czasu
timer1:
ldi r16, 0xDC ; załaduj do młodszego bajtu licznika TC1
out TCNT1L, r16 ; wartość początkową

ldi r16, 0x0B ; załaduj do starszego bajtu licznika TC1
out TCNT1H, r16 ; wartość początkową

rcall aktualizuj_czas ; wywołaj procedurę aktualizacji czasu

clt ; zeruj flagę T
; UWAGA:
; flaga T jest ustawiana przez procedurę obsługi klawiatury po pierwszym naciśnięciu przycisku
; zerowana jest co sekundę - najprostsza filtracja drgań styków przycisku
reti ; powrót z procedury przerwania od TC1

; procedura obsługi klawiatury
; realizuje funkcję ustawiania czasu
klawiatura:
brts k2 ; jesli flaga T jest ustawiona, to skocz do k2
; czyli jeśli w bieżącej sekundzie naciśnięto już raz przycisk to nie reaguj na dalsze naciskanie
; przycisku, bądˇ drgania styków
sbic PIND, 0 ; jeśli PIND.0 jest wyzerowany to pomiń następną instrukcję
rjmp k1 ; skocz do k1
set ; ustaw flagę T
clr r20 ; wyzeruj licznik sekund
clr r21
rcall zwieksz_minuty ; wywołaj procedurę zwiększenia licznika minut
cpi r23, 6 ; sprawdˇ, czy minuty == 60
brne k1 ; jeśli nie, to skocz do k1
clr r23 ; w przeciwnym wypadku zeruj dziesiątki minut
k1:
sbic PIND, 1 ; jeśli PIND.1 jest wyzerowany to pomiń następną instrukcję
rjmp k2 ; skocz do k2
set ; ustaw flagę T
rcall zwieksz_godziny ; wywołaj procedurę zwiększenia licznika godzin
k2:
ret ; powrót z procedury

; procedura aktualizacji czasu
aktualizuj_czas:
rcall zwieksz_sekundy ; wywołaj procedurę zwiększenia licznika sekund
cpi r21, 6 ; sprawdˇ, czy dziesiątki sekund == 6
brne a1 ; jeśli nie, skocz do a1
clr r21 ; w przeciwnym wypadku zeruj dziesiątki sekund
rcall zwieksz_minuty ; i wywołaj procedurę zwiększania licznika minut
cpi r23, 6 ; sprawdˇ, czy dziesiątki minut == 6
brne a1 ; jeśli nie, skocz do a1
clr r23 ; w przeciwnym wypadku zeruj dziesiątki minut
rcall zwieksz_godziny ; i wywołaj procedurę zwiększenia licznika godzin
a1:
ret

; procedura zwiększenia licznika sekund
zwieksz_sekundy:
inc r20 ; zwieksz licznik jednostek sekund
cpi r20, 10 ; sprawdˇ, czy jednostki sekund == 10
brne z3 ; jeśli nie, skocz do z3
clr r20 ; w przeciwnym wypadku zeruj jednostki sekund
inc r21 ; i zwiększ dziesiątki sekund
z3:
ret ; powrót z procedury

; procedura zwiększenia licznika minut
zwieksz_minuty:
inc r22 ; zwiększ licznik jednostek minut
cpi r22, 10 ; sprawdĽ, czy licznik jednostek minut == 10
brne z1 ; jeśli nie, skocz do z1
clr r22 ; w przeciwnym wypadku zeruj licznik jednostek minut
inc r23 ; i zwiększ licznik dziesiątek minut
z1:
ret ; powrót z procedury

; procedura zwiększenia licznika godzin
zwieksz_godziny:
inc r24 ; zwiększ licznik jednostek godzin
cpi r24, 10 ; sprawdˇ, czy jednostki godzin == 10
brne z21 ; jeśli nie, skocz do z21
clr r24 ; w przeciwnym wypadku zeruj jednostki godzin
inc r25 ; i zwiększ dziesiątki godzin
rjmp z2 ; skocz do z2
z21:
cpi r24, 4 ; porównaj czy jednostki godzin == 4
brne z2 ; jeśli nie, skocz do q1
cpi r25, 2 ; porównaj, czy dziesiątki godzin == 2
brne z2 ; jeśli nie, skocz do q1
; jeśli natomiast obydwa poprzednie warunki
clr r25 ; są jednocześnie spełnione to wyzeruj
clr r24 ; jednostki i dziesiątki godzin
z2:
ret ; powrót z procedury

; Tablica z kodami znaków
znaki: .db 192,249,164,176,153,146,130,248,128,144,136,131,198,161,134,142,0,0

Powyższy program jest dosyć rozbudowany. Tak niestety wygląda program pisany w asemblerze - rozwlekły i mało czytelny. Jednak zyskujemy wiele na rozmiarze kodu wynikowego. Program realizujący podobne funkcje napisany w Bascomie zajmuje ponad trzy razy więcej pamięci programu - różnica jest znaczna. Funkcja odmierzania czasu realizowana jest w ramach obsługi przerwania od timera TC1. Zdecydowałem się na podzieleniu programu odmierzania czasu na kilka mniejszych procedur, w celu poprawienia czytelności programu. Obsługa wyśiwetlacza jest zrealizowana, podobnie jak w poprzednim przykładzie, w ramach obsługi przerwania od timera TC0.

Dane o bieżącej godzinie są przechowywane w rejestrach r20-r25. Zreazygnowałem z umieszczenia ich w pamięci RAM, ze względu na fakt, że mikrokontrolery AVR mogą operawać bezpośrednio tylko na danych umieszczonych w rejestrach roboczych. W sytuacji, gdy do dyspozycji mamy 32 rejestry robocze możemy sobie na taki zabieg spokojnie pozwolić. Gdyby zaś przechowywać te dane w pamięci RAM, konieczne by było przed każdą operacją przeniesienie ich do rejestrów roboczych, co komplikowało by program i zwiększyło jego rozmiar.

Przykład 8 - wyświetlacz LCD
W przykładzie tym zawarte są podstawowe procedury obsługi alfanumerycznego wyswietlacza LCD, pracującego w trybie 4-bitowym. Podstawowe informacje o wyświetlaczach alfanumerycznych są umieszczone w dziale Ogólne

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 8 - Wyświetlacz LCD

.include "2313def.inc"

.equ LCD = PORTB
.equ RS = 2
.equ E = 3

; makroinstrukcja realizująca iloczyn logiczny
; portu I/O z 8-bitową stałą
; jako pierwszy parametr wymagany jest adres portu I/O
; natomiast jako drugi - stała 8-bitowa
.macro anl
in r24, @0
andi r24, @1
out @0, r24
.endmacro

; makroinstrukcja realizująca sumę logiczną
; portu I/O z 8-bitową stałą
; jako pierwszy parametr wymagany jest adres portu I/O
; natomiast jako drugi - stała 8-bitowa
.macro orl
in r24, @0
ori r24, @1
out @0, r24
.endmacro

.cseg
.org 0x00
start:

ldi r16, 0b11111111 ; załaduj do r16 liczbę 255 (0xff)
out DDRB, r16 ; wpisanie do DDRB samych jedynek powoduje ustawienie pinów jako wyjście
out PORTB, r16

ldi r16, 0b1111100
out DDRD, r16 ; piny D0 i D1 są ustawione jako wejścia pozostałe jako wyjścia
ldi r16, 127 ; ustaw wskaźnik stosu na 127
out SPL, r16

rcall lcdinit ; wywołanie procedury inicjalizującej wyświetlacz

ldi ZL, low(tekst << 1) ; załaduj do rejestru Z adres tablicy z tekstem
ldi ZH, high(tekst << 1)

rcall writetext ; wywołanie procedury wyświetlenia tekstu
petla:
rjmp petla ; pętla nieskończona

; Procedura zapisu bajtu do wyświetlacza
writetolcd:
push r16 ; zapamiętaj na stosie r16
sbi LCD, E ; ustaw linię E w stan wysoki
orl LCD, 0xF0 ; ustaw 4 starsze bity szyny danych wyświetlacza
ori r16, 0x0F ; ustaw 4 młodsze bity rejestru r16
in r26, LCD ; prześlij do r16 stan szyny danych wyświetlacza
and r16, r26 ; iloczyn logiczny r16 i r26
out LCD, r16 ; prześlij wynik na szynę danych wyświetlacza
cbi LCD, E ; ustaw linię E w stan niski - zapis do wyświetlacza

pop r16 ; odtwórz ze stosu rejestr r16
swap r16 ; zamień połówki rejestru r16

sbi LCD, E ; ustaw linię E w stan wysoki
orl LCD, 0xF0 ; ustaw 4 starsze bity szyny danych wyświetlacza
ori r16, 0x0F ; ustaw 4 młodsze bity rejestru r16
in r26, LCD ; prześlij do r16 stan szyny danych wyświetlacza
and r16, r26 ; iloczyn logiczny r16 i r26
out LCD, r16 ; prześlij wynik na szynę danych wyświetlacza
cbi LCD, E ; ustaw linię E w stan niski - zapis do wyświetlacza

ldi r16, 0x01 ; czekaj 1ms
rcall waitms
ret

; Procedura zapisu instrukcji do wyświeltlacza
writecommand:
cbi LCD, RS ; ustaw linię RS w stan niski - zapis instrukcji
rcall writetolcd ; wywołanie procedury zapisu bajtu do wyświetlacza
ret

; Procedura zapisu danych do wyświetlacza
writechar:
sbi LCD, RS ; ustaw linię RS w stan wysoki - zapis danych
rcall writetolcd ; wywołanie procedury zapisu bajtu do wyświetlacza
ret

; Procedura zapisu tekstu do wyświetlacza
; przed wywołaniem tej procedury do rejestru Z
; należy załadować adres tablicy z tekstem
writetext:
lpm ; odczytaj z tablicy w pamięci programu znak
mov r16, r0 ; przenieś odczytany znak do r16
cpi r16, 0 ; sprawdź, czy koniec tekstu
breq end1 ; jeśli tak, to skocz do end1
rcall writechar ; w przeciwnym razie zapisz znak do LCD
adiw ZH:ZL, 1 ; zwiększ adres o 1
rjmp writetext ; skocz na początek procedury
end1:
ret


; Procedura inicjalizacji wyświetlacza
lcdinit:
ldi r16, 0x0F ; czekaj 15ms
rcall waitms
cbi LCD, E ; E = 0
cbi LCD, RS ; RS = 0
ldi r18, 0x03 ; załaduj do r18 liczbę powtórzeń pętli ll
ll:
sbi LCD, E ; E = 1
anl LCD, 0x3f ; na szynę danych wyślij 0x3f - wartość inicjalizująca
cbi LCD, E ; E = 0 - zapis do rejestru wyświetlacza
ldi r16, 0x05 ; czekaj 5ms
rcall waitms
dec r18 ; zmniejsz licznik pętli o 1
brne ll ; jeśli licznik pętli > 0 to powtórz pętlę
sbi LCD, E ; E = 1
anl LCD, 0x2E ; przełącz interfejs na 4-bitowy
cbi LCD, E ; E = 0
ldi r16, 0x01 ; czekaj 1ms
rcall waitms
ldi r16,0x28 ; interfejs 4 bity,znaki 5x7
rcall writecommand
ldi r16,0x08 ; wyłączenie LCD
rcall writecommand
ldi r16,0x01 ; kasowanie ekranu, powrót do pozycji home
rcall writecommand
ldi r16,0x06 ; przesuwanie kursora z inkrementacją
rcall writecommand
ldi r16,0x0C ; załączenie wyswietlacza
rcall writecommand
ret

waitms:
mov r22, r16; ustaw czas opóźnienia
; powtórz n razy pętlę L,
; co da opóźnienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóźnienie ok 1ms
ldi r21, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóźnienie ok. 100 us
ldi r20, 100
L1:
nop ; 1 cykl
dec r20 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec r21
brne l0
; koniec pętli L0
dec r22
brne L
; koniec pętli L
ret

tekst: .db "RadoslawKwiecien", 0

Sterowanie wyświetlaczem odbywa się w typowy dla trybu 4-bitowego sposób. Linie DB4 - DB7 wyświetlacza są połączone odpowiednio z liniami PORTB.4 - PORTB.7. Linia RS jest podłączona do PORTB.2, a linia E do PORTB.3. Ze względu na stosunkowo dużą szybkość mikrokontrolera może się okazać, że nie każdy moduł wyświetlacza będzie poprawnie współpracował z mikrokontrolerem. W takim przypadku należy wydłużyć czas pomiędzy poszczególnymi impulsami strobującymi zapis do wyświetlacza (linia E), poprzez wstawienie w odpowiednim miejscu kodu kilku instrukcji "nop". Jednak w moim przypadku ten problem nie wystąpił i daltego nie umieściłem tych instrukcji, jak zalecają niektórzy autorzy.
W niniejszym przykładzie po raz pierwszy zastosowałem makroinstrukcje. Zastosowałem je w celu poprawy czytelnoiści programu, który byłby bardziej rozbudowany w przypadku gdyby zadanie spełnianie przez makroinstrukcję byłoby zapisane za pomocą kilku instrukcji asemblera.

Przykład 9 - magistrala I2C
W tym odcinku kursu asembelra zajmiemy się magistralą I2C, Program, który napiszemy będzie odczytywał z przetwornika PCF8591 wynik pomiaru i wyświetlał go w postaci binarnej na wyświetlaczu LED. Przed przystąpieniem do przerabiania materiału zawartego w tym przykładzie zalecane jest zapoznanie się z podstawowymi informacjami dotyczącymi transmisji szyną I2C zawartymi w tym dokumencie, lub na stronie : http://www.autokacik.pl/i2c/. Aby możliwe było przeprowadzenie tego ćwiczenia, należy dołączyć do płytki testowej potencjometr podłączony w sposób pokazany na rysunku :

0x01 graphic

Kręcąc osią potencjimetru zmieniamy napięcie podawane na wejście przetwornika A/C. Zakres przetwarzania przetwornika wynosi 0..2.55V (chyba, że ustawimy inne napięcie referencyjne), tak więc nie należy przekraczać tego zakresu. Wyświetlany wynik pomiaru jest podany w postaci binarnej. Nie dopisałem żadnych procedur przetwarzających wynik na postać bardziej czytelną dla człowieka, ponieważ będzie to tematem na inny artykuł. Celem tego programu jest wyłącznie zaprezentowanie sposobu korzystania z szyny I2C.

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 9 - magistrala I2C
;
.include "2313def.inc"
; nadanie odpowiednim rejestrom I/O nazw symbolicznych
.equ I2CDir = DDRD
.equ I2COut = PORTD
.equ I2CIn = PIND
; zdefiniowanie pinów magistrali I2C
.equ SDA = 6
.equ SCL = 5
; określenie adresów układu PCF8591
.equ PCF8591_ADDR = 144
; bity R/W
.equ R = 1
.equ W = 0
; potwierdzenie
.equ ACK = 0
.equ NOACK = 1
; nadanie odpowiednim rejestrom nazw symbolicznych
.def acc = r16
.def licznik = r17
.def pomoc = r18

.cseg ; segment kodu
.org 0x00 ; adres 0x00
ldi acc, 0xFF
out DDRB, acc ; skonfigurowanie protu PORTB jako wyjścia
out PORTB, acc ; wszystkie piny PORTB w stanie wysokim
; włączenie pierwszej cyfry wyświetlacza
sbi DDRD, 3 ; skonfigurowanie PORTD.3 jako wyjścia
cbi PORTD, 3 ; ustawienie stanu niskiego na PORTD.3
; ustawienie wskaˇnika stosu na koniec obszaru RAM
ldi acc, RAMEND ; załaduj do acc adres końca obszaru pamięci RAM
out SPL, acc ; zapisz ten adres do SPL


; początek programu głównego
start:
rcall i2cstart ; rozpoczęcie transmisji szyną I2C
ldi acc, PCF8591_ADDR | R ; załaduj do acc adres układu PCF8591 do odczytu
rcall i2cwrite ; wyślij ten adres na szynę I2C
ldi acc, NOACK ; zładuj do acc wartość bitu ACK (brak potwierdzenia - odczytany zostanie tylko 1 bajt)
rcall i2cread ; odczytaj bajt z szyny I2C
rcall i2cstop ; koniec transmisji
; przygotowanie danych do wyswietlenia
com acc ; zeneguj wszystkie bity acc
; wyświetlenie danych na wyświetlaczu
out PORTB, acc ; wyślij do PORTB zawartość acc

rjmp start ; skok na początek programu głównego

; procedura opóˇniająca
delay:
cp r0, r0
breq ll1
ll1: breq ll2
ll2: breq ll3
ll3: breq ll4
ll4: ret

; procedura generująca sekwencję startu
i2cstart:
sbi I2CDir, SCL ;linie SDA i SCL pracują jako wyjście
sbi I2CDir, SDA

sbi I2COut, SDA ; ustawienie linii SDA i SCL w satn wysoki
sbi I2COut, SCL

rcall delay ;opóˇnienie

cbi I2COut, SDA ; ustaw SDA w stan niski
rcall delay ; opóˇnienie

cbi I2COut, SCL ;ustaw SCL w stan niski
rcall delay ;opóˇnienie

ret

; procedura generująca sekwencję stopu
i2cstop:
sbi I2CDir, SDA ; SDA jako wyjście

sbi I2COut, SCL ; ustaw na SCL stan wysoki
rcall delay ; opóˇnienie

sbi I2COut, SDA ; ustaw na SDA stan wysoki
rcall delay ; opóˇnienie
ret

; procedura wysłania bajtu na szynę I2C
i2cwrite:
sbi I2CDir, SDA ; SDA jako wyjście
ldi licznik, 8 ; ustaw ilość powtórzeń pętli
petla1:
; wysłanie na szynę I2C najstarszego bitu z acc
sbrs acc, 7 ; jeśli bit 7 w acc jest ustawiony, to pomiń następną instrukcję
cbi I2COut, SDA ; ustaw na SDA stan niski
sbrc acc, 7 ; jeśli bit 7 w acc jest wyzerowany, to pomiń następną instrukcję
sbi I2COut, SDA ; ustaw na SDA stan wysoki
; wygenerowanie opadającego zbocza na SCL -> zapis do SLAVE'a
sbi I2COut, SCL ; ustaw na SCL stan wysoki
rcall delay ; opóˇnienie
cbi I2COut, SCL ; ustaw na SCL stan niski
; przygotowanie następnego bitu do transmisji
lsl acc ; przesunięcie w lewo acc
; modyfikacja i sprawdzenie wartości licznika pętli
dec licznik ; zmniejsz licznik pętli o 1
brne petla1 ; jeśli niezerowy, to skocz do petla1

rcall delay ; opóˇnienie
; przygotowanie do odczytu sygnału ACK ze SLAVE'a
cbi I2CDir, SDA ; SDA jako wejście
cbi I2COut, SDA ; ustaw na SDA stan niski
sbi I2COut, SCL ; ustaw na SCL stan wysoki
; odczyt sygnału ACK ze SLAVE'a
sbis I2CIn, SDA ; jeśli SDA jest w stanie wysokim, to pomiń następną instrukcję
ldi acc, ACK ; ACK = 0
sbic I2CIn, SDA ; jeśli SDA jest w stanie niskim, to pomiń następną instrukcję
ldi acc, NOACK ; ACK = 1
rcall delay ; opóˇnienie

cbi I2COut, SCL ; ustaw na SCL stan niski
ret

; procedura odczytu bajtu z szyny I2C
i2cread:
mov r19, acc
cbi I2CDir, SDA ; SDA jako wejście
cbi I2COut, SDA ; ustawienie stanu niskiego na SDA
ldi licznik, 8
petla2:
sbi I2COut, SCL ; stan wysoki na SCL
rcall delay ; opóˇnienie
lsl acc ; przesunięcie acc w lewo
sbic I2CIn, SDA ; jeśli SDA jest w stanie niskim to pomiń następną instrukcję
ori acc,1 ; ustaw bit 0 acc
cbi I2COut, SCL ; stan niski na SCL

dec licznik ; zmniejsz licznik pętli o 1
brne petla2 ; jeśli niezerowy to skocz do petla2

rcall delay ; opózninie
; transmijsa bitu ACK
sbi I2CDir, SDA ; SDA jako wyjście

sbrs r19, 0 ; jeśli bit 0 w acc jest ustawiony to pomiń następną instrukcję
cbi I2COut, SDA ; ustaw na SDA stan niski (ACK)
sbrc r19, 0 ; jeśli bit 0 w acc jest wyzerowany to pomiń następną instrukcję
sbi I2COut, SDA ; ustaw na SDA stan wysoki (NOACK)

sbi I2COut, SCL ; ustaw na SCL stan wysoki

rcall delay ; opóˇnienie

cbi I2COut, SCL ; ustaw na SCL stan niski
ret

Przykład 10 - magistrala 1-wire (wersja wstępna)

Poniższy program odczytuje temperaturę z czujnika DS1822 (18B20) i wyświetla ją na dwóch cyfrach wyświetlacza siedmiosegmentowego płytki AVT-3500. Czujnik należy podłączyć do złącza śrubowego SCL (końcówka DQ) oraz do zasilania (+5V, GND). Zworki J1 i J2 należy przełączyć w pozycję B-C.

; Kurs asemblera mikrokontrolerów AVR
;
; Przykład 10 - magistrala 1wire

.include "2313def.inc"

#define DQ_PORT DDRD
#define DQ 5
#define SET_DQ cbi DQ_PORT, DQ
#define CLR_DQ sbi DQ_PORT, DQ

#define IN_DQ PIND

#define acc r19
#define count r20
#define temp r21
#define rdel r22
#define lsb r23
#define msb r24

#define D1 r25
#define D2 r26
#define ktora_cyfra r27

.org 0x00
rjmp start
; umieszczenie w niewykorzystanym obszarze wektorów przerwań talblicy z kodami znaków
; w celu zaoszczędzenia miejsca w pamięci programu :-)
znaki: .db 192,249,164,176,153,146,130,248,128,144
; !w razie korzystania z przerwań o wektorach zajętych przez tablicę
; należy przenieść ją w inny obszar pamięci!
.org 0x06
rjmp timer0
.org 0x0B

; procedura opóźniająca o (5+5*rdel) * 0,25 us
delay:
nop
subi rdel, 1
brne delay
ret

; procedura generujaca sygnał reset na linii 1wire
ow_reset:
cli
CLR_DQ ; stan niski na linii 1wire
ldi rdel, 255 ;
rcall delay ;
ldi rdel, 119 ; opóźnienie ok 480 us
rcall delay ;
SET_DQ ; stan wysoki na linii 1wire
ldi rdel, 255 ;
rcall delay ;
ldi rdel, 119 ; opóźnienie ok 480 us
rcall delay ;
sei
ret

; procedura nadania bitu przez linię 1wire
ow_write_bit:
cli ; zablokowanie przerwań
CLR_DQ ; stan niski na linii 1wire
ldi rdel, 7
rcall delay
cpi acc, 0
breq dalej
SET_DQ
dalej:
ldi rdel, 80
rcall delay
SET_DQ
sei
ret

; procedura odczytu bitu z linii 1wire
ow_read_bit:
cli
CLR_DQ
ldi rdel, 1
rcall delay
SET_DQ
ldi rdel, 11
rcall delay
ldi acc, 1
sbis IN_DQ, DQ
ldi acc, 0
sei
ret


; procedura wsłania bajtu na linię 1wire
ow_write:
mov temp, acc ; zapamiętanie danej wejściowej w rejestrze temp
ldi count, 1 ; załadowanie do licznika wartości początkowej
loop1:
mov acc, temp ; przywrócenie danej wejściowej
and acc, count ; iloczyn logiczny danej wejściowej i licznika
rcall ow_write_bit ; wywołanie procedury zapisu bitu
lsl count ; przesuniecie licznika o 1 bit w lewo
cpi count, 0 ; sprawdzenie, czy licznik = 0
brne loop1 ; jeśli nie to wróc na początek pętli
ret

; procedura odczytu bajtu z linii 1wire
ow_read:
ldi temp, 0 ; wyzerowanie rejestru temp
ldi count, 1 ; załadowanie do licznika wartości początkowej
loop2:
rcall ow_read_bit ; wywołanie procedury odczytu bitu z linii 1wire
cpi acc, 0 ; porównanie zwróconej wartośći z liczbą 0
breq rd1 ; jeśli równe to skocz do rd1
or temp, count ; jeśli rózne to wykonaj sumę logiczną temp i licznika
rd1:
ldi rdel, 6 ; opóźnienie
rcall delay
lsl count ; przesunięcie licznika o 1 bit w lewo
cpi count, 0 ; sprawdzenie, czy licznik = 0
brne loop2 ; jeśli nie to skocz na początek pętli
mov acc, temp ; przepisanie temp do acc
ret

; program główny
start:
; inicjalizacja stosu
ldi acc, RAMEND
out SPL, acc
; konfiguracja PORTB
ldi acc, 255
out DDRB, acc

; ustawnienie PORTB.3 w tryb wyjściowy
sbi DDRD, 3
; ustawnienie PORTB.4 w tryb wyjściowy
sbi DDRD, 4
; ustawnienie PORTB.3 w stan wysoki
sbi PORTD, 3
; ustawnienie PORTB.4 w stan wysoki
sbi PORTD, 4
; inicjalizacja timera TIMER0
ldi acc, 4 ; ustaw preskaler timera 0 na 64
out TCCR0, acc ;

ldi acc, 128 ; wpisz wartosć początkową licznika
out TCNT0, acc ;

ldi acc, 2 ; odblokuj przerwanie od timera 0
out TIMSK, acc
; odblokowanie przerwań
sei

main:
rcall ow_reset ; reset
ldi acc, 0xCC
rcall ow_write ; wyślij rozkaz SKIP ROM
ldi acc, 0x44
rcall ow_write ; wyślij rozkaz rozpoczęcia pomiaru temperatury
ldi acc, 250
rcall waitms
rcall waitms
rcall waitms ; odczekaj 750 ms na zakończenie pomiaru

rcall ow_reset ; reset
ldi acc, 0xCC
rcall ow_write ; wyślij rozkaz SKIP ROM
ldi acc, 0xBE
rcall ow_write ; wyślij rozkaz odczytu danych z układu DS1822

rcall ow_read ; odczytaj mniej znaczący bajt wyniku pomiaru
mov lsb, acc
rcall ow_read ; odczytaj bardizej znaczacy bajt wyniku pomiaru
mov msb, acc

lsr lsb ;
lsr lsb ;
lsr lsb ; przesunięcie lsb o 4 bity w prawo
lsr lsb ;

andi msb, 0x07 ; wyzerowanie 5 najstarszych bitów msb

lsl msb ;
lsl msb ;
lsl msb ; przesunięcie msb o 4 bity w lewo
lsl msb ;

or lsb, msb ; suma logiczna lsb i msb, w lsb znajduje się temperatura

; przygotowanie temperatury do wyświetlenia na wyświetlaczu
; dzielenie temperatury przez 10
mov dd8u, lsb ; przeniesienie temperatury do rejestru dzielenia
ldi dv8u, 10 ; załadowanie dzielnika
rcall div8u ; wywołanie procedury dzielenia

ldi ZH, high(znaki << 1) ; załadowanie do rejestru Z adresu
ldi ZL, low(znaki << 1) ; tablicy z kodami znaków

add ZL, dres8u ; do adresu początku tablicy dodaj
clr acc ; wynik dzielenia przez 10
adc ZH, acc

lpm ; załaduj do r0 bajt z tablicy znaków
mov D1, r0 ; przenieś r0 do D1

ldi ZH, high(znaki << 1) ; załadowanie do rejestru Z adresu
ldi ZL, low(znaki << 1) ; tablicy z kodami znaków

add ZL, drem8u ; do adresu początku tablicy dodaj
clr acc ; reszte z dzielenia przez 10
adc ZH, acc

lpm ; załaduj do r0 bajt z tablicy znaków
mov D2, r0 ; przenieś r0 do D2

rjmp main ; skok na początek pętli głównej





; Przybliżone czasy opóźnień są podane dla
; rezonatora 4MHz
.def licz1 = r20
.def licz2 = r21
.def licz3 = r22
waitms:
mov licz3, acc ; ustaw czas opóźnienia
; powtórz n razy pętlę L,
; co da opóźnienie ok. n * 1 ms
L:
; powtórz 10 razy pętlę L0,
; co da opóźnienie ok 1ms
ldi licz2, 10
L0:
; powtórz 100 razy pętlę L1,
; co da opóźnienie ok. 100 us
ldi licz1, 100
L1:
nop ; 1 cykl
dec licz1 ; 1 cykl
brne l1 ; 2 cykle
; koniec pętli L1
dec licz2
brne l0
; koniec pętli L0
dec licz3
brne L
; koniec pętli L
ret

; Procedura dzielenia 8bitowego
; pochodzi z noty AVR200 firmy ATMEL
.def drem8u =r15 ;remainder
.def dres8u =r16 ;result
.def dd8u =r16 ;dividend
.def dv8u =r17 ;divisor
.def dcnt8u =r18 ;loop counter

div8u: sub drem8u,drem8u ;clear remainder and carry
ldi dcnt8u,9 ;init loop counter
d8u_1: rol dd8u ;shift left dividend
dec dcnt8u ;decrement counter
brne d8u_2 ;if done
ret ;return
d8u_2: rol drem8u ;shift dividend into remainder
sub drem8u,dv8u ;remainder = remainder - divisor
brcc d8u_3 ;if result negative
add drem8u,dv8u ;restore remainder
clc ;clear carry to be shifted into result
rjmp d8u_1 ;else
d8u_3: sec ;set carry to be shifted into result
rjmp d8u_1
; koniec procedury dzielenia

; procedura obsługi przerwania od TIMER0
timer0:
ldi acc, 250 ; ustaw wartość początkową licznika
out TCNT0, acc ;

sbi PORTD, 3 ; wyłącz anody wyświetlaczy
sbi PORTD, 4 ;

inc ktora_cyfra ; zwiększ ktora_cyfra o 1

cpi ktora_cyfra, 1 ; jeśli ktora_cyfra != 1 to
brne t1 ; skocz do t1, w przeciwnym razie
cbi PORTD, 4 ; włącz pierwszą cyfrę wyświetlacza
out PORTB, D1 ; wyświetl na wyświetlaczu zawartość r10
rjmp end1 ; skocz do end1
t1:
cpi ktora_cyfra, 2 ; jeśli ktora_cyfra != 2 to
brne t2 ; skocz do t2, w przeciwnym razie
cbi PORTD, 3 ; włącz drugą cyfrę wyświetlacza
out PORTB, D2 ; wyświetl na wyświetlaczu zawartość r11
rjmp end1 ; skocz do end1
t2:
ldi ktora_cyfra, 0 ; zeruj ktora_cyfra
end1:
reti
 

Kurs pochodzi ze strony http://mikrokontrolery.net/index.htm



Wyszukiwarka

Podobne podstrony:
Mikrokontroler Elektronika analogowa Kurs asemblera dla AVR w przykładach
Atmel AVR Assembler id 71678 Nieznany (2)
Uniwersalny tester elementów na AVR
Atmel AVR Assembler id 71678 Nieznany (2)
Praktyczny kurs asemblera pkasem
kurs-asembler-zlotowicz, [ Algorytmy: Szyfrowanie danych ]
Może kurs chodzenia na obcasach
Asembler na uP 8051, Elektronika
Kurs decoupage na świecy, cooooooooooooos
Kurs ASEMBLER'A
Kurs robienia na szydełku
Gitara lekcje pl Lekcja 2 (teoria) tercjowa budowa akordów (darmowy kurs gry na gitarze on line)
Kurs Gry na Forex 2
Gitara lekcje pl Lekcja 3 Modyfikacja bicia na 3 4 (darmowy kurs gry na gitarze on line)
Gitara lekcje pl Lekcja 2 Bicie na 3 4 (darmowy kurs gry na gitarze on line)

więcej podobnych podstron