AVR Assembler - Unterprogramme: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
K (Syntaxhighlight (asm), R/G, Tippfehler, Kleinigkeiten)
 
(4 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
[[Category:AVR]]
== Aufruf ==
== 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.  
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.&nbsp;B. „<code>loop</code>“). Es ist natürlich auch möglich, die relative Adresse direkt anzugeben (z.&nbsp;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.
Da die Rücksprungadresse beim Aufruf von Unterprogrammen auf dem [[Stack]] abgelegt wird, muss dieser beim Programmstart ordnungsgemäß initialisiert werden.
Zeile 9: Zeile 7:
== Parameterübergabe ==
== Parameterübergabe ==


Auf Prozessoren mit vielen [[Register]]n ist es sinnvoll ein paar Register zur Parameterübergabe zu reservieren, z.&nbsp;B. R12-R15 (je nach Bedarf mehr oder weniger). Man kann es sich dadurch sparen die nötigen Parameter über den Stack zu übergeben.
Auf Prozessoren mit vielen [[Register]]n ist es sinnvoll, ein paar Register zur Parameterübergabe zu reservieren, z.&nbsp;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>  
<syntaxhighlight lang="asm">
.def par1 = r12
.def par1 = r12
.def par2 = r13
.def par2 = r13
Zeile 21: Zeile 19:
main:
main:
         ; Stackpointer sollte insbes. bei Nutzung von Unterprogrammen immer
         ; Stackpointer sollte insbes. bei Nutzung von Unterprogrammen immer
         ; initialisiert werden, z.&nbsp;B. beim ATmega16 so:
         ; initialisiert werden, z. B. beim ATmega16 so:
         ldi temp, HIGH(RAMEND) ; HIGH-Byte of upper RAM-Adress
         ldi temp, HIGH(RAMEND) ; High-Byte der obersten RAM-Adresse
         out SPH, temp      
         out SPH, temp
         ldi temp, LOW(RAMEND)  ; LOW-Byte of upper RAM-Adress
         ldi temp, LOW(RAMEND)  ; Low-Byte der obersten RAM-Adresse
         out SPL, temp
         out SPL, temp


         ; Umweg über r16, da r0-r15 nicht direkt mit Konstanten arbeiten können
         ; 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
Zeile 37: Zeile 35:
         ; irgendetwas tun
         ; irgendetwas tun
         ret
         ret
 
</syntaxhighlight>
</avrasm>
 


== Hinweis zu den verschiedenen Assemblern ==
== Hinweis zu den verschiedenen Assemblern ==


Adressen im Programmspeicher der AVRs werden von den verschiedenen Assemblern für den AVR unterschiedlich gehandhabt:
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'''.


Der '''gnu-Assembler''' adressiert den Programmspeicher '''byteweise'''
Das hat zur Konsequenz, daß man je nach benutztem ASM anders verfahren muß, um eine gültige Code-Adresse in ein Register zu laden:


Die '''AVR-Assembler''' adressieren den Programmspeicher '''wortweise'''
<syntaxhighlight lang="asm">
    ; GNU-Assembler
    ldi    r30, pm_lo8(label)
    ldi    r31, pm_hi8(label)
    push    r30                ; label als ret-Adresse pushen
    push    r31


Das hat zur Konsequenz, daß man je nach benutzem ASM anders verfahren muß, um eine gültige Code-Adresse in ein Register zu laden:
    ; AVR-Assembler
    ldi    r30, low(label)
    ldi    r31, high(label)
    push    r30                ; label als ret-Adresse pushen
    push    r31


<avrasm>
label:
; GNU-Assembler
</syntaxhighlight>
ldi r30, pm_lo8(label)
  ldi r31, pm_hi8(label)
  push    r30      ; label als ret-Adresse pushen
  push    r31


Umgekehrt muß man verfahren, wenn man mit <code>lpm</code> Daten aus dem Programmspeicher lesen will:
; AVR-Assembler
 
ldi r30, low(label)
<syntaxhighlight lang="asm">
ldi r31, high(label)
    ; GNU-Assembler
  push    r30      ; label als ret-Adresse pushen
    ldi     r30, lo8(data_label)
  push    r31
    ldi     r31, hi8(data_label)
    lpm    r24, z
label:
</avrasm>


Umgekehrt muß man verfahren, wenn man mit LPM Daten aus dem Programmspeicher lesen will:
    ; AVR-Assembler
    ldi    r30, low(data_label<<1)
    ldi    r31, high(data_label<<1)
    lpm    r24, z


<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:
data_label:
</avrasm>
</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: