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