Updated: 00.12.10

Useful Snippets and Macros

Here are a collection of code Snippets and Macros that can be used in your applications to solve the various problems that you will encounter. Many of these "snippets" of code are so clever that I shake my head each time I see them. I want to thank everybody that has contributed these code items as they have made my life easier quite a few times.

If you have some that you think should be included, please send an email to me and I will add them to this page.

Note that these pieces of code can be "cut and pasted" into your applications.

1. Negating the Contents of a File Register 14. Swap the Contents of Two Registers
2. Negating the Contents of "w" 15. Compare and Swap if Y < X
3. Incrementing/Decrementing "w" 16. Convert ASCII to Upper Case
4. Rotating a Byte in Place 17. Counting the number of "1"s in a Byte
5. Copy Bits from One Register to Another 18. Generating Parity for a Byte
6. Converting a Nybble to ASCII 19. Keeping a Variable within a Range
7. Converting an ASCII Byte to a Hex Nybble 20. Swapping Bit Pairs
8. Dividing by Three 21. "ANDing" Two Bits
9. Sixteen bit counter with a Constant Loop Delay 22. "ORing" Two Bits
10. Precise Delay Macro 23. "NOT" Operation on a Bit
11. Detect a Change in a Register 24. Fast Multiplication on a Byte
12. Test a byte within a range 25. Button Debounce Macro
13. Swap the Contents of "w" with a Register 26. 200,000 Cycle Delay

1. Negating the Contents of a File Register

Converting the contents of a File Register to it's 2's complement value without affecting "w" is simply accomplished by the code Snippet below. This code should not be used on any special hardware control registers.


  comf  Reg, f            ;  Invert the bits in the Register

  incf  Reg, f            ;  Add One to them to turn into 2's 

                          ;   Complement

              

2. Negating the Contents of "w"

If you have to Negate the contents of the "w" register, you could use the code above (after saving the value in "w" into a register) or you could use the code below for low-end devices (PIC16C5x, PIC12C5xx, PIC16C505). Any file register can be used for this code because its contents are never changed.


  addwf  Reg, w           ;  w = w + Reg

  subwf  Reg, w           ;  w = Reg - w

                          ;  w = Reg - ( w + Reg )

                          ;  w = -w

              

In mid range parts, the single instruction below can be used:


  sublw  0                ;  w = 0 - w

              

3. Incrementing/Decrementing "w"

Here are a couple of snippets that will allow you to increment and decrement the "w" register without affecting any file registers if you don't have "addlw"/"sublw" instructions (ie in the case of the low-end processor).

"Reg" can be any register that does not change during the execution of the three instructions. For the low-end parts, any file register can be used because there is no danger of them being updated by an interrupt handler.

To Increment:


  xorlw 0x0FF              ;  Get 1s Complement of Number

  addwf Reg, w             ;  w = Reg + (w^0x0FF)

  subwf Reg, w             ;  w = Reg + ((Reg + (w^0x0FF))^0x0FF) + 1

                           ;  w = w + 1

              

To decrement, the instructions are re-arranged:


  subwf Reg, w             ;  w = Reg + (2^0x0FF) + 1

  xorlw 0x0FF              ;  Get 1s Complement of Result

  addwf Reg, w             ;  w = w - 1

              

4. Rotating a Byte in Place

These two lines will rotate the contents of a file register without loosing data in the "Carry Flag". Rotates right and left can be implemented with this snippet. Note that the carry flag is changed.


  rlf   Register, w        ;  Load Carry with the high bit

  rlf   Register, f        ;  Shift over with high bit going low

              

5. Copy Bits from One Register to Another

Here is a fast way to save specific bits from one register into another.


  movf  Source, w

  xorwf Destination, w

  andlw B'xxxxxxxx'        ;  Replace "x" with "1" to Copy the Bit

  xorwf Destination, f     ;   Replace "x" with "0" to Leave the Bit as is

              

6. Converting a Nybble to ASCII

This is a question that comes up all the time when the contents of a byte are to be displayed/output.

The most obvious way of doing this is:


NybbletoASCII



  addwf  PCL, f            ;  Add the Contents of the Nybble to PCL/ 

  dt	 "0123456789ABCDEF"  ;  return the ASCII as a Table Offset

              

But, a much better way of doing this is:


NybbletoASCII             ;  Convert a Nybble in "w" to ASCII



  addlw  0x036            ;  Add '0' + 6 to Value

  btfsc  STATUS, DC       ;  If Digit Carry Set, then 'A' - 'F'

   addlw 7                ;   Add Difference Between '9' and 'A'

  addlw  0-6



  return                  ;  Return the ASCII of Digit in "w"

              

This method will take three instruction cycles longer than the previous code, but it requires twelve less instructions.


7. Converting an ASCII Byte to a Hex Nybble

The code below is really a rearrangement of the previous snippet. Using the aspect that the high nybble of ASCII "A" to "F" is one greater than the high nybble of "0" to "9", a value is conditionally added to make the result 0x000 to 0x00F.


ASCIItoNybble



  addlw  0x0C0            ;  If "A" to "F", Set the Carry Flag

  btfss  STATUS, C        ;  If Carry Set, then 'A' - 'F'

   addlw 7                ;   Add Difference Between '9' and 'A'

  addlw  9



  return                  ;  Return the ASCII of Digit in "w"

              

Note that ASCII characters other than "0" to "9" and "A" to "F" will result in an incorrect result.


8. Dividing by Three

As much as you try to avoid it, sometimes you have to divide. Here's an algorithm from Andy Warren for dividing a positive value by three, by knowing that divide by three can be represented by the series:


x/3 = x/2 - x/4 + x/8 - x/16 + x/32 - x/64...

              

The code to implement this is:


Div3:                     ;  Divide Contents of "w" by 3



  movwf  Dividend

  clrf   Quotient



Div3_Loop                 ;  Loop Until the Dividend == 0



  bcf    STATUS, C

  rrf    Dividend, f      ;  Dividend /2 (ie "x/2" in Series)

  movf   Dividend, w      ;   Is it Equal to Zero?

  btfsc  STATUS, Z

   goto  Div3_Done        ;  If it is, then Stop



  addwf  Quotient         ;  Add the Value to the Quotient



  rrf    Dividend, f      ;  Dividend /2 (ie "x/4" in Series)

  movf   Dividend, w

  btfsc  STATUS, Z

   goto  Div3_Done



  subwf  Quotient, f      ;  Quotient = Quotient-(Dividend / 4)



  goto   Div3_Loop



Div3_Done



  movf   Quotient, w      ;  Return the Quotient



  return

              

9. Sixteen bit counter with a Constant Loop Delay

Then Marc Heuler showed me the code:


  movlw  HIGH TimeDelay  ;  Load the Delay Values

  movwf  HiCount

  movlw  LOW TimeDelay

Dlay:

  addlw  1               ;  Increment the Counter by 1

  btfsc  STATUS, Z

   decfsz HiCount, f     ;  Decrement the High Counter

    goto Dlay

              

This loop takes five cycles to execute, regardless of whether or not "HiCount" is to be decremented (and uses one less File Register than the method above).

The actual time delay is calculated using the sixteen bit number from:


Time Dlay = (16BitDlay * 5 ins/loop * 4 clocks/ins / clock frequency) + 256

              

10. Precise Delay Macro

Here is a good, generic Delay Macro that does not change "w" or the STATUS Flag. Note that it assumes that the "DlayCount" variable is equal to zero upon code entry.


DlayMacro Macro Cycles          ;  Delay Macro for Edges

 variable i, TCycles, Value, TFlag

TCycles = Cycles

Value = 1 << 7

i = 7

TFlag = 0

 if (TCycles > 5)

 while (i >= 0)

 if ((TFlag == 0) && ((Value * 3) <= TCycles))

  bsf    DlayCount, i

TFlag = 1

TCycles = TCycles - (Value * 3)

 else

 if ((TFlag != 0) && (((Value * 3) + 1) <= TCycles))

  bsf    DlayCount, i

TCycles = TCycles - ((Value * 3) + 1)

 endif

 endif

Value = Value >> 1

i = i - 1

 endw

 if (TCycles > 3)

 Error "Delay Cycles too Large for Macro"

 endif

  decfsz  DlayCount, f

   goto   $ - 1

 endif

 while (TCycles > 1)

  goto   $ + 1

TCycles = TCycles - 2

 endw

 if (TCycles == 1)

  nop				;  Delay the Last Cycle

 endif

 endm

              

11. Detect a Change in a Register

Bob Fehrenbach has passed on a number of his snippets for inclusion in my web page and have shown up in this book. The first detects the change in a bit and saves the new data for later execution. This code can be used to detect changes in the I/O Ports, Timers, or other registers that can be updated externally to the software execution.


  movf   Reg, w                 

  andlw  Mask                   ;  Mask out unused bits

  xorwf  old, w                 ;  Compare to previous value

  btfsc  STATUS, Z              ;  If Zero set, bits are the Same

   goto  no_change

  xorwf  old                    ;  Bits are different, Store New 

                                ;   pattern in "old"

              

12. Test a byte within a range

This is an algorithm that I continually re-invent (although I don't think I've ever come up with anything as efficient as Bob Fehrenbach's routine).


  movf   Num, w

  addlw  255 - hi_lim           ;  "Num" is equal to -hi_lim

  addlw  hi_lim - lo_lim + 1    ;  "Num" is > 255 if it is above

  btfsc  STATUS, C              ;   the lo-lim

   goto  in_range               

              

13. Swap the Contents of "w" with a Register

This one has been around forever, but it never hurts to be reminded of it and to have available for easy access.


   xorwf   Reg, f               ;  w = w, Reg = Reg ^ w

   xorwf   Reg, w               ;  w = w ^ (Reg ^ w), Reg = Reg ^ w

                                ;  w = Reg, Reg = Reg ^ w

   xorwf   Reg, f               ;  w = Reg, Reg = Reg ^ w ^ Reg

                                ;  w = Reg, Reg = w

              

14. Swap the Contents of Two Registers

Using the code from the previous snippet, here's a fast way to swap the contents of two file registers:


   movf    X, w                 

   subwf   Y, w                 ;  W = Y - X

   addwf   X, f                 ;  X = X + (Y - X)

   subwf   Y, f                 ;  Y = Y - (Y - X)

              

15. Compare and Swap if Y < X

Here's a compare and swap (uses the Swap Presented above):


   movf    X, w                 

   subwf   Y, w                 ;  Is Y >= X?

   btfsc   STATUS, C            ;  If Carry Set, Yes

    goto   $ + 2                ;   Don't Swap

   addwf   X, f                 ;  Else, X = X + (Y - X)

   subwf   Y, f                 ;        Y = Y - (Y - X)

              

16. Convert ASCII to Upper Case

This is a practical application of the previous snippet. I think this subroutine demonstrates how the code works quite well.


ToUpper:

   addlw   255 - 'z'            ;  Get the High limit

   addlw   'z' - 'a' + 1        ;  Add Lower Limit to Set Carry

   btfss   STATUS, C            ;  If Carry Set, then Lower Case

    addlw  h'20'                ;   Carry NOT Set, Restore Character

   addlw   'A'                  ;  Add 'A' to restore the Character

   return

              

17. Counting the number of "1"s in a Byte

This snippet comes from Dmitry Kiryashov and the code below, is his optimization of the classic problem of counting the number of "1"s in a byte in twelve instructions/twelve cycles.


                                ;  (c) 1998 by Dmitry Kirashov



  rrf    X, w                   ;  "X" Contains Byte

  andlw  0x55                   ;  -a-c-e-g

  subwf  X, f                   ;  ABCDEFGH

                                ;  where AB=a+b, etc.

                                ;  the same trick as in example_1

  movwf  X

  andlw  0x33                   ;  --CD--GH

  addwf  X, f

  rrf    X, f                   ;  0AB00EF0

                                ;  00CD00GH

  addwf  X, f                   ;  0AB00EF0

                                ;  0CD00GH0

  rrf    X, f                   ;  0ABCD.0EFGH



  swapf  X, w

  addwf  X, w

  andlw  0x0F			;  Bit Count in "w"

              

18. Generating Parity for a Byte

The six instructions below (provided by John Payson) will calculate the "Even" Parity for a Byte. At the end of the routine, bit 0 of "X" will have the "Even" Parity bit of the original number. "Even" Parity means that if all the "1"s in the byte is summed along with Parity Bit, an even number will be produced.


  swapf  X, w

  xorwf  X, f



  rrf    X, w

  xorwf  X, f



  btfsc  X, 2

   incf  X, f

              

19. Keeping a Variable within a Range

The four instructions below will make sure that the variable "Temp" will always be in the range of Zero to "Constant".


  movlw   Constant		;  0 <= Temp <= Constant

  subwf   Temp, w

  btfsc   STATUS, C

   subwf  Temp, f

              

20. Swapping Bit Pairs

Another of Dmitry Kiryashov's routines is this sequence of instructions for swapping bit pairs in a byte in five instructions/cycles.


                                ;  (c) 1998 by Dmitry Kirashov



  movwf  X                      ;  Save the Incoming Byte in 

                                ;   a temporary register

                                ;  w = X = ABCDEFGH

  andlw  0x055                  ;  w = 0B0D0F0H

  addwf  X, f                   ;  X = ABCDEFGH + 0B0D0F0H

                                

  rrf    X, f                   ;  X = (ABCDEFGH + 0B0D0F0h) >> 1

  addwf  X, w                   ;  w = BADCFEHG

              

21. "ANDing" Two Bits

Using the code below, two bits will be "ANDed" together to find set/reset a third bit.


  bsf    Result                 ;  Assume the result is True

  btfsc  BitA                   ;  If BitA != 1 then result is False 

   btfss BitB                   ;  If BitB == 0 then result is False

    bcf  Result                 ;  Result is False, Reset the Bit

              

22. "ORing" Two Bits

"ORing" two bits together is similar to the "AND" operation, except the result is expected to be false and when either bit is set, the result is true:


  bcf    Result                 ;  Assume the result is False

  btfss  BitA                   ;  If BitA != 0 then result is True

   btfsc BitB                   ;  If BitB == 0 then result is False

    bsf  Result                 ;  Result is True, Set the Bit

              

23. "NOT" Operation on a Bit

There are two ways of implementing this operation based on where the input value is relative to the output value. If they are the same (ie the operation is to complement a specific bit), the code to be used is simply:


  movlw  1 << BitNumber         ;  Complement Specific Bit for "NOT"

  xorwf  BitRegister, f

              

If the bit is in another register, then the value stored is the complement of it:


  bcf    Result                 ;  Assume that the Input Bit is Set

  btfss  Bit                    ;   - If it is Set, then Result Correct

   bsf   Result                 ;  Input Bit Reset, Set the Result

              

24. Fast Multiplication on a Byte

Sometimes you have to do a fast multiply on a register value with a constant. in 16 Bit Operations and Macros, I go through a number of methods, but I use the following Macro for this purpose for a single byte:


FastMul Macro Register, Constant

 variable i = Constant

 clrw                           ;  Put the Product in "w"

 while (i != 0)

 if ((i & 1) != 0)

  addwf  Register, f            ;  Add the Current Contents of "Register" to "w"

 endif

 bcf     STATUS, C              ;  Shift up the Register's Contents

 rlf     Register, f

 i = i / 2                      ;  Shift down the counter value

 endw

 endm

              

25. Button Debounce Macro

This macro is designed to produce a 20 msec delay for a pulled up button to be up ("Dir" < 0) or down ("Dir" > 0). Note that the 16 bit "Dlay" variable is required with this macro.


Debounce MACRO Dir, Speed    ;  "Speed" is the PICmicro's Clock Speed

 variable DlayCount

DlayCount = 250000000 / Speed  ;  Calculate the number of Cycles in 20 msecs

DlayCount = 156250 / DlayCount

DlayCount = 0x010000 - DlayCount

                              ;   on the PICmicro MCU clock speed

 if (Direction < 0)           ;  Going Down

  btfsc  PORTA, 0

 else

  btfss  PORTA, 0             ;  Wait for Button Released

 endif

   goto  $ - 1



  movlw  LOW DlayCount        ;  Initialize Dlay for a 20 msec

  movwf  Dlay                 ;   Delay

  movlw  HIGH DlayCount

  movwf  Dlay + 1



  bcf    STATUS, Z            ;  Make Sure that Zero is Reset



  incfsz Dlay, f

   goto  $ + 2

  incf   Dlay + 1, f

 if (Direction < 0)

  btfsc  PORTA, 0

 else

  btfss  PORTA, 0             ;  Button Still Released?

 endif

   goto  $ - 11               ;  No - Loop Around Again

  btfss  STATUS, Z            ;  Zero Flag Set (20 mSecs Past?)

   goto  $ - 6



 Endm                         ;  End the Macro

              

26. 200,000 Cycle Delay

This delay code is useful for user interface situations where an easy to code delay is required. The code itself will delay for approximately 200,000 instruction cycles or 1/5 of a second for PICmicro MCU's running at 4 MHz.


  clrf   DlayCount            ;  Clear the Delay Counters

  clrf   DlayCount + 1

  decfsz DlayCount, f

   goto  $ - 1

  decfsz DlayCount + 1, f

   goto  $ - 3