Very high speed DUAL 4X-resolution quadrature encoder

;======================================================================================
; Very high speed DUAL 4X-resolution quadrature encoder routine.
;
; This module defines a routine that will allow a 20MHz PIC16xxx to encode two 
; simultaneous quadrature encoders. Transitions on both edges of both signals
; from each encoder are processed to provide 4x resolution.
;
;     (c) 2003 - Robert V. Ammerman
;     RAm Systems 
;     rammerman@adelphia.net (as of 21-Apr-2003)
;     via the PICLIST (www.piclist.com)
;
; This code may be freely used in any application, commercial or otherwise, with the following
; provisos:
;
; 1. The above copyright notice and these conditions remain intact within the source code.
; 2. Robert V. Ammerman, RAm Systems, the PICLIST and PICLIST.COM have no responsibility
;    for this code working in your application, or any consequences of it not working.
; 3. This code is provided without any form of warranty whatsoever.
; 4. You must make a diligent effort to contact Robert V. Ammerman via email or the
;    PICLIST to inform him, in general terms, about how you are using this code (he
;    is curious!)
;
; PERFORMANCE: Unlike many quadrature decoding schemes, this code does not depend on 
; interrupts from edges of the input signals. Rather, it periodically checks the 
; inputs to see what changes have occured. It can be driven either from a timer 
; interrupt, or simply by being called from a non-interrupt-driven loop.
;
; In the non-interrupt-driven case, each call of the encoder polling routine uses
; a maximum of 22 instruction cycles, including the call and return. It uses as few
; as 12 cycles if no inputs have changed.
;
; A reasonable computation for the maximum edge rate at which this routine 
; can work is:
;
;    22 instructions per poll
; x 1.5 (to allow for 'distortion' in the encoder waveforms)   
; x 1.5 (to give the 'application' code at least 1/3 of the total CPU time).
; -------
; = 50 instructions per edge
; 
; At a 20Mhz clock rate, or 5Mhz instruction rate, this gives a maximum edge rate
; of 100,000 edges per second or 25,000 full encoder cycles per second.
;
; Note that at the rate given above, an application has to limit itself to 
; no more than 11 instruction times between calls on the ENCPOLL macro to
; meet the 1.5 factor in the above comptution. Changing that factor, for example,
; to 2.0 would allow 22 application instructions per poll at a maximum edge rate
; of about 67,000 per second. A factor of 3.0 would allow 44 application instructions 
; per poll at a maximum edge rate of about 50,000 per second.
;
; When interrupt driven, a number of cycles will be used to enter and exit the 
; interrupt routine, which will reduce the maximum possible edge rate.
;
; Note that if any interrupts are enabled, then the worst case ISR time has to 
; be added to the clocks per edge. This is true even if the encoder is using
;   the polling mode.
;
; This code is provided in ABSOLUTE mode to avoid the complexity of defining and
; distributing a linker control file with it (sorry Olin).
;
; NOTE: The sample application code that is included herein is not complete. Appropriate
; initialize code needs to be added.
;
    list p=16F628,r=DEC,x=OFF
    include "p16F628.inc"
;==============================================================
    org 0x000 
    goto initialize
 
;==============================================================
; Where to put the lookup table
enc_lookup_table = 0x100
 
;==============================================================
; Variables used by the encoder
 cblock 0x20
 
 enc1_count:2  ; current count for encoder 1
 enc2_count:2  ; current count for encoder 2
 enc1_faults   ; number of faults seen on encoder 1
 enc2_faults   ; number of faults seen on encoder 2
 enc_inputs   ; most recent (old,new) input input pair
 
 endc
;==============================================================
; Routine to poll the encoder. This code assumes that the encoder
; inputs are connected to pins RA3..RA0, and that PCLATH is 
; always set to HIGH(enc_lookup_table)
POLL_ENCODER:
    movlw 0x0F          ;[3]
    andwf enc_inputs,F  ;[4]
    swapf enc_inputs,F  ;[5]
    andwf PORTA,W       ;[6]
    iorwf enc_inputs,W  ;[7]
    movwf enc_inputs    ;[8]
    movwf PCL           ;[9-10]
;==============================================================
; Define a macro to poll the encoders
; Min time (including call and return) = 12 instructions
; Max time (including call and return) = 22 instructions
POLLENC macro 
    call  POLL_ENCODER ;[1-2]
 endm
;==============================================================
; Define the four possible actions for an encoder
A_NOP = 0   ; do nothing (neither input changed)
A_INC = 1   ; increment count
A_DEC = 2   ; decrement count
A_FLT = 3   ; fault condition (both inputs changed)
;==============================================================
; Macro to generate the label for an action routine
A_LABEL macro m1,m2
ACT_#v((m1<<2)|m2):
 endm
;==============================================================  
; Macros to perform the actions for the two encoders. To store
; a larger count than 16 bits these routines would have to be 
; changed. 
INC1 macro             
    incf   enc1_count+1,f ;[1] assume carry
    incfsz enc1_count,f   ;[2] bump low bits
    decf   enc1_count+1,f ;[3] no carry actually happened
 endm
 
INC2 macro
    incf   enc2_count+1,f  ;[1] assume carry
    incfsz enc2_count,f    ;[2] bump low bits
    decf   enc2_count+1,f  ;[3] no carry actually happened
 endm
 
DEC1 macro
    movf   enc1_count,f    ;[1] check for zero
    skpnz                  ;[2]
    decf   enc1_count+1,f  ;[3] decrement msbits
    decf   enc1_count,f    ;[4] decrement lsbits
 endm
 
DEC2 macro
    movf   enc2_count,f    ;[1] check for zero
    skpnz                  ;[2]
    decf   enc2_count+1,f  ;[3] decrement msbits
    decf   enc2_count,f    ;[4] decrement lsbits
 endm
 
FLT1 macro 
    incf   enc1_faults,f   ;[1]
 endm
 
FLT2 macro
    incf   enc2_faults,f   ;[1]
 endm
 
;==============================================================
; Define the routines that implement the actions. 
 A_LABEL A_INC,A_NOP
     INC1      ;[13-15] fall thru
     return    ;[13-14] or [16-17]
  
 A_LABEL A_INC,A_INC
     INC1      ;[13-15] fall thru
 A_LABEL A_NOP,A_INC
     INC2      ;[13-15] or [16-18]
     return    ;[16-17] or [19-20]
    
 A_LABEL A_INC,A_DEC
     INC1      ;[13-15] fall thru
 A_LABEL A_NOP,A_DEC
     DEC2      ;[13-16] or [16-19]
     return    ;[17-18] or [20-21]
  
 A_LABEL A_INC,A_FLT
     INC1      ;[13-15] fall thru
 A_LABEL A_NOP,A_FLT
     FLT2      ;[13] or [16]
     return    ;[14-15] or [17-18]
  
 A_LABEL A_DEC,A_INC
     INC2      ;[13-15] fall thru
 A_LABEL A_DEC,A_NOP
     DEC1      ;[13-16] or [16-19]
     return    ;[17-18] or [20-21]
  
 A_LABEL A_DEC,A_DEC
     DEC1      ;[13-16]
     DEC2      ;[17-20]
     return    ;[21-22]
  
 A_LABEL A_DEC,A_FLT
     DEC1      ;[13-16]
     FLT2      ;[17]
     return    ;[18-19]
 A_LABEL A_FLT,A_INC
     INC2      ;[13-15] fall thru
 A_LABEL A_FLT,A_NOP
     FLT1      ;[13] or [16]
     return    ;[14-15] or [17-18]
  
 A_LABEL A_FLT,A_DEC
     FLT1      ;[13]
     DEC2      ;[14-17]
     return    ;[18-19]
  
 A_LABEL A_FLT,A_FLT
     FLT1      ;[13]
     FLT2      ;[14]
     return    ;[15-16]
;==============================================================
;==============================================================
;==============================================================
;==============================================================
;      START OF SAMPLE APPLICATION CODE
;
; This dummy application shows how the PIC could be polled 
; via UART to supply the current input values.
;
; Note: the numbers in [] identify the number of instructions
; between polls. To support a 100,000 edge per second rate
; this must be kept at 11 or below. As you can see, it really
; isn't too difficult.
 cblock
 send_save  ; saved MSBits of current count value being sent
 xmit_hold  ; byte held while waiting for TX to be ready
 endc
 
initialize:
    ; -- perform initialization of UART, ports, etc --
 
mainloop:
    POLLENC
    btfss RCSTA,OERR  ;[1] do we have an overrun?
    goto  no_error    ;[2-3]
    bcf   RCSTA,CREN  ;[3] disable
    nop               ;[4]
    bsf   RCSTA,CREN  ;[5] and reenable to correct overrun 
no_error:
    POLLENC
    btfss PIR1,RCIF   ;[1] do we have a character?
    goto  mainloop    ;[2-3] no
    movf  RCREG,W     ;[3] get character
    xorlw 'P'         ;[4] is it the poll command?
    skpz              ;[5]
    goto mainloop     ;[6-7] no
 
    POLLENC
    movf  enc1_count+1,W ;[1] save MSBits of counter
    movwf send_save      ;[2] ...so we get a consistent view
    movf  enc1_count,W   ;[3] send ls byte firs
    call  xmit_byte      ;[4-5] send it off
 
    movf  send_save,W    ;[3] now the ms byte
    call  xmit_byte      ;[4-5]
 
    movf  enc1_faults,W  ;[3] and the number of faults
    call  xmit_byte      ;[4-5]
 
    movf  enc2_count+1,W ;[3] save MSBits of counter
    movwf send_save      ;[4] ...so we get a consistent view
    movf  enc2_count,W   ;[5] send ls byte first 
    call  xmit_byte      ;[6-7]
 
    movf  send_save,W    ;[3] now the ms byte
    call  xmit_byte      ;[4-5]
 
    movf  enc2_faults,W  ;[3] and the number of faults
    call xmit_byte       ;[4-5]
 
    goto mainloop        ;[3-4]
 
 ; -- send the byte in W to the UART. 
 
xmit_byte:
    movwf xmit_hold      ;[8] remember it
xmit_loop:
    POLLENC
    btfss PIR1,TXIF      ;[1]
    goto  xmit_loop      ;[2-3]
    movf  xmit_hold,W    ;[4]
    movwf TXREG          ;[5]
    POLLENC
    return               ;[1-2] 
 
;      END OF SAMPLE APPLICATION CODE
;==============================================================
;==============================================================
;==============================================================
;==============================================================
  
;==============================================================
; Macro to encode an old and new state to determine what 
; action to perform for a single encoder
SENCODE macro result,old,new
; no change
 if  old == new
result = A_NOP
 exitm
 endif
; fault condition (both bits changed)
 
 if  old == (new ^ B'11')
result = A_FLT
 exitm
 endif
 
; increment is: 00->01->11->10->00
; decrement is: 00->10->11->01->00
  local x
x = (old << 2) | new
  if x == B'0001' || x == B'0111' || x == B'1110' || x == B'1000'
result = A_INC  ; it is an increment
  else
result = A_DEC  ; it must be a decrement
  endif
 endm
;==============================================================  
; Macro to encode the old and new states for both encoders
; to determine the action routine to be executed.
SACTIONS macro old,new
 local m1,m2,action
 ; figure out the action for the first encoder
    SENCODE m1,((old >> 2)&B'11'),((new >> 2)&B'11')
 ; figure out the action for the second encoder
    SENCODE m2,(old&B'11'),(new&B'11')
 ; combine the two
action = (m1<<2)|m2
 ; no sense generating a goto instruction to a return. just generate
 ; the return instead
 if action == 0
  LIST X=ON
    return      ;[11-12]
  LIST X=OFF
 else
  LIST X=ON
    goto ACT_#v(action)  ;[11-12]
  LIST X=OFF
 endif
 endm
 
;==============================================================
; Define a 256 instruction table that is indexed by the 
; concatenation of the old and new state bits to determine the 
; actions to be taken. It is interesting to note that each 
; possible action routine is used exactly 16 times by this code
; Now build the lookup table. It consumes 0x100 locations starting
; at 'enc_lookup_table'
 
 org enc_lookup_table
 while $ < enc_lookup_table + 0x100 
    SACTIONS (($>>4)&B'1111'),($&B'1111')
 endw
 enc
 

Questions: