PIC Microcontoller IO Method

Decode Quadrature Encoders

High Speed DUAL 4X-Resolution Quadrature Encoder for PIC16

Morgans Reglerteknik, mrt at iname.com says:

To decode Quadrature Encoders: Generally,

An easy way of getting the bits right is to left rotate them into a register, then mask using andlw 1111b before addwf PCL. Save the register until next call to this routine.

Interrupt on change is excellent for getting an interrupt on any change. But the routine can also be called anytime as it will handle no change too. I did this on a F877, using interrupt on change. It did reach 13kHz+ count and at the same time 4kHz timer 0 int driving some software timers etc, using 4,096 MHz xtal and 2-level interrupt structure. (Interrupt get what happened / Interrupt post service with interrupt re-enabled (soft timers, safety checks, buffers, etc) / Main prog)

         ifc             INTCON,RBIF     ;Har porttillståndet ändrats?
           goto  isrP_NoRBIF             ;Om inte: hoppa till nästa kollrutin

;Läs in nya tillståndet 
         movf    PORTB,w                 ; *** Ingen annanstans får PORTB läsas dierkt! ***
         c               INTCON,RBIF     ;Kvittera att vi har läst
         movwf   B_PORTBIN               ;lagra för denna samt andra rutiner på port B

;Bearbeta tvåfassignal genast!
;Hämta fasbitarna från B_PORTBIN till B_2phState 1:0, de förra i 3:2...
         cc                              ;Rotate the new bits in...
         ifs             B_PORTBIN,5
           sc
         rlf             B_2phState,f
         cc
         ifs             B_PORTBIN,4
           sc
         rlf             B_2phState,f
         movf    B_2phState,w
         andlw   b'1111'
         addwf   PCL,F           ;Before Now     don´t care this below

         goto    isrBchange_NO   ; 0 0   0 0             00      00
         goto    isrBchange_UP   ; 0 0   0 1             01      10
         goto    isrBchange_DN   ; 0 0   1 0             10      01
         goto    isrBchange_ERR  ; 0 0   1 1             11      11

         goto    isrBchange_DN   ; 0 1   0 0             01      01
         goto    isrBchange_NO   ; 0 1   0 1             00      11
         goto    isrBchange_ERR  ; 0 1   1 0             11      00
         goto    isrBchange_UP   ; 0 1   1 1             10      10

         goto    isrBchange_UP   ; 1 0   0 0             10      10
         goto    isrBchange_ERR  ; 1 0   0 1             11      00
         goto    isrBchange_NO   ; 1 0   1 0             00      11
         goto    isrBchange_DN   ; 1 0   1 1             01      01

         goto    isrBchange_ERR  ; 1 1   0 0             11      11
         goto    isrBchange_DN   ; 1 1   0 1             10      01
         goto    isrBchange_UP   ; 1 1   1 0             01      10
	 goto    isrBchange_NO   ; 1 1   1 1             00      00
isrBchange_NO
;       DEBUGIND_const 4        ; TEST 
         s               F_2phGlitch     ;Indikera "glitch"      GLITCH?
         ;ev godkänna glitch?  Näe; lös ut = prioritera säkerhet
isrBchange_ERR
         DEBUGIND_const 5        ; TEST
         s               F_2phSpeed      ;Indikera "överfart"  OVERSPEED
         goto    isrP_2phEnd
         
isrBchange_UP
         INC2    W_2phcnt
         ifzc
           goto isrP_2phEnd
         s               F_2phRange      ;Indikera "utanför skala"
         comf    W_2phcnt,f      ;Ändra 0000h till FFFFh
         comf    W_2phcnt+1,f
;       DEBUGIND_const 7        ; TEST 
         goto    isrP_2phEnd

isrBchange_DN
         FDEC2   W_2phcnt
         comf    W_2phcnt+1,W    ;Är högbyten annat än FF så är det OK
         ifzc                                    ;Alltså: är ettkomplementet annat än 0?
           goto isrP_2phEnd              ;I så fall OK!
         comf    W_2phcnt,W      ;Annars kolla lågbyten likadant...
         ifzc
           goto isrP_2phEnd
                                                 ;Om wrappade till FFFF:
         s               F_2phRange      ;Indikera "utanför skala"
         clrf    W_2phcnt        ;nollställ räknaren..
         clrf    W_2phcnt+1      ;

;       DEBUGIND_const 6        ; TEST 

isrP_2phEnd

;       DEBUGIND_low4 W_2phcnt  ; TEST 
         
isrP_NoRBIF

         DEBUGIND_const 4	;Int on change klar

Matthew Ballinger [MattBeck at AOL.COM] says:

Or a much more efficient routine would be (in parallax mnemonics):
  ChkEnc  mov new,Enc     ;get new input
    and new,#%00000011  ;get rid of all but 2 enc bits
    mov temp,new    ;make copy
    xor temp,old    ;is old = new
    jz  ChkEnc      ;then no change, go back
    cje temp,#3,err ;goto "error" if both bits changed
    clc         ;get ready for right shift
    rl  Old     ;align bits
    xor old,new     ;check for direction
    jb  old.1,up    ;if 1 then up direction, if 0 then down direction

Alvaro Deibe Diaz [adeibe at CDF.UDC.ES] says:

; Encoder read
#define encx    PORTA,1    ; Encoder inputs
#define ency    PORTA,2
;
;
; Inic (usually needless)
        clrf    auxint     ; Clear aux var
        btfsc   encx       ; and get encoder inputs
        bsf     auxint,0   ; in bit 0...
        btfsc   ency
        bsf     auxint,1   ; ...and 1
;
; Here starts the hard work
        movf    auxint,W   ; encod <- (actual state) xor (previous one)
        xorwf   encod,F    ; (only bits 0 and 1 affected)
;
        rrf     auxint,F   ; XOR results, reordered, get
        rlf     encod,F    ; into encod, in bits 0 and 1.
        rrf     auxint,F   ; This bits are the motion indicators
        rlf     encod,F
;
At this point you have a new read of the encoder. Bits 2 and 3 of 'encod'
have the information about the direction of movement of the encoder. If you
have to translate this information into a variable, then you can do
something like this:
;
; NOTE: This last part does NOT work. You also have to check if X and Y 
; are equal or different. e.g. 
; if (X) if (X==Y) ++pos else --pos
; else if (X==Y) --pos else ++pos
        btfsc   encod,2    ; Direction 'X' motion
        incf    posenc,F
        btfsc   encod,3    ; ...or 'Y'
        decf    posenc,F
;
Bits 4,5 and 6,7 of 'encod' have the previous reads.

Lance Allen says:

The easiest way (in my opinion) is to feed one of the two outputs (I assume its a quadrature type) to B0 and the other one to any other input (probably B1). Set the B0 input to ext interrupt enabled . Whenever an interrupt is detected by an edge presenting itself (rising or falling .. its all programable) a movement of (pulses-per-rev divided by 360) deg has been made and if you read B1and there is a 1 there the encoder turned one way, if a 0 then it turned the other.

If no interrupts are available (B0 is the only ext one) then you will need to poll one input and read the other on a change.... instead.

Morgan Olsson says:

Encoders use to have between 32 to several thousands cycles per revolution.

If you want perfect synchornization to a 0° sync signal, set the two gotos that you wish will count into the zero position to point to specialized count up/down routines that also checks the sync signal.

This will give the best possible zero position synchronization, with a resolution of four steps per cycle, making the most out of the encoder.

Using interrupt on change for the two lines is the best.

If you need to poll for changes instead, we we might want to add a few lines of code before my example that just check if any line has changed, thus minimizing the cycles needed when no change. (but will add total cycles when a change has occurred)

PIC C I/O Routine - Quadrature Encoder ISR

Archive posts on Quadrature encoders

Questions:

Comments: