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 myMacro can be defined as and . For example, a macro
.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.
Post a Comment