Continue to Site

# 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
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:
;
;
; A quadrature encoder traverse a couple of states
; when it is rotating these are:
;       00      |  Counter
;       10      |  Clockwise
;       11      |     ^
;       01      V     |
;       00  Clockwise |
;
;
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.
;
MOVWF   PCLATH
MOVF    QUAD_ACT,W      ; Get button state
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
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
;
; *******************************************************************************
; *    Code for pushbutton selection of step size                *
; *******************************************************************************
;
btfsc    STEP_sw        ; Is Step switch pushed?

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

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
;
movf    PORTB,w        ; Get the current encoder value
andlw    b'00000011'    ; mask encoder B and A switches
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
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...

#### augustinetez

##### Active Member
I found what I believe is the original of that code on Mcmanis' robotics site but haven't had a chance to sort the missing bits yet.

#### Ian Rogers

##### User Extraordinaire
Forum Supporter
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
ANDLW   0x03             ; And it with 0011
IORWF   Q_OLD,F       ; Store it

;
; Computed jump based on Quadrature pin state.

#### augustinetez

##### Active Member
Thanks Ian, I'll give that a go tomorrow.

#### rjenkinsgb

##### Well-Known 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.

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

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

Regards, Dana.

#### Ian Rogers

##### User Extraordinaire
Forum Supporter
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
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
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):-

#### augustinetez

##### Active Member
I'd do it thus.. I need to test this, but a generalization
Got to testing that code - it works but still getting the frequency change of direction problem if I give the encoder a good twist.

Having it tested in a setup that is fully geared to see if it still does it with the encoder geared down (saves me having to do some mechanical work).

#### Pommie

##### Well-Known Member
I'm guessing you're already using interrupts for something else.

Mike.

#### 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.

#### augustinetez

##### Active Member
The problem might come when I'm using the Timer2 flag to do the variable rate part - ie count number of pulses in a set time period eg 5mS.

#### Pommie

##### Well-Known Member
Which interrupt do you use to detect power down?

Mike.

Replies
4
Views
684
Replies
0
Views
2K
Replies
5
Views
564
Replies
37
Views
2K
Replies
3
Views
2K