Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

ASM - encoder routine for high PPR optical encoders - ideas please?

augustinetez

Active Member
Rather than load up my timer preload thread with this, I've started this seperately (but it is all related).

For the cheap and nasty mechanical detented encoders, I've got a few routines that work well enough, but are basically hopeless for high (400) ppr optical encoders, even after taking out the divide by 4 bit for the detents - no detents in the encoder I'm using.

Bottom line is that all the stuff I've either found here or elsewhere on the 'net suffer from the inability to keep up with the encoder - they either jitter back and forth between CW and CCW too much, or the most common problem - the faster you spin the encoder, the slower the returned count or they start showing a reversed count ie instead of counting up, they (the code) start counting down and vice versa.

At the moment, I am polling the encoder because everything else in the program does nothing until the encoder moves, so interrupt driven routines aren't needed (yet).

Here are a couple or three bits of code I have been playing with - bottom line, all I need at the end of the routine is to set a direction bit (dir,0 in my program).

Ultimately, the aim is to produce a bit of code that will automatically change the frequency step size of a DDS chip dependent on how fast the encoder is moving.

A modification of Mike K8LH's bit of code - works until you crank up the speed:

Code:
movf    PORTA,W        ; load switch data
    andlw    b'00000011'    ; mask encoder B and A switches
    xorwf    enc_old,W    ; same as last reading?
    btfsc    STATUS,Z    ; yes, branch (no change), else
    goto    poll_encoder
    xorwf    enc_old,W    ; restore encoder bits in W
    rrf    enc_old,f    ; prep for B-old ^ A-new
    xorwf    enc_old,f    ; ENCOLD bit 0 = direction
    rrf    enc_old,f    ; now Carry bit = direction
    movwf   enc_old        ; update ENCOLD (new BA bits)

;****************** For optical encoder *****************************************
;       Prevent encoder slip from giving a false change in direction.

    movf    STATUS,w    ; Carry bit = direction
    andlw    b'00000001'
    movwf    next_dir    ; Save result (in W) as direction
    xorwf    dir,w        ; See if direction is same as before
    btfsc    STATUS,Z    ; Zero flag set? (i.e, is direction same?) 
    goto    enc_exit    ; Yes, same direction so no slip; keep going
    movf    next_dir,w    ; No Zero-flag, so direction changed       
    movwf    dir        ; Update the direction indicator
    goto    poll_encoder    ; Try again
;********************************************************************************         
;
;  set <up> or <dn> switch flag bits based on bit 0 in next_dir
;
enc_exit
    bcf    dir,0        ; set <dn> switch flag for Main
    btfsc    next_dir,0
    bsf    dir,0        ; set <up> switch flag for Main

    return

From a DDS program by Curtis W. Preuss - also works if you don't crank up the spin speed too much:

Code:
        movf    PORTB,w        ; Get the current encoder value
    andlw    b'00000011'    ; mask encoder B and A switches
    movwf    enc_read    ; Save it
    movlw    b'00000011'    ; Get encoder mask (to isolate RB0 and RB1)
    andwf    enc_read,w    ; Isolated encoder bits into W
    movwf    enc_new        ; Save new value
    xorwf    enc_old,w    ; Has it changed?
    btfsc    STATUS,Z    ; Check zero-flag (zero if no change)
    goto    poll_encoder    ; No change, keep looking until it changes
                ; Else, Zero-flag is not set, so continue on 

; It changed. Now determine which direction the encoder turned.

    bcf    STATUS,C    ; Clear the carry bit to prepare for rotate 
    rlf    enc_old,f    ; Rotate old bits left to align "Right-Bit" 
    movf    enc_new,w    ; Set up new bits in W                      
    xorwf    enc_old,f    ; XOR old (left shifted) with new bits      
    movf    enc_old,w    ; Put XOR results into W also               
    andlw    b'00000010'    ; Mask to look at only "Left-Bit" of pair
    movwf    next_dir    ; Save result (in W) as direction (bit=UP)  
    xorwf    last_dir,w    ; See if direction is same as before 
       
;****************** For optical encoder *****************************************
;       Prevent encoder slip from giving a false change in direction.

    btfsc    STATUS,Z    ; Zero flag set? (i.e, is direction same?)  
    goto    enc_continue    ; Yes, same direction so no slip; keep going
    movf    next_dir,w    ; No Zero-flag, so direction changed        
    movwf    last_dir    ; Update the direction indicator            
    movf    enc_new,w    ; Save the current encoder bits (now in W)  
    movwf    enc_old        ; for next time                           
    goto    poll_encoder    ; Try again
;********************************************************************************
;
enc_continue
    clrf    last_dir    ; Clear last_dir (default is DN)
    bcf    dir,0
    btfsc    enc_old,1    ; Are we going UP?             
    goto    enc_up        ; Yes, go process it.
                ; Else, we are goiong down
    goto    enc_movement    ; Indicate that the encoder has moved

enc_up 
    movlw    b'00000010'    ; Get UP value
    movwf    last_dir    ; and set in last_dir
    bsf    dir,0

enc_movement            ; Arrive here when encoder is being turned
    movf    enc_new,w    ; Get the current encoder bits
    movwf    enc_old        ; Save them in ren_old for the next time
    bsf    flags,2        ; Set encoder changed flag

    return            ; Return to the caller

And one from Leon - which has an error in it - this is extraneous as in doesn't do anything -> MOVWF Q_NOW
This one misbehaves the most:

Code:
;
; QUAD State
;
; A quadrature encoder traverse a couple of states
; when it is rotating these are:
;       00      |  Counter
;       10      |  Clockwise
;       11      |     ^
;       01      V     |
;       00  Clockwise |
;
;
QUAD_STATE:
    BCF     STATUS,C        ; Force Carry to be zero
    MOVF    PORTB,W         ; Read the encoder
    ANDLW   0x03            ; And it with 0011
    MOVWF   Q_1             ; Store it
       IORWF   Q_1,W           ; Or in the current value
    MOVWF   QUAD_ACT        ; Store at as next action
    MOVF    Q_1,W           ; Get last time
    MOVWF   Q_NOW           ; And store it.
    ;
    ; Computed jump based on Quadrature pin state.
    ;
    MOVLW   high QUAD_STATE
    MOVWF   PCLATH
    MOVF    QUAD_ACT,W      ; Get button state
    ADDWF   PCL,F           ; Indirect jump
    RETURN                  ; 00 -> 00
    GOTO    DEC_COUNT       ; 00 -> 01 -1
    GOTO    INC_COUNT       ; 00 -> 10 +1
    RETURN                  ; 00 -> 11
    GOTO    INC_COUNT       ; 01 -> 00 +1
    RETURN                  ; 01 -> 01
    RETURN                  ; 01 -> 10
    GOTO    DEC_COUNT       ; 01 -> 11 -1
    GOTO    DEC_COUNT       ; 10 -> 00 -1
    RETURN                  ; 10 -> 01
    RETURN                  ; 10 -> 10
    GOTO    INC_COUNT       ; 10 -> 11 +1
    RETURN                  ; 11 -> 00
    GOTO    INC_COUNT       ; 11 -> 01 +1
    GOTO    DEC_COUNT       ; 11 -> 10 -1
    RETURN                  ; 11 -> 11
INC_COUNT:
    INCF    COUNT,F
    MOVLW   D'201'
    SUBWF   COUNT,W
    BTFSS   STATUS,Z
    RETURN
    DECF    COUNT,F
    RETURN
DEC_COUNT
    DECF    COUNT,F
    MOVLW   H'FF'
    SUBWF   COUNT,W
    BTFSS   STATUS,Z
    RETURN         
    INCF    COUNT,F
    RETURN   

    end
 

BobW

Active Member
Just saw this topic now. (I've been away from this forum for quite a while because of the annoying popup ads.) Since there've been no posts for 6 weeks, I'm wondering if the problem has been resolved.

As others have suggested, I think that the encoder routine will need to be interrupt driven for best operation, but it also depends on how much stuff is going on in the main program loop.

A few years ago, I wrote an encoder routine for a 4096 ppr encoder to read machine position for an industrial project. The algorithm is simple and fast on a low end 4MHz 16F630. More about it in this post:

More recently I've used it to adjust frequency on an AD9850/51 DDS frequency generator chip, though I used a much lower resolution encoder for this. Tuning is very smooth and it can handle a fast spin of the dial without any glitches.
 

augustinetez

Active Member
Hi Bob.

Testing of the code I ended up using has proven to do the job - forgot what I ended up using without looking it up - the mechanical aspect of the project means it can't be spun all that fast anyway.

But I will keep a note of the code in your post linked above to play with when I get my current project sorted.
Thanks.
 

Latest threads

New Articles From Microcontroller Tips

Top