Sections:
related pages:
Archive:
[FIXME: lots of redundant stuff here.] [FIXME: Should I break this into several pages ? How ?]
http://www.forth.com/Content/Handbook/FPH22.html has a little illustration trying to explain signed, unsigned, and ``circular'' interpretation of a number. Comparing one time with another time should almost always use ``circular'' interpretation to avoid problems at rollover ... FIXME: circular comparison code ? Is failure to use the circular comparisons the cause of the "fatal subtraction error" mentioned at http://www.emeagwali.com/interviews/capstone_press/ ?
How can the PIC do comparison ? There's no ``compare'' instruction !
The fundamental instruction for most comparisons is
subwf Y,w
. After that instruction executes, the state of the flag bits in the STATUS register reflects whether Y was bigger, smaller, or identical to the (original) value in w.
(In your own code, you will somehow get one of the values you want to compare into the w register, and replace the letter ``Y'' with the name of the register containing the other value).
The subwf instruction, all by itself, compares 2 unsigned 8 bit values. Later we'll see how to compare 16 bit values and signed values.
As a side effect, it also performs subtraction. I'll call the value that you load into w before the subwf ``w''; I'll call the value the PIC puts into w after the subwf ``wnew''.
If you're already familiar with how "addwf" sets the carry register, you might reflect on the fact that
subwf Y,w
gives exactly the same results (in wnew, Carry, and Zero) as the final add in this sequence:
; w(intermediate) := -w (two's complement) xorlw 0xff addlw 1 ; wnew = Y + w(intermediate) = Y-w addwf Y
There's only 3 possible results:
You can combine those possibilites in pairs to get these other commonly used operators:
Hotshot PIC assembly-language programmers try to force their comparisons to be (w <= Y) or (Y < w), because then they only need to check Status,C -- they can ignore Status,Z.
-- with help from Robin Abbott - robin.abbott@dial.pipex.com
If you're used to programming in assembly language on a non-PIC chip, check this out:
Comparisons to 0 -- if( Y <= 0 ) or if( 0 < Y ), where Y is a signed (8 or 16 bit) number -- are the fastest comparisons on the PIC. They are even faster than comparing ``if( w == Y )'' or even ``if( w == 0 )''.
So how do you actually use this nifty instruction ?
I'm going to use ``RAMx'' and ``RAMy'' to indicate values in a RAM register, ``K'' to indicate some fixed constant value (often defined using the ``EQU'' assembler directive). (If you want to compare 2 constant values, use the assembler directive ``#if ... #else ... #endif''. The grid below *should* cover all other combinations. ).
Q: Hey ! you left out all the ``greater than'' operators !
A: Whoops. Well, if you need ``A > B'', you can always use ``B < A''.
Most of these are from Tony Nixon.
I'm going to go on the assumption that you want to execute a chunk of code only if the conditional is *true*. (This is what ``if()'' means in C and other decent high-level languages. Don't let those BASIC programmers confuse you.)
Put that chunk of code (or a CALL to it) immediately after these blocks of code, and follow that chunk with some unique label. Then replace the word "Endif" with the name of that label.
If that chunk of code is *exactly* one instruction long (say ``CALL''), and you're very clever, you can optimize this code even more.
( swstmt.htm describes how to code ``if - then - else - endif'' blocks).
Q: Is there a *reason* Tony Nixon uses ``addlw'' and ``addwf'' rather than ``sublw'' and ``subwf'' ? All PICs support subwf, right ? And the ones that don't support sublw don't support ``addlw'' either, right ?
If you want to use a ``12 bit core PIC'' such as the 12C509, note that it does not have the SUBLW, ADDLW instructions. See the PIC Microcontroller Instruction Set Quick Reference and Core Comparison Matrix . If you're using one of those chips, then you can use MOVLW then SUBWF (or ADDWF) ...
;if RAMx <= K ;Tony Nixon movf RAMx,w addlw 255 - K ; eg if RAMx > 5 ... addlw d'250' skpnc goto Endif ;if K <= RAMx ;Tony Nixon movf RAMx,w addlw 255 - K + 1 ; eg if RAMx < 5 ... addlw d'251' skpc goto Endif ;if RAMx < K ; Tony Nixon movf RAMx,w addlw 255 - K + 1 ; eg if RAMx >= 5 ... addlw d'251' skpnc goto Endif ;if K < RAMx ; Tony Nixon movf RAMx,w addlw 255 - K ; eg if RAMx <= 5 ... addlw d'250' skpc goto Endif
;if RAMx <= RAMy ; Scott Dattalo movf RAMx,w subwf RAMy,w skpc goto Endif
;if RAMy < RAMx ; Scott Dattalo movf RAMx,w subwf RAMy,w skpnc goto Endif
;if RAMx < RAMy /* obsolete */ ; unknown -- perhaps James Newton ? movf RAMx, w subwf RAMy, w skpz ;for the case that RAMx=RAMy where C will be 1 skpc goto Endif;if RAMx <= RAMy ; better, but only works on 18cxxx chips ! ; Scott Dattalo movf RAMx,w ;wreg = RAMx subwf RAMy,w ;wreg = RAMy - RAMx bn Endif ;Branch if negative only available on 18cxxx ; (You could use the bnc [branch if no carry] to achieve the same effect. ; The negative bit has a clearer meaning in this context). ;The N bit will be cleared if RAMx == RAMy or ;RAMy > RAMx, and will be set if RAMy < RAMx ;18cxxx 3*16 = 48 bits of program memory ;16cxxx 5*14 = 70 bits of program memory;if RAMy < RAMx ; better, but only works on 18cxxx chips ! ; Scott Dattalo movf RAMx,w ;wreg = RAMx subwf RAMy,w ;wreg = RAMy - RAMx bnn Endif ;branch if not negative ;The N bit will be cleared if RAMx == RAMy or ;RAMy > RAMx, and will be set if RAMx < RAMy ; Again, you could use the bc (branch on carry) instruction too.
Regulus Berdin says: For readability, one could write a jump if equal macro:
JIFEQ MACRO register,literal,address movlw literal xorwf register,w skpnz goto address ENDM switch: JIFEQ data,'A',Process_A JIFEQ data,'B',Process_B JIFEQ data,'C',Process_C . . . process_A: ... return process_B: ... return process_C: ... return . . .
16 bit compares. Here I use
You must change the ``then:'' and ``endif:'' labels to some unique name. [FIXME: Or should I change these to macro locals ? ]
; signed and unsigned 16 bit comparison routine: ; by David Cary 2001-03-30 ; returns the correct flags (Z and C) ; to indicate the X=Y, Y<X, or X<Y. ; Does not modify X or Y. compare_signed16: ; 7 ; uses a "temp" register. movf Yhi,w xorlw 0x80 movwf temp movf Xhi,w xorlw 0x80 subwf temp,w ; subtract Y-X goto Are_they_equal compare_unsigned_16: ; 7 movf Xhi,w subwf Yhi,w ; subtract Y-X Are_they_equal: ; Are they equal ? skpz goto results16 ; yes, they are equal -- compare lo movf Xlo,w subwf Ylo,w ; subtract Y-X results16: ; if X=Y then now Z=1. ; if Y<X then now C=0. ; if X<=Y then now C=1. return
After calling the above routine (or cutting-and-pasting a copy of it), you can use the result flags (Z and C) just like the 8 bit compares . For example,
;if( X <= Y ) call compare_unsigned_16 skpc goto endif ; then: ;... endif: ;if( X <= K ) (signed) movlw Khi movwf Yhi movlw Klo movwf Ylo call compare_signed_16 skpc goto endif ; then: ;... endif:
I need signed 16bit compare and branch macros for PIC16F877 working on pseudo 16bit registers ACCaLO,ACCaHI like some Microchip application notes. There are heaps of 16bit subtract/add routines but none set any useful status bits that can be used to branch on the 8 possibilities (signed & unsigned)I have made a crude version of these using the status code boolean algebra found in a Motorola 68000 data book and it appears to work ok. I was rather hoping that someone (microchip)would have a thoroughly tested and more elegant version of this. Maths routines are not my speciality & surely this should be readily available. Doesn't anyone out there compare 2 signed numbers then branch if one is greater than the other?
Any help would be greatly appreciated. If anyone can improve on my code then they are most welcome to have it.
Nikolai Golovchenko answers:
Just an idea. How about modifying the signed format to a biased format and compare biased values as unsigned numbers?By a biased format I mean inverted sign bit. That would shift the least signed value to 0, zero to middle, and highest signed value to highest unsigned value. For example,
-128 ^ 128 = 0 -127 ^ 128 = 1 -127 ^ 128 = 2 ... -1 ^ 128 = 127 0 ^ 128 = 128 1 ^ 128 = 129 2 ^ 128 = 130 ... 127 ^ 128 = 255Then just use unsigned routines for comparison. To restore the input values, invert sign bits again.
Most of these are based on code from Tony Nixon. These only do *unsigned* comparisons.
;********** 16 BIT ;if( X <= K ) movfw XH sublw KH ;not available on 12 bit core skpc goto endif skpz goto then movfw XL sublw KL skpc goto endif then: ... endif: ;if( K <= Y ) movlw KH subwf YH,w ; *is* available on 12 bit core skpc goto endif skpz goto then movlw KL subwf YL,w skpc goto endif then: ... endif: ;if(K < X) movlw KH subwf XH,w ; *is* available on 12 bit core skpc goto endif skpz goto then movf XL,w sublw KL ;not available on 12 bit core skpnc goto endif then: ... endif: ;if(Y < K) movf YH,w sublw KH ;not available on 12 bit core skpc goto endif skpz goto then movlw KL subwf YL,w skpnc goto endif then: ... endif: ;if( X <= Y ) /* obsolete */ movfw XH subwf YH,w ; *is* available on 12 bit core skpc goto endif skpz goto then movfw XL subwf YL,w ; Use ,w rather then ,f to preserve Y. skpc goto endif ; X and Y are unchanged. then: ... endif:
Scott Dattalo (on 2001-04-10) said ``the classic 6 instruction subtract ... I'm not the original author. I think Bob also got accused of writing that code too. But in reality it predates both of us by years.''
Here is the 6 instruction subtract, (from math/sub/16bb.htm ) and a couple of compare routines based on it. This works even on a 12 bit core [right ?].
;---------------------------- ; 16-bit Subtraction-with-Borrow ; SourceH:SourceL = Number to be subtracted ; DestH:DestL = Number to be subtracted FROM ;Out DestH:DestL = Result = dest-source ; Carry = NOT( Borrow result) ; by Rudy Wieser (2000-02-17) movfw SourceL subwf DestL,f movfw SourceH skpc incfsz SourceH,W subwf DestH,f ;dest = dest - source, WITH VALID CARRY ;(although the Z flag is not valid). ;---------------------------- ; if( y < x ) ; by unknown ; Y and X are unchanged. movfw xl subwf yl,w movfw xh skpc ; c=0 indicates a borrow we need to propagate. incfsz xh,w ; handle xh=0xff correctly, unlike ``incf xh,w''. subwf yh,w skpnc goto endif ; then: ; /* y is less than x */ ... endif: ; if( x <= y ) ; by unknown ; Y and X are unchanged. movfw xl subwf yl,w movfw xh skpc ; c=0 indicates a borrow we need to propagate. incfsz xh,w ; handle xh=0xff correctly, unlike ``incf xh,w''. subwf yh,w skpc goto endif ; then: ; /* x is less than or equal to y */ ... endif:
Scott Dattalo says:
Similar sequences exist for the other cases.
;if( x < y ) ; originally by Scott Dattalo (2001 ?) movfw y_lo subwf x_lo,w movfw y_hi subwfc x_hi,w ; only works on 18cxxx chips ! bc endif ; /* x is less than y */ ... endif: ;if( y <= x ) ; originally by Scott Dattalo (2001 ?) movfw y_lo subwf x_lo,w movfw y_hi subwfc x_hi,w ; only works on 18cxxx chips ! bnc endif ; /* y is less than or equal to x */ ... endif: ;if( x <= y ) ; originally by Scott Dattalo (2001 ?) movfw y_lo subwf x_lo,w movfw y_hi subwfc x_hi,w ; only works on 18cxxx chips ! bn endif ; /* x is less than or equal to y */ ... endif:
Antonio L Benci [Nino.Benci at SPME.MONASH.EDU.AU or possibly spme.monash.edu] http://www.physics.monash.edu.au/~ninob on 2001-04-10 11:53:45 PM wrote:
I wrote this code about 6 years ago...;******************************************************************* ; A not too optimised 16 bit compare routine for 16 absolute values, ; ie 0 -> 65536. ; Compare WORD to COMP (a word value). ; If WORD = COMP return with 00 ; If WORD > COMP return with 01 ; If WORD < COMP return with 80 ;******************************************************************* include "p16c5x.inc" ; include file for processor type hword equ 0x10 ; storage for high byte of WORD lword equ hword+1 ; storage for low byte of WORD hcomp equ 0x12 ; storage for high byte of COMP lcomp equ hcomp+1 ; storage for low byte of COMP COMP: movfw hcomp ; get high byte of comp value subwf hword,0 ; subtract values btfsc status,z ; first check if result is 0 goto COMPL ; if zero compare low bytes btfsc status,c ; else test carry bit retlw 0x01 ; if WORD > COMP, return with 01h retlw 0x80 ; if WORD < COMP, return with 80h COMPL: movfw lcomp ; get low byte of comp value subwf lword,0 ; subtract values btfsc status,z ; first check if result is 0 retlw 0x00 ; if result is 0, return with 00 btfsc status,c ; if c set then WORD > COMP retlw 0x01 ; if WORD > COMP, return with 01h retlw 0x80 ; if WORD < COMP, return with 80h end
Alan "the Rocket Scientist" says:
;This macro is for a 16 bit absolute value subtraction. ;The smaller of the two numbers will be subtracted from ;the larger. A bit will be set if the first of the two ;numbers is larger. The bit will be cleared if the second ;of the two numbers is larger. The results of the subtraction ;will be placed in the first set of registers (fr0,fr1) if the ;first number is the higher, The results will be placed in both ;register if the second number is higher. subabs macro fr0,fr1,fr2,fr3,fr4,bit local above,label local abovelo,label local below,label local belowlo,label local equal,label local out,label movf fr2,0 subwf fr0,0 btfsc _Z goto equal above: movf fr2,0 subwf fr0,0 btfss _C goto below movf fr3,0 subwf fr1,1 btfsc _C goto abovelo decf fr0,1 abovelo: movf fr2,0 subwf fr0,1 bcf fr4,bit goto out below: movf fr1,0 subwf fr3,1 btfsc _C goto belowlo decf fr2,1 belowlo: movf fr0,0 subwf fr2,1 movf fr3,0 movwf fr1 movf fr2,0 movwf fr0 bsf fr4,bit goto out equal: movf fr1,0 subwf fr3,0 btfss _C goto above goto below out: endmHere is the line of code I actually use
subabs cmdhi,cmdlo,poshi,poslo,PORTB,2I use this to find the difference between the commanded position of the actuator and current position. The calculated error is then used to find a value from a look up table that is output to a motor controller IC via the PWM output (CCP1). The direction of rotation input on the IC is output from PORTB. I have reused this macro in several different designs.
Here is another one that I have used much more extensively. It will compare the 16 bit value stored in fr, fr-1 to a 16 bit literal number and then jump to an address is the value in the registers is higher than the literal.
cjalwd macro fr1,L1,L2,addr movf fr1,W sublw L1 btfss _C goto addr movlw L1 subwf fr1,W btfss _C goto $+5 movf fr1-1,W sublw L2 btfss _C goto addr endmHere is an example of this macro that uses the 16 bit value stored in temp_hi,temp_lo, note that the lower byte is not in the line of code but is used anyway. Care must be taken to insure the lower byte is one address space lower in memory than the higher byte.
cjalwd temp_hi,0x01,0xA9,store_tempIf the value in temp_hi,temp_lo is greater than 0x10A9 then the program will jump to the address location labeled store_temp.
Questions:
Footnotes:
2001-06-23:DAV: Deleted a bunch of redundant if(RAMx < RAMy) sequences.
2001-06-17:DAV: updated the above code to use the mnemonics
skpc ; also known as ``btfss status,carry'' skpnc ; also known as ``btfsc status,carry''
. All good assemblers http://piclist.com/techref/microchip/languages.htm (in particular, GPASM http://gpasm.sourceforge.net/ and MPASM which comes with MPLAB http://www.microchip.com/0/tools/picmicro/devenv/mplabi/ ) support these ``skip if carry'' and ``skip if no carry'' mnemonics.
David A Cary of Motorguide Pinpoint shares this code:
;mnemonics: ;Set the Z flag: Z = (0==Y); tstf Y ; also known as movf Y,f on 16xxx processors tstf Y ; apparently known as comf Y,f ; comf Y,f; on 17Cxxx processors ;skip the next instruction (typically a ``goto'') if Z is set: skpz ; ``btfss status,z'', or ``btfss alusta,z'' on 17cxxx processors skpnz ; ``btfsc status,z'', or ``btfsc alusta,z'' on 17cxxx processors ;carry ... ``not borrow'' skpc ; also known as ``btfss alusta,carry'' on 17Cxxx processors skpnc ; also known as ``btfsc alusta,carry'' on 17cxxx processors
(is there a better page for the above mnemonics?)
There is some excellent code for MIN() and MAX() at /techref/microchip/condrepl.htm .
See also:
Comments:
Since these are all 1 assembly instruction, it's OK to use them right after one of the ``skip'' instructions.
summarized from: Thread: instructions not in the instruction set ! http://piclist.com/techref/postbot.asp?by=time&id=piclist/2001/06/09/225915a
(from Olin) SKPWGT skip if w greater than SKPWLE skip if w less or equal SKPWEQ skip if w equal SKPWNE skip if w not equal (from Roman Black) INCW DECW (from Dwayne Reid) ve got a few more that I find useful: 12 bit core only: tstw MACRO ;test w, valid Z; C & DC unchanged xorlw 0 endm comw MACRO ;complement W, valid Z; C & DC unchanged xorlw 0xFF endm 12 bit and 14 bit core parts: decw MACRO ;decrement w, valid Z, C & DC trashed addlw -1 endm ;C & DC =1 if w>0 after decrement negw MACRO ;negate w sublw 0 endm ;valid z; C & DC trashed
David A Cary Says: " http://massmind.org/techref/microchip/seepicsrc/psbpix/if.htm "
Code:
Routine comparing two unsigned 16-bit numbers: <code> ;Written by Martin Viteznik, jsem () einstein ddot cz ;this routine compares two unsigned 16-bit values, ;X and Y. The H-byte is hbyte(X|y) etc. :-) movf hbyteX,0 subwf hbyteY,0 btfss STATUS,C ;X>Y..zc,X<Y..zC,X=Y..ZC goto Xisgreater ;result: X>Y btfss STATUS,Z goto Yisgreater ;result: Y>X movf lbyteX,0 subwf lbyteY,0 btfss STATUS,C ;X>Y..zc,X<Y..zC,X=Y..ZC goto Xisgreater ;result: X>Y btfss STATUS,Z goto Yisgreater ;result: Y>X goto XequY ;result: X=Y </code>
Interested:
See:
Questions:
Hi
I have problem in comparing 8 bit operands when the first operand is negative (in 2's complement). I have the doubt that the carry is to be complemented when the sign of the 2 operands are opposite. In the example below the latter 2 cases fail.
clrc
clrz
movlw 1
sublw 2 ;Carry=1
nop
clrc
clrz
movlw 2
sublw 2 ;Carry=1
nop
clrc
clrz
movlw 3
sublw 2 ;Carry=0
nop
clrc
clrz
movlw 4
sublw 2 ;Carry=0
nop
clrc
clrz
movlw -3
sublw -2 ;Carry=1
nop
clrc
clrz
movlw -2
sublw -2 ;Carry=1
nop
clrc
clrz
movlw -1
sublw -2 ;Carry=0
nop
clrc
clrz
movlw 0
sublw -2 ;Carry=1
nop
clrc
clrz
movlw 1
sublw -2 ;Carry=1
nop