PIC Microcontroller Comparison Math Methods

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/ ?

Comparing values in a PIC (THEORY)

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

8 bit compares

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

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: 


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:
endm

Here is the line of code I actually use

       subabs     cmdhi,cmdlo,poshi,poslo,PORTB,2

I 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 
endm

Here 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_temp

If 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:

David A Cary Says: " http://massmind.org/techref/microchip/seepicsrc/psbpix/if.htm "

Code:

Interested:

See:

Questions: