Updated: 99.12.15
- Added Additional Snippets
- Deleted Web Counter

Myke's Home Page

The Book Room

Microchip PICMicro
Programming and Customizing the PIC Microcontroller
PICMicro 16 Bit Operations
Useful PICMicro code snippets
PICLite Language
PICMicro Applications
PICMicro Resource Web Pages
PICMicro Books

8051 Microcontroller

Atmel Microcontrollers

Motorola 68HCxx Microcontrollers

Parallax Basic Stamp

PC Interfacing

Wirz Electronics

Electrical Engineering Resources

Listservers

Survey Results

Enigma Code/Decode Page

My Favourite Web Sites

Powered By Passport

Since writing "Programming and Customizing the PIC Microcontroller", I've discovered a number of small algorthims for making programming the PICMicro easier. Where I wasn't the author of the code, I have included the source of the information (which, in some cases is a copy of an E-Mail Note).

  1. Negating the Contents of a File Register
  2. Negating the Contents of "w"
  3. Incrementing/Decrementing "w"
  4. Rotating a Byte in Place
  5. Copy Bits from One Register to Another
  6. Converting a Nybble to ASCII
  7. Converting an ASCII Character to a Hex Nybble
  8. Using T0CKI as an interrupt source pin
  9. Dividing by Three
  10. Timer Interrupt Handler without Saving STATUS or "w"
  11. Sixteen bit counter with a Constant Loop Delay
  12. Sixteen bit Pulse Measurement with 5 Cycle Delay
  13. Detect a Change in a Register
  14. Test a byte within a range
  15. Swap the Contents of "w" with a Register
  16. Swap the Contents of Two Registers
  17. Compare two Registers and Swap if Y < X
  18. Convert ASCII to Upper Case
  19. Counting the Number of "1"s in a Byte
  20. Generating Parity for a Byte
  21. Swapping Bit Pairs
  22. Bitwise Operations
  23. Button Debounce Macro
  24. A PBASIC like "Button" Debounce Macro

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:

  comf  Reg,f             ;  Invert the bits in the Register
  incf  Reg,f             ;  Add One to them to turn into 2's 
                          ;   Complement
             
This code should not be used on any special hardware control registers.


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 for low-end devices (16C5x or 12C5xx):

  addwf  Reg,w            ;  w' = w + Reg
  subwf  Reg,w            ;  w" = Reg - w
                          ;  w" = Reg - ( w + Reg )
                          ;  w" = -w
             
Any file register can be used for this code because it's contents are never changed.

In mid range parts, the single instruction:

  sublw  0                ;  w' = 0 - w
             
could be used.

Thanks to Rudy Wieser for reminding me of this method.


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
             

There should also be a general case for adding and subtracting values to "w" without affecting any other registers for the low end. When I discover it, I'll post it here.


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

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, I think 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. Using T0CKI as an interrupt source pin

Some time ago, the question came up on the PICList asking if the Timer input pin could be used as an interrupt source pin. The answer to this is yes, if the Timer (and prescaler) are set up so the next transistion will increment the timer and cause an interrupt. Here's some code to do it in a 16F84:

;
;  When a rising edge is received by the TOCK1 (RA4) Pin, the Timer 
;   is incremented.  Before Enabling Interrupts, The /2 Counter is 
;   loaded with 1 and TMR0 is loaded with 0x0FF.
; 
;  Let's see if this works in the Simulator.
;
;  Hardware Notes:
;   There are no issues regarding clock type or speed
;   RB0 - RB1 is Assumed to be used elsewhere
;   RA4 is the Interrupt Input
;
;  Myke Predko
;  96.07.20
;
  LIST P=16F84, R=DEC
  errorlevel 0,-305
  errorlevel 1,-224             ;  Ignore "option" Warnings
  INCLUDE "d:\mplab\P16F84.inc"

;  Registers/Variables

 __CONFIG _CP_OFF & _XT_OSC & _PWRTE_ON  & _WDT_OFF
                                ;  Note that the WatchDog Timer is OFF

  PAGE
;  Code for ClockIn

  org    0

  goto   MainLine               ;  Skip Over Output Tables

  org	 4
Int                             ;  Interrupt Handler
  
;  #### - There Should be Some Context Register Saving/Event Display

  goto	 $                  ;  Infinite Loop When you Get Here


MainLine                        ;  Enable Interrupts with the Timer 
                                ;   waiting for a Rising Edge

  movlw	 B'11000000'        ;  First Setup with Instruction Clock
  option                        ;   as TMR0 Source

  movlw	 B'11100000'        ;  Option Setup For TOCK1 TMR0 Source

  clrf	 TMR0               ;  Set TMR0 to 0x0FF
  decf	 TMR0

  option                        ;  Enable Timer on Outside Interrupt 
                                ;  Edge
                                ;   NOTE - Executing this Instruction 
                                ;    after "decf" will Load the 
                                ;    Synchronizer with a "1"

  movlw	 0x0A0              ;  Enable TMR0 Overflow Interrupt
  movwf	 INTCON

  goto	 $                  ;  Loop Forever Waiting for Interrupt

  end
             
This code can also be used on a low-end PICMicro to monitor when an input changes.


9. 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 algorithm:
  SAVE DIVIDEND

  QUOTIENT = 0

DIVIDE:

  DIVIDEND = INT(DIVIDEND/2): IF DIVIDEND = 0 THEN GOTO DONE
  QUOTIENT = QUOTIENT + DIVIDEND

  DIVIDEND = INT(DIVIDEND/2): IF DIVIDEND = 0 THEN GOTO DONE
  QUOTIENT = QUOTIENT - DIVIDEND

  GOTO DIVIDE

DONE:
             
Can be implemented in the PICMicro as:
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		;  Quotient = Quotient-(Dividend / 4)

  goto	 Div3_Loop

Div3_Done

  movf	 Quotient,w		;  Return the Quotient

  return
             

10. Timer Interrupt Handler without Saving STATUS or "w"

When you first learned about doing PICMicro Interrupts, you were probably shown some code for saving the STATUS and "w" registers before executing the interrupt handler. But this is not always required.

Incrementing a counter when the Timer Overflows could be accomplished by:

 org	 4
Int			  ;  Interrupt Handler

  bcf	 INTCON,T0IF	  ;  Reset the Interrupt Source

  incfsz Counter,f	  ;  Make Sure next is not missed if Counter 
  nop			  ;   Rolls Over

  retfie
             
Looking at executing the interrupt handler without affecting "w" or STATUS will free up cycles, File Registers and Control Store.


11. Sixteen bit counter with a Constant Loop Delay

When I first started working with the PICMicro, I thought I was exceedingly clever when I came up with:

  movlw  HiDlay		;  Load the Delay Values
  movwf	 HiCount
  movlw	 LoDlay
  movwf	 LoCount
Dlay:                   ;  Loop Here Until HiCount/LoCount == 0 
  decfsz LoCount,f
   goto  Dlay
  decfsz HiCount,f
   goto	 Dlay
             

Then Marc Heuler showed the code:

  movlw   HiDlay	;  Load the Delay Values
  movwf	  HiCount
  movlw	  ( LoDlay ^ 0x0FF ) + 1
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
             
Which is a lot easier than figuring out the delay for the first "Dlay" loop.


12. Sixteen bit Pulse Measurement with 5 Cycle Delay

This is an improvement on the 16 Bit delay code that I presented in "Programming and Customizing the PIC Microcontroller". "PulseWidth" is a sixteen bit value that contains the number of times through the loop. The code measures the pulse width for a "high" pulse

  clrf   PulseWidth             ;  Reset the Timer
  clrf   PulseWidth + 1

  btfss  PORTn, Bit             ;  Wait for the Pulse to go high
   goto  $ - 1

  incfsz PulseWidth             ;  Increment the Counter
   decf  PulseWidth + 1
  btfsc  PORTn, Bit             ;  Loop while Still High
   goto  $ - 3

  movf   PulseWidth             ;  Make 16 Bit Result Valid
  addwf  PulseWidth + 1
             

13. Detect a Change in a Register

Bob Fehrenbach has passed on a number of his snippets for inclusion in his page. 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 which 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"
             

14. 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'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               
             

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

Yes I know this one has been around forever, but it never hurts to be reminded of it.

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

16. Swap the Contents of Two Registers

Here's a fast snippet to swap two file registers.

   movf    X, w                 
   subwf   Y, w                 ;  W = Y - X
   addwf   X                    ;  X = X + (Y - X)
   subwf   Y                    ;  Y = Y - (Y - X)
             

17. Compare and Swap if Y < X

Writing a bubble sort routine? Here's your 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                    ;  Else, X = X + (Y - X)
   subwf   Y                    ;        Y = Y - (Y - X)
             

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

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

If you're a regular on the PICList, you will know of Dmitry Kiryashov and his interest in providing the most efficient routines possible for carrying out operations. 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
  movfw  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"
             

20. 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 are 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
             

21. 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                      ;  X = ABCDEFGH + 0B0D0F0H
                                
  rrf    X                      ;  X = ( ABCDEFGH + 0B0D0F0h ) >> 1
  addwf  X, w                   ;  w = BADCFEHG
             

22. Bitwise Operations

Setting and Resetting bits based on the state of other bits is not something the PICMicro seems to be able to do well naturally. By using multiple bit condition tests, the actual operations are pretty easy to implement.

Note that these routines should not be used for changing I/O port values because there may be an incorrect intermediate value. If you are using this code for changing an I/O Port or Hardware Control Register bit, make sure that you read the register's contents into "w" and use the "andlw" and "iorlw" instructions to change the bit before writing the new value back to the register. The intermediate value could initiate some hardware operation that is not desired.

Setting a bit by "ANDing" two others together is accomplished by:

  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
             

To show how this operation could be accomplished on an I/O port bit, I have included the code:

  movf   PORTB, w               ;  Store PORTB in "w" for "AND" Operation
  iorlw  1 << Result            ;  Assume the Result is True
  btfsc  BitA                   ;  If BitA != 1 then result is False 
   btfss BitB                   ;  If BitB == 0 then result is False
    andlw 0x0FF ^ (1 << Result) ;  Result is False, Reset the Bit
  movwf  PORTB                  ;  Save the Result
             

"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
             

The final operation is the "NOT". 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 the 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
             

23. Button Debounce Macro

This Macro is inserted to wait for a PORT Pin to reach a set state for a specific amount of time before continuing.

Debounce macro HiLo, Port, Bit
 if HiLo == Lo
  btfss Port,Bit        ;  Is the Button Pressed?
 else
  btfsc Port,Bit
 endif
   goto $ - 1           ;   Yes - Wait for it to be Released
  movlw InitDlay        ;  Wait for Release to be Debounced
  movwf Dlay            ;  Have to Delay 20 msecs
  movlw 0
 if HiLo == Lo
  btfss Port,Bit        ;  If Button Pressed, Wait Again for it
 else
  btfsc Port,Bit
 endif
   goto $ - 6           ;   to be Released
 ifndef Debug           ;  Skip Small Loop if "Debug" Defined
  addlw 1               ;  Increment the Delay Count
  btfsc STATUS, Z       ;   Loop If Low Byte (w) Not Equal to Zero
 else
  nop                   ;  Match the Number of Instructions
  nop
 endif
   decfsz Dlay
    goto $ - 5
 endm
             

The "InitDlay" constant is calculated using the formula:

  TimeDelay = (((InitDlay - 1 ) * 256) * 7) / (Frequency / 4)
             

or

  InitDlay = ((TimeDelay * (Frequency / 4)) / (256 * 7)) + 1
             

This means that with an "InitDlay" value of 12, a 19.7 msec debounce delay will be produced for a PICMicro running at 4 MHz.


24. A PBASIC like "Button" Debounce Macro

Here is a Macro that works similarly to the Parallax Basic Stamp's "Button" Function:

Button macro Port, Pin, Down, Delay, Rate, Variable, Target, Address
 local ButtonEnd
  incf Variable, w              ;  Increment the Counter Variable
 if ((Down == 0) && (Target == 0)) || ((Down == 1) && (Target == 1))
  btfsc Port, Pin               ;  If Low, then Valid Pin
 else
  btfss Port, Pin               ;  If High, then Valid Pin
 endif
  clrw                          ;  Not Pressed, Clear the Counter
  movwf Variable                ;  Save the Counter Value
  movlw Delay & 0x07F
  subwf Variable, w             ;  Button Debounced?
  btfsc STATUS, Z
   goto Address                 ;  If Equal, then "Yes"
 if ((Delay & 0x080) != 0)      ;  Is Autorepeat used?
  btfsc STATUS, C               
   decf Variable                ;  No - Decrement if > "Delay"
 else
  btfss STATUS, C               
   goto ButtonEnd               ;  Less than Expected - End
  xorlw Rate                    ;  At the Autorepeat Point yet?
  btfsc STATUS, Z
   goto ButtonEnd               ;  No - Keep Incrementing
  movlw Delay                   ;  Yes, Reset back to the Original 
  movwf Variable                ;   Count and Repeat
  goto Address
 endif 
ButtonEnd                       ;  Macro Finished
 endm
             

The parameters are defined as:

 Port, Pin - the Button Pin (ie "PORTA, 0").
 Down - The State When the Button is Pressed.
 Delay - The number of iterations of the Macro code before the 
   "Address" is jumped to (to 127).  If Set to 0, then Jump if 
   "Target" met without any debouncing.  If Bit 7 of "Delay" is 
   set, then no auto-repeats.
 Rate - After the Initial jump to "address", the number of cycles 
   (to 127) before autorepeating.  
 Target - The state ("1" or "0") to respond to.
 Address - The Address to Jump to when the Button is pressed or 
   Auto-repeats
             

If you have any others that you think are clever, please drop me a line and I'll add them to this page.

myke

Comments? emailme@myke.com
copyright © Myke Predko 1998
Order from Amazon.com
Enter keywords to Search for...