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
 

rjenkinsgb

Well-Known Member
Most Helpful Member
How and when are you calling the encoder input routine?
If it's just polling in a normal program loop that has time-consuming operations, it will not work very well, as it cannot guarantee seeing every input change.

It needs to be in a very high frequency interrupt, 100KHz+, or eg. use two interrupt on change or input captures to call the routine when either input changes state.

From your other thread, you are only running a 32MHz PIC at 4MHz? If you increase the clock it should give you plenty of CPU time for fast interrupts to handle the encoder.

Or use a counter IC something like a 4516 to interface the encoder - connecting the two signals to clock and direction should give an up/down count once per quadrature cycle, and as long as it's less than eight cycles between reading the count, you should be able to track it and update the count in software; 64x less critical.

Or, use either a PIC with configurable logic cells, or a motor control series PIC, that has a hardware quadrature interface built in.
 

augustinetez

Active Member
I did try running it at 32 MHz, but with any of the routines I tried, it actually made the problem worse.

Basically program flow is -> program setup ->moves in to poll encoder routine ->stays there continually polling the encoder and another input until it moves -> leaves routine and does other stuff -> returns to polling routine.

There are two triggers in the polling routine and the first one to trigger executes the routine exit - the encoder is the second of the two triggers - neither of the triggers will happen at the same time - think coarse and fine frequency controls.

The hardware is already set, so no options to change anything on that front.
 

augustinetez

Active Member
This is the polling routine as it currently stands - anything to do with the 'push button' selection will be removed so not applicable to what I'm trying to get to.

Code:
poll_encoder
    bcf    flags,2        ; Clear encoder changed flag

    btfss    CAL_sw        ; Test if in calibrate mode
    BRA    read_encoder
;
; *******************************************************************************
; *    Code for pushbutton selection of step size                *
; *******************************************************************************
;
    btfsc    STEP_sw        ; Is Step switch pushed?

    BRA    read_rit    ;

    incf    step_size,f

    movfw   step_size    ; Keep step_size in range of 0 - 2
    xorlw   3
    btfsc   STATUS,Z
    clrf    step_size

step_exit
    btfss    STEP_sw
    BRA    step_exit    ; Wait for Step switch to be released
    call    step_led

read_rit
    call    rit        ; Test RIT, skip to encoder polling if no change
    btfsc    flags,1        ; flags,1 = 0 - RIT = same as last time
    return            ; flags,1 = 1 - RIT = changed
;
read_encoder
    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
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
LOL "Leon's code" has a shift << missing, the old value has to be shifted and or'd with the new to make a nibble..

first the jump value ( act) has to be cleared. then the new value applied, the the old is shifted twice and or'd into the jump value (act)

This must have been copied badly previously, as you say... Don't work...
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
I'd do it thus.. I need to test this, but a generalization ( I use C predominantly )
Code:
QUAD_STATE:
    ; sort old value
    MOVF    Q_OLD,W      ; Read the old jump vector
    ANDLW   0x03            ; And it with 0011 to lose the penultimate state
    MOVWF Q_OLD         ; ready to shift the previous state
    BCF     STATUS,C        ; Force Carry to be zero
    RLF     Q_OLD, F
    BCF     STATUS,C        ; Force Carry to be zero
    RLF     Q_OLD, F
    ;sort new value
    MOVF    PORTB,W        ; Read the encoder (newest state )
    ANDLW   0x03             ; And it with 0011
    IORWF   Q_OLD,F       ; Store it
    

   ; ready to jump 
    ;
    ; Computed jump based on Quadrature pin state.
 

rjenkinsgb

Well-Known Member
Most Helpful Member
The hardware is already set, so no options to change anything on that front.
OK, another minimal-CPU option:

Port B has interrupt on change.
Configure one input on rising edge only.

At each interrupt, grab the other input - then increment if that is high and decrement if that is low.

Done.

Zero the counter at the start of the existing program encoder input point, and look for changes to teh count as appropriate.
 

danadak

Active Member
Not an expert here but this onchip component maxes out at 33 Mhz

1664022193042.png


Here is the other stuff onchip, multiple copies in many cases -

1664022339290.png




Regards, Dana.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
You haven't said how fast the encoder turns.... I use a 64PPR (way over the top) as I count the slew of an excavator... Normally around 100+ teeth and I make a small 5/6 tooth gear to drive the encoder..

When quad encoders go wrong its normally the passive types that use "Schmitt triggers to straighten the signal, then they find the pulses start to deteriorate at high speeds so the quad doesn't exsist.

Like I said.. A small pic running at 32Hmz (8 TOSC) just seeing the state of the switches is ideal as the output is a clean clock with direction..

I have run my encoder on a lathe @ 3000 rpm (that's a lotto pulses). Then remember the output is 1 clock per change, so 50PPs x 64 PPR x 4 state changes = 12.8Khz. It was still solid.. Do you need to run a fast encoder?
 

Mike - K8LH

Well-Known Member
Ian, I think I may have tried something similar in the past. Did your table (array) look something like this, Sir?

Code:
enc_sub
        lsrf    _PORTA,W        ; new B in Carry
        rlf     encoder,F       ; ---BABAB
        lsrf    WREG,W          ; new A in Carry
        rlf     encoder,W       ; --BABABA
        andlw   b'00001111'     ; ----BABA
        movwf   encoder         ; update 'encoder'
        brw                     ;
        retlw   +0              ; 00.00 same
        retlw   -1              ; 00.01 dec
        retlw   +1              ; 00.10 inc
        retlw   +0              ; 00.11 skip
        retlw   +1              ; 01.00 inc
        retlw   +0              ; 01.01 same
        retlw   +0              ; 01.10 skip
        retlw   -1              ; 01.11 dec
        retlw   -1              ; 10.00 dec
        retlw   +0              ; 10.01 skip
        retlw   +0              ; 10.10 same
        retlw   +1              ; 10.11 inc
        retlw   +0              ; 11.00 skip
        retlw   +1              ; 11.01 inc
        retlw   -1              ; 11.10 dec
        retlw   +0              ; 11.11 same
 

Nigel Goodwin

Super Moderator
Most Helpful Member
You haven't said how fast the encoder turns.... I use a 64PPR (way over the top) as I count the slew of an excavator... Normally around 100+ teeth and I make a small 5/6 tooth gear to drive the encoder..
I helped a friend with a project MANY years ago - with a large encoder (bought from RS Components), which was simply connected to an RS Components counter module. It was fitted to a large stone circular saw at a quarry, on the track that carried the stone underneath the blade. I seem to recall I've still got one of the encoders 'somewhere', but I've not seen it recently - and it's about the size of a large grapefruit?.
 

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
Hi Mike... Nope.. Because the original I did was in Basic... I rewrote in C... But it was definitely based on that.
As I didnt use the jump table I just use if... You only need to cater for 1,7, 8 14 for dir 1 and 2, 4, 11 13 for the other. As it runs at mega speed... being high level isn't a problem.

Nigel Goodwin I use a tiny EM14 from RS but I know the one you mean... I have two of those hideously large encoders on the shelf... Black barrel with ally top and bottom
 

augustinetez

Active Member
You haven't said how fast the encoder turns....
Only turned by hand and it is geared down at 6:1, even so, it is still possible to turn it by hand fast enough to cause the problem - the knob has the finger indent that means you can crank it at a reasonable speed if you get over zealous.

The encoder being used is the 400 ppr version of this one and checked on the scope, the pulses are still good square waves when cranked up in speed (it was one of the first things I checked):-

encoder.JPG
 

augustinetez

Active Member
Yes, I'm using interrupts for something else but not ruling it out for the encoder as well, just prefer not to if possible at this stage.

The interrupt is only being used to save data at power down currently so I'm guessing that interrupt flag testing would make it possible to do both.
 

Latest threads

New Articles From Microcontroller Tips

Top