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.
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
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
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
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
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
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.
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.
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
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
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
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"
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
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
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)
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)
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
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"
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
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
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
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
"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
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
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
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
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