Quad Encoder Problem


Closed Thread
Results 1 to 37 of 37

Hybrid View

  1. #1


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    This works. Probably a bit inefficient having the TMR1 interrupt firing @ 8Hz, but if only writes to EEPROM if the value has changed:

    Code:
     
    ' ***************************************************************
    ' Pin Connections
    ' ***************************************************************
    
    ' RA0/pin 13                       -> B pin of rotary encoder
    ' RA1/pin 12                       -> A pin of rotary encoder
    ' RA2/CCP3/pin 11                  -> Stbd motor PWM output (motor 1)
    ' RA3/MCLR/pin 4                   -> Button switch pin of rotary encoder
    ' RC0/pin 10                       -> Stbd motor direction signal (motor 1)
    ' RC1/CCP4/pin 9                   -> Port motor PWM output (motor 2)
    ' RC2/pin 8                        -> Port motor direction signal (motor 2)
    ' RC4/Tx/pin 6                     -> Serial LCD output
    ' RC5/Rx/pin 5                     -> Serial input
          
    DEFINE OSC 16               ; Set oscillator 16Mhz
    DEFINE HSER_TXSTA   20h     ; Set transmit status and control register
    DEFINE HSER_BAUD    2400    ; Set baud rate
    
    #CONFIG
       __config _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF
       __config _CONFIG2, _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF
    #ENDCONFIG
    
    ' ***************************************************************
    ' Initialization
    ' ***************************************************************
    
    OSCCON   = %01111000        ' 16MHz internal osc
    
    pause 100                   ' As advised by Darrel Taylor for EEPROM issue
    
    APFCON0.2 = 0               ; Tx on RC4 for LCD display
    APFCON0.7 = 0               ; Rx on RC5
    
    BAUDCON.4 = 1               ; Transmit inverted data to the Tx pin
    
    ANSELA    = 0               ; Digital only for all PortA pins
    ANSELC    = 0               ; Diginal only for all PortC pins
    
    TRISA     = %00001011       ' Make PORTA pins 0-1 input for rotary encoder,
                                ' pin 3 for rotary button
    TRISC     = 0               ' Make all PORTC pins output
    
    ' The INTEDG bit of the OPTION_REG register determines on which edge the 
    ' interrupt will occur. When the INTEDG bit is set, the rising edge will 
    ' cause the interrupt. When the INTEDG bit is clear, the falling edge will 
    ' cause the interrupt. 
    OPTION_REG.6 = 1             ' 1=Rising edge (default) or button "PRESS";
                                 ' 0=Falling edge or button "RELEASE"
    
    Old_Bits       VAR BYTE
    New_Bits       VAR BYTE
    RotEncDir      VAR BIT       ' 1=CW, 0=CCW
                                 ' Rot Enc pin A connected to PortA.1;
                                 ' Rot Enc pin B connected to PortA.0
    Old_RPM        VAR BYTE
    i              VAR BYTE
    
    ButtonPress    VAR PORTA.3   ' Alias PORTA.3 as "ButtonPress"
    
    TimeCnt     VAR Word      
    
    ISRCounter  VAR BYTE
    ValueDirty  VAR BIT
    
    ' ***************************************************************
    ' Includes
    ' ***************************************************************
    
    INCLUDE "DT_INTS-14.bas"    ' Base Interrupt System
    INCLUDE "ReEnterPBP.bas"    ' Include if using PBP interrupts
                                ' --> copy both files to PBP main folder 
                                ' (i.e. c:\pbp3)
                                                                      
    ' ***************************************************************
    ' ASM Interrupt Definitions
    ' ***************************************************************
                                                                      
    ASM
    INT_LIST  macro    ; IntSource,         Label,  Type, ResetFlag?
            INT_Handler   TMR1_INT,    ReloadTMR1,   ASM,  no       ; MUST be first
            INT_Handler   TMR1_INT,    _T1handler,   PBP,  yes
            INT_Handler    IOC_INT, _RotEncAdjust,   PBP,  yes
        endm
        INT_CREATE     ; Creates the interrupt processor
    ENDASM
    
    ;--- Change these to match the desired interrupt frequency -------------------
    ;--- See http://DarrelTaylor.com/DT_INTS-14/TimerTemplate.html for more Info.
    @Freq       = 8                   ; Frequency of Interrupts in Hz
    @Prescaler  = 8                   ; Timers Prescaler setting
    T1CON = $30                       ; $30 = Prescaler 1:8, TMR1 OFF
    ; $00=1:1, $10=1:2, $20=1:4, $30=1:8 --  Must match @Prescaler value
    
    ' ***************************************************************
    ' Set default values
    ' ***************************************************************
    
    Old_RPM = MotorRPM
    Old_Bits = PORTA & (%00000011)
    ISRCounter = 0
    
    
    ' Enable interrupts on RPM control now that motors are fully spun up
    INTCON   = %10001000        ' Global int enabled, IOCI enabled, 
                                ' INTF flag bit 0 clr, IOCI flag bit 0 clr
    IOCAP.0  = 1                ' Enable positive (rising edge) change
    IOCAP.1  = 1                ' Enable positive (rising edge) change
    IOCAN.0  = 1                ' Enable negative (falling edge) change
    IOCAN.1  = 1                ' Enable negative (falling edge) change
    IOCAF.0  = 0                ' Clear interupt-on-change flag
    IOCAF.1  = 0                ' Clear interupt-on-change flag
    
    @ INT_ENABLE   IOC_INT      ; Interrupt-on-Change interrupt
    @ INT_ENABLE  TMR1_INT      ; enable Timer 1 interrupts
    GOSUB StartTimer            ; Start Timer 1 ('MotorRPM' will be saved on every
                                ; interrupt if dirty
    
    Main:
        ' Check if motor RPM has changed
        IF MotorRPM <> Old_RPM Then
            Old_RPM = MotorRPM
            GOSUB ChngMotorHPWM
        EndIF   
       
        TimeCnt = 0
    	While ButtonPress = 0
    	    TimeCnt = TimeCnt + 1
    	    Pause 10
    	    If TimeCnt > 500 Then BtnAction
    	Wend
     
        BtnAction:
            If TimeCnt > 0 and TimeCnt < 200 Then
                PortEngDir = PortEngDir + 1
                If PortEngDir > 1 Then PortEngDir = 0
        
                WRITE EE_PortEngDir, PortEngDir
                #IFDEF USE_LCD_FOR_DEBUG
                    HSEROUT [LCD_INST, LCD_CLR]
                    pause 5
                    HSEROUT ["PortEngDir=", DEC PortEngDir, "  ", 13, 10] ' Send text followed by carriage return and linefeed
                #ENDIF
    
                GOSUB ReversePortEng
            ElseIf TimeCnt >= 200 Then
                WRITE EE_MotorRPM, MotorRPM_Default  ; restore Default value
            
                MotorRPM = MotorRPM_Default
            EndIf
     
        GOTO Main
    
    
    ' ***************************************************************
    ' [IOC - interrupt handler]
    ' ***************************************************************
    RotEncAdjust:
        New_Bits = PORTA & (%00000011)
    ;    IF (New_Bits & %00000011) = (Old_Bits & %00000011) Then DoneRotEnc
        IF New_Bits = Old_Bits Then DoneRotEnc
    
        ' Increment ISR counter (since quad encoder throws 4 interrupts
        ' for every single detent). Only want to update MotorRPM if this counter
        ' reaches 4 (i.e. a single detent)
        ISRCounter = ISRCounter + 1
        IF ISRCounter < 4 THEN DoneRotEnc
    
        RotEncDir = New_Bits.1 ^ Old_Bits.0
        IF RotEncDir = 1 Then
            ; CW rotation - increase speed but only to a max of 'MaxDuty'
            IF MotorRPM < MaxDuty then MotorRPM = MotorRPM + 1
        Else
            ' CCW rotation - decrease speed to a min of 0
            IF MotorRPM > 0 Then MotorRPM = MotorRPM - 1
        EndIF
    
    ;    GOSUB StartTimer            ; Start the TMR1 Timer to save MotorRPM
        ValueDirty = 1
    
        ISRCounter = 0
    ;    WRITE EE_MotorRPM, MotorRPM
    ;    pause 100
    
    DoneRotEnc:
        Old_Bits = New_Bits
    
        IOCAF.0 = 0      ' Clear interrupt flags
        IOCAF.1 = 0
    
    @ INT_RETURN
    
    ;____This routine is Called on each TMR1 Interrupt____________________________
    T1handler:
        IF ValueDirty = 1 THEN
           WRITE EE_MotorRPM, MotorRPM  
    ;    GOSUB StopTimer             ; Stop the Timer
       
           ValueDirty = 0
        EndIf
    
    @ INT_RETURN
    
    ;---[TMR1 reload - interrupt handler]-----------------------------------------
    ASM                               ; Calculate Timer Reload Constant
    ReloadInst  = 8                   ; # of Intructions used to reload timer
      if ((Prescaler == 1)||(Prescaler == 2)||(Prescaler == 4)||(Prescaler == 8))
    MaxCount    = 65536 + (ReloadInst / Prescaler)
    TimerReload = MaxCount - (OSC*1000000/4/Prescaler/Freq)
        if ((TimerReload < 0) || (TimerReload > (65535-ReloadInst)))
            error Invalid Timer Values - check "OSC", "Freq" and "Prescaler"
        endif
      else
          error Invalid Prescaler
      endif
    ENDASM
    
    @Timer1 = TMR1L                   ; map timer registers to a word variable
    Timer1       VAR WORD EXT
    TimerReload  CON EXT              ; Get the External Constant
    TMR1ON       VAR T1CON.0          ; Alias the Timers ON/OFF bit
    
    ;---Reload Timer1------
    ASM
    ReloadTMR1
        MOVE?CT  0, T1CON, TMR1ON     ;  1     stop timer
        MOVLW    LOW(TimerReload)     ;  1     Add TimerReload to the 
        ADDWF    TMR1L,F              ;  1     value in Timer1
        BTFSC    STATUS,C             ;  1/2
        INCF     TMR1H,F              ;  1
        MOVLW    HIGH(TimerReload)    ;  1
        ADDWF    TMR1H,F              ;  1
        MOVE?CT  1, T1CON, TMR1ON     ;  1     start timer
      INT_RETURN
    ENDASM
    
    ;---Start/Stop controls -----
    StartTimer:
        Timer1  = TimerReload         ; Load Timer
        TMR1ON = 1                    ; start timer
    RETURN
    
    StopTimer:
        TMR1ON = 0                    ; stop timer
    RETURN
    (When I turn off the PIC and turn it on again, it sometimes seems to speed up the motor to (MotorRPM - 1), i.e. 1 less than what was displayed on the LCD before turning off power.

  2. #2
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,621


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    I'll try this out. At first glance, though, the ISR is always setting TimeToSave to 5000 so I think it needs a check like:
    Well, yes, that was the whole idea. As long as the user keeps turning the knob (generating interrupts) the counter will get reset to 5000 (or whatever). But as soon as the user has NOT changed the RPM for the duration of 5000 iterations thru the Main loop the value is saved. When the knob isn't being turned no interrupts are being generated so the Main routine eventually gets the counter down to 0 and saves the value.

    /Henrik.

  3. #3


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    Oop. Yeah, of course that makes sense. Thanks.

  4. #4


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    I incorporated your idea, Henrik, and all is well. I could have sworn this was working earlier this year when I last touched it but perhaps if using the same chip and saving too often to EEPROM means it starts to take longer to do the WRITE and hence only then really missed out on the IOC's. I thought it also incremented MotorRPM by 1 but I can't prove that now. In the end, it works with using a ISR counter and only on the 4th one does it increment MotorRPM.

    I upped the TimeToSave value to 50000 - seems like a good compromise.

  5. #5


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    Should I be setting Old_Bits to New_Bits in the 'DoneRotEnc' label for the first 3 of 4 interrupts related to an IOC?

    Code:
    ' ***************************************************************
    ' [IOC - interrupt handler]
    ' ***************************************************************
    RotEncAdjust:
        New_Bits = PORTA & (%00000011)
    
        IF New_Bits = Old_Bits Then DoneRotEnc
    
        ' Increment ISR counter (since quad encoder throws 4 interrupts
        ' for every single detent), only want to update MotorRPM when counter
        ' reaches 4 (i.e. a single detent 'click')
        ISRCounter = ISRCounter + 1
        IF ISRCounter < 4 THEN DoneRotEnc
    
        RotEncDir = New_Bits.1 ^ Old_Bits.0
        IF RotEncDir = 1 Then
            ; CW rotation - increase speed but only to a max of 'MaxDuty'
            IF MotorRPM < MaxDuty then MotorRPM = MotorRPM + 1
        Else
            ' CCW rotation - decrease speed to a min of 0
            IF MotorRPM > 0 Then MotorRPM = MotorRPM - 1
        EndIF
    
        ValueDirty = 1                   ; Set dirty flag
        ISRCounter = 0                   ; Reset ISR entry counter
        TimeToSave = 50000               ; Set value to begin countdown to EEPROM save
    
    DoneRotEnc:
        Old_Bits = New_Bits
    
        IOCAF.0 = 0                     ' Clear interrupt flags
        IOCAF.1 = 0
    
    @ INT_RETURN

  6. #6
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,621


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    Hi,
    I'm afraid I don't quite understand. A quadrature signal consists of four states (00-10-11-01), from any one state there are TWO valid states which it can "transition" to, one in either direction. If you don't keep track of Old_Bits it doesn't know in which state it was and will get confused, count in the wrong direction etc - just as it did when you had the delay in there. Are you trying to keep the MotorRPM from incrementing by 4 for each click?

    Why don't you simply have an EncoderCount variable which is the one you save to EEPROM, and then you do MotorRPM = EncoderCount / 4. Also, instead of triggering on both the rising and falling edges you could trig on only the rising (or falling) which would give you two counts per click instead of four.

    /Henrik.

  7. #7


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    But if the interrupts are meant to fire on either a rising or falling edge, then going from '00' to '10' has only one edge (A rises from 0 to 1; B stays the same). The 00 to 10 state change represents one detent, right?

    I got this ISR code from another post (http://www.picbasic.co.uk/forum/show...5396#post25396) and it seemed to work back then (incrementing MotorRPM by 1)
    Last edited by RossWaddell; - 17th March 2013 at 14:23.

  8. #8
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    4,170


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    Are you sure this code does work on both directions?
    Quote Originally Posted by RossWaddell View Post
    Should I be setting Old_Bits to New_Bits in the 'DoneRotEnc' label for the first 3 of 4 interrupts related to an IOC?

    Code:
    ' ***************************************************************
    ' [IOC - interrupt handler]
    ' ***************************************************************
    RotEncAdjust:
        New_Bits = PORTA & (%00000011)
    
        IF New_Bits = Old_Bits Then DoneRotEnc
    
        ' Increment ISR counter (since quad encoder throws 4 interrupts
        ' for every single detent), only want to update MotorRPM when counter
        ' reaches 4 (i.e. a single detent 'click')
        ISRCounter = ISRCounter + 1
        IF ISRCounter < 4 THEN DoneRotEnc
    
        RotEncDir = New_Bits.1 ^ Old_Bits.0
        IF RotEncDir = 1 Then
            ; CW rotation - increase speed but only to a max of 'MaxDuty'
            IF MotorRPM < MaxDuty then MotorRPM = MotorRPM + 1
        Else
            ' CCW rotation - decrease speed to a min of 0
            IF MotorRPM > 0 Then MotorRPM = MotorRPM - 1
        EndIF
    
        ValueDirty = 1                   ; Set dirty flag
        ISRCounter = 0                   ; Reset ISR entry counter
        TimeToSave = 50000               ; Set value to begin countdown to EEPROM save
    
    DoneRotEnc:
        Old_Bits = New_Bits
    
        IOCAF.0 = 0                     ' Clear interrupt flags
        IOCAF.1 = 0
    
    @ INT_RETURN
    By the time that ISRCounter is 4, both outputs of the encoder will be at 0. So the RotEncDir will be 0 too and only the ELSE expression will be executed.

    Maybe I am missing something here?

    Ioannis

  9. #9


    Did you find this post helpful? Yes | No

    Default Re: Quad Encoder Problem

    I have to admit, Ioannis, the code was grabbed from an example here and implemented on my side using a Grayhill encoder which only triggered the interrupt once per detent. Works perfectly with that device, With the CUI ACZ11, I added the check on the ISRCounter but it **does** work - both CW & CCW rotations are captured correctly.

Similar Threads

  1. RF600E & RF600D encoder/decoder problem
    By Megahertz in forum Off Topic
    Replies: 0
    Last Post: - 17th April 2012, 17:18
  2. using Quad UART
    By harryweb in forum mel PIC BASIC Pro
    Replies: 9
    Last Post: - 30th April 2011, 00:13
  3. quad encoders
    By cpayne in forum mel PIC BASIC Pro
    Replies: 0
    Last Post: - 13th March 2007, 17:49
  4. Quad encoder problems
    By smitty505000 in forum mel PIC BASIC Pro
    Replies: 51
    Last Post: - 5th October 2006, 22:44
  5. Encoder problem
    By precision in forum mel PIC BASIC Pro
    Replies: 6
    Last Post: - 12th September 2006, 22:21

Members who have read this thread : 0

You do not have permission to view the list of names.

Posting Permissions

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