Unterprogrammaufrufe verursachen einen relativ hohen Prozess-Overhead, da meist zahlreiche Register auf den Stack gesichert werden und beim Rücksprung wiederhergestellt werden müssen. Ein Near-Subroutine Aufruf (Assembler-Mnemonic rcall) benötigt 3 Prozessortakte um Programmcounter zu sichern und 4 Takte um den ,,alten'' Zustand wiederherzustellen, des Weiteren pro zu sicherndes Register 2 Takte und dann nochmal dasselbe zur Wiederherstellung. Deshalb sind oft 10-20 Takte notwendig um ein einfaches Unterprogramm aufzurufen, das evtl. sogar weniger Takte benötigt um die geforderte Funktion auszuführen.
Ein weiteres Manko der Assemblersyntax ist, dass es nicht möglich ist, die in Hochsprachen übliche Schreibweise von Funktionsaufrufen mit Parametern zu verwenden, sondern man ist gezwungen, Parameter entweder in bestimmte Register zu laden oder in Speichervariablen abzulegen, auf die im Unterprogramm zugegriffen werden kann. Die zuletzt erwähnte Methode ist dabei weniger effizient, da für das Zwischenspeichern und Auslesen der Variablen ebenfalls wertvolle Prozessorzeit verbraucht wird.
Eine Möglichkeit, eine Hochsprachen ähnliche Syntax zu verwenden, existiert mit der Verwendung von Assembler-Makros, die als Platzhalter ,,Pseudoparameter'' verwenden können.
Beispiel:
Makros für die Ausgabe beliebiger, im Codesegment definierter ASCII Text-Strings.
Makrodefinitionen:
; Parameter: @0 .. REG[LH]
; @1 .. flash_address
;
; affected: none
.macro FLASH2REG
ldi @0H,high(@1<<1)
ldi @0L,low(@1<<1) ;init indirect register-pointer
endmacro
; ----------------------------------
; write string function using a label
; label text MUST be terminated by a 0x0 byte
;
; Parameter: @0 .. SRAM label (must be unique!)
;
; affected: REG_Z
.macro LCDINIT_WRITE_ASTRING
FLASH2REG REG_Z,@0
rcall LCDINIT_SEND_ASTRING
.endmacro
Makroverwendung:
COPYRIGHT_AUTHOR: .db "G. Reithofer",$0,$0
;
; send string to LCD
LCDINIT_WRITE_ASTRING COPYRIGHT_AUTHOR
Der damit erzeugte Makrocode wird vom Assembler in syntaktisch korrekten
Assemblercode umgesetzt und die Ausgabeanweisung vom obigen Beispiel
wird dann in folgende Assembleranweisungen übersetzt.
ldi ZH,high(COPYRIGHT_AUTHOR<<1)
ldi ZL,low(COPYRIGHT_AUTHOR<<1)
rcall LCDINIT_SEND_ASTRING
Dabei ist zu beachten, dass in dieser Situation ein Registerpaar (nämlich
Z, bzw. R30 und R31) für den Aufruf verwendet wird. Ist der vorherige
Inhalt des verwendeten Registers später von Belang, muss dessen Inhalt
zwischengespeichert werden (z.B. auf den Stack) und nach Abarbeitung
des Makros wieder hergestellt werden. Allerdings tritt u. U. wieder
die vorhin erwähnte Prozessorzeitverschwendung zu Tage, wenn diese
Vorgangsweise bei jedem Aufruf eines Makros durchgeführt werden würde,
das ,,Sichern'' im Einzelfall aber nicht notwendig gewesen wäre.
In diesem Fall ist man bei der Verwendung von Makros flexibler als
bei einem Unterprogrammaufruf, der in jedem Fall einen Aufruf-Overhead
verursacht.
Einige Makros werden hauptsächlich zur Verbesserung der Lesbarkeit des Codes verwendet, obwohl sie Unterprogamm ähnliche Strukturen verwenden, wie das Sichern aller Register. Dies führt dann zu sehr kompaktem und überschaubarem Code, kann sich aber bei unkontrollierter Verwendung auch nachteilig auswirken.
Definitionen:
; ----------------------------------
; set LCD cursor to specific location
; includes II2C init
; Parameter: @0 .. lcd display column number (0..11)
; @1 .. lcd display line number (0..2)
; affected: flags
.macro LCDINIT_GOTO_XY
push temp1
push temp2
ldi temp1,@0
ldi temp2,@1
rcall LCDINIT_GOTO_POS
pop temp2
pop temp1
.endmacro
.macro DRAW_MENU
PUSHW Z ; used in *STRING macros...
rcall LCDINIT_CLEAR_DISP
FLASH2REG Z,@0
rcall LCDINIT_SEND_ASTRING
LCDINIT_GOTO_XY 0,1
FLASH2REG Z,@1
rcall LCDINIT_SEND_RSTRING
LCDINIT_GOTO_XY 0,2
FLASH2REG Z,@2
rcall LCDINIT_SEND_ASTRING
POPW Z
.endmacro
Verwendung:
; ---------------------------------
goto_menumode:
DRAW_MENU MNU_FUNC_GOTO,MNU_LEVEL_CHNG,MNU_FUNC_MCTL
ret
Hinweis:
Dieser Aufruf gibt ein komplettes Benutzermenü aus, das alle 3 LCD-Zeilen
umfasst (siehe dazu auch Absch. 7.6.2.1).
Als Nachteil der Makroverwendung tritt ein erhöhter Speicherverbrauch
auf, da jeder Makroaufruf in eigene Assembleranweisungen umsgesetzt
wird, wohin gegen der Code einer Subroutine nur einmal im Programmspeicher
abgelegt ist.
Als Beispiel sei hier der Aufruf des oben angeführten DRAW_MENU Makros betrachtet, das in 30 Assembleranweisungen mündet und nicht mehr als effizienter Assemblercode bezeichnet werden kann.
goto_menumode:
push ZH
push ZL
rcall LCDINIT_CLEAR_DISP
ldi ZH,high(MNU_FUNC_GOTO<<1)
ldi ZL,low(MNU_FUNC_GOTO<<1)
rcall LCDINIT_SEND_ASTRING
push temp1
push temp2
ldi temp1,0
ldi temp2,1
rcall LCDINIT_GOTO_POS
pop temp2
pop temp1
ldi ZH,high(MNU_LEVEL_CHNG<<1)
ldi ZL,low(MNU_LEVEL_CHNG<<1)
rcall LCDINIT_SEND_RSTRING
push temp1
push temp2
ldi temp1,0
ldi temp2,2
rcall LCDINIT_GOTO_POS
pop temp2
pop temp1
ldi ZH,high(MNU_FUNC_MCTL<<1)
ldi ZL,low(MNU_FUNC_MCTL<<1)
rcall LCDINIT_SEND_ASTRING
pop ZL
pop ZH
ret
Dies bedeutet, dass der Programmierer sehr konsequent bei der Verwendung
von Makros (aber auch bei Unterprogrammaufrufen) vorgehen muss, da
sonst leicht Fehler durch ,,Seiteneffekte'' auftreten können oder
der Programmspeicher nicht optimal genutzt wird. Es ist auf jeden
Fall ratsam, alle Register, Variablen, ... die sich in einem
Unterprogramm oder Makro verändern können, konsequent in der entsprechenden
Routine bzw. Makrodefinition zu dokumentieren.
Da der ATMEL Prozessor ATmega8 grundsätzlich ein 8-Bit Prozessor ist, für das Projekt aber sehr häufig 16-Bit Operationen benötigt werden, wurde eine 16-Bit Arithmetikbibliothek in Form einer Makrosammlung implementiert.
Um die Makros leicht verständlich zu halten wurden sie namentlich an die Mnemonik der 8-Bit Arithmetikbefehle angelehnt. Die Makros erhielten den selben Namen wie die entsprechenden 8-Bit Operationen, jedoch mit dem Postfix ,,WR'' (Abkürzung für Word Register). Auf das Thema Namenskonventionen wird später in Absch. 7.5.6 noch einmal detailliert eingegangen.
Beispiele:
ADD R1, R2 ; 8-bit addition ADDWR REG_A, REG_B ; 16-bit addition CPI R16, 0x0f ; immediate compare with 8-bit register CPIWR Z, 0x00ff ; immediate compare with 16-bit registerDie 16-Bit Makros erfordern eine bestimmte Namenskonvention in der Bezeichnung der Register, nämlich die Postfix-Zeichen H und L für den jeweiligen High-Byte und Low-Byte Anteil der 16-Bit Werte, auch darauf wird im Abschnitt 7.5.5 noch einmal eingegangen.
Alle produktiven Makrodefinitionen sind in der Datei inc/macros.inc definiert.
gerhard.reithofer@tech-edv.co.at