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 :
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.
|
|
|
|
16 <= d <=31, 0 <= K <= 255
|
|
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.
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.
|
|
|
|
|
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.
|
|
|
|
0 <= A <= 31, 0 <= b <= 7
|
|
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.
|
|
|
|
0 <= A <= 31, 0 <= b <= 7
|
|
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.
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 :
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.
|
|
|
|
0 <= d <=31, 0 <= r <= 31
|
|
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.
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.
|
|
|
|
|
PC <- PC + k + 1 PC <- PC + 1, jeśli warunek niespełniony
|
Instrukcja DEC (Decrement) zmniejsza o jeden zawartość rejestru Rd.
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 :
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.
|
|
|
|
0 <= d <=31, 0 <= r <= 31
|
|
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 :
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ń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
|
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
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.
|
|
|
|
d E {24,26,28,30}, 0 <= K <= 63
|
|
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 - Lcznik 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 na 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 ; przerwania
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
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
|
|
|
|
|
(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 nie, 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 razie
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
.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
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 :
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
; 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ładachAtmel AVR Assembler id 71678 Nieznany (2)Uniwersalny tester elementów na AVRAtmel AVR Assembler id 71678 Nieznany (2)Praktyczny kurs asemblera pkasemkurs-asembler-zlotowicz, [ Algorytmy: Szyfrowanie danych ]Może kurs chodzenia na obcasachAsembler na uP 8051, ElektronikaKurs decoupage na świecy, cooooooooooooosKurs ASEMBLER'AKurs robienia na szydełkuGitara lekcje pl Lekcja 2 (teoria) tercjowa budowa akordów (darmowy kurs gry na gitarze on line)Kurs Gry na Forex 2Gitara 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