Tuesday, June 1, 2010

Macros and Defines for the PIC Baseline and Mid-Range Microcontrollers

[I had this article up on my website about a year or so ago. I've changed ISP and that website is now defunct. I'm posting it here since it may be useful to PIC newbies. I personally still use these macros and defines. Can't do assembly without them.

Because blogspot truncates text beyond a certain width, I suggest you copy the include file and paste it in MPLAB or a text editor so you can see all the comments to the right of the code.
]


The following include file contains a good number of macros that I've created to make writing code a little easier for the PIC. Also makes the code shorter--albeit only apparently--and easier to read and understand. As in my case, you'll probably find the relational macros (e.g., <, >=, <>0, etc.) the most useful and most used. I'd tear my remaining hair out if I didn't have these macros.

You won't fail to notice that I have four defines each for the btfss and btfsc instructions. I really have a difficult time with these two. The reason is because they force me to think in terms of skipping the next instruction when the test condition is true. I don't know about you but I find it way easier and natural to think of performing the next instruction should the bit test be true. To address this quirk of mine (and in retaliation against what seems to be the defacto assembly way), I've redefined btfsc and btfss as "do next" instructions. "dnx" translates into "do next."

While on the subject of mnemonics, the ones I use for the other macros are as follows:

bit0 = bit is equal to zero
bit1 = bit is equal to one
lt = less than
gt = greater than
eq = equals
not = not
l = literal
lit = literal
f = file register
w = the w register
dest = destination

If you don't like the mnemonics above then by all means change them to something that suits you. Feel free to make up your own macro labels/names.

One very important caveat: Do always keep in mind that these are macros and will be expanded inline (i.e., the macro label will be replaced by the lines of assembly instructions). As such make sure you don't write something like:

dnxgteq    reg1, reg2             ; check if reg1 >= reg2
addlf      0xA, reg3              ; do this if it is
; other instructions follow here

This code will fail when reg1 < reg2. The second macro has two lines of assembly and thus when reg1 < reg2 the second line of the macro will be executed along with whatever instructions come after it. The moral is: Never have any (multi-instruction) macros right after a dnx macro.

Now here's the include file that I #include in all my PIC Baseline and Mid-Range asm files.

; ==================================================================================
;
;      Defines and Macros  
;
;      Include this file in all PIC Midrange assembly files
;      Edwardson Tan
;      April 2007
;
; ==================================================================================

      ;------------------------------------------------
      ; do next instruction if bit = 1 (skip next instruction if bit = 0)
      #define      donextif1    btfsc
      #define      dnxbit1      btfsc
      #define      dnxtrue      btfsc
      #define      dnxhigh      btfsc
      ;------------------------------------------------

      
      ;------------------------------------------------
      ; do next instruction if bit = 0 (skip next instruction if bit = 1)
      #define      donextif0    btfss
      #define      dnxbit0      btfss
      #define      dnxfalse     btfss
      #define      dnxlow       btfss
      ;------------------------------------------------


      ;------------------------------------------------
      ; copies the contents of one register to another
      ; same as macro copyregister
movff macro      reg1,reg2
      movfw      reg1
      movwf      reg2
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; copies a literal value to a register
      ; same as macro copyliteral
movlf macro      literal, register
      movlw      literal
      movwf      register
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; "sets" a register (opposite of "reset")
      ; places a literal value of 1 in the register
setf  macro      register
      movlw      .1
      movwf      register
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; gets the high bits <12:8> of program counter PC and pastes them into PCLATH
      ; this routine is necessary just prior to doing a table read (retlw) 
      ; with a computed goto, i.e., with a ADDWF PCL,f instruction 
getpchigh macro  routine_label         ; routine_label is the name/label of the subroutine      
      movlw      high routine_label    ; "high" is an arithmetic operation that retrieves the high bits of routine_label
      movwf      PCLATH
      endm            
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; mimics the pagesel assembler directive
pageselect macro label                 ; label is the name/label of the subroutine       
      movlw      high label            ; "high" is an arithmetic operation that retrieves the high bits of routine_label
      movwf      PCLATH
      endm            
      ;------------------------------------------------



; ==============================================================================================
;      macros that compare two registers
;      >, <, >=, <=, =, <>
; ==============================================================================================

      ;------------------------------------------------
      ; do next instruction if N1 > N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxgt macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS,Z              ; check if N1 = N2
      goto       $+3      
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2 
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N1 < N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxlt macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2  

      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if N1 >= N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxgteq macro    N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2  
      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if N1 <= N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxlteq macro    N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS,C              ; note that if C = 0 then subtraction resulted in a negative number
      goto       $+3                   ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2 
      donextif0  STATUS,Z
      goto       $+2
      endm
      ;------------------------------------------------

      
      ;------------------------------------------------
      ; do next instruction if N1 = N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxeq macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N1 <> N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxnoteq macro   N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that compare a register and a literal
;      >, <, >=, <=, =, <>

; ==============================================================================================

      ;------------------------------------------------
      ; do next instruction if N > k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxgtlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS,Z              ; check if N = k
      goto       $+3      
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k 
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N < k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxltlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif0  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k  
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N >= k, skip if not
      ; N= register, k = literal
      ; in arguments list of macro call, specify N first, comma, then k
dnxgteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k  
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N <= k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify N first, comma, then k
dnxlteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif0  STATUS,C              ; note that if C = 0 then subtraction resulted in a negative number
      goto       $+3                   ; if N >= k then C = 1, C = 0 only when N < k 
      donextif0  STATUS,Z
      goto       $+2
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N = k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxeqlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS,Z              ; check if N = k
      endm
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N <> k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxnoteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N1 minus N2
      donextif0  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if register = 0, skip if not
dnxzero macro    register
      movf       register,w
      donextif1  STATUS,Z
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if register <> 0, skip if not
dnxnotzero macro register
      movf       register,w
      donextif0  STATUS,Z
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that perform math functions
; ==============================================================================================


      ;------------------------------------------------
      ; add literal k to register and put sum in register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for results > 255
addlf macro      k,register
      movlw      k
      addwf      register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add literal k to register and put sum in W register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for results > 255
addlfw macro     k,register
      movlw      k
      addwf      register,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add literal k to register1 and put sum in register3
      ; this is the general macro for adding literal to a register
      ; addlf and addlfw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify literal first, comma, reg1, comma, then reg3
      ; NOTE: no provision for results > 255
addlfdest macro  k,reg1,reg3
      movlw      k
      addwf      reg1,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in register2
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for results > 255
addff macro      reg1,reg2
      movfw      reg1
      addwf      reg2,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in W register
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for results > 255
addffw macro     reg1,reg2
      movfw      reg1
      addwf      reg2,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in register3
      ; this is the general macro for adding two registers
      ; addff and addffw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify reg1 first, comma, reg2, comma, then reg3
      ; NOTE: no provision for results > 255
addffdest macro  reg1,reg2,reg3
      movfw      reg1
      addwf      reg2,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register and put difference in register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for negative results
sublf macro      k,register
      movlw      k
      subwf      register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register and put difference in W register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for negative results
sublfw macro     k,register
      movlw      k
      subwf      register,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register1 and put difference in register3
      ; this is the general macro for subtracting a literal from a register
      ; sublf and subfw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify literal first, comma, reg1, comma, then reg3
      ; NOTE: no provision for negative results
sublfdest macro  k,reg1,reg3
      movlw      k
      subwf      reg1,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in register2
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for negative results
subff macro      reg1,reg2
      movfw      reg1
      subwf      reg2,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in W register
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for negative results
subffw macro     reg1,reg2
      movfw      reg1
      subwf      reg2,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in register3
      ; this is the general macro for subtracting a register from another
      ; subff and subffw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify reg1 first, comma, reg2, comma, then reg3
      ; NOTE: no provision for negative results
subffdest macro  reg1,reg2,reg3
      movfw      reg1
      subwf      reg2,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ; ------------------------------------------------
      ; divide content of register by 4 and round off to nearest integer
      ; to do this just move the "decimal point" to the left two places
      ; AND the register with b'00111111'. This zeroes bits <7:6> but leaves other bits untouched
      ; if Carry bit = 1 this means remainder is >= 0.5 so add 1 to register
divby4 macro     register
      rrf        register,f
      rrf        register,f
      movlw      b'00111111'
      andwf      register,f
      dnxbit1    STATUS,C
      incf       register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; alternature divide by 4 macro
      ; this also rounds off to nearest integer
      ; this routine keeps clearing STATUS,C so that the digits that drop off on the right 
      ; don't appear on the left
;divby4 macro   register
;      bcf      STATUS,C
;      rrf      register,f
;      bcf      STATUS,C
;      rrf      register,f
;      dnxbit1  STATUS,C
;      incf     register,f
;      endm      
      ;------------------------------------------------
      

      ; ------------------------------------------------
      ; divide content of register by 8 and round off to nearest integer
      ; to do this just move the "decimal point" to the left three places
      ; AND the register with b'00011111'. This zeroes bits <7:5> but leaves other bits untouched
      ; if Carry bit = 1 this means remainder is >= 0.5 so add 1 to register
divby8 macro     register
      rrf        register,f
      rrf        register,f
      rrf        register,f
      movlw      b'00011111'
      andwf      register,f
      dnxbit1    STATUS,C
      incf       register,f
      endm      
      ;------------------------------------------------


      ;------------------------------------------------
      ; divide content of register by 16 and round off to nearest integer
      ; to do this swap nibbles, i.e., bits <7:4> and <3:0> switch places
      ; AND the register with b10001111'. This zeroes bits <6:4> but leaves other bits untouched
      ; if bit7 = 1 this means remainder of division >=0.5; hence, add 1 to register
divby16 macro    register
      swapf      register,f
      movlw      b'10001111'
      andwf      register,f
      dnxbit1    register,7
      incf       register,f
      bcf        register,7
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that perform logic functions
; ==============================================================================================


      ;------------------------------------------------
      ; AND literal k with register and put output in register
      ; in arguments list of macro call, specify literal first, comma, then register
andlf macro      k,register
      movlw      k
      andwf      register,f
      endm
      ;------------------------------------------------




; ==============================================================================================
;      miscellaneous macros
; ==============================================================================================

      ;------------------------------------------------
      ; toggles the value of bit of register
toggle macro     register,bit
      dnxbit1    register,bit
      goto       $+3
      bsf        register,bit
      goto       $+2
      bcf        register,bit
      endm
      ;------------------------------------------------


No comments:

Post a Comment