AVR Assembler

Übungsblatt

Zu diesem Teil gibt es Übungsaufgaben

Allgemeines

Der Befehlssatz des Atmel AVR ist ein typischer RISC-Befehlssatz. Bei der Entwicklung der AVR Reihe stand vor allem eine möglichst effiziente Nutzung durch C-Compiler im Vordergrund.

Blockschaltbild

Blockschaltbild des AVR (Quelle: Datenblatt ATMega16 © Atmel Corporation)

Im Blockschaltbild des Atmel AVR ATMega16 erkennt man am oberen und unteren Ende die vier IO-Ports.

Rund um den Prozessorkern (AVR CPU) befindet sich folgende Peripheriebausteine:

  • ADC, mit Multiplexer auf die Pins von Port A
  • I²C Schnittstelle (TWI - Two Wire Interface) auf Port C
  • Timer/Counter
  • Watchdogtimer mit dem internen Oszillator
  • MCU Ctrl. & Timing - zuständig für den Prozessortakt und Reset
  • Interrupt Einheit
  • EEPROM
  • USART auf Port D
  • SPI auf Port B
  • Komperator

Diese Peripheriebausteine sind über einen Adress/Datenbus mit dem Prozessorkern verbunden.

Der Prozessorkern besteht aus dem Flash Speicher für das eigentliche Programm und dem SRAM für die Laufzeitvariablen. Der Programmzeiger (Program Counter) zeigt auf den aktuellen Befehl der vom Instruction Register zwischengespeichert wird und durch den Instruction Decoder dekodiert wird.

Der Stack Pointer dient zum Ablegen von Werten und Rücksprungadressen im SRAM. Für Berechnungen mit der ALU werden die Register R0 bis R31 genutzt. 3 16Bit Indexregister (X, Y und Z) dienen der indirekten Adressierung des SRAMs. Das Statusregister ist unter anderem für die Flags der ALU zuständig (Carry, Overflow, usw.).

Im Prozessorkern sieht man auch die Harvardarchitektur, da der SRAM Speicher und der Flash Speicher durch getrennte Adress/Datenbusse angesteuert werden.

Registersatz

Die AVR Serie besitzt 32 allgemein verwendbare Register(R0 bis R31). Die Register R0 bis R15 sind nicht verfügbar für Befehle mit unmittelbaren Konstanten (z.B. ldi-load immediate).

Die Register R27:R26 bilden gemeinsam das 16 Bit X-Register, wobei R27 das höherwertige Byte darstellt und R26 das niederwertige. Neben dem X-Register gibt es analog das Y und Z Register:

  • R27:R26: X-Register
  • R29:R28: Y-Register
  • R31:R30: Z-Register

Diese Register können für die indirekte Adressierung genutzt werden.

Stack Pointer

Der Stack Pointer ist eine 16 Bit Adresse und zeigt auf die aktuelle Position im Stack. Auf dem Stack werden die Rücksprungadressen bei einem call-Befehl und bei einem Interruptaufruf gespeichert. Zusätzlich kann der Stack genutzt werden, um Register zu sichern oder Zwischenergebnisse zu speichern.

Der Stackpointer muss vor dem ersten Zugriff initialisiert werden. Dazu wird er an das Ende des Datenspeichers gesetzt. Der AVR Assembler unterstützt das Symbol RAMEND, das die letzte Adresse des Datenspeichers darstellt. Die Makros HIGH und LOW liefern die oberen bzw. unteren 8 Bit eines 16 Bit Wertes.

ldi R16, HIGH(RAMEND)
out SPH, R16
ldi R16, LOW(RAMEND)
out SPL, R16

Adressräume

Bedingt durch die Harvard-Architektur der AVR Serie gibt es eine Trennung der Adressräume für den Befehlsspeicher (Flash), den Datenspeicher (SRAM) und dem EEPROM.

Befehlsspeicher

Der Adressraum im Befehlsspeicher wird in folgende Bereiche unterteilt:

  • Interruptvektoren: Sprungmarken für Reset und die Interruptquellen
  • Programmspeicher: Nach den Interruptvektoren befindet sich das eigentliche Programm
  • Optionaler Bootloader: Ein Teil des Befehlsspeichers kann geschützt und als Bootloader verwendet werden

Datenspeicher

Adresse Beschreibung
0x00-0x1F Register R0 bis R31
0x20-0x5F I/O Register 0x00 bis 0x3F
0x60-Ende des internen SRAM als Datenspeicher verwendbar

EEPROM

Das EEPROM wird mittels I/O Register angesprochen.

Befehlsübersicht

Transferbefehle

Kopieren von Registern mittels mov

Kopiert den Inhalt des Registers Rr in das Register Rd.

mov r0, r16 ; Kopiert den Inhalt von R16 nach R0

Eine spezielle Variante ist movw . Hier werden zwei Register gleichzeitig kopiert, wobei als Basisregister nur geradzahlige Register möglich sind.

movw r17:r16, r1:r0 ; Kopiert Register R1 nach R17 und Register R0 nach R16

Laden von Registern mittels ld

Beim Laden gibt es mehrere Möglichkeiten der Adressierung der Quelle. Soll ein Konstante geladen werden, wird ldi verwendet.

ldi R16, 0x20 ; Lädt den Wert 0x20 in das Register R16

Soll von einer bestimmten Speicheradresse geladen werden, wird lds verwendet.

lds R0, 0x60 ; Lädt den Wert an der Adresse 0x60 ins Register R0

Die Register X,Y und Z können zum indirekten Laden von Werten verwendet werden. Dabei wird der Inhalt der Register als Adresse verwendet und an der Wert von der entsprechenden Adresse im Speicher geladen. Weiters ist es möglich, die Adresse nach dem Zugriff um 1 zu erhöhen (Post-Inkrement) oder vor dem Zugriff um 1 zu erniedrigen (Pre-Dekrement).

ld r0, X ; Lädt den Wert an der durch das Register X dargestellten Adresse
ld r1, Y+ ; Erhöht nach dem Laden das Y Register um 1
ld r3, -Y; Erniedrigt vor dem Laden das Y Register um 1

Für den Zugriff auf Tabellen oder auf den Stack Frame eignet sich das Laden mittels Displacment. Dabei wird das Y oder Z Register verwendet und ein Offset hinzugerechnet.

ldd r4, Y+20 ; Lädt den Wert an der durch Y+20 dargestellten Adresse

Speichern von Werten im SRAM

Beim Speichern auf eine bestimmte Speicheradresse wird der Befehl sts benutzt.

sts 0x60, R0 ; Speichert den Wert des Registers R0 an der Adresse 0x60

Ähnlich zu den Load Befehlen kann auch die indirekte Adressierung über X,Y und Z Register verwendet werden.

st X, r0 ; Speichert das Register an der durch das Register X dargestellten Adresse
st Y+, r1 ; Erhöht nach dem Speichern das Y Register um 1
st -Y, r1; Erniedrigt vor dem Speichern das Y Register um 1

Zugriff auf I/O Register

Der Zugriff auf I/O Register erfolgt mittels in und out.

out PORTD, R0 ; Kopiere den Wert von R0 ins IO Register PORTD
in R29, PINA   ; Kopiere den Wert des IO Registers PINA ins Register R29

Arbeiten mit dem Stack

Der Stackpointer wird in den beiden Register SPH und SPL gespeichert. Mittels push und pop können Werte auf den Stack gelegt bzw. wieder vom Stack geholt werden. Der Stack wird außerdem genutzt, um die Rücksprungadresse bei Subroutinen-Aufrüfen mittels call bzw. rcall zu speichern.

Arithmetische Befehle

Arthmetische Befehle verknüpfen üblicherweise entweder zwei Register miteinander oder ein Register mit einer Konstante.

Als Operationen stehen die Addtion (add) und Subtraktion (sub) zur Verfügung. Es gibt auch jeweils eine Veriante, in der das Übertragsbit aus einer vorhergehenden Operation verwendet wird (adc bzw. sbc). Für die Addition der Konstante 1 stehen die Befehle inc (Inkrement) bzw. dec (Dekrement) zur Verfügung.

Logische Befehle

Zu den logischen Befehlen gehört die AND Verknüpfung (and bzw. andi), die ODER Verknüpfung (or bzw. ori), die Exclusive-ODER Verknüpfung (eor) und die Invertierung aller Bits (com).

Sprünge

Unbedingte Sprünge

Bei unbedingten Sprüngen ist der Sprung nicht von einer Bedingung abhängig. Es gibt relative Sprünge (rjmp) und absolute Sprünge (jmp). Die relativen Sprünge können den Befehlszähler um +/-2048 verändern. Dies benötigt zwar eine entsprechende Berücksichtigung vom Assembler bzw. Compiler aus, stellt aber durch die kompaktere Ausführung (ein Befehlswort statt zwei) eine Optimierung dar.

Bedingte Sprünge

Die bedingten Sprünge bedienen sich der Überprüfung von Flags aus dem Statusregister und entscheiden anhand deren Zustandes, ob der Sprung genommen wird oder nicht. Häufig genutzt werden hier breq (branch if equal), brne (branch if not equal), brlo (branch if lower) und brsh (branch if same or higher).

Die Statusflags müssen durch einen vorhergehenden Befehl entsprechend gesetzt werden. Will man kein Register für einen Vergleich ändern, sondern nur die Statusflags, so eignet sich der cp (compare) Befehl. Dieser Vergleicht zwei Register mittels Subtraktion und setzt die Flags entsprechend.