Macros and subprograms
Introduction
Same or similar sequence of instructions is frequently used during programming. Assembly language is very demanding. Programmer is required to take care of every single detail when writing a program, because just one incorrect instruction or label can bring about wrong results or make the program doesn't work at all. Solution to this problem is to use already tested program parts repeatedly. For this kind of programming logic, macros and subprograms are used.
Macros
Macro is defined with directive macro containing the name of macro and parameters if needed. In program, definition of macro has to be placed before the instruction line where macro is called upon. When during program execution macro is encountered, it is replaced with an appropriate set of instructions stated in the macro's definition.
macro_name |
macro par1, par2,.. |
set of instructions | |
set of instructions | |
endm |
The simplest use of macro could be naming a set of repetitive instructions to avoid errors during retyping. As an example, we could use a macro for selecting a bank of SFR registers or for a global permission of interrupts. It is much easier to have a macro BANK1 in a program than having to memorize which status bit defines the mentioned bank. This is illustrated below: banks 0 and 1 are selected by setting or clearing bit 5 (RP0) of status register, while interrupts are enabled by bit 7 of INTCON register. First two macros are used for selecting a bank, while other two enable and disable interrupts.
bank0 | macro | ; Macro bank0 |
bcf STATUS, RP0 | ; Reset RP0 bit = Bank0 | |
endm | ; End of macro | |
bank1 | macro | ; Macro bank1 |
bsf STATUS, RP0 | ; Set RP0 bit = Bank1 | |
endm | ; End of macro | |
enableint | macro | ; Interrupts are globally enabled |
bsf INTCON, 7 | ; Set the bit | |
endm | ; End of macro | |
disableint | macro | ; Interrupts are globally disabled |
bcf INTCON, 7 | ; Reset the bit | |
endm | ; End of macro |
These macros are to be saved in a special file with extension INC (abbrev. for INCLUDE file). The following image shows the file bank.inc which contains two macros, bank0 and bank1.
|
Macros Bank0 and Bank1 are given for illustrational purposes more than practical, since directive BANKSEL NameSFR does the same job. Just write BANKSEL TRISB and the bank containing the TRISB register will be selected. |
As can be seen above, first four macros do not have parameters. However, parameters can be used if needed. This will be illustrated with the following macros, used for changing direction of pins on ports. Pin is designated as input if the appropriate bit is set (with the position matching the appropriate pin of TRISB register, bank1) , otherwise it's output.
input | macro par1, par2 | ; Macro input |
bank1 | ; In order to access TRIS registers | |
bsf par1, par2 | ; Set the given bit - 1 = input | |
bank0 | ; Macro for selecting bank0 | |
endm | ; End of macro | |
output | macro par1, par2 | ; Macro output |
bank1 | ; In order to access TRIS registers | |
bcf par1, par2 | ; Reset the given bit - 0 = output | |
bank0 | ; Macro for selecting bank0 | |
endm | ; End of macro |
Macro with parameters can be called upon in following way:
output TRISB, 7 ; pin RB7 is output
When calling macro first parameter TRISB takes place of the first parameter, par1, in macro's definition. Parameter 7 takes place of parameter par2, thus generating the following code:
output | TRISB, 7 | ; Macro output |
bsf STATUS, RP0 | ; Set RP0 bit = BANK1 | |
bcf TRISB, 7 | ; Designate RB7 as output | |
bcf STATUS, RP0 | ; Reset RP0 bit = BANK0 | |
endm | ; End of macro |
Apparently, programs that use macros are much more legible and flexible. Main drawback of macros is the amount of memory used - every time macro name is encountered in the program, the appropriate code from the definition is inserted. This doesn't necessarily have to be a problem, but be warned if you plan to use sizeable macros frequently in your program.
In case that macro uses labels, they have to be defined as local using the directive local. As an example, below is the macro for calling certain function if carry bit in STATUS register is set. If this is not the case, next instruction in order is executed.
callc | macro label | ; Macro callc |
local | Exit | ; Defining local label within macro |
bnc Exit | ; If C=0 jump to Exit and exit macro | |
call label | ; If C=1 call subprogram at the | |
; address label outside macro | ||
Exit | ; Local label within macro | |
endm | ; End of macro |
Subprograms
Subprogram represents a set of instructions beginning with a label and ending with the instruction return or retlw. Its main advantage over macro is that this set of instructions is placed in only one location of program memory. These will be executed every time instruction call subprogram_name is encountered in program. Upon reaching return instruction, program execution continues at the line succeeding the one subprogram was called from. Definition of subprogram can be located anywhere in the program, regardless of the lines in which it is called.
Label | ; subprogram is called with "call Label" | |
set of instructions | ||
set of instructions | ||
set of instructions | ||
return or retlw |
With macros, use of input and output parameters is very significant. With subprograms, it is not possible to define parameters within the subprogram as can be done with macros. Still, subprogram can use predefined variables from the main program as its parameters.
Common course of events would be: defining variables, calling the subprogram that uses them, and then reading the variables which may have been changed by the subprogram.
The following example, addition.asm adds two variables, PAR1 and PAR2, and stores the result to variable RES. As 2-byte variables are in question, lower and higher byte has to be defined for each of these. The program itself is quite simple; it first adds lower bytes of variables PAR1 and PAR2, then it adds higher bytes. If two lower bytes total exceeds 255 (maximum for a byte) carry is added to variable RESH.
Basic difference between macro and subprogram is that the macro stands for its definition code (sparing the programmer from additional typing) and can have its own parameters while subprogram saves memory, but cannot have its own parameters. |
PROCESSOR 16f84A ; Defining the processor
#include "P16F84A.inc" ; Microchip's INC file
__CONFIG _CP_0FF & _HDT_0FF & _PWRTE_0N & _XT_0SC
CHLOCK 0X0C ;RAM starting address
PAR1H ;Parameter 1 higher byte
PAR1L ;Parameter 1 lower byte
PAR2H ;Parameter 2 higher byte
PAR2L ;Parameter 2 lower byte
RESH ;higher byte of result
RESL ;lower byte of result
ENDC ;End of variables
ORG 0X00 ;Reset vector
GOTO START
START ;writing values to variables
MOVLW 0X01 ;PAR1=0x0104
MOVWF PAR1H
MOVLW 0X04
MOVWF PAR1L
MOVLW 0X07 ;PAR2=0x0705
MOVWF PAR2H
MOVLW 0X05
MOVWF PAR2L
MAIN ;main program
CALL ADD16 ;Calling subprogram Addl6
LOOP GOTO LOOP ;remain at this line
ADD16 ;subprogram for adding 2 16-bit numbers
CLRF RESH ;RESH=0
MOVF PAR1L ,W ;w=PAR1L
ADDWF PAR2L,W ;w=w+PAR2L
MOVWF RESL ;RESL=w
BTFSC STATUS,C ;does result exceed 255?
INCF RESH,F ;if true increment RESH by one
MOVF PARLH,W ;w=PAR1H
ADDWF PAR2H,W ;ra=ra+PAR2
ADDWF RESH,F ;RESH=ra
RETURN ;return from subprogram
END ;end of program
Macros used in the examples
Examples given in chapter 6 frequently use macros ifbit, ifnotbit, digbyte, and pausems, so these will be explained in detail. The most important thing is to comprehend the function of the following macros and the way to use them, without unnecessary bothering with the algorithms itself. All macros are included in the file ROMUX_LIB.inc for easier reference.
5.3.1 Jump to label if bit is set
ifbit | macro par1, par2, par3 |
btfsc par1, par2 | |
goto par3 | |
endm |
Macro is called with : ifbit Register, bit, label
5.3.2 Jump to label if bit is cleared
ifnotbit | macro par1, par2, par3 |
btfss par1, par2 | |
goto par3 | |
endm |
Macro is called with : ifnotbit Register, bit, label
Next example shows how to use a macro. Pin 0 on port A is checked and if set, program jumps to label ledoff, otherwise macro ifnotbit executes, directing the program to label ledon.
PROCESSOR 16f84A ;Defining the processor
#include "P16F84A.inc" ;Microchip's INC file
__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
ORG 0X00 ;Reset vector
GOTO START
ORG 0X04 ;Interrupt vector
GOTO START ;no interrupt routine
Start
BSF STATUS,RPO ;Selecting the bank containing TOISA
BSF TRISA,0 ;TRISA0=1, meaning the pin is input
BCF TRISB,7 ;TRISB7=0, meaning the pin is output
BCF STATUS,RPO ;Selecting the bank containing PORTA
#INCLUDE "ROMUX_LIB.INC" ;0ur INC file with 2 macros
MAIN ;main program
IFBIT PORTA,0,LEDOFF ;if bit RA0=1 switch off LED
IFNOTBIT PORTA,0,LEDON ;if bit RA0=0 switch on LED
GOTO MAIN ;repeat all
LEDON ;Switch on LED on RB7
BSF PORTB,7
GOTO MAIN
LEDOFF ;Switch off LED on RB7
BCF PORTB, 7
GOTO MAIN
END
5.3.3 Extracting ones, tens and hundreds from variable
Typical use for this macro is displaying variables on LCD or 7seg display.
digbyte | macro par0 | |
local Pon0 | ||
local Exit1 | ||
local Exit2 | ||
local Positive | ||
local Negative | ||
clrf Dig1 | ||
clrf Dig2 | ||
clrf Dig3 | ||
Positive | ||
movf par0, w | ||
movwf Digtemp | ||
movlw .100 | ||
Pon0 | incf Dig1 | ;computing hundreds digit |
subwf Digtemp | ||
btfsc STATUS, C | ||
goto Pon0 | ||
decf Dig1, w | ||
addwf Digtemp, f | ||
Exit1 | movlw .10 | ;computing tens digit |
incf Dig2, f | ||
subwf Digtemp, f | ||
btfsc STATUS, C | ||
goto Exit1 | ||
decf Dig2, f | ||
addwf Digtemp, f | ||
Exit2 | movf Digtemp, w | ;computing ones digit |
movwf Dig3 | ||
endm | ||
Macro is called with :
movlw .156 | ; w = 156 |
movwf RES | ; RES = w |
digbyte RES | ; now Dec1<-1, Dec2<-5, Dec3<-6 |
The following example shows how to use macro digbyte in program. At the beginning, we have to define variables for storing the result, Dig1, Dig2, Dig3, as well as auxiliary variable Digtemp.
PROCESSOR 16f84A ;Defining the processor
#INCLUDE "P16F84A..INC" ; Microchip's INC file
__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
CBLOCK OXOC ;RAM starting address
RESS ;Numerical value
DIGTEMP ;auxiliary variable
DIGL ;First digit
DIG2 ;Second digit
DIG3 ;Third digit
ENDC ;end of variables
ORG 0x00
GOTO START ;Reset vector
INCLUDE "DIGBYTE.INC" ;including file with macro
START
MOVLW OXFF ;ra=255
MOVWF RESS ;RESS=255
MAIN ;retain program
DIGBYTE RESS ;calling a macro
LOOP GOTO LOOP ;remain at this line
END ;end of program
5.3.4 Generating pause in miliseconds (1~65535ms)
Purpose of this macro is to provide exact time delays in program.
pausems | macro par1 | |
local | Loop1 | |
local | dechi | |
local | Delay1ms | |
local | Loop2 | |
local | End | |
movlw high par1 | ; Higher byte of parameter 1 goes to HIcnt | |
movwf HIcnt | ||
movlw low par1 | ; Lower byte of parameter 1 goes to LOcnt | |
movwf LOcnt | ||
Loop1 | ||
movf LOcnt, f | ; Decrease HIcnt and LOcnt necessary | |
btfsc STATUS, Z | ; number of times and call subprogram Delay1ms | |
goto dechi | ||
call Delay1ms | ||
decf LOcnt, f | ||
goto Loop1 | ||
dechi | ||
movf HIcnt, f | ||
btfsc STATUS, Z | ||
goto End | ||
call Delay1ms | ||
decf HIcnt, f | ||
decf LOcnt, f | ||
goto Loop1 | ||
Delay1ms: | ; Delay1ms produces a one milisecond delay | |
movlw .100 | ; 100*10us=1ms | |
movwf LOOPcnt | ; LOOPcnt<-100 | |
Loop2: | ||
nop | ||
nop | ||
nop | ||
nop | ||
nop | ||
nop | ||
nop | ||
decfsz LOOPcnt, f | ||
goto Loop2 | ; Time period necessary to execute loop Loop2 | |
return | ; equals 10us | |
End | ||
endm |
This macro is written for an 4MHz oscillator. For instance, with 8MHz oscillator, pause will be halved. It has very wide range of applications, from simple code such as blinking diodes to highly complicated programs that demand accurate timing. Following example demonstrates use of macro pausems in a program. At the beginning of the program we have to define auxiliary variables HIcnt, LOcnt, and LOPcnt.
PROCESSOR 16f84
#include "P16F84.inc"
__CONFIG _CP_0FF & _WDT_0FF & _PWRTE_0N & _XT_0SC
CBLOCK 0x0C ;RAM starting address
HICNT
;Auxiliary variables for macro pausems
LOCNT
LOOPCNT
ENDC
ORG 0X00 ;Reset vector
GOTO MAIN
ORG 0X04 ;Interrupt vector
GOTO ISR ;no interrupt routine
INCLUDE "ROMUX_LIB.INC"
MAIN ;Main program
BANKSEL TRISB ;Select bank containing TRISB
CLRF TRISB ;Port B is output
BANKSEL P0RTB ;Select bank containing P0RTB
LOOP
MOVLW 0X00 ;Sraitch off diodes on port B
MOVWF P0RTB
PAUSEMS .500 ;500ms delay (0.5sec)
MOVLW OXFF ;Switch on diodes on port B
MOVWF P0RTB
PAUSEMS .500 ;500ms delay (0.5sec)
GOTO LOOP ;Jump to label Loop
END
User Comments
No Posts found !Login to Post a Comment.