4 Verwendung von Assemblermakros

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 register
Die 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