AVR Assembler - Unterprogramme: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
Zeile 74: Zeile 74:
ldi r30, lo8(data_label)
ldi r30, lo8(data_label)
   ldi r31, hi8(data_label)
   ldi r31, hi8(data_label)
   lmp   r24
   lpm   r24
Zeile 80: Zeile 80:
ldi r30, low(data_label<<1)
ldi r30, low(data_label<<1)
ldi r31, high(data_label<<1)
ldi r31, high(data_label<<1)
   lmp   r24
   lpm   r24
data_label:
data_label:
</avrasm>
</avrasm>

Version vom 24. August 2011, 12:08 Uhr


Aufruf

Unterprogramme werden beim AVR mit dem Befehl rcall bzw call aufgerufen. rcall erhält als Operand die relative Adresse des Unterprogramms in Form einer symbolischen Sprungmarke (z. B. "loop"). Es ist natürlich auch möglich die relative Adresse direkt anzugeben (z. B. "-20"). Dieser Möglichkeit sollte jedoch weitestgehend vermieden werden, da sie eine zusätzliche Fehlerquelle darstellt und allgemein schlechter lesbare Programme zur Folge hat. Mittels rcall ist es nur möglich, relative Adressen im Bereich von -2K+1 und +2K Worten anzuspringen. Darüber hinaus muss der Befehl call verwendet werden, mit dem der ganze Programmspeicher adressiert werden kann. Warum dann nicht immer call verwenden? Zum einen gibt es den Befehl auf den AVRs nicht, bei denen der gesammte Speicher mit rcall erreichbar ist. Daneben benötigt call zwei Worte im Speicher und einen Zyklus mehr zur Abarbeitung.

Da die Rücksprungadresse beim Aufruf von Unterprogrammen auf dem Stack abgelegt wird, muss dieser beim Programmstart ordnungsgemäß initialisiert werden.

Parameterübergabe

Auf Prozessoren mit vielen Registern ist es sinnvoll ein paar Register zur Parameterübergabe zu reservieren, z. B. R12-R15 (je nach Bedarf mehr oder weniger). Man kann es sich dadurch sparen die nötigen Parameter über den Stack zu übergeben.

<avrasm> .def par1 = r12 .def par2 = r13 .def par3 = r14 .def par4 = r15

.def temp = r16

main:

       ; Stackpointer sollte insbes. bei Nutzung von Unterprogrammen immer
       ; initialisiert werden, z. B. beim ATmega16 so:
       ldi temp, HIGH(RAMEND) ; HIGH-Byte of upper RAM-Adress
       out SPH, temp        
       ldi temp, LOW(RAMEND)  ; LOW-Byte of upper RAM-Adress
       out SPL, temp
       ; Umweg über r16, da r0-r15 nicht direkt mit Konstanten arbeiten können
       ldi temp, 0x20
       mov par1, temp
       rcall unterprogramm

mainloop:

       rjmp mainloop

unterprogramm:

       ; irgendetwas tun
       ret

</avrasm>


Hinweis zu den verschiedenen Assemblern

Adressen im Programmspeicher der AVRs werden von den verschiedenen Assemblern für den AVR unterschiedlich gehandhabt:

Der gnu-Assembler adressiert den Programmspeicher byteweise

Die AVR-Assembler adressieren den Programmspeicher wortweise

Das hat zur Konsequenz, daß man je nach benutzem ASM anders verfahren muß, um eine gültige Code-Adresse in ein Register zu laden:

<avrasm> ; GNU-Assembler ldi r30, pm_lo8(label)

 	ldi		r31, pm_hi8(label)
 	push  	  	r30  	  	  	; label als ret-Adresse pushen
 	push  	  	r31


; AVR-Assembler ldi r30, low(label) ldi r31, high(label)

 	push  	  	r30  	  	  	; label als ret-Adresse pushen
 	push  	  	r31

label: </avrasm>

Umgekehrt muß man verfahren, wenn man mit LPM Daten aus dem Programmspeicher lesen will:

<avrasm> ; GNU-Assembler ldi r30, lo8(data_label)

 	ldi		r31, hi8(data_label)
 	lpm  	  	r24


; AVR-Assembler ldi r30, low(data_label<<1) ldi r31, high(data_label<<1)

 	lpm  	  	r24

data_label: </avrasm>