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