News:

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

Main Menu

Positron16 - Quadrature Rotary Encoder using a PIC24F device.

Started by top204, Jul 11, 2026, 08:24 PM

Previous topic - Next topic

top204

There are many examples of interfacing to a Quadrature Rotary Encoder using an 8-bit device on the forum, but none using a PIC24 device, so after a post from one of our users, having issues with it, I decided to create some demos, and show how it can be accomplished on any PIC24 or dsPIC33 device.

The code listing below, shows how to interface to a Rotary Encoder on a PIC24FJ64GA002, using standard pin readings, and no interrupt peripherals:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' PIC24FJ64GA002 Rotary Encoder Interface, using a standard program loop mechanism.
'
' Written for the Positron16 compiler by Les Johnson.
' https://sites.google.com/view/rosetta-tech/positron-compilers-experimenters-notebook.
'
    Device = 24FJ64GA002                                    ' Tell the compiler what device to compile for
    Declare Xtal = 32                                       ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup USART1 for an Amicus16A board
'
    Declare Hserial1_Baud = 9600                            ' Set the Baud rate of USART1
    Declare HSerout1_Pin  = PORTB.4                         ' Set pin PORTB.4 for TX (PPS pin RP4)

$define Enc_Pin1 PORTB.7                                    ' Connects to a pin on a rotary encoder
$define Enc_Pin2 PORTB.8                                    ' Connects to the other pin on a rotary encoder
'
' Create global variables, aliases and constants here
'
    Dim dCounter  As SDword = 0                             ' Increments or decrements depending on the direction of the rotary encoder
    Dim bEncValue As SByte                                  ' Holds -1 if the encoder is moving anti-clockwise, 0 if not moving, 1 if moving clockwise

'-----------------------------------------------------------------------------------------------------
' The main program starts here
' Read a rotary encoder and increment or decrement variable 'dCounter' depending on the direction of the rotation.
' The value and the direction are transmitted to a serial terminal.
'
Main:
    Setup()                                                 ' Setup the program and any peripherals

    Do                                                      ' Create a loop
        bEncValue = Enc_Read()                              ' Read the rotary encoder
        If bEncValue <> 0 Then                              ' Is the value read from it 0?
            dCounter = dCounter + bEncValue                 ' No. So add the value to dCounter
            If bEncValue < 0 Then                           ' Was the value read -1?
                HRSOutLn "CCW: ", SDec dCounter             ' Yes. So display that the rotation was anti-clockwise
            Else                                            ' Otherwise... The value read was 1. So...
                HRSOutLn "CW:  ", SDec dCounter             ' Display that the rotation was clockwise
            EndIf
        EndIf
    Loop

'---------------------------------------------------
' Read a rotary encoder
' Input     : Enc_Pin1 and Enc_Pin2 hold the pins used for the rotary encoder
' Output    : A valid Clockwise move returns 1, and a valid Anti-Clockwise move returns -1. If not moving it returns 0
' Notes     : None
'
Proc Enc_Read(), SByte
Global Dim Enc_bValidValue As Byte Shared                   ' Holds the valid value for the encoder pins
Global Dim Enc_bStore      As Byte Shared                   ' Holds the stored value for the encoder pins
    Dim Enc_wTable         As Word = %0110100110010110      ' Holds the bit table for valid moves of the rotary encoder

    Result = 0                                              ' Default to a 0 return value
    Enc_bValidValue = Enc_bValidValue << 2                  ' \
    Enc_bValidValue.0 = Enc_Pin1                            ' |
    Enc_bValidValue.1 = Enc_Pin2                            ' | Find the position of the encoder's pins
    Enc_bValidValue = Enc_bValidValue & %00001111           ' /

    If GetBit(Enc_wTable, Enc_bValidValue) <> 0 Then        ' Is the move possibly valid?
        Enc_bStore = Enc_bStore << 4                        ' Yes. So shift up by 4-bits
        Enc_bStore = Enc_bStore | Enc_bValidValue           ' Or in the pin positions
        If Enc_bStore = %00101011 Then                      ' Is the encoder moving Anti-Clockwise?
            Result = -1                                     ' Yes. So return a value of -1
        ElseIf Enc_bStore = %00010111 Then                  ' Otherwise... Is the encoder moving Clockwise?
            Result = 1                                      ' Yes. So return a value of 1
        EndIf
    EndIf
EndProc

'-----------------------------------------------------------------------------------------------------
' Setup the pins and variables to read a rotary encoder
' Input     : None
' Output    : None
' Notes     : None
'
Proc Enc_Setup()
    PinInput Enc_Pin1                                       ' \ Make the encoder pins inputs
    PinInput Enc_Pin2                                       ' /
Global Dim Enc_bValidValue As Byte Shared = 0               ' Reset the valid value for the encoder pins
Global Dim Enc_bStore As Byte Shared = 0                    ' Reset the stored value for the encoder pins
EndProc

'-----------------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    CLKDIV = 0                                              ' \
    Write_OSCCONH(%00010000)                                ' / Enable the PLL to operate the device at 32MHz
    PPS_Unlock()
    PPS_Output(cOut_Pin_RP4, cOut_Fn_U1TX)                  ' Make Pin RB4 USART1 TX (PPS Pin RP4)
    Enc_Setup()                                             ' Setup for the rotary encoder
EndProc

'-----------------------------------------------------------------------------------------------------
' Set the config fuses for internal 8MHz oscillator with PLL on a PIC24FJ64GA002 device
' OSC pins are general purpose I/O
'
    Config Config1 = JTAGEN_OFF,_
                     GCP_OFF,_
                     BKBUG_OFF,_
                     COE_OFF,_
                     ICS_PGx1,_
                     FWDTEN_OFF,_
                     WINDIS_OFF,_
                     FWPSA_PR128,_
                     WDTPOST_PS256

    Config Config2 = IOL1WAY_OFF,_
                     COE_OFF,_
                     IESO_OFF,_
                     FNOSC_FRCPLL,_
                     FCKSM_CSECME,_
                     OSCIOFNC_ON,_
                     POSCMOD_NONE

Running the program, will display the texts "CW" or "CCW" for Clockwise or Counter-Clockwise on a serial terminal, and a value of a counter that is incremented or decremented, depending on the direction the encoder is being rotated.

The code listing below, performs the same task on a PIC24FJ64GA002, however, it uses an INT1 interrupt, so that a rotation of the encoder will be detected, even if the main program is busy.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' PIC24FJ64GA002 Rotary Encoder Interface, using an INT1 interrupt mechanism.
'
' Written for the Positron16 compiler by Les Johnson.
' https://sites.google.com/view/rosetta-tech/positron-compilers-experimenters-notebook.
'
    Device = 24FJ64GA002                                    ' Tell the compiler what device to compile for
    Declare Xtal = 32                                       ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup USART1 for an Amicus16A board
'
    Declare Hserial1_Baud = 9600                            ' Set the Baud rate of USART1
    Declare HSerout1_Pin  = PORTB.4                         ' Set pin PORTB.4 for TX (PPS pin RP4)
'
' Define the pins used for the rotary encoder
'
$define Enc_Pin1 PORTB.7                                   ' Connects to a pin on a rotary encoder
$define Enc_Pin2 PORTB.8                                   ' Connects to the other pin on a rotary encoder
'
' Create global variables, aliases and constants here
'
    Symbol cAntiClockwise = 0                               ' Signals that the rotation movement is anti-clockwise
    Symbol cClockwise = 1                                   ' Signals that the rotation movement is clockwise
    Dim Enc_dCounter    As SDword = 0                       ' Increments or decrements depending on the direction of the rotary encoder
    Dim Enc_tTriggered  As Bit = 0                          ' Holds the encoder triggered flag
    Dim Enc_tDir        As Bit                              ' Holds the direction of the rotary encoder

'-----------------------------------------------------------------------------------------------------
' Meta-macros for INT1 on a PIC24FJ64GA002 device
'
$define INT1_Flag           IFS1bits_INT1IF                 ' The interrupt flag for INT1
$define INT1_FlagClear()    INT1_Flag = 0                   ' Clear the INT1 flag
$define INT1_IntEnable()    IEC1bits_INT1IE = 1             ' Enable the interrupt for INT1
$define INT1_IntDisable()   IEC1bits_INT1IE = 0             ' Disable the interrupt for INT1
$define INT1_NegativeEdge() INTCON2bits_INT1EP = 1          ' Set the edge detect of INT1 to negative edge
$define INT1_PositiveEdge() INTCON2bits_INT1EP = 0          ' Set the edge detect of INT1 to positive edge

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

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

'-----------------------------------------------------------------------------------------------------
' Setup and enable the INT1 interrupt
' Input     : None
' Output    : None
' Notes     : The procedure also contains the INT1 interrupt handler, so it is wrapped
'
Proc INT1_Setup()
    Dim Enc_wTable    As Word = %0110100110010110           ' Holds the bit table for valid moves of the rotary encoder
    Dim Enc_bPinStore As Byte                               ' Stores the pin conditions when the INT1 interrupt triggers

    PinInput Enc_Pin1                                       ' \ Make sure the encoder pins are inputs
    PinInput Enc_Pin2                                       ' /
    PPS_Input(cIn_Pin_RP8, cIn_Fn_INT1)                     ' Set the PPS for INT1 pin as PORTB.8
    INT1_NegativeEdge()                                     ' Set the edge detect of INT0 to negative edge
    INT1_FlagClear()                                        ' Clear the INT1 flag
    INT1_IntEnable()                                        ' Enable the interrupt for INT1
'
' INT1 Interrupt handler
' Output    : Variable Enc_dCounter 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 travelling anti-clockwise, else holds 1 if travelling clockwise
' Notes     : None
'
ISR_Start INT1Interrupt
    Enc_tTriggered = 1                                      ' 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
        Inc Enc_dCounter                                    ' Increment the rotation counter
    Else                                                    ' Otherwise...
        Enc_tDir = cAntiClockwise                           ' Indicate that it is an anti-clockwise movement
        Dec Enc_dCounter                                    ' Decrement the rotation counter
    EndIf
    INT1_FlagClear()                                        ' Clear the INT1 flag
ISR_End
EndProc

'-----------------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    CLKDIV = 0                                              ' \
    Write_OSCCONH(%00010000)                                ' / Enable the PLL to operate the device at 32MHz
    PPS_Unlock()
    PPS_Output(cOut_Pin_RP4, cOut_Fn_U1TX)                  ' Make Pin RB4 USART1 TX (PPS Pin RP4)
    INT1_Setup()                                            ' Setup and enable the INT1 interrupt
    DelayMS 10                                              ' A small delay to allow things to stabilise
EndProc

'-----------------------------------------------------------------------------------------------------
' Set the config fuses for internal 8MHz oscillator with PLL on a PIC24FJ64GA002 device
' OSC pins are general purpose I/O
'
    Config Config1 = JTAGEN_OFF,_
                     GCP_OFF,_
                     BKBUG_OFF,_
                     COE_OFF,_
                     ICS_PGx1,_
                     FWDTEN_OFF,_
                     WINDIS_OFF,_
                     FWPSA_PR128,_
                     WDTPOST_PS256

    Config Config2 = IOL1WAY_OFF,_
                     COE_OFF,_
                     IESO_OFF,_
                     FNOSC_FRCPLL,_
                     FCKSM_CSECME,_
                     OSCIOFNC_ON,_
                     POSCMOD_NONE

In order to get the interrupt based program to work on other 16-bit devices, change the INT1 Meta-Macros, so that they suit the SFRs and Bit values on the device being used, which are available in the device's compiler ".def" file, and the details of them are available in the device's datasheet.

The code listings were tested in the proteus simulator, and on a real device sitting on an Amicus16A board.

A screenshot of the interrupt code running in the Proteus simulator is shown below, and the source files and proteus files are attached to this post, and are named: "Rotary_Encoder_PIC24.zip"

Rotary_Encoder_Screenshot.jpg

Regards
Les