News:

Let's find out together what makes a PIC Tick!

Main Menu

Encoder Issue

Started by GDeSantis, Nov 06, 2022, 01:01 AM

Previous topic - Next topic

GDeSantis

I've been working with the PIC16F18446 and a program to interface to a Bourns rotary quadrature encoder (Part Number PEC11R-4125F-S10018).

The encoder is used to control the output frequency of an NCO peripheral and the program status is "under development."

When powered up, the default NCO output is a 200 Hz square wave.  As the encoder shaft is clicked, the following frequencies can be selected:  20, 50, 100, 200, 500, 1K, 2K, 5K, 10K and 20KHz.
 
The NCO can obviously operate at frequencies above 1MHz and below 20Hz, but 20Hz – 20KHz is all that's required at this time.

The encoder includes two 2nF snubber capacitors (Figure 1) and the Positron program includes short time delays to allow the encoder output to stabilize before its logic state is sampled.

As of this writing, the program powers up properly and NCO frequencies over the range of interest can be selected.  However, nothing happens immediately after power up when the encoder is moved one click.  After that, it works properly.

While I can live with this issue, I'm not sure how to resolve it.

HAL

Hello

That sounds like Murphy's Law at work.  I don't have a great deal of experience with encoders
but I have used the following configuration for the same encoder with a pic16F1703.

                                  +5
                                    |
                            ____|____
                            |              |
                          10k        10k
                            |              |
to PIC -----10k-----| encoder |-----10k----- to PIC
              |              |__/ __  \_|              |
              |                      |                      |
          .01uf                  |                  .01uf
            |                    GND                  |
          GND                                        GND                       
                       

towlerg

A while ago I used a cheap mechanical one that produced a stream on one pin and the opposite on another. One pins positive going edge generated a interrupt, after tiny debounce delay, if interrupt pin high the clockwise if low anticlockwise (or the other way round). Test that logic before you use it, it was a while ago.

towlerg

Too late to edit. Just realized the above is rubbish. The interrupt should be triggered by either edge (IOC) not just positive (which will obviously always yield true).

GDeSantis

The encoder outputs are connected to MCU port pins B.4 and B.5 with positive and negative Interrupt-On-Change enabled on each pin.
 
Prior to writing the program, a function generator was used to simulate the encoder output (red and blue traces of attached file) and the green trace reflects and IOC event. 

As far as I can tell, the IOC response works as expected

trastikata

#5
GDeSantis,

have you tried enabling the NCO after setting up all corresponding registers to 200Hz in your preset routine and not before?

NCO_PRESET:
    VAR1 = 3
    GoSub NCO_3
    NCO1CON  = %10000000        ;Enable NCO, Fixed Duty Cycle Mode
    Return

tumbleweed

I don't see where the interrupt is setup properly... the code is just polling for IOCBF.

I would get rid of these to start (or setup the ISR):
    INTCON.7 = 1                ;Enable Global Interrupts
    INTCON.6 = 1                ;Enable Peripheral Interrupts

    PIE0.4   = 1                ;Enable Interrupt on Change

trastikata

Quote from: tumbleweed on Nov 06, 2022, 10:09 PMI don't see where the interrupt is setup properly... the code is just polling for IOCBF.


Right after it is being setup:

    IOCBP.4  = 1                ;Enable PORTB.4 POSITIVE Trigger
    IOCBN.4  = 1                ;Enable PORTB.4 NEGATIVE Trigger
    IOCBP.5  = 1                ;Enable PORTB.5 POSITIVE Trigger
    IOCBN.5  = 1                ;Enable PORTB.5 NEGATIVE Trigger

GDeSantis

Trastikata
Thanks for your comments. I modified the program to bypass the NCO and pulse an LED with every encoder click – a 10 uSec pulse for a CW rotation and 20 uSec for a CCW rotation.

Immediately after power-up, the LED pulses fully agree with the encoder rotation from the first click onward.  On this basis, I now need evaluate options to fully enable the NCO peripheral. 

tumbleweed

None of this sets up an ISR for interrupt mode, so don't enable the GIE/interrupt bits... there's no vector.

GDeSantis

I agree.  The program is just polling for IOCBF and the GIE interrupt bits were removed.

Stephen Moss

#11
Quote from: GDeSantis on Nov 06, 2022, 01:01 AMHowever, nothing happens immediately after power up when the encoder is moved one click.  After that, it works properly.
Usually the IOC works by comparing the previous state of the pins with the current state, so the issue could be that on the first click, there is no previous state to compare with.
Therefore, if your code does not already do so after configuring PortB pins and enabling the interrupts try performing a read of PortB to get the reference value for the IOC to compare against and see if that helps to pick up the first click.

When the interrupt is triggered your code should jump to your ISR pointed to by your ON_Hardware_Interrupt command and you change the frequency and clear the interrupt flag in the ISR.

keytapper

I recall the fact that for certain MCU must read the PORTB in order to remove the IOC mismatch,  which is what triggers the IOC flag. For the OP MCU I don't have the datasheet to see if the case is different.
Ignorance comes with a cost

top204

I've always used an interrupt for standard rotary encoders, and the IOC (Interrupt On Change) mechanism is a nice and fast method of detecting the ticks, especially on the newer 8-bit devices. Below is a program listing for detecting a rotary encoder using IOC on a PIC18F27K42 device. It enables the internal pull-up resistors for the pins used, and an external 100nF capacitor on each pin to ground is all that is needed for debouncing the encoder movements.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Rotary encoder reader using an IOC (Interrupt On Change) interrupt
' It never misses a rotation because the IOC always gets fired when a rotation happens
'
' Written by Les Johnson for the Positron8 BASIC compiler
'
    Include "Positron.inc"                              ' Include the header for the Positron board (18F27K42 device operating at 64MHz)
    On_Hardware_Interrupt GoTo ISR_Handler

    Symbol Enc_Pin1 = PORTB.0                          ' Connects to a pin on the rotary encoder
    Symbol Enc_Pin2 = PORTB.1                          ' Connects to the other pin on the rotary encoder
'
' Create variables for the rotary encode routines
'
    Dim Enc_bPinStore   As Byte Access                  ' Stores the pin conditions when the IOC interrupt triggers
    Dim Enc_wCounter    As Word Access                  ' Incrments or decrements depending on the direction of the rotary encoder
    Dim Enc_tTriggered  As Bit                          ' Holds the IOC triggered flag
    Dim Enc_tDir        As Bit                          ' Holds the direction of the rotary encoder

    Symbol cAntiClockwise = 0                           ' Signals that the rotation movement is anti-clockwise
    Symbol cClockwise = 1                               ' Signals that the rotation movement is clockwise

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif

'------------------------------------------------------------------------------------------------
' General interrupt meta-macros
'
$define IntGlobal_Enable()  INTCON0bits_GIE = 1         ' Enable global interrupts
$define IntGlobal_Disable() INTCON0bits_GIE = 0         ' Disable global interrupts

$define IntPeriph_Enable()  INTCON0bits_GIEL = 1        ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCON0bits_GIEL = 0        ' Disable peripheral interrupts

'------------------------------------------------------------------------------------------------
' IOC (Interrupt On Change) meta-macros for a PIC18F27K42 device
'
$define IOC_Flag() PIR0bits_IOCIF                       ' The IOC flag
$define IOC_ClearFlag() IOC_Flag() = 0                  ' Clear the IOC flag
$define IOC_Enable()  PIE0bits_IOCIE = 1                ' Enable IOC Interrupts
$define IOC_Disable() PIE0bits_IOCIE = 0                ' Disable IOC Interrupts
'
' PORTB.0 IOC meta-macros for a PIC18F27K42 Device
'
$define IOC_RB0_Rising()  '
    IOCBN.0 = 0           '
    IOCBP.0 = 1
$define IOC_RB0_Falling() '
    IOCBN.0 = 1           '
    IOCBP.0 = 0
$define IOC_RB0_Flag() IOCBF.0
'
' PORTB.1 IOC meta-macros for a PIC18F27K42 Device
'
$define IOC_RB1_Rising()  '
    IOCBN.1 = 0           '
    IOCBP.1 = 1
$define IOC_RB1_Falling() '
    IOCBN.1 = 1           '
    IOCBP.1 = 0
$define IOC_RB1_Flag() IOCBF.1

'------------------------------------------------------------------------------------------------
' The main program starts here
' Displays on a serial terminal the counts created from a rotary encoder movement
'
Main:
    Setup()                                             ' Set up the program

    Do                                                  ' Create a loop
        If Enc_tTriggered = True Then                   ' Is a rotary encoder movement ready?
            Enc_tTriggered = False                      ' Yes. So reset its flag
            If Enc_tDir = cAntiClockwise Then           ' Is Enc_tDir holding 0?
                HRSOutLn "CCW: ", Dec Enc_wCounter      ' Yes. So display that the rotation was anti-clockwise
            Else                                        ' Otherwise... The value in Enc_tDir was 1. So...
                HRSOutLn "CW:  ", Dec Enc_wCounter      ' Display that the rotation was clockwise
            EndIf
        EndIf
        DelayMS 512                                     ' Place a, silly, very large delay to demonstate the rotation counter still working
    Loop                                                ' Do it forever

'------------------------------------------------------------------------------------------------
' Setup the program and peripherals
'
Proc Setup()
    PinMode(Enc_Pin1, Input_PullUp)                     ' \ Make sure the encoder pins are inputs
    PinMode(Enc_Pin2, Input_PullUp)                     ' / And enable the internal pullup resistors on them

    Enc_wCounter = 0                                    ' Clear the rotation counter
    Enc_tTriggered = False                              ' Reset the rotation flag

    IOC_RB0_Falling()                                   ' IOC Interrupt on a falling edge on pin PORTB.0
    IOC_RB0_Flag() = False                              ' Clear the IOC flag for PORTB.0
    IOC_ClearFlag()                                     ' Clear the IOC interrupt flag
    IOC_Enable()                                        ' Enable IOC interrupts

    IntGlobal_Enable()                                  ' Enable global interrupts
EndProc

'--------------------------------------------------------------------------
' Interrupt handler
' Input     : None
' Output    : Variable Enc_wCounter is incremented or decremented depending on the rotation direction
'           : Enc_tTriggered is 1 if there was a movement on the rotary encoder
'           : Enc_tDir holds 0 if the rotary encoder is traveling anti-clockwise, else holds 1 if travelling clockwise
' Notes     : Uses an IOC (Interrupt On Change) on PORTB.0
'
ISR_Handler:
    Context Save

    If IOC_Flag() = True Then                           ' Is it an Interrupt On Change that triggered the interrupt?
        If IOC_RB0_Flag() = True Then                   ' Yes. So is it an IOC on PORTB.0?
            Enc_tTriggered = True                       ' Yes. So set the rotation triggered flag
            Enc_bPinStore = %00000000                   ' Clear the variable Enc_bPinStore
            Enc_bPinStore.0 = Enc_Pin1                  ' Place the condition of pin Enc_Pin1 into bit-0 of Enc_bPinStore
            Enc_bPinStore.1 = Enc_Pin2                  ' Place the condition of pin Enc_Pin2 into bit-1 of Enc_bPinStore
            If Enc_bPinStore = %00000000 Then           ' Is Enc_bPinStore 0?
                Enc_tDir = cClockwise                   ' Yes. Indicate that it is a clockwise movement
                If Enc_wCounter < 65535 Then            ' \
                    Inc Enc_wCounter                    ' | Increment the rotation counter
                EndIf                                   ' /
            Else                                        ' Otherwise...
                Enc_tDir = cAntiClockwise               ' Indicate that it is an anti-clockwise movement
                If Enc_wCounter > 0 Then                ' \
                    Dec Enc_wCounter                    ' | Decrement the rotation counter
                EndIf                                   ' /
            EndIf
            IOC_RB0_Flag() = 0                          ' Clear the IOC flag for PORTB.0
        EndIf
        IOC_ClearFlag()                                 ' Clear IOC interrupt flag
    EndIf

    Context Restore


keytapper

I guess that the maximum frequency wouldn't be much more than few hundred Hertz.
Probably excluding high PPR.
Ignorance comes with a cost