AVR Assembler - Unterprogramme: Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
K (Syntaxhighlight (asm), R/G, Tippfehler, Kleinigkeiten) |
||
(17 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
== Aufruf == | == Aufruf == | ||
Unterprogramme werden beim | Unterprogramme werden beim [[AVR]] mit einem der Befehle '''<code>rcall</code>''' oder '''<code>call</code>''' aufgerufen. <code>rcall</code> erhält als Operand die relative Adresse des Unterprogramms in Form einer symbolischen Sprungmarke (z. B. „<code>loop</code>“). Es ist natürlich auch möglich, die relative Adresse direkt anzugeben (z. B. „<code>-20</code>“). Diese Möglichkeit sollte jedoch weitestgehend vermieden werden, da sie eine zusätzliche Fehlerquelle darstellt und allgemein schlechter lesbare Programme zur Folge hat. Mittels <code>rcall</code> ist es nur möglich, relative Adressen im Bereich von −2K+1 bis +2K Worten anzuspringen. Darüber hinaus muss der Befehl <code>call</code> verwendet werden, mit dem der ganze Programmspeicher adressiert werden kann. Warum dann nicht immer <code>call</code> verwenden? Zum einen gibt es den Befehl auf den AVRs nicht, bei denen der gesamte Speicher mit <code>rcall</code> erreichbar ist. Daneben benötigt <code>call</code> 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 == | == Parameterübergabe == | ||
Auf Prozessoren mit vielen [[Register]]n ist es sinnvoll ein paar Register zur Parameterübergabe zu reservieren, z.B. | Auf Prozessoren mit vielen [[Register]]n 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. | ||
< | <syntaxhighlight lang="asm"> | ||
.def par1 = r12 | .def par1 = r12 | ||
.def par2 = r13 | .def par2 = r13 | ||
Zeile 17: | Zeile 19: | ||
main: | main: | ||
; Stackpointer sollte insbes. bei Nutzung von Unterprogrammen immer | ; Stackpointer sollte insbes. bei Nutzung von Unterprogrammen immer | ||
; initialisiert werden, z.B. beim ATmega16 so: | ; initialisiert werden, z. B. beim ATmega16 so: | ||
ldi temp, LOW(RAMEND) ; | ldi temp, HIGH(RAMEND) ; High-Byte der obersten RAM-Adresse | ||
out SPH, temp | |||
ldi temp, LOW(RAMEND) ; Low-Byte der obersten RAM-Adresse | |||
out SPL, temp | out SPL, temp | ||
; Umweg über r16, da r0 | ; Umweg über r16, da r0..r15 nicht direkt mit Konstanten arbeiten können | ||
ldi temp, 0x20 | ldi temp, 0x20 | ||
mov par1, temp | mov par1, temp | ||
rcall unterprogramm | rcall unterprogramm | ||
mainloop: | |||
rjmp mainloop | |||
unterprogramm: | unterprogramm: | ||
; irgendetwas tun | ; irgendetwas tun | ||
ret | ret | ||
</syntaxhighlight> | |||
== 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 benutztem ASM anders verfahren muß, um eine gültige Code-Adresse in ein Register zu laden: | |||
<syntaxhighlight lang="asm"> | |||
; 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: | |||
</syntaxhighlight> | |||
Umgekehrt muß man verfahren, wenn man mit <code>lpm</code> Daten aus dem Programmspeicher lesen will: | |||
<syntaxhighlight lang="asm"> | |||
; GNU-Assembler | |||
ldi r30, lo8(data_label) | |||
ldi r31, hi8(data_label) | |||
lpm r24, z | |||
; AVR-Assembler | |||
ldi r30, low(data_label<<1) | |||
ldi r31, high(data_label<<1) | |||
lpm r24, z | |||
data_label: | |||
</syntaxhighlight> | |||
[[Category:AVR]] |
Aktuelle Version vom 25. November 2022, 19:14 Uhr
Aufruf
Unterprogramme werden beim AVR mit einem der Befehle rcall
oder 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
“). Diese 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 bis +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 gesamte 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.
.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 der obersten RAM-Adresse
out SPH, temp
ldi temp, LOW(RAMEND) ; Low-Byte der obersten RAM-Adresse
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
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 benutztem ASM anders verfahren muß, um eine gültige Code-Adresse in ein Register zu laden:
; 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:
Umgekehrt muß man verfahren, wenn man mit lpm
Daten aus dem Programmspeicher lesen will:
; GNU-Assembler
ldi r30, lo8(data_label)
ldi r31, hi8(data_label)
lpm r24, z
; AVR-Assembler
ldi r30, low(data_label<<1)
ldi r31, high(data_label<<1)
lpm r24, z
data_label: