Assembler Macros

Assembler macros can be powerful tools that allow you to reuse sequences of code over and over. Creating a library of macros for your most commonly used functions will allow you to code faster and make less mistakes.

Unlike subroutines where the microcontroller will jump to a new section of code, the assembler will simply replace macros with the code it represents during assembly.

Defining Macros

Macros are defined by placing code between the assembler directives .macro and .endmacro. For example, a macro myMacro can be defined as

.macro	myMacro
	...			; macro code
.endmacro

Macros are very useful for blocks of code that need to be used in many programs that don't warrant a subroutine. For example, you may find that every program you write needs to have the Stack Pointer initialized. A macro for this can be written as

.macro	setStack
	ldi	r16,LOW(RAMEND)
	out	SPL,r16
	ldi	r16,HIGH(RAMEND)
	out	SPH,r16
.endmacro

Once a macro is defined, it can be used by simply placing the name of the macro in code

start:	setStack		; initialize stack pointer

	...			; program code

loop:	rjmp	loop		; infinite loop

Passing Parameters to Macros

Macros can be written which accept parameters. When writing the code for such a macro, the first parameter passed is represented by @0, the second by @1, the third by @2, and so on... For example, the macro above can be redefined to accept a parameter to use as the Stack Pointer address.

.macro	setStack
	ldi	r16,LOW(@0)
	out	SPL,r16
	ldi	r16,HIGH(@0)
	out	SPH,r16
.endmacro

This macro can then be used as

start:	setStack RAMEND		; initialize stack pointer

	...			; program code

loop:	rjmp	loop		; infinite loop

Perhaps you would like to add the register used to set The Stack Pointer as a parameter as well. This can be done as

.macro	setStack
	ldi	@1,LOW(@0)
	out	SPL,@1
	ldi	@1,HIGH(@0)
	out	SPH,@1
.endmacro

When a macro is called with multiple parameters, they must be separated with a comma.

start:	setStack RAMEND,r16	; initialize stack pointer

	...			; program code

loop:	rjmp	loop		; infinite loop

Conditional Directives And Error Handling

Conditional directives can be used to enhance the flexibility of your macros and catch errors. There are a number of conditional directives and outputs that you can use, shown in the table below.

Directive Description
.else else
.elif else if
.if if
.ifdef if defined
.ifndef if not defined
.error output an error
.message output a message
.warning output a warning

For example, you may want to include a check that a passed parameter is within a valid range.

	.macro	setStack
	.if @0>RAMEND
	.error "Value greater than RAMEND used for setting stack"
	.else
	ldi	@1,LOW(@0)
	out	SPL,@1
	ldi	@1,LOW(@0)
	out	SPL,@1
	.endif
	.endmacro

Now if a value larger than the device's RAMEND is used, the assembler will generate an error and output a message at the command line indicating what the problem was.

Combining Macros and Subroutines

By placing subroutine calls in macro definitions we can create subroutines that are more flexible and easier to use.

For example, the delay10ms subroutine we wrote previously required loading a parameter into an input register before it was called. We can just make this part of a macro so the delay subroutine can be called in a single line. In addition, we can choose to save the registers we know the subroutine will modify by pushing them to the stack and restoring them when it is finished.

	.macro	delayms
	push	r18
	push	r24
	push	r25

	ldi	r18,@0/10
	rcall	delay10ms

	pop	r25
	pop	r24
	pop	r18
	.endmacro

Now our subroutine is much easier to use.

	delayms 500			; delay for 500ms

Note that the input parameter for delay10ms represented how many times we would delay for 10ms. For example, to delay for 1s we would have to pass the parameter 100. By dividing it by 10 in the macro above, we have abstracted that detail. The macro lets us simply call the subroutine with the number of ms we want to delay.

If you will be using a macro in many different programs, you can create a new file to put the macro in and include it in any programs that need it. For example, the following could be placed in the file delayMacro.inc.

;**************************************************************
;* macro: delayms
;*
;* description: creates a delay for the specified number of
;*		milliseconds
;*
;* inputs: @0 - number of milliseconds to delay for
;*
;* registers modified: none
;**************************************************************

	.macro	delayms
	push	r18
	push	r24
	push	r25

	ldi	r18,@0/10
	rcall	delay10ms

	pop	r25
	pop	r24
	pop	r18
	.endmacro

Using this, our program from the previous example can be simplified even further.

	.include "m328pdef.inc"
	.include "delayMacro.inc"

	.def	mask 	= r16		; mask register
	.def	ledR 	= r17		; led register
	.def	loopCt	= r18		; delay loop count

	.equ	iVal 	= 39998		; inner loop value

	.cseg
	.org	0x00
	ldi	r16,LOW(RAMEND)		; initialize
	out	SPL,r16			; stack pointer
	ldi	r16,HIGH(RAMEND)	; to RAMEND
	out	SPH,r16			; "

	clr	ledR			; clear led register
	ldi	mask,(1<<PINB0)		; load 00000001 into mask register
	out	DDRB,mask		; set PINB0 to output

start:	eor	ledR,mask		; toggle PINB0 in led register
	out	PORTB,ledR		; write led register to PORTB

	delayms 500			; delay for 500ms

	rjmp	start			; jump back to start

	.include "delay10ms.asm"	; include delay subroutine
	

Macros cannot be called before they are defined. Thus, you should not place their definitions in an include file that appears at the end of your code. Instead, you should create a separate include file for your macro definitions and place it at the beginning of your program.


rjhcoding.com 2018