Pic tutorial :: Macros and subprograms

Macros and subprograms



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.


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 par1, par2,..
  set of instructions
  set of instructions

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



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.

    AUTHOR : romux team     */ 
            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

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

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.

    AUTHOR : romux team     */ 
        PROCESSOR  16f84A                   ;Defining the processor
        #include  "P16F84A.inc"              ;Microchip's  INC  file

            ORG    0X00                ;Reset vector
            GOTO    START
            ORG    0X04                ;Interrupt vector
            GOTO    START           ;no   interrupt routine

            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 

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

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.

    AUTHOR : romux team     */ 
        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        
            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  
  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  
  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
  decfsz LOOPcnt, f  
  goto Loop2 ; Time period necessary to execute loop Loop2
  return ; equals 10us

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.

    AUTHOR : romux team     */ 
        PROCESSOR   16f84 
        #include "P16F84.inc"        
        __CONFIG  _CP_0FF  &   _WDT_0FF  &   _PWRTE_0N  &  _XT_0SC        
        CBLOCK 0x0C                  ;RAM starting address 
                                      ;Auxiliary variables   for  macro  pausems     
            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        
            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        

anonymous   said:
1 year ago

I tried using the macro and the above delay code but it didn't work.
The LED was just on without any flick or flash.
What am I getting wrong?

Points :   0

anonymous   said:
1 year ago

Hi there! It is really vital material.

I have a question, I can't figure out how did you use "high" in the following line "movlw high par1". Is the "high" a key word or what is it and how it works in this particular case?


Points :   0

anonymous   said:
3 years ago

Wow.... all I could have ever wanted. Explained well with emphasis on "doing". Thank you very much!

Points :   0

anonymous   said:
4 years ago

From digbyte macro the line with " decf Dig1, w" doesnot work , its when you change it to "decf Dig1, F"

But I've loved your tutorial, thanks alot , you've truely saved me.

Points :   0

Post Your Comments Here :

User Login

Username :

Password :