decoding quadrature encoders - Page 2


+ Reply to Thread
Page 2 of 3 FirstFirst 123 LastLast
Results 41 to 80 of 94
  1. #41
    Join Date
    Aug 2005
    Posts
    4

    Default

    hello everyone!

    i'm dealing with a similiar problem this weekend.

    i believe that it might be better to solve some of
    the functions via hardware, using one D-type flip-flop
    and one AND logical gate.

    if you connect A to D input of FF and B to a
    raising edge clock, the output (Q) would
    indicate the direction of movement, it would
    be high when A goes high before B and low
    when B goes high before A. output of FF would
    be connected to B.3 (i want to use 16f628 with
    B.1 and B.2 as hardware UART).

    then i'd lead A and B through AND gate and connect
    its output to B.0/INT (interrupts enabled)

    counter would then be changed (incremented
    or decremented, according to B.3) whenever
    INT occours.

    ICE, can you please please send me complete
    working code in PBP?

  2. #42
    J_Brittian's Avatar
    J_Brittian Guest

    Default

    Luciano,
    Thank you for the clarity and detail with which you answered Ice's questions. I was able to get my own optical encoder running using your guidance and assembly language code.

  3. #43
    Join Date
    Oct 2004
    Location
    Italy
    Posts
    695

    Smile

    You're welcome!

    Luciano

  4. #44
    J_Brittian's Avatar
    J_Brittian Guest

    Default Variable Incrementation

    Is it possible to increment/decrement enc_counter by 10's or 100's once some threshold has been reached? My encoder is a front panel switch with pushbutton which is used to select and change setpoints, turning the switch 32000 times can't be good for its lifetime not to mention my hand.

  5. #45
    Join Date
    Oct 2004
    Location
    Italy
    Posts
    695

    Default

    Hi,

    I'm not sure I understand your problem.

    * * *

    Set the variable enc_counter to 30000.

    If the encoder does one step you will
    have either 29999 or 30001 in enc_counter. (CCW or CW).

    The interrupt assembly code only decrements or increments
    the 16 bit value stored in the variable enc_counter.

    Is up to your PicBasic code in the main program loop to do
    something with the value stored in the variable enc_counter.

    Best regards,

    Luciano
    Last edited by Luciano; - 29th October 2005 at 22:44.

  6. #46
    mytekcontrols's Avatar
    mytekcontrols Guest

    Default

    I don't know if this will help or not, but I made some changes to some earlier code I posted which was aimed at the 18F series. Besides there being a few flaws with the syntax of the rotate function in the original code, I just didn't like some aspects of it.

    So here is an example of what I am now using to check an encoder's direction and movement (within Interrupt Service Routine):
    Code:
    chk_encoder
    ;Read latest input from ENCODER & put the value in NEW.
        clrf    _newenc         ; clear "new" encoder bit pair storage,
        btfsc   _ENCA           ; and transfer state of encoder bits.
        bsf     _newenc,0
        btfsc   _ENCB
        bsf     _newenc,1
    
    ;Compare previous encoder inputs (OLD) with latest ones (NEW).
    
        movf _newenc,W		;Move the contents of NEW to TEMP and OLD to W
       movwf   _tmpenc      	;in order to compare them with XOR.
       movf    _oldenc,W     
       xorwf   _tmpenc,F    	;XOR previous inputs (in W) with latest inputs 
                             	;(in TEMP) to see if they are equal.
       btfsc   STATUS,Z        ;Test result and skip next line if zero flag clear.
    
        goto    restore      	;Result = zero.  Previous inputs equal latest 
                             	;inputs.  Rotary encoder did not move. Return
                             	;from interrupt.
    
    ;Result is a non-zero value.  Rotary encoder moved.  Determine direction.  
    
        bsf     _ENCDRnew       ; flag that encoder value has changed
        bcf	STATUS,C            ;Clear the carry bit in the status register and
       rlcf     _oldenc,F     	;left shift it into OLD to align bit 1 of OLD
                             	;with bit 0 of NEW.
       movf    _newenc,W     	;Move the contents of NEW to W in order to XOR.
       xorwf   _oldenc,F    	;XOR previous inputs (in OLD) with latest
                             	;inputs (in W) to determine CW or CCW.
       btfsc   _oldenc,1     	;Test bit 1 of result (in OLD).  Skip next line
                             	;if it is 0 (direction is CCW).
       goto    encoder_up      ;Bit is 1 (direction is CW).  Go around Down
                             	;and increment counter.
    encoder_down
        bcf     _direnc         ; clear direction bit = CCW (count down)
        goto    countdown
    
    encoder_up
        bsf     _direnc         ; set direction bit = CW (count up)
    ;drops into countup part of routine
    In this first section:
    Code:
    ;Read latest input from ENCODER & put the value in NEW.
        clrf    _newenc         ; clear "new" encoder bit pair storage,
        btfsc   _ENCA           ; and transfer state of encoder bits.
        bsf     _newenc,0
        btfsc   _ENCB
        bsf     _newenc,1
    We are using aliases for the actual encoder "A" and "B" inputs. By doing this , it makes your code more transportable to different devices and/or board configurations.

    These aliases would have been earlier equated as follows:
    Code:
    ENCA    VAR PORTC.4 ; any port, any bit
    ENCB    VAR PORTC.5 ; any port, any bit
    Also if your counter is going backwards from the desired direction, all you have to do is swap the previous assignments:
    Code:
    ENCB    VAR PORTC.4 ; any port, any bit
    ENCA    VAR PORTC.5 ; any port, any bit
    And here is the code that determines if the encoder has moved or not:
    Code:
    ;Compare previous encoder inputs (OLD) with latest ones (NEW).
    
        movf _newenc,W		;Move the contents of NEW to TEMP and OLD to W
       movwf   _tmpenc      	;in order to compare them with XOR.
       movf    _oldenc,W     
       xorwf   _tmpenc,F    	;XOR previous inputs (in W) with latest inputs 
                             	;(in TEMP) to see if they are equal.
       btfsc   STATUS,Z        ;Test result and skip next line if zero flag clear.
    
        goto    restore      	;Result = zero.  Previous inputs equal latest 
                             	;inputs.  Rotary encoder did not move. Return
                             	;from interrupt.
    Here is where we determine the encoder's direction of travel:
    Code:
    ;Result is a non-zero value.  Rotary encoder moved.  Determine direction.  
    
        bsf     _ENCDRnew       ; flag that encoder value has changed
        bcf	STATUS,C            ;Clear the carry bit in the status register and
       rlcf     _oldenc,F     	;left shift it into OLD to align bit 1 of OLD
                             	;with bit 0 of NEW.
       movf    _newenc,W     	;Move the contents of NEW to W in order to XOR.
       xorwf   _oldenc,F    	;XOR previous inputs (in OLD) with latest
                             	;inputs (in W) to determine CW or CCW.
       btfsc   _oldenc,1     	;Test bit 1 of result (in OLD).  Skip next line
                             	;if it is 0 (direction is CCW).
       goto    encoder_up      ;Bit is 1 (direction is CW).  Go around Down
                             	;and increment counter.
    Besides determining which counter routine to execute (countup or countdown), a direction bit (direnc) is also assigned that can be accessed by the main PBP program and used to determine, or display the last known direction of the encoder.
    Code:
    encoder_down
        bcf     _direnc         ; clear direction bit = CCW (count down)
        goto    countdown
    
    encoder_up
        bsf     _direnc         ; set direction bit = CW (count up)
    ;drops into countup part of routine
    Lastly, be sure to include this as part of your ISR "restore" code:
    Code:
    restore
       	movf 	_newenc,W       ; update encoder OLD to equal NEW
       	movwf   _oldenc
    In order to determine if an encoder change has occured within your PBP program just check and then clear the ENCDRnew bit.

    And here are the equates for some of the variables I am using:
    Code:
    newenc var byte
    oldenc var byte
    tmpenc var byte
    direnc var bit
    ENCDRnew var bit
    I hope this helps and doesn't just confuse the situation,
    Last edited by mytekcontrols; - 29th October 2005 at 16:19.

  7. #47
    Join Date
    Oct 2004
    Location
    Italy
    Posts
    695

    Default

    Quote Originally Posted by mytekcontrols
    I hope this helps and doesn't just confuse the situation,
    Michael utilizes a timer interrupt to sample the encoder.

    I utilize the interrupt-on-pin change for the A/B encoder signals.

    These are two different approaches.

    Luciano

  8. #48
    mytekcontrols's Avatar
    mytekcontrols Guest

    Wink Opps! I posted to the wroung forum

    Luciano is absolutely correct, and I apologize for the confusion I might have caused.

    The "encoder check" code I posted is indeed part of a timer interrupt sampled encoder situation. It was actually meant as a continuation for the following thread: http://www.picbasic.co.uk/forum/showthread.php?t=1886 which is a discussion that started out to solve a problem with using indirect addressing in an ASM IRQ. And later delved into the details of a timer based 6 digit rotary encoder interrupt routine.

    Sometimes in my rush to release information that's about to pop my head open, I forget where I am. Perhaps topic #46 of this forum can be moved to where it belongs - Lester?
    Last edited by mytekcontrols; - 30th October 2005 at 16:13.

  9. #49
    Join Date
    May 2007
    Posts
    65

    Thumbs up

    Quote Originally Posted by Luciano View Post
    File attachment = Incremental quadrature encoder direction drawing.

    Luciano
    Luciano: five stars!

  10. #50
    Join Date
    Apr 2011
    Location
    León
    Posts
    5

    Lightbulb Re: decoding quadrature encoders

    Quote Originally Posted by mat janssen View Post
    Hallo,
    I completed the programm for testing and it works ok.
    With the & ~ look in the manual it takes the not value of the bit.

    here is the complete source and also the hex file

    'PIC 16F628A test


    @ DEVICE PIC16F628A,INTRC_OSC
    @ DEVICE PIC16F628A,MCLR_OFF
    @ DEVICE PIC16F628A,BOD_OFF
    @ DEVICE PIC16F628A,LVP_OFF
    @ DEVICE PIC16F628A,CPD_OFF
    @ DEVICE PIC16F628A,PROTECT_OFF


    DEFINE OSC 4




    CMCON = 7
    VRCON = 0
    OPTION_REG.7 = 0

    TRISA = %11111111
    TRISB = %00000000


    A_INPUT VAR PORTA.0
    B_INPUT VAR PORTA.1

    HULP1 VAR BIT
    HULP2 VAR BIT

    COUNTER VAR WORD

    Clear

    COUNTER = 128

    START:


    HULP2 = A_INPUT & ~ HULP1 'EVERY POSITIVE GOWING EDGE OF A_INPUT
    HULP1 = A_INPUT 'GIVES A PULSE OF ONE PROGRAMM CYCLE

    IF HULP2 = 1 AND B_INPUT = 1 Then 'MOTOR TURNS RIGHT
    COUNTER = COUNTER + 1
    EndIF

    IF HULP2 = 1 AND B_INPUT = 0 Then 'MOTOR TURNS LEFT
    COUNTER = COUNTER - 1
    EndIF

    PORTB = COUNTER
    GoTo START
    I'm trying to do the example with PIC16f877a but i have problems for example the VRCON = 0 my compiler says it's a bad instruction how can i probe with this pic

    thanks

  11. #51
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,175

    Default Re: decoding quadrature encoders

    877A does not have VRCON register. Instead it has CVRCON.

    Look in the P16F877A.inc file in the C:\Program Files\Microchip\MPASM Suite path.

    Also pf of the chip you are using is a must.

    Ioannis

  12. #52
    Join Date
    Apr 2011
    Location
    León
    Posts
    5

    Question Re: decoding quadrature encoders

    Hi Ioannis exactly i'm using PIC16f877a i have the same problem that ice i cant see my count on my LCD and i cant see using pbp
    only add my count never sub my count do you know what's my problem?

    i tried all in my hands but i continue with same errors
    My code is:

    Code:
    '****************************************************************
    '*  Name    : UNTITLED.BAS                                      *
    '*  Author  : .                                                            *
    '*  Notice  : Copyright (c) 2011 [Ing..]  *
    '*          : All Rights Reserved                               *
    '*  Date    : 19/04/2011                                        *
    '*  Version : 1.0                                               *
    '*  Notes   :                                                   *
    '*          :                                                   *
    '****************************************************************
    'Parte de configuraciÛn de LCD
    DEFINE LCD_DREG PORTB
    DEFINE LCD_DBIT  4
    DEFINE LCD_RSREG PORTB
    DEFINE LCD_RSBIT 1
    DEFINE LCD_EREG  PORTB
    DEFINE LCD_EBIT  3
    DEFINE OSC 4
    
    contador var byte
    ON INTERRUPT GOTO SUMA
    INTCON.4 = 1
    INTCON.7 = 1
    OPTION_REG.6 = 1  
    CMCON=7
    CVRCON=0
    
    TRISB = %00000101
    CONTADOR = 12
    
    principal:
    lcdout $FE,1,"ENCODER= ",#CONTADOR," VALOR "
    pause 500
    goto principal
    DISABLE
    end
    
    SUMA:
    IF PORTB.0 = 1 THEN
        OPTION_REG.6 = 0
        IF PORTB.2 = 1 THEN
        contador = contador + 1
        ENDIF
    else
        OPTION_REG.6 = 1
        IF PORTB.2 = 1 THEN
        CONTADOR = CONTADOR - 1
        ENDIF
    ENDIF
    INTCON.1 = 0
    RESUME
    ENABLE

  13. #53
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,175

    Default Re: decoding quadrature encoders

    After interrupt you reset Option_Reg.6.

    And then never set it again. Is it that what you want?

    Ioannis

  14. #54
    Join Date
    Apr 2011
    Location
    León
    Posts
    5

    Question Re: decoding quadrature encoders

    Code:
    ...
    else
        OPTION_REG.6 = 1
        IF PORTB.2 = 1 THEN
        CONTADOR = CONTADOR - 1
        ENDIF
    ENDIF
    INTCON.1 = 0
     OPTION_REG.6 = 0 'Here???
    RESUME
    ENABLE
    My problem is the count never sub only add and add if the encoder turn left add and if the encoder turn right add too that's my problem


    I'm working with
    http://www.bourns.com/data/global/pdfs/pec11.pdf

  15. #55
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,175

    Default Re: decoding quadrature encoders


  16. #56
    Join Date
    Apr 2011
    Location
    León
    Posts
    5

    Thumbs up Re: decoding quadrature encoders

    Thank you so much Ioannis!!

  17. #57
    Join Date
    Oct 2005
    Posts
    34

    Default Re: decoding quadrature encoders

    Hi mat I see you are using ASM to set up your config word, it is a greate idea, but I get a compiler error when I do it. I always edit the device include file 16F628.INC:
    NOLIST
    ifdef PM_USED
    LIST
    include 'M16F62x.INC' ; PM header
    device pic16F628, intrc_osc, wdt_off, pwrt_off, mclr_off, lvp_off, protect_off
    XALL
    NOLIST

    You can't teach an old dog new tricks, but I'm always willing to try.

  18. #58
    Join Date
    Sep 2004
    Location
    montreal, canada
    Posts
    6,898

    Default Re: decoding quadrature encoders

    With PBP3 you no longer need to comment out the INC file.

    BTW you should'nt edit it each and every time. Comment out the DEVICE and CONFIG lines in the INC file, save it... and set the fuses on your code period.

    If you still experiment compiler error or warning, check out the syntax used.. is it PM or MPASM... PM need DEVICE line(s), while MPASM need __CONFIG line(s)

    Better get familiar with MPASM syntax though... PBP3 use it, PM is no longer supported.
    Steve

    It's not a bug, it's a random feature.
    There's no problem, only learning opportunities.

  19. #59
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hello,

    I know this is an old thread but I have reached the extent of my knowledge with PBP. I have been testing the code published by several forum members and the code works wonderful. Using the asm interrupts provides very fast response and no missed counts. The issue I have is trying to display the results on LCD in 4 digit result (xx.xx). I'm using the following hardware:

    16F876 @ 20MHz
    US digital S4T-360 (360 CPR)
    2x16 LCD connected to PortA

    I'm using the encoder to measure angles from a center position (12 o'clock). It can turn +/- 90 degrees. I need to display the results as +/- any value between 0 and 90 such as 46.15 or -58.23 (cw,ccw respectively).

    Since the count value is one byte, I can't figure out a way to get greater resolution for the count. I'm very new to programming period!!! I realize I have a lot to learn and I will continue to learn.
    I've tried using sdec enc_Count/10, enc_count//10 in the LCD lines but the results are not giving me what I need.

    Maybe I'm missing something really simple and I hope someone can point me in the right direction.

    Here is the code I'm using:


    Code:
    'Read Quadrature Encoder and display value on LCD. for pic16f876a
    
    'setup PIC Config Fuses
        @ Device pic16F876A, HS_OSC, BOD_OFF, PWRT_ON, WDT_OFF, PROTECT_OFF
    
    '************************ DEFINES HERE *************************************
    DEFINE OSC 20               ' set to 20mhz
    DEFINE LCD_DREG PORTA       ' Set Data Registers port
    DEFINE LCD_DBIT 0           ' Set starting data bit
    DEFINE LCD_RSREG PORTB      ' Set LCD RS Port
    DEFINE LCD_RSBIT 1          ' Set LCD RS Bit
    DEFINE LCD_EREG PORTB       ' Set LCD Enable Port
    DEFINE LCD_EBIT 2           ' Set LCD Enable Bit
    DEFINE LCD_BITS 4           ' Set LCD 4bit Mode
    DEFINE LCD_LINES 2          ' Set number of LCD Lines
    DEFINE LCD_COMMANDUS 2000   ' Set Command Delay time in uS
    DEFINE LCD_DATAUS 60        ' Set Data delay time in uS 
    
    clear                       'clear out variables
    
    '*********************** CHIP REGISTER SETUP *******************************
    
    ADCON0 = 7              ' turn off analog porta, set to digital IO
    ADCON1 = 7              '
    CMCON =  7              ' Turn off Port A Comparator
    TRISA = 0               ' output ports
    TRISB = %11000001       ' set input and output ports
    TRISC = 0               ' output ports
    
    '************************ PROGRAM VARIABLES HERE ***************************
    
    symbol led = portc.2        ' status led
    symbol led2 = portc.0       ' status led2
    symbol lcdbkl = portc.7     ' lcd pannel backlight
    symbol sw1 = portb.0        ' encoder switch
    
    enc_old VAR BYTE
    enc_new VAR BYTE
    enc_tmp var byte
    enc_counter VAR WORD
    enc_counter_old VAR WORD
    enc_scaler var word
    
    
    
    
    '*********************** ASSEMBLY INTERUPT VARIABLES ***********************
    wsave   var     byte $20 system
    wsave1  var     byte $a0 system  ' Necessary for devices with RAM in bank1
    wsave2  var     byte $120 system ' Necessary for devices with RAM in bank2
    wsave3  var     byte $1a0 system ' Necessary for devices with RAM in bank3
    ssave   var     byte bank0 system
    psave   var     byte bank0 system     
    
    goto start			'skip over interupt handler
    '*********************** ASSEMBLY INTERUPT HANDLER *************************
    
    define  INTHAND myint
    Asm
           
    myint 
    	; Save W, STATUS and PCLATH registers
    	; Not Necessary for Chips with >2k of Codespace
           ; movwf   wsave
           ; swapf   STATUS, W
           ; clrf    STATUS
           ; movwf   ssave
           ; movf    PCLATH, W
           ; movwf   psave  
             
    
    
    
    	;====== BEGINNING OF THE ROTARY ENCODER CODE ========
    	;The Rotary Encoder is connected to PORTB  
    	;The A signal of the encoder connected to the PIN portB.7
    	;The B signal of the encoder connected to the PIN portB.6
    	;
    	;The 4 variables used are declared in the PicBasic code.
    	;
    	;	enc_new VAR BYTE
    	;	enc_old VAR BYTE
    	;       enc_tmp VAR BYTE
    	;	enc_counter VAR WORD
    	;
    	;================================================
    		
    	   ;Read latest input from PORTB & put the value in _enc_new.
         	movf    PORTB,W
         	movwf  _enc_new
    
         	;Strip off all but the 2 MSBs in _enc_new.
         	movlw	0xc0    	     ;Create bit mask (bits 7 & 6). b'11000000' ?
         	andwf   _enc_new,F       ;Zero bits 5 thru 0.
    
            ;check to see if encoder has moved
            movf    _enc_old,W         ;move enc_old to W
            movwf   _enc_tmp           ;put W to enc_tmp
            movf    _enc_new,W         ;move enc_new to W for XOR
            xorwf   _enc_tmp,F         ;XOR enc_tmp to detect encoder movement
            btfsc   _enc_tmp,7         ;if bit is clear, encoder moved.
            goto    Continue           ;no movement exit isr
    
         	;Determine the direction of the Rotary encoder.  
         	rlf     _enc_old,F     	 ;left shift it into _enc_old to align bit 6 of 
                                     ;_enc_old with bit 7 of _enc_new.
    
         	movf    _enc_new,W     	 ;Move the contents of _enc_new to W in order to XOR.
         	xorwf   _enc_old,F    	 ;XOR previous inputs (in _enc_old) with latest
                                     ;inputs (in W) to determine CW or CCW.
     
          	btfsc   _enc_old,7     	 ;Test bit 7 of result (in _enc_old).  Skip next line
          	                         ;if it is 0 (direction is CCW).
         	goto    Up               ;Bit is 1 (direction is CW).  Go around Down
                                     ;and increment counter.
    
    Down
         	;Decrements _enc_counter because the rotary encoder moved CCW.
    	    ;Decrements _enc_counter (16 bit value), sets Z on exit.
    	     	 
            decf    _enc_counter,F      ; Decrement low byte
            incfsz  _enc_counter,W      ; Check for underflow
            incf    _enc_counter+1,F    ; Update
            decf    _enc_counter+1,F    ; Fixup
            movf    _enc_counter,W
            iorwf   _enc_counter+1,W    ; Set Z bit
    
    		
    	    ;Add here code for the CCW LED if needed.
         	bsf     _led		    ;turn on led
         	
         	goto    Continue  	    ;Branch around UP.
    
    Up
            ;Increments _enc_counter because the rotary encoder moved CW.
            ;Increments _enc_counter (16 bit value), sets Z on exit.
    
            incfsz  _enc_counter,W      ; Add one to low byte
            decf    _enc_counter+1,F    ; No carry (negates next step)
            incf    _enc_counter+1,F    ; Add one to high byte
            movwf   _enc_counter        ; Store updated low byte back.
            iorwf   _enc_counter+1,W    ; Set Z flag
    
    		
    	   ;Add here code for the CW LED if needed.
    	   bsf     _led2		    ;turn on led
    	
    Continue     	
         	;Assign the latest encoder inputs (in _enc_new) to _enc_old.
         	movf 	_enc_new,W
         	movwf   _enc_old
    
            ; Restore saved registers
            movf    psave, W
            movwf   PCLATH
            swapf   ssave, W
            movwf   STATUS
            swapf   wsave, F
            swapf   wsave, W
            bcf     INTCON, RBIF		  ; Clear the Interupt Flag
            RETFIE                 	  	  ; Return from interrupt
    endasm
    
    '***************************************************************************
    '************************* PROGRAM STARTS HERE *****************************
    '***************************************************************************
    
    START:              ' Main Program starts here
    
    LCDOUT $FE,1		'Init The LCD
    pause 500		    'wait for LCD to start 
    
    lcdout $FE,1,"ENCODER"	    'display splash screen
    LCDOUT $FE,$C0," TEST "       
    high lcdbkl                 ' turn on backlight
    
    
    '************************** SET DEFAULT SETTINGS HERE **********************
    pause 1000		    ' just wait a bit to read splash screen
    enc_counter = 0	' set default encoder value
    enc_counter_old = 0	
    
    lcdout $FE,1,"ENCODER"  ' change display
    lcdout $fe,$C0, DEC5 enc_counter
    INTCON = %10001000      ' Enable PortB Change Interupts  
    
    
    '************************** TESTING ROUTINES HERE **************************
    test:
                if  enc_counter <> enc_counter_old then 'see if value has changed
                    enc_counter_old = enc_counter		'move new value to old
               
                 ' Here is were I struggle.....
                 '*********************************************************************************
                    lcdout $fe,$C0, SDEC2 enc_counter/10, ".", SDEC2 enc_counter//10     	'display enc_counter value
                 '*********************************************************************************
    
                    low led		                        'turn off CCW led
                    low led2	                        'turn off CW led
                endif
    goto test
    Thank you in advance,

    cbrun17

  20. #60
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,382

    Default Re: decoding quadrature encoders

    Hi,
    If I'd venture a guess it would be that with a 360CPR encoder and 4x decoding the counter variable will "tick" 4 counts per degree but your math assumes the resolution is 0.1 degrees.

    As a simple workaround try multiplying your count value by 2.5 before you enter your display routine.
    Code:
    ' Here is were I struggle.....
    enc_counter = enc_counter */ 640   ' Multiply by 2.5
    '*********************************************************************************
    lcdout $fe,$C0, SDEC2 enc_counter/10, ".", SDEC2 enc_counter//10     	'display enc_counter value
    '*********************************************************************************
    Obviously, the encoder only provides 0.25 degree resolution and no math can change that.

    If it doesn't work properly then strip out the formating and display the raw value (pre or post multiplying with 2.5) and tell us between what values it ranges.

    /Henrik.

  21. #61
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Henrik,

    Thank you so much for the quick response. I will give your suggestion a try this evening and report back with the results.

    Kind regards,

    Chris

  22. #62
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    I changed the multiplier as suggested but I received very erroneous results. Using just "lcdout $fe,$C0, SDEC enc_counter"

    The count results in 2,5,7,10,12,15,,,455. So 2 and 3 counts alternately.
    Then if I multiply enc_counter by 250, I get exactly + 180 degrees which I think confirms 2 "ticks" Also note that by altering the value of enc_counter, I loose my negative count.

    Counting enc_counter without multiplier results in ~ +/- 184.

    To clarify, the shaft has a 12:00 o'clock "zero" position. Turning in either direction from center results in ~ +/- 184. Counting the number of pulses on my Fluke counter indicates the same count.

    So, the way I understand this quadrature coding, it compares the A/B channels to determine the direction. So changing the post result would only be wiped away by any subsequent updates from the interrupt routine.

    I would be satisfied with .25 degree increments if I could get the LCD output correctly. Showing +/- 90 in .25 increments in the form of xx.xx.

    Thank you again for sharing your knowledge.

    Chris

  23. #63
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,382

    Default Re: decoding quadrature encoders

    So you're getting ~360 counts for 180 degrees movement. Then the interrupt code must be doing 2x decoding instead of 4x - provided the encoder is actually 360cpr.
    You loose the negative value because PBP doesn't handle the multiplication properly and you're of course better off copying the enc_counter variable to a second one for scaling and display purposes, sorry about all that.

    Try this (untested):

    Code:
    Degrees VAR WORD
    Sign VAR BIT
    
    Degrees = enc_counter   ' Degrees is now -180 to 180
    Sign = Degrees.15   ' Save sign
    Degrees = (ABS Degrees) * 5   ' Degrees is now 0 to 900
    If Sign THEN Degrees = -Degrees   ' Restore sign, value is now -900 to 900
    /Henrik.

  24. #64
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Henrik,

    No worries... I learn from mistakes as well. I appreciate all your help.

    I believe we're getting very close. I have the following code:


    if enc_counter <> enc_counter_old then 'see if value has changed
    enc_counter_old = enc_counter 'move new value to old
    Counter = enc_counter
    Sign = Counter.15 ' Save sign
    Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900

    If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900

    lcdout $FE,1, sdec Counter / 10, ".", sdec Counter //10
    lcdout $FE,$C0, sdec Counter


    First, the counter increments by 5.
    Also, in the first LCD statement, the count increments to +90 as expected. However, the negative value shows as 6553.1, 6552.6, etc...
    The second LCD out statement displays +/- 900 as you explained.

    Seems the absolute value gets lost when trying to display negative after formatting.

    Is there maybe a way I can parse the decimal values prior to display statement to maintain the value?

    Chris
    Last edited by cbrun17; - 7th February 2017 at 17:15.

  25. #65
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    I made some changes to the formatting and I successfully display the +/- values. Displays +/- xx.x. I updated the code to:

    if enc_counter <> enc_counter_old then 'see if value has changed
    enc_counter_old = enc_counter 'move new value to old
    Counter = enc_counter
    Sign = Counter.15 ' Save sign
    Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
    If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900

    If NOT sign then
    lcdout $FE,1, sdec Counter /10 , ".", sdec Counter // 10
    else
    lcdout $FE,1, "-", sdec -Counter /10 , ".", sdec -Counter // 10
    endif

    This takes care of displaying properly. Now is there a way I can bring the count down to .25 increments instead of .5?

    Thanks again.

    Chris

  26. #66
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,382

    Default Re: decoding quadrature encoders

    Hi,
    Great job! The problem with the display routine is, again, that PBP doesn't handle math with negative numbers properly so the result of the Counter / 10 operation gets messed up when Counter is negative.

    You can probably make that snippet a bit more efficient, somelike like this perhaps:
    Code:
    if enc_counter <> enc_counter_old then 'see if value has changed
    enc_counter_old = enc_counter 'move new value to old
    Counter = enc_counter 
    
    Sign = Counter.15 ' Save sign
    Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
    
    LCDOUT $FE, 1  ' Clear screen
    IF Sign THEN
      LCDOUT "-"    ' Print a minus sign if the value is negative
    ENDIF
    LCDOUT SDEC Counter / 10, ".", SDEC Counter // 10   ' now print the value
    I must admit that I can't really follow the ASM code for the interrupt but since it's using Interrupt on change I think it "should" do x4 decoding so you "should" get 1440 counts per revoultion on a 360CPR encoder. I don't know why that's not happening. Hopefully someone else is able to help with that part.

    /Henrik.

  27. #67
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Thank you Henrik.

    I couldn't have made it this far without you're help. The displayed number is perfect. As you said, the counts aren't making much sense. I'll continue to experiment with this to see if I can figure out how to get the correct resolution.

    Thanks again and kind regards....

    Chris

  28. #68
    Join Date
    May 2013
    Location
    australia
    Posts
    2,032

    Default Re: decoding quadrature encoders

    you have some isr issues
    1.
    ; Not Necessary for Chips with >2k of Codespace
    ; movwf wsave
    ; swapf STATUS, W
    ; clrf STATUS
    ; movwf ssave
    ; movf PCLATH, W
    ; movwf psave
    ; Not Necessary for Chips with >2k of Codespace what ?
    yet at the end of your isr pclath and status are restored
    all isrs need to save and restore mcu status

    2.
    the chip you are using has more than 1 code pages ,your isr is located in page 0 ,
    pclath should be set accordingly
    USUAL
    ISR BEGIN
    movwf wsave
    swapf STATUS, W
    clrf STATUS
    movwf ssave
    movf PCLATH, W
    movwf psave
    CLRF PCLATH
    YOUR CODE
    .............
    3.
    a RE stream moving clockwise goes phase 1 - 2 - 3 - 4 -1 -2 .......
    where phase 1=00
    2=10
    3=11
    4=01
    your code
    ;check to see if encoder has moved
    movf _enc_old,W ;move enc_old to W
    movwf _enc_tmp ;put W to enc_tmp
    movf _enc_new,W ;move enc_new to W for XOR
    xorwf _enc_tmp,F ;XOR enc_tmp to detect encoder movement
    btfsc _enc_tmp,7 ;if bit is clear, encoder moved.
    goto Continue ;no movement exit isr


    say enc_old=01 ,enc_new=00 ; ie clockwise move
    your xor would yield 0b01000000
    and bit 7 is clear so no move is recorded , wrong it did move .it may also cause the 0 point to creep as direction
    changes occur
    the test should be is the result 0 not is bit7 0

    4. bank sel ?
    your isr forces bank0 the chip has more than one bank available
    all vars used in isr need to be assigned to bank 0
    ie enc_old VAR BYTE bank0 .
    etc
    5. enc_counter a word a non atomic entity
    any non atomic entity that can be changed in an isr and is used outside the isr needs to be handled
    correctly to prevent glitches caused by rollover/under of the low byte.
    best way is to use a buffered copy or that entity
    eg

    enc_counter VAR WORD bank0
    buffered_enc_counter VAR WORD
    gosub get_enc_count


    get_enc_count:
    intcon.3=0 ; rbc_int off
    buffered_enc_counter=enc_counter
    intcon.3=1 ; rbc_int on
    return
    Last edited by richard; - 8th February 2017 at 03:44.
    Warning I'm not a teacher , prose like a gushing embroilment is not my style

  29. #69
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Richard,

    Thank you very much for the information. It's a little over my head at this point with regard to asm. I'm still very new to asm coding, but I'm going to try working through your suggestions and see what I can accomplish.

    Chris
    Last edited by cbrun17; - 8th February 2017 at 23:34.

  30. #70
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Richard,

    Here is what I've done so far. Since I'm just learning asm (and coding in general), I made the changes you suggested as I understand them (haven't tried to compile yet). The part I am still not too sure about is the reference to is testing bit7. Are you saying I should not be testing bit7 (btfsc _enc_old,7)? How do I reference this in the asm code?

    I've tried going through this step by step and it's starting to make a little sense. I guess until I've done this a few times, I'll have to ask some lame questions....

    Again, I really appreciate your input.

    Code:
    'Read Quadrature Encoder and display value on LCD. for ****pic16f876****
    
    '************************ DEFINES HERE *************************************
    DEFINE OSC 20               ' set to 20mhz
    DEFINE LCD_DREG PORTA       ' Set Data Registers port
    DEFINE LCD_DBIT 0           ' Set starting data bit
    DEFINE LCD_RSREG PORTB      ' Set LCD RS Port
    DEFINE LCD_RSBIT 1          ' Set LCD RS Bit
    DEFINE LCD_EREG PORTB       ' Set LCD Enable Port
    DEFINE LCD_EBIT 2           ' Set LCD Enable Bit
    DEFINE LCD_BITS 4           ' Set LCD 4bit Mode
    DEFINE LCD_LINES 2          ' Set number of LCD Lines
    DEFINE LCD_COMMANDUS 2000   ' Set Command Delay time in uS
    DEFINE LCD_DATAUS 60        ' Set Data delay time in uS 
    
    clear                       'clear out variables
    
    '*********************** CHIP REGISTER SETUP *******************************
    
    ADCON0 = 7              ' turn off analog porta, set to digital IO
    ADCON1 = 7              '
    CMCON =  7              ' Turn off Port A Comparator
    TRISA = 0               ' output ports
    TRISB = %11000001       ' set input and output ports
    TRISC = 0               ' output ports
    
    '************************ PROGRAM VARIABLES HERE ***************************
    
    symbol led = portc.2         ' status led
    symbol led2 = portc.0       ' status led2
    symbol lcdbkl = portc.7     ' lcd pannel backlight
    symbol sw1 = portb.0       ' encoder switch
    
    enc_old 	          VAR     BYTE
    enc_new 	          VAR     BYTE
    enc_tmp 	          VAR     BYTE
    enc_counter 	  	  VAR    WORD bank0
    enc_counter_old  	  VAR    WORD bank0
    enc_scaler 	  	  VAR    WORD bank0
    
    
    '*********************** ASSEMBLY INTERUPT VARIABLES ***********************
    wsave     VAR     byte $20 system
    wsave1    VAR     byte $a0 system  ' Necessary for devices with RAM in bank1
    wsave2    VAR     byte $120 system ' Necessary for devices with RAM in bank2
    wsave3    VAR     byte $1a0 system ' Necessary for devices with RAM in bank3
    ssave     VAR     byte bank0 system
    psave     VAR     byte bank0 system     
    
        goto start			'skip over interupt handler
    
    '*********************** ASSEMBLY INTERUPT HANDLER *************************
    
    define  INTHAND myint
    Asm
           
    myint 
    	; Save W, STATUS and PCLATH registers
    	; Not Necessary for Chips with >2k of Codespace
    	
    	; ****Changed from original isr initialization settings****
            ;movwf   wsave
            ;swapf   STATUS, W
            ;clrf    STATUS
            ;movwf   ssave
            ;movf    PCLATH, W
            ;movwf   psave  
             
    ; Changed from original isr initialization settings
    	movwf wsave
     	swapf STATUS, W
     	clrf STATUS
     	movwf ssave
     	movf PCLATH, W
     	movwf psave 
     	CLRF PCLATH
    
    
    	;====== BEGINNING OF THE ROTARY ENCODER CODE ========
    	;The Rotary Encoder is connected to PORTB  
    	;The A signal of the encoder connected to the PIN portB.7
    	;The B signal of the encoder connected to the PIN portB.6
    	;
    	;The 4 variables used are declared in the PicBasic code.
    	;
    	;	enc_new VAR BYTE
    	;	enc_old VAR BYTE
    	;       enc_tmp VAR BYTE
    	;	enc_counter VAR WORD
    	;
    	;================================================
    		
    	   ;Read latest input from PORTB & put the value in _enc_new.
         	movf    PORTB,W
         	movwf  _enc_new
    
         	;Strip off all but the 2 MSBs in _enc_new.
         	movlw	0xc0    	     ;Create bit mask (bits 7 & 6). b'11000000' ?
         	andwf   _enc_new,F       ;Zero bits 5 thru 0.
    
            ;check to see if encoder has moved
            movf    _enc_old,W         ;move enc_old to W
            movwf   _enc_tmp           ;put W to enc_tmp
            movf    _enc_new,W         ;move enc_new to W for XOR
            xorwf   _enc_tmp,F         ;XOR enc_tmp to detect encoder movement
            btfsc   _enc_tmp,7         ;if bit is clear, encoder moved.       ************Not sure what needs to happen here**************
            goto    Continue           ;no movement exit isr
    
         	;Determine the direction of the Rotary encoder.  
         	rlf     _enc_old,F     	 ;left shift it into _enc_old to align bit 6 of 
                                     ;_enc_old with bit 7 of _enc_new.
    
         	movf    _enc_new,W     	 ;Move the contents of _enc_new to W in order to XOR.
         	xorwf   _enc_old,F    	 ;XOR previous inputs (in _enc_old) with latest
                                     ;inputs (in W) to determine CW or CCW.
     
          	btfsc   _enc_old,7     	 ;Test bit 7 of result (in _enc_old).  Skip next line ************Not sure what needs to happen here**************
          	                         ;if it is 0 (direction is CCW).
         	goto    Up               ;Bit is 1 (direction is CW).  Go around Down
                                     ;and increment counter.
    
    Down
         	;Decrements _enc_counter because the rotary encoder moved CCW.
    	    ;Decrements _enc_counter (16 bit value), sets Z on exit.
    	     	 
            decf    _enc_counter,F      ; Decrement low byte
            incfsz  _enc_counter,W      ; Check for underflow
            incf    _enc_counter+1,F    ; Update
            decf    _enc_counter+1,F    ; Fixup
            movf    _enc_counter,W
            iorwf   _enc_counter+1,W    ; Set Z bit
    
    		
    	    ;Add here code for the CCW LED if needed.
         	bsf     _led		    ;turn on led
         	
         	goto    Continue  	    ;Branch around UP.
    
    Up
            ;Increments _enc_counter because the rotary encoder moved CW.
            ;Increments _enc_counter (16 bit value), sets Z on exit.
    
            incfsz  _enc_counter,W      ; Add one to low byte
            decf    _enc_counter+1,F    ; No carry (negates next step)
            incf    _enc_counter+1,F    ; Add one to high byte
            movwf   _enc_counter        ; Store updated low byte back.
            iorwf   _enc_counter+1,W    ; Set Z flag
    
    		
    	   ;Add here code for the CW LED if needed.
    	   bsf     _led2		    ;turn on led
    	
    Continue     	
         	;Assign the latest encoder inputs (in _enc_new) to _enc_old.
         	movf 	_enc_new,W
         	movwf   _enc_old
    
            ; Restore saved registers
            movf    psave, W
            movwf   PCLATH
            swapf   ssave, W
            movwf   STATUS
            swapf   wsave, F
            swapf   wsave, W
            bcf     INTCON, RBIF		  ; Clear the Interupt Flag
            RETFIE                 	  	  ; Return from interrupt
    endasm
    
    '***************************************************************************
    '************************* PROGRAM STARTS HERE *****************************
    '***************************************************************************
    
    START:              ' Main Program starts here
    
       LCDOUT $FE,1		'Init The LCD
          pause 500		    'wait for LCD to start 
    
       LCDOUT $FE,1,"ENCODER"	    'display splash screen
       LCDOUT $FE,$C0," TEST "       
         high lcdbkl                 ' turn on backlight
    
    
    '************************** SET DEFAULT SETTINGS HERE **********************
    pause 1000		    ' just wait a bit to read splash screen
    enc_counter = 0	' set default encoder value
    enc_counter_old = 0	
    
            ' Initial display
        LCDOUT $FE,1,"ENCODER"  ' Initial display
        LCDOUT $FE,$C0, "00.00"
    
    INTCON = %10001000      ' Enable PortB Change Interupts  
    
    
    '************************** TESTING ROUTINES HERE **************************
    
    test:
    	  
                if buffered_enc_counter <> enc_counter_old then 'see if value has changed
                   	'enc_counter_old = enc_counter 'move new value to old ******Removed this line and replaced with*******
                     
                    gosub get_enc_count                                  '******this line******************************
    
     		 Counter = buffered_enc_counter
     
     		Sign = Counter.15 ' Save sign
     		Counter = ((ABS Counter) * 5) ' Degrees is now 0 to 900
     	If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
    
     	   If NOT sign then
     		LCDOUT $FE,1, sdec Counter /10 , ".", sdec Counter // 10 
     	   else
     		LCDOUT $FE,1, "-", sdec -Counter /10 , ".", sdec -Counter // 10
     	   endif
    
    '*********************************************************************************
    
                    low led		                        'turn off CCW led
                    low led2	                        'turn off CW led
                endif
    goto test
    
    get_enc_count:
     intcon.3=0 ; rbc_int off
     buffered_enc_counter=enc_counter
     intcon.3=1 ; rbc_int on
     return
    Last edited by cbrun17; - 9th February 2017 at 19:08.

  31. #71
    Join Date
    May 2013
    Location
    australia
    Posts
    2,032

    Default Re: decoding quadrature encoders

    ;check to see if encoder has moved
    movf _enc_old,W ;move enc_old to W
    movwf _enc_tmp ;put W to enc_tmp
    movf _enc_new,W ;move enc_new to W for XOR
    xorwf _enc_tmp,F ;XOR enc_tmp to detect encoder movement
    btfsc _enc_tmp,7 ;if bit is clear, encoder moved. ************Not sure what needs to happen here**************
    goto Continue ;no movement exit isr
    this should correct the quadrature decoding ,the big problem with this technique for quadrature decoding is that its is completely intolerant to noise.
    given that you encoder is optical I would expect noise problems would be minimal


    Code:
            movf    _enc_old,W         ;move enc_old to W
            movwf   _enc_tmp           ;put W to enc_tmp
            movf    _enc_new,W         ;move enc_new to W for XOR
            xorwf   _enc_tmp,F         ;XOR enc_tmp to detect encoder movement
            btfsc   STATUS,Z         ;if result is not zero, encoder moved.      
            goto    Continue           ;no movement exit isr
    Warning I'm not a teacher , prose like a gushing embroilment is not my style

  32. #72
    Join Date
    Mar 2003
    Location
    Commerce Michigan USA
    Posts
    1,166

    Default Re: decoding quadrature encoders

    Hey guy's, Why not just use 2 external interrupt lines (int0 & int1) in high priority mode, and just increment or decrement a counter? I have used this approch for years on my mill and I haven't lost a count yet. I have attached a URL for the front end circuit I used. I have NO problem a tracking 1000 Rpm, 720 CPR encoder using LONG's.
    Here is the URL: http://electronicdesign.com/analog/s...ation-encoders

    I eliminated the caps and just use 10k resistors. Read the artical as it is informative.
    Dave Purola,
    N8NTA
    EN82fn

  33. #73
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,382

    Default Re: decoding quadrature encoders

    Because INT0 and INT1 trips on EITHER the rising OR falling edge which means that you either get x1 decoding or that you need to reconfigure the interrupt edge every interrupt. The IOC feature handles that automatically since it interrupts on change - any change.

    The circuit in your link provides up/down count but still only x1 decoding, see the pink trace only pulses on the rising edge of the yellow trace, same thing for the green trace. It's effectively using only 25% of the encoders available resolution. A 720CPR encoder, when "properly" read will give you 2880 "pulses" per revolution.

    If that's enough resolution it's perfectly fine but in that case I probably wouldn't use interrupts to count the pulse. I'd have hardware timer/counters in the PIC count the pulses and periodically update the position register.

    /Henrik.

  34. #74
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Thank you Richard. I will test this and let you know the results.

    Also, though the encoder is optical, I am going through a 74C14 hex Schmitt trigger with rc filtering to remove any stray noise. all cabling from the encoder is shielded. Monitoring the output results in clean square wave with fast rise and fall times.

    Chris

  35. #75
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Dave,

    I did see this article, however, as Henrik mentioned, the circuit described does not interpret quadrature encoding (grey code) to gain accurate angle measurement. I also looked at another solution using an LS7183 which does interpret the output to two separate clock signals (see datasheet here: http://www.lsicsi.com/pdfs/Data_Shee...083_LS7084.pdf), this looks promising if I can't successfully make my code work properly.

    I truly appreciate your suggestion.

    Chris

  36. #76
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Richard,

    The asm change you suggested worked like a charm. I now get the correct counts!

    Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
    By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.

    I can't express how grateful I am for all the help from both you and Henrik.

    Here is the working code. I hope others will find this useful.

    Code:
    'Read Quadrature Encoder and display value on LCD in .25 increments. for pic16f876
    
     '************************ DEFINES HERE *************************************
     DEFINE OSC 20 ' set to 20mhz
     DEFINE LCD_DREG PORTA ' Set Data Registers port
     DEFINE LCD_DBIT 0 ' Set starting data bit
     DEFINE LCD_RSREG PORTB ' Set LCD RS Port
     DEFINE LCD_RSBIT 1 ' Set LCD RS Bit
     DEFINE LCD_EREG PORTB ' Set LCD Enable Port
     DEFINE LCD_EBIT 2 ' Set LCD Enable Bit
     DEFINE LCD_BITS 4 ' Set LCD 4bit Mode
     DEFINE LCD_LINES 2 ' Set number of LCD Lines
     DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
     DEFINE LCD_DATAUS 60 ' Set Data delay time in uS 
    
     clear 'clear out variables
    
     '*********************** CHIP REGISTER SETUP *******************************
    
     ADCON0 = 7 ' turn off analog porta, set to digital IO
     ADCON1 = 7 '
     CCP1CON = 0
     CCP2CON = 0
     
     TRISA = %00000000 ' output ports
     TRISB = %11000000 ' set input and output ports
     TRISC = %00100000 ' output ports
      
     portB.4 = 0
     portB.5 = 0
     
     '************************ PROGRAM VARIABLES HERE ***************************
    
     symbol   ledUp     = portC.2 ' status led
     symbol   ledDown = portC.3 ' status led2
     symbol   lcdbkl     = portC.4 ' lcd pannel backlight
     symbol   sw1        = portC.5 ' encoder switch
     symbol   ledTest   = portC.6
     
    
     enc_old                VAR BYTE
     enc_new              VAR BYTE
     enc_tmp              VAR byte
     enc_counter         VAR word bank0
     enc_counter_old   VAR word bank0
     enc_scaler            VAR word
     enc_dir                VAR enc_old.bit7
     Counter               VAR word bank0
     enc_SIGN            VAR counter.bit15
     Sign                    VAR BIT
    
    
     '*********************** ASSEMBLY INTERUPT VARIABLES ***********************
     wsave     VAR  byte $20 system
     wsave1   VAR  byte $a0 system ' Necessary for devices with RAM in bank1
     wsave2   VAR  byte $120 system ' Necessary for devices with RAM in bank2
     wsave3   VAR  byte $1a0 system ' Necessary for devices with RAM in bank3
     ssave     VAR  byte bank0 system
     psave     VAR  byte bank0 system 
    
     goto start 'skip over interupt handler
     '*********************** ASSEMBLY INTERUPT HANDLER *************************
    
     define INTHAND myint
     Asm
    
    myint 
     ; Save W, STATUS and PCLATH registers
     ;movwf wsave
     ;swapf STATUS, W
     ;clrf STATUS
     ;movwf ssave
     ;movf PCLATH, W
     ;movwf psave 
     ;CLRF PCLATH
    
    
     ;====== BEGINNING OF THE ROTARY ENCODER CODE ========
     ;The Rotary Encoder is connected to PORTB 
     ;The A signal of the encoder connected to the PIN portB.7
     ;The B signal of the encoder connected to the PIN portB.6
     ;
     ;The 4 variables used are declared in the PicBasic code.
     ;
     ; enc_new VAR BYTE
     ; enc_old VAR BYTE
     ; enc_tmp VAR BYTE
     ; enc_counter VAR WORD
     ;
     ;================================================
    
     ;Read latest input from PORTB & put the value in _enc_new.
     movf PORTB,W
     movwf _enc_new
    
     ;Strip off all but the 2 MSBs in _enc_new.
     movlw 0xc0 ;Create bit mask (bits 7 & 6). b'11000000' ?
     andwf _enc_new,F ;Zero bits 5 thru 0.
    
        ;Check to see if encoder has moved
            movf    _enc_old,W         ;move enc_old to W
            movwf   _enc_tmp           ;put W to enc_tmp
            movf    _enc_new,W         ;move enc_new to W for XOR
            xorwf   _enc_tmp,F         ;XOR enc_tmp to detect encoder movement
            btfsc   STATUS,Z           ;if result is not zero, encoder moved.      
            goto    Continue           ;no movement exit isr
    
     ;Determine the direction of the Rotary encoder. 
     rlf _enc_old,F ;left shift it into _enc_old to align bit 6 of _enc_old with bit 7 of _enc_new.
    
     movf _enc_new,W ;Move the contents of _enc_new to W in order to XOR.
     xorwf _enc_old,F ;XOR previous inputs (in _enc_old) with latest
     ;inputs (in W) to determine CW or CCW.
    
     btfsc _enc_old,7 ;Test bit 7 of result (in _enc_old). Skip next line
     ;if it is 0 (direction is CCW).
     goto Up ;Bit is 1 (direction is CW). Go around Down and increment counter.
    
    Down
     ;Decrements _enc_counter because the rotary encoder moved CCW.
     ;Decrements _enc_counter (16 bit value), sets Z on exit.
    
     decf _enc_counter,F ; Decrement low byte
     incfsz _enc_counter,W ; Check for underflow
     incf _enc_counter+1,F ; Update
     decf _enc_counter+1,F ; Fixup
     movf _enc_counter,W
     iorwf _enc_counter+1,W ; Set Z bit
    
    
     ;Add here code for the CCW LED if needed.
     bsf _ledDown ;turn on led
    
     goto Continue ;Branch around UP.
    
    Up
     ;Increments _enc_counter because the rotary encoder moved CW.
     ;Increments _enc_counter (16 bit value), sets Z on exit.
    
     incfsz _enc_counter,W ; Add one to low byte
     decf _enc_counter+1,F ; No carry (negates next step)
     incf _enc_counter+1,F ; Add one to high byte
     movwf _enc_counter ; Store updated low byte back.
     iorwf _enc_counter+1,W ; Set Z flag
    
    
     ;Add here code for the CW LED if needed.
     bsf _ledUp ;turn on led
    
    Continue 
     ;Assign the latest encoder inputs (in _enc_new) to _enc_old.
     movf _enc_new,W
     movwf _enc_old
    
     ; Restore saved registers
     movf  psave, W
     movwf PCLATH
     swapf ssave, W
     movwf STATUS
     swapf wsave, F
     swapf wsave, W
     bcf INTCON, RBIF ; Clear the Interupt Flag
     RETFIE ; Return from interrupt
     endasm
    
     '************************************************* **************************
     '************************* PROGRAM STARTS HERE *****************************
     '************************************************* **************************
    
     START: ' Main Program starts here
     INTCON = %10001000 ' Enable PortB Change Interupts 
     LCDOUT $FE,1 'Init The LCD
     pause 500 'wait for LCD to start 
    
     lcdout $FE,$80,  "TEST ANGLE DISP " 'display splash screen
     LCDOUT $FE,$C0,  "Initializing... " 
     high lcdbkl ' turn on backlight
    
    
     '************************** SET DEFAULT SETTINGS HERE **********************
     pause 2000 ' just wait a bit to read splash screen
     lcdout $FE,1
     enc_counter = 0 ' set default encoder value
     enc_counter_old = 0 
     Counter = 0
     
     
     lcdout $FE,$80, "Measurement   "
     lcdout $FE,$C0,"TST ANGLE: "
    
    
     '************************** TESTING ROUTINES HERE **************************
     test:
       If portC.5 = 0 then clearall
       
    if enc_counter <> enc_counter_old then 'see if value has changed
        enc_counter_old = enc_counter 'move new value to old
            Counter = enc_counter '(enc_counter */ 250)/2
            Sign = Counter.15   ' Save sign
            Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
            
    
    If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
    
    
    If not sign AND (-Counter > 0 or Counter < 0) then
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", "-",sdec Counter /100 , ".", sdec Counter // 100, " " 
    else
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", " ", sdec -Counter /100 , ".", sdec -Counter // 100, " "
    endif 
    
     
     low ledUp 'turn off CCW led
     low ledDown 'turn off CW led
     endif
     
     goto test
    
     ClearAll:
     
        enc_counter = 0
        enc_counter_old = 0 
        Counter = 0
        clear
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", " 0.00 "
            high ledtest
                pause 1000
            low ledtest
     goto test
     
    @INT_RETURN

  37. #77
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Richard,

    The asm change you suggested worked like a charm. I now get the correct counts!

    Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
    By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.

    I can't express how grateful I am for all the help from both you and Henrik.

    Here is the working code. I home others will find this useful.

    Code:
    'Read Quadrature Encoder and display value on LCD in .25 increments. for pic16f876
    
     '************************ DEFINES HERE *************************************
     DEFINE OSC 20 ' set to 20mhz
     DEFINE LCD_DREG PORTA ' Set Data Registers port
     DEFINE LCD_DBIT 0 ' Set starting data bit
     DEFINE LCD_RSREG PORTB ' Set LCD RS Port
     DEFINE LCD_RSBIT 1 ' Set LCD RS Bit
     DEFINE LCD_EREG PORTB ' Set LCD Enable Port
     DEFINE LCD_EBIT 2 ' Set LCD Enable Bit
     DEFINE LCD_BITS 4 ' Set LCD 4bit Mode
     DEFINE LCD_LINES 2 ' Set number of LCD Lines
     DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
     DEFINE LCD_DATAUS 60 ' Set Data delay time in uS 
    
     clear 'clear out variables
    
     '*********************** CHIP REGISTER SETUP *******************************
    
     ADCON0 = 7 ' turn off analog porta, set to digital IO
     ADCON1 = 7 '
     CCP1CON = 0
     CCP2CON = 0
     
     TRISA = %00000000 ' output ports
     TRISB = %11000000 ' set input and output ports
     TRISC = %00100000 ' output ports
      
     portB.4 = 0
     portB.5 = 0
     
     '************************ PROGRAM VARIABLES HERE ***************************
    
     symbol   ledUp     = portC.2 ' status led
     symbol   ledDown = portC.3 ' status led2
     symbol   lcdbkl     = portC.4 ' lcd pannel backlight
     symbol   sw1        = portC.5 ' encoder switch
     symbol   ledTest   = portC.6
     
    
     enc_old                VAR BYTE
     enc_new              VAR BYTE
     enc_tmp              VAR byte
     enc_counter         VAR word bank0
     enc_counter_old   VAR word bank0
     enc_scaler            VAR word
     enc_dir                VAR enc_old.bit7
     Counter               VAR word bank0
     enc_SIGN            VAR counter.bit15
     Sign                    VAR BIT
    
    
     '*********************** ASSEMBLY INTERUPT VARIABLES ***********************
     wsave     VAR  byte $20 system
     wsave1   VAR  byte $a0 system ' Necessary for devices with RAM in bank1
     wsave2   VAR  byte $120 system ' Necessary for devices with RAM in bank2
     wsave3   VAR  byte $1a0 system ' Necessary for devices with RAM in bank3
     ssave     VAR  byte bank0 system
     psave     VAR  byte bank0 system 
    
     goto start 'skip over interupt handler
     '*********************** ASSEMBLY INTERUPT HANDLER *************************
    
     define INTHAND myint
     Asm
    
    myint 
     ; Save W, STATUS and PCLATH registers
     ;movwf wsave
     ;swapf STATUS, W
     ;clrf STATUS
     ;movwf ssave
     ;movf PCLATH, W
     ;movwf psave 
     ;CLRF PCLATH
    
    
     ;====== BEGINNING OF THE ROTARY ENCODER CODE ========
     ;The Rotary Encoder is connected to PORTB 
     ;The A signal of the encoder connected to the PIN portB.7
     ;The B signal of the encoder connected to the PIN portB.6
     ;
     ;The 4 variables used are declared in the PicBasic code.
     ;
     ; enc_new VAR BYTE
     ; enc_old VAR BYTE
     ; enc_tmp VAR BYTE
     ; enc_counter VAR WORD
     ;
     ;================================================
    
     ;Read latest input from PORTB & put the value in _enc_new.
     movf PORTB,W
     movwf _enc_new
    
     ;Strip off all but the 2 MSBs in _enc_new.
     movlw 0xc0 ;Create bit mask (bits 7 & 6). b'11000000' ?
     andwf _enc_new,F ;Zero bits 5 thru 0.
    
        ;Check to see if encoder has moved
            movf    _enc_old,W         ;move enc_old to W
            movwf   _enc_tmp           ;put W to enc_tmp
            movf    _enc_new,W         ;move enc_new to W for XOR
            xorwf   _enc_tmp,F         ;XOR enc_tmp to detect encoder movement
            btfsc   STATUS,Z           ;if result is not zero, encoder moved.      
            goto    Continue           ;no movement exit isr
    
     ;Determine the direction of the Rotary encoder. 
     rlf _enc_old,F ;left shift it into _enc_old to align bit 6 of _enc_old with bit 7 of _enc_new.
    
     movf _enc_new,W ;Move the contents of _enc_new to W in order to XOR.
     xorwf _enc_old,F ;XOR previous inputs (in _enc_old) with latest
     ;inputs (in W) to determine CW or CCW.
    
     btfsc _enc_old,7 ;Test bit 7 of result (in _enc_old). Skip next line
     ;if it is 0 (direction is CCW).
     goto Up ;Bit is 1 (direction is CW). Go around Down and increment counter.
    
    Down
     ;Decrements _enc_counter because the rotary encoder moved CCW.
     ;Decrements _enc_counter (16 bit value), sets Z on exit.
    
     decf _enc_counter,F ; Decrement low byte
     incfsz _enc_counter,W ; Check for underflow
     incf _enc_counter+1,F ; Update
     decf _enc_counter+1,F ; Fixup
     movf _enc_counter,W
     iorwf _enc_counter+1,W ; Set Z bit
    
    
     ;Add here code for the CCW LED if needed.
     bsf _ledDown ;turn on led
    
     goto Continue ;Branch around UP.
    
    Up
     ;Increments _enc_counter because the rotary encoder moved CW.
     ;Increments _enc_counter (16 bit value), sets Z on exit.
    
     incfsz _enc_counter,W ; Add one to low byte
     decf _enc_counter+1,F ; No carry (negates next step)
     incf _enc_counter+1,F ; Add one to high byte
     movwf _enc_counter ; Store updated low byte back.
     iorwf _enc_counter+1,W ; Set Z flag
    
    
     ;Add here code for the CW LED if needed.
     bsf _ledUp ;turn on led
    
    Continue 
     ;Assign the latest encoder inputs (in _enc_new) to _enc_old.
     movf _enc_new,W
     movwf _enc_old
    
     ; Restore saved registers
     movf  psave, W
     movwf PCLATH
     swapf ssave, W
     movwf STATUS
     swapf wsave, F
     swapf wsave, W
     bcf INTCON, RBIF ; Clear the Interupt Flag
     RETFIE ; Return from interrupt
     endasm
    
     '************************************************* **************************
     '************************* PROGRAM STARTS HERE *****************************
     '************************************************* **************************
    
     START: ' Main Program starts here
     INTCON = %10001000 ' Enable PortB Change Interupts 
     LCDOUT $FE,1 'Init The LCD
     pause 500 'wait for LCD to start 
    
     lcdout $FE,$80,  "TEST ANGLE DISP " 'display splash screen
     LCDOUT $FE,$C0,  "Initializing... " 
     high lcdbkl ' turn on backlight
    
    
     '************************** SET DEFAULT SETTINGS HERE **********************
     pause 2000 ' just wait a bit to read splash screen
     lcdout $FE,1
     enc_counter = 0 ' set default encoder value
     enc_counter_old = 0 
     Counter = 0
     
     
     lcdout $FE,$80, "Measurement   "
     lcdout $FE,$C0,"TST ANGLE: "
    
    
     '************************** TESTING ROUTINES HERE **************************
     test:
       If portC.5 = 0 then clearall
       
    if enc_counter <> enc_counter_old then 'see if value has changed
        enc_counter_old = enc_counter 'move new value to old
            Counter = enc_counter '(enc_counter */ 250)/2
            Sign = Counter.15   ' Save sign
            Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
            
    
    If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
    
    
    If not sign AND (-Counter < 0 or Counter > 0) then
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", "-",sdec Counter /100 , ".", sdec Counter // 100, " " 
    else
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", " ", sdec -Counter /100 , ".", sdec -Counter // 100, " "
    endif 
    
     
     low ledUp 'turn off CCW led
     low ledDown 'turn off CW led
     endif
     
     goto test
    
     ClearAll:
     
        enc_counter = 0
        enc_counter_old = 0 
        Counter = 0
        clear
        lcdout $FE,$80, "Measurement   "
        lcdout $FE,$C0,"TST ANGLE:", " 0.00 "
            high ledtest
                pause 1000
            low ledtest
     goto test
     
    @INT_RETURN

  38. #78
    Join Date
    May 2013
    Location
    australia
    Posts
    2,032

    Default Re: decoding quadrature encoders

    Just had to make one other change that I hoped you could explain to me. Once I made the change to check the STATUS instead of _enc_tmp,7 bit, my counting was all backwards.
    By changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7", the issue was resolved. So basically the bit value for _enc_old,7 is set rather than clear on cw.
    Direction is a matter of interpretation.
    changing the direction test from "btfsc _enc_old,7" to "btfss _enc_old,7 OR
    changing the direction test from "btfsc _enc_old,6" to "btfsc _enc_old,6 OR
    swaping the "A","B" connections ,will all cause the "direction " to reverse


    I notice you are still incorrectly not saving the mcu context upon isr entry
    Warning I'm not a teacher , prose like a gushing embroilment is not my style

  39. #79
    cbrun17's Avatar
    cbrun17 Guest

    Default Re: decoding quadrature encoders

    Hi Richard,

    Not sure why my last post went in twice... Sorry.

    When I added the mcu context save routine the display crashed. It will ~ work if I move the shaft slowly, however, moving quickly caused the code to trip over itself and lockup the screen.
    My first guess was that when jumping out of the ISR, the next return in the main program uses the most recent return address on the stack and returns to the point the interrupt occurred, crashing the program.

    However, I noticed my up/down LED indicators are still operational which suggests the code is still working okay and its either the formatting or calculations that are causing some latency. if I perform a display reset, the counting resumes normally.

    So I made two changes which seem to have resolved the issue.

    I removed the two LCD defines:
    DEFINE LCD_COMMANDUS 2000 ' Set Command Delay time in uS
    DEFINE LCD_DATAUS 60 ' Set Data delay time in uS

    And added a 50ms pause after the display routine to give the display time to settle.

    I know there must be a more efficient way to format the display to reduce the number of instructions necessary to accomplish the xx.xx by .25 increment, but this seems to smooth out he results.

    Thanks again for you feedback.

    Chris
    Last edited by cbrun17; - 11th February 2017 at 18:37.

  40. #80
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,382

    Default Re: decoding quadrature encoders

    Hi Chris,
    In your test routine loop you're re-Writing alot of static text on the LCD and you're doing some "strange" things to get the correct formating. Here's an idea which probably is a bit more efficient:
    Code:
     ' Print static information on LCD once, then leave it alone.
     lcdout $FE,$80, "Measurement   "
     lcdout $FE,$C0,"TST ANGLE: "
    
    
     '************************** TESTING ROUTINES HERE **************************
     test:
       If portC.5 = 0 then clearall
       
    if enc_counter <> enc_counter_old then 'see if value has changed
      enc_counter_old = enc_counter  ' move new value to old
      Counter = enc_counter
      Sign = Counter.15              ' Save sign
      Counter = ((ABS Counter) * 25) ' Degrees is now 0 to 900
            
      ' Not doing this here, later (or not at all).
      'If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
    
      LCDOUT $FE, $CB   'Position cursor at 11th character on 2nd row.
      IF SIGN THEN
        LCDOUT "-"
      ELSE
        LCDOUT " "
      ENDIF
    
      LCDOUT DEC Counter / 100, ".", DEC Counter // 100, " " 
    
      ' To save execution cycles you're better off not using HIGH/LOW
      low ledUp 'turn off CCW led
      low ledDown 'turn off CW led
    
      ' If the only purpose of the Counter variable is to display the value 
      ' the there's no need to restore the sign of it so the following line
      ' could be deleted.
      If Sign THEN Counter = -Counter ' Restore sign, value is now -900 to 900
    
    endif
     
    goto test
    /Henrik.

Similar Threads

  1. Quick thoughts: DT Ints with encoders
    By kevlar129bp in forum mel PIC BASIC Pro
    Replies: 19
    Last Post: - 7th January 2010, 02:01
  2. PMDC SERVO MOTOR WITH quadrature encoder DRIVE ?
    By phoenix_1 in forum Schematics
    Replies: 37
    Last Post: - 22nd November 2009, 20:45
  3. 32-bit Quadrature Counter With Serial Interface
    By precision in forum mel PIC BASIC Pro
    Replies: 4
    Last Post: - 10th June 2008, 03:49
  4. quad encoders
    By cpayne in forum mel PIC BASIC Pro
    Replies: 0
    Last Post: - 13th March 2007, 18:49
  5. "momentary" IR decoding
    By sporker in forum mel PIC BASIC Pro
    Replies: 0
    Last Post: - 20th June 2005, 02:53

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts