Powrót

Mikrokontroler jednoukładowy. Asembler i adresowanie cz. 1

W poprzednim artykule (UR032)  zamieszczone były bardzo ważne informacje na temat obsługi urządzeń peryferyjnych za pomocą rejestrów w przestrzeni I/O pamięci RAM. W niniejszym artykule podane są ogólne informacje o innych bardzo ważnych zagadnieniach – o rozkazach asemblera oraz o sposobach adresowania. Teoretycznie zagadnienia te nie mają bezpośredniego związku z mocno nas interesującym językiem C, ale ich znajomość okaże się bardzo pożyteczna. Szersza wiedza usuwa strach i obawy oraz pomaga zrozumieć ścisły związek licznych poleceń języka C z asemblerem i z kodem maszynowym procesorów AVR. A w przyszłości być może w celu optymalizacji swoich programów w C zechcesz stosować w nich wstawki asemblerowe…

Grupy rozkazów

Jak już wiesz, w procesorach AVR rozkazy umieszczone w komórkach pamięci FLASH mają długość 16 bitów. Można powiedzieć, że rozkazy w kodzie maszynowym to 16-bitowe liczby dwójkowe. W kartach katalogowych procesorów AVR (na końcu) znajdziesz listę wszystkich rozkazów-instrukcji, ale nie w postaci liczb (dwójkowych czy szesnastkowych), tylko tzw. mnemoników – skrótów używanych w asemblerze.

Rysunek 1 pokazuje początek listy 131 rozkazów procesora ATmega328PB (str. 417 karty katalogowej). Liczba rozkazów (instrukcji), zależnie od „wielkości” procesora AVR, wynosi od kilkudziesięciu do stu kilkudziesięciu. Rozkazy te można podzielić na kilka głównych grup: arytmetyczno-logiczne, skoki, przesyłania danych oraz operacje na pojedynczych bitach. Omówmy je z grubsza.

Rysunek 1

Arytmetyczno-logiczne. Realizowane są w rejestrach o adresach 0…31. Wynik operacji automatycznie modyfikuje też pewne bity (flagi) w rejestrze stanu (rejestr SREG). Jak pokazuje rysunek 1, rozkazy arytmetyczne są zaskakująco nieliczne: dodawanie (ADD, ADC) i odejmowanie (SUB, SUBI, SBC, SBCI), a tylko w „najmocniejszych” procesorach proste mnożenie (MUL…, FMUL…). Więcej jest operacji logicznych na bitach całych rejestrów (AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR, INC, DEC, TST, CLR, SER).

Skoki. W skokach chodzi o licznik programu (PC) i zaadresowane przezeń komórki pamięci FLASH. „Podstawowy” wydaje się zawarty w dwóch komórkach skok (JMP, CALL), gdzie adres komórki FLASH, do której „program ma skoczyć”, określony jest w drugiej komórce takiego „podwójnego” rozkazu. W mniejszych procesorach AVR wykorzystywane są „pojedyncze” rozkazy skoków (RJMP, IJMP, RCALL, ICALL). Oprócz tego istnieje szereg rozkazów ominięcia (skip) i skoków (branch) w zależności od różnych warunków (SBRC, SBRS, SBIC, SBIS i liczne BR…). Opis niektórych rozkazów związanych ze skokami znajdziesz na rysunku 2.

Rysunek 2

Przesyłanie danych. W tej grupie jedną ze „stron” przesyłania danych jest jeden z rejestrów roboczych R0…R31. W rozkazie MOV „drugą stroną” też jest jeden z tych rejestrów. Zdecydowana większość rozkazów (odmiany LD, LDS, ST, STS) dotyczy przesyłania bajtu danych w jednym lub drugim kierunku między rejestrem (R0…R31) a inną, dowolną komórką pamięci danych, zaadresowaną w jeden z rozmaitych sposobów. Na rysunku 3 sposoby te przedstawione są w nieco dziwny sposób w ostatniej kolumnie. Istnieją „proste” rozkazy IN, OUT, służące do wymiany zawartości dowolnego z rejestrów R0…R31 oraz 64 „podstawowych” rejestrów przestrzeni I/O. Rozkaz LDI ładuje do jednego z rejestrów roboczych (R16…R31) ośmiobitową stałą, zawartą w kodzie rozkazu (czyli z pamięci FLASH do rejestru). Trzeba też wiedzieć, że istnieje rozkaz LPM (ELPM), który służy do przeniesienia danych trwale zapisanych w pamięci FLASH do rejestrów pamięci RAM.

Rysunek 3

Operacje bitowe. Jak pokazuje rysunek 4, istnieje kilka rozkazów przesuwania (shift) i zamiany (swap) bitów w rejestrach R0…R31 (co jest wykorzystywane m.in. do mnożenia/dzielenia przez potęgi liczby 2), a także szereg rozkazów ustawiania i zerowania pojedynczych bitów rejestru stanu SREG.

Rysunek 4

Jak pokazuje rysunek 5, wiele rozkazów dotyczy obsługi pojedynczych bitów w rejestrze stanu SREG. Istnieją też rozkazy SBI, CBI, które pozwalają bezpośrednio ustawiać i zerować pojedyncze bity w pierwszych 32 rejestrach I/O, co wykorzystywane jest m.in. do szybkiej obsługi pojedynczych pinów portów procesora. Natomiast do takich operacji na 32 „wyższych” rejestrach standardowej przestrzeni I/O oraz przestrzeni rozszerzonej (Extended I/O) trzeba stosować kombinację innych poleceń.

Rysunek 5

Bardzo pobieżnie zapoznaliśmy się z dostępnymi rozkazami. Jeśli chcesz, możesz znaleźć dodatkowe informacje o poleceniach kodu maszynowego i asemblera także na polskojęzycznych stronach, na przykład krótkie opisy:

http://goo.gl/18vaY8

http://goo.gl/X2Tkdh

Nie musisz wszystkiego rozumieć i wnikać w szczegóły. Gdy będziesz poznawać język C, zauważysz, że niektóre jego rozkazy są bezpośrednimi odpowiednikami rozkazów maszynowych. Choć C zaliczany jest do języków wysokiego poziomu, to jest on ściśle związany z kodem maszynowym, a to pozwala pisać w C zwięzłe i „szybkie” programy.

Liczba rozkazów

Rozkazy (instrukcje) w kodzie maszynowym AVR to 16-bitowe liczby dwójkowe, więc sumaryczna liczba rozkazów wynosi 216 =65536. W zapisie szesnastkowym są to czterocyfrowe liczby 0x0000…0xFFFF. Tymczasem na liście mamy poniżej 140 rozkazów AVR.

Na przykład rozkaz NOP (nic nie rób) ma numer 0000000000000000, czyli szesnastkowo 0x0000,  rozkaz RETI (powrót z obsługi przerwania) ma numer  1001010100011000, czyli 0x9518, rozkaz WDR (wyzeruj licznik watchdoga) ma numer 1001010110101000, czyli 0x95A8.

Czy wykorzystujemy poniżej 1% „możliwości”? Nie!

Wykorzystujemy ponad 97% rozkazów spośród 65536 możliwych. „Podstawowych” rozkazów mamy rzeczywiście tylko nieco ponad 130, ale w kodach większości rozkazów od razu zawarte są dodatkowe informacje (liczby, adresy). I tak na przykład rozkaz wymiany zawartości dwóch spośród rejestrów R0…R31 w asemblerze zapisujemy ogólnie jako: MOV Rd, Rr, podając w rozkazie dwa 5-bitowe numery: Rr rejestru źródłowego, Rd rejestru przeznaczenia (Rd ← Rr). Tak samo w rozkazie dodawania zawartości dwóch rejestrów:  ADD Rd, Rr (Rd ← Rd + Rr). Analogicznie jest w innych rozkazach, gdzie w treści rozkazu występują inne liczby. Na przykład rozkaz CPI porównuje zawartość rejestru R16…R31 z liczbą 8-bitową (K), a wynik porównania trafia do rejestru stanu SREG.

Rozkaz SBI ustawia jeden z siedmiu bitów (b) w jednym z 32 „pierwszych” rejestrów I/O o numerze A. W 16-bitowym kodzie maszynowym rozkazy


wyglądają następująco:
Z uwagi na dodatkowe dane, w istocie mamy więc po 1024 rozkazy MOV i ADD, aż 4096 rozkazów CPI oraz 256 rozkazów SBI.

Więcej informacji zawiera rysunek 6, będący fragmentem pełnej listy, którą można znaleźć  w Wikipedii na stronie:

https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set

w skrócie: https://goo.gl/7dTF9i.

Warto zajrzeć na tę stronę i poznać dalsze szczegóły.

Rysunek 6

Szczegółowy opis rozkazów rodziny AVR (po angielsku) jest na stronie:

www.atmel.com/images/atmel-0856-avr-instruction-set-manual.pdf

w skrócie:

http://goo.gl/yA1RFY.

Rysunek 7

Rysunek 7 pokazuje opis rozkazu MOV, pochodzący z karty katalogowej. Pełne informacje o kodach wszystkich rozkazów (65536) można znaleźć na bardzo obszernej stronie:

http://lyons42.com/AVR/Opcodes/AVRAllOpcodes.html

w skrócie:

http://goo.gl/8xZTEJ.

Stamtąd pochodzi też rysunek 8, pokazujący w zarysie, ile w rzeczywistości mamy poszczególnych rozkazów.

Rysunek 8

Z tejże strony pochodzi też rysunek 9, pokazujący niektóre rozkazy, których kody zaczynają się od 0x2F… (MOV) i 0x30… (CPI).

Rysunek 9

Jeśli chcesz, możesz poszukać w sieci dalszych informacji. Zachęcam, żebyś w ten sposób rozszerzył swoje horyzonty. Podkreślam jednak, że nie jest to niezbędne przy programowaniu w języku C. Niemniej chcąc programować mikroprocesory AVR, powinieneś przynajmniej z grubsza rozumieć tryby adresowania. Zajmujemy się tym w następnym artykule (UR034).

 Piotr Górecki