AVR Asembler. Podstawowe operacje arytmetyczne.
W tym odcinku kursu nieco odbiegniemy od prezentowanych wcześniej sposobów wykorzystywania zasobów mikrokontrolera. Każda aplikacja musi czasami dodać, odjąć lub porównać jakieś wartości. Tworząc ją w języku wysokiego poziomu wszystko jest proste: deklarujemy typy zmiennych A i B a następnie prosty zapis A+B rozwiązuje problem. W asemblerze nie jest niestety tak łatwo.
W lekturze dzisiejszego odcinka przydadzą się zdobyte wcześniej informacje na temat bitów wskazujących lub inaczej flag. Szczególnej uwadze polecam zachowanie się flag C (przeniesienia) i Z (zera) bardzo użytecznych w prezentowanych dziś przykładach procedur.
Dodawanie liczb: 8+8 bitów, 16+16 bitów, 16+8 bitów
Do realizacji dodawania służą rozkazy add oraz adc. Ten pierwszy nie uwzględnia bitu przeniesienia, ten drugi bierze go pod uwagę. Jak pamiętamy, bit przeniesienia sygnalizuje sytuację, w której wynik nie mieści się w rejestrze go przechowującym. Wówczas to flaga C przyjmuje wartość logiczną 1. Rozkaz adc powoduje, że wartość flagi C dodawana jest do bajtu jako bit znajdujący się na najmłodszej jego pozycji. Na listingu 1 przedstawiono wybrane implementacje funkcji dodawania. Są to: dodawanie dwóch liczb 8 bitowych (add_8_8), dodawanie dwóch liczb 16 bitowych (add_16_16), dodawanie liczby 16 i 8 bitowej (add_16_8) oraz dodanie stałej o długości 16 bitów do zmiennej 16 bitowej (add_16_const). Realizacja pierwszych trzech funkcji dodawania jest bardzo prosta. Młodsze bajty dodaje się przy pomocy add. Operacje wykonywane na starszych bajtach muszą już uwzględniać stan flagi C, która może być ustawiona przez wcześniej wykonane dodawanie młodszych bajtów licz. Umożliwia to rozkaz adc. Dodawanie liczby 16 i 8 bitowej może spowodować sytuację, w której wynik przekroczy rozmiar pojedynczego bajtu. Dlatego też rozkaz rol wprowadza flagę C na pozycję najmłodszego bitu starszego bajtu wyniku.
Na uwagę zasługuje funkcja dodawania stałej do zmiennej - obie o długości 16 bitów. Lista rozkazów AVR nie oferuje rozkazu dodawania stałej do zmiennej z przeniesieniem, więc dodawanie zostało przeprowadzone przez ... odejmowanie liczb ujemnych. Radzę zapamiętać tę metodę na przyszłość.
List. 1. Przykłady realizacji funkcji dodawania
;deklaracje zmiennych
.def A1 = R16 ;młodszy (A1)
.def A2 = R17 ;i starszy (A2) bajt zmiennej A
.def B1 = R18 ;młodszy (B1)
.def B2 = R19 ;i starszy (B2) bajt zmiennej B
;dodawanie dwóch liczb 8-bitowych,
;pierwsza liczba w A1,druga w B1
;wynik przechowywany w A2:A1
add_8_8:
clr A2 ;zerowanie rejestru, w którym będzie zapamiętany
;starszy bajt wyniku
add A1,B1 ;dodanie zmiennych bez uwzględnienia flagi C
rol A2 ;wprowadzenie flagi przeniesienia jako wartości liczbowej
ret
;dodawanie dwóch liczb 16-bitowych
;pierwsza liczba w A2:A1, druga w B2:B1
;wynik przechowywany w A2:A1 + flaga C
add_16_16:
add A1,B1 ;dodanie młodszych bajtów liczby bez uwzględnienia flagi C
adc A2,B2 ;dodanie starszych bajtów liczby z uwzględnieniem flagi C
ret ;powrót do wywołania z ustawioną lub nie flagą C
;dodawanie liczby 16 i 8-bitowej
;pierwsza liczba w A2:A1, druga w B2
;wynik przechowywany w A2:A1
add_16_8:
clr B2
call add_16_16
ret
;dodawanie stałych od długości 16 bitów do zmiennej 16-bitowej
;zmienna w A2:A1, stała zdefiniowana po dyrektywie .equ
;wynik przechowywany w A2:A1 + flaga C
.equ ccc = $18A0
add_16_const:
subi A1,low(-ccc) ;dodaj młodszy bajt (A1-(-ccc)=A1+ccc)
sbci A2,high(-ccc) ;dodaj starszy bajt z przeniesieniem
ret
Odejmowanie liczb: 8-8 bitów, 16-16 bitów, 16-8 bitów
Na listingu 2 przedstawiono funkcje realizujące odejmowanie liczb. Analogicznie do dodawania, zdefiniowane są funkcje odejmujące liczbę 8 bitową od 8 bitowej, 8 od 16, 16 od 16 oraz odejmują stałą od zmiennej. W przypadku odejmowania nie ma problemu braku odpowiedniego rozkazu, toteż procedura może być zrealizowana bez żadnych „sztuczek”. Do realizacji odejmowania służą rozkazy sub oraz sbc isbci. Ten pierwszy nie uwzględnia bitu przeniesienia, dwa następne wykonują działanie z uwzględnieniem przeniesienia. Rozkaz sbci odejmuje od zmiennej stałą podaną jako jeden z parametrów wywołania.
List. 2. Przykłady realizacji funkcji odejmowania
;deklaracje zmiennych
.def A1 = R16 ;młodszy (A1)
.def A2 = R17 ;i starszy (A2) bajt zmiennej A
.def B1 = R18 ;młodszy (B1)
.def B2 = R19 ;i starszy (B2) bajt zmiennej B
;odejmowanie dwóch liczb 8-bitowych,
;pierwsza liczba w A1,druga w B1
;wynik przechowywany w A2:A1
sub_8_8:
clr A2 ;wyzerowanie starszego bajtu wyniku
sub A1,B1 ;wykonanie operacji dla młodszych bajtów
rol A2 ;wprowadzenie flagi C na pozycję najmłodszego bitu wyniku
ret
;odejmowanie dwóch liczb 16-bitowych
;pierwsza liczba w A2:A1, druga w B2:B1
;wynik przechowywany w A2:A1 + flaga C
sub_16_16:
sub A1,B1 ;wykonanie operacji dla młodszych bajtów
sbc A2,B2 ;wykonanie operacji dla starszych bajtów z uwzgl. flagi C
ret
;odejmowanie od liczby 16 liczby 8-bitowej
;pierwsza liczba w A2:A1, druga w B2
;wynik przechowywany w A2:A1
sub_16_8:
sub A1,B1 ;wykonanie operacji dla młodszych bajtów
sbci A2,0 ;uwzględnienie przeniesienia
ret
;odejmowanie od zmiennej o długości 16 bitów stałych o długości 16 bitów
;zmienna w A2:A1, stała zdefiniowana po dyrektywie .equ
;wynik przechowywany w A2:A1 + flaga C
.equ ccc = $18A0
sub_16_const:
subi A1,low(ccc) ;odejmij młodszy bajt
sbci A2,high(ccc) ;odejmij starszy bajt z przeniesieniem
ret
Porównywanie liczb: 8 i 8 bitów, 16 i 16 bitów
Listing 3 zawiera operacje porównania liczb. Co prawda można prezentowane funkcje zastąpić przez odejmowanie, jednak listing 3 to również próba pokazania w jaki sposób używa się rozkazów porównań oferowanych przez język asembler AVR. Podobnie jak poprzednio, również rozkazy porównań ustawiają flagi Z i C. Tak więc do porównania dwóch liczb bez uwzględnienia stanu flagi przeniesienia służy rozkaz cp, natomiast porównanie z uwzględnieniem stanu flagi C przeprowadza rozkaz cpc. W przypadku porównania stałej ze zmienną istnieje pewne niekonsekwencja: o ile można bowiem porównać stałą i zmienną z pominięciem stanu flagi C, o tyle brak jest rozkazu uwzględniającego przeniesienie przy porównaniu stałej i zmiennej. Dlatego też konieczne jest wykorzystanie zmiennej temp, do której załadowana zostanie stała do porównania.
List. 3. Porównywanie liczb
;deklaracje zmiennych
.def A1 = R16 ;młodszy (A1)
.def A2 = R17 ;i starszy (A2) bajt zmiennej A
.def B1 = R18 ;młodszy (B1)
.def B2 = R19 ;i starszy (B2) bajt zmiennej B
;porównanie dwóch liczb 8-bitowych zapamiętanych w zmiennych A1 i B1
;jeśli liczby są równe, ustawiana jest flaga Z
cp_8_8:
cp A1,B1 ;lista rozkazów zawiera rozkaz porównania-tu bez flagi
ret
;porównanie dwóch liczb 16-bitowych
;jeśli liczby są równe, ustawiana jest flaga Z
cp_16_16:
cp A1,B1 ;porównanie młodszych bajtów
cpc A2,B2 ;porównanie starszych bajtów ale z uwzględnieniem flagi C
ret
;porównanie liczby 16-bitowej i stałej o długości 16 bitów
;jeśli liczby są równe, ustawiana jest flaga Z
.equ ccc = $18A0
.def temp R20
cp_16_const:
cpi A1,low(ccc) ;porównanie młodszych bajtów
ldi temp,high(ccc) ;nie istnieje wariant cpc dla stałych stąd
;konieczność użyciazmiennej temp
cpc B2,temp ;i porównania starszego bajtu z tą zmienną
ret
Znajomość sposobów wykonywania podstawowych działań umożliwia budowę innych funkcji np. mnożenia (przez dodawanie) czy dzielenia (przez odejmowanie). Umożliwia również konwersję liczb szesnastkowych na dziesiętne – jest to zagadnienie bardzo często spotykane wśród pytań zadawanych przez początkujących programistów na grupach tematycznych.
Jacek Bogusz
j.bogusz@easy-soft.net.pl
Dodaj nowy komentarz