Proton BASIC Compiler - Working with an Optical Rotary Encoder


  • PicŪ Basic


  • Working with an Optical Rotary Encoder

    Working with a Bournes optical rotary encoder
    Here's a simple interrupt driven routine to increment and decrement a counter using an Optical Rotary Encoder, the one used for this example is a Bournes 256 cycle per revolution unit. The code is not complex, but I have added a lot of symbol declares for explanation. Many can be removed, see comments. Also, I added lots of comments to the code to assist. Examples are provided for a volume type control that counts up and down to a predetermined value and stops at the limits until the direction is reversed. The other is for a clock over type of counter.

    Hope you find it useful. I am sure it can be used with many encoders.

    '************************************************* ************************************************
    '* Bournes Optical Encoder with PICŪ 18F452 or possibly ANY 18F PICŪ. *
    '* Uses Hardware Interrupt on PortB.1 with a Bournes 256 Cycle per rev encoder *
    '* Joe Hanley (Captainslarty at yahoo.co.uk) *
    '* Feb 2009 *
    '* WE ARE USING PORTB.1 for the interupt, and PORTB.3 for the other signal *
    '* THIS FREES UP THE OTHER INTERRUPT PINS FOR OTHER USES.. *
    '************************************************* ************************************************


    Device 18F452
    XTAL 20

    Include "proton18_g20joe.int" ' GLCD Routines only... add your own here.

    ' Here are our register symbols that include the Interrupts in use
    ' we are only using a limited few, but the COMPLETE register contents are listed for information
    ' If you are new to Interrupts, dont be put of by this huge list. !
    ' It could be written as just ....

    'Symbol IPEN = RCON.7 ' Interrupt Priority Enable bit
    'Symbol PEIE = INTCON.6 ' Peripheral Interrupt Enable
    'Symbol GIE = INTCON.7 ' Global Interrupt Enable
    'Symbol INT1IF = INTCON3.0 ' INT1 External Interrupt Flag 1 = The INT1 external interrupt occurred (must be cleared in software)
    'Symbol INT1IE = INTCON3.3 ' INT1 External Interrupt Enable 1 = Enables the INT1 external interrupt PortB1
    'Symbol INT1IP = INTCON3.6 ' INT1 External Interrupt Priority bit 1 = High priority PortB1

    ' and nothing more is needed - you could delete all the other symbol declares.

    ' But for a fuller explanations, here are the contents of all the register used, see the datasheet for your device.

    Symbol NOT_BOR = RCON.0 ' Brown-out Reset Status bit
    Symbol NOT_POR = RCON.1 ' Power-on Reset Status bit
    Symbol NOT_PD = RCON.2 ' Power-down Detection Flag bit
    Symbol NOT_TO = RCON.3 ' Watchdog Time-out Flag bit
    Symbol RI = RCON.4 ' RESET Instruction Flag bit
    Symbol IPEN = RCON.7 ' Interrupt Priority Enable bit
    '1 = Enable priority levels on interrupts

    Symbol RBIF = INTCON.0 ' RB Port Interrupt Flag
    Symbol INT0IF = INTCON.1 ' INT0 External Interrupt Flag
    '1 = The INT0 external interrupt occurred (must be cleared in software) PortB0 ALways HIGH
    '0 = The INT0 external interrupt did not occur PortB0
    Symbol TMR0IF = INTCON.2 ' TMR0 Overflow Interrupt Flag
    Symbol RBIE = INTCON.3 ' RB Port Change Interrupt Enable
    Symbol INT0IE = INTCON.4 ' INT0 External Interrupt Enable
    '1 = Enables the INT0 external interrupt PortB0 Alwyas HIGH
    '0 = Disables the INT0 external interrupt PortB0
    Symbol T0IE = INTCON.5 ' TMR0 Overflow Interrupt Enable

    Symbol PEIE = INTCON.6 ' Peripheral Interrupt Enable same as GIEL
    Symbol GIEL = INTCON.6 ' Low priority peripheral interrupt
    'When IPEN = 1:
    '1 = Enables all low priority peripheral interrupts
    '0 = Disables all low priority peripheral interrupts

    Symbol GIEH = INTCON.7 ' Enable all high priority interrupts same as GIEH
    Symbol GIE = INTCON.7 ' Global Interrupt Enable
    'When IPEN = 1:
    '1 = Enables all high priority interrupts
    '0 = Disables all interrupts

    Symbol RBIP = INTCON2.0 ' RB Port Change Interrupt Priority bit 1 high 0 low
    Symbol INT3IP = INTCON2.1 ' unimplemented - read as 0
    Symbol TMR0IP = INTCON2.2 ' TMR0 Overflow Interrupt Priority bit 1 High 0 Low
    Symbol INTEDG3 = INTCON2.3 ' Unimplemented - read as 0
    Symbol INTEDG2 = INTCON2.4 ' External Interrupt2 Edge Select bit PortB2 edge Rising If 1 falling if 0
    Symbol INTEDG1 = INTCON2.5 ' External Interrupt1 Edge Select bit PortB1 edge Rising If 1 falling if 0
    Symbol INTEDG0 = INTCON2.6 ' External Interrupt0 Edge Select bit PortB0 edge Rising if 1 falling if 0 Always High Priority
    Symbol NOT_RBPU = INTCON2.7 ' PORTB Pull-up Enable bit - 1 disabled - 0 PORTB pull-ups are enabled by individual port latch values

    Symbol INT1IF = INTCON3.0 ' INT1 External Interrupt Flag 1 = The INT1 external interrupt occurred (must be cleared in software)
    Symbol INT2IF = INTCON3.1 ' INT2 External Interrupt Flag 1 = The INT2 external interrupt occurred (must be cleared in software)
    Symbol INT3IF = INTCON3.2 ' unimplemented - Read As 0
    Symbol INT1IE = INTCON3.3 ' INT1 External Interrupt Enable 1 = Enables the INT1 external interrupt PortB1
    Symbol INT2IE = INTCON3.4 ' INT2 External Interrupt Enable 1 = Enables the INT2 external interrupt PortB2
    Symbol INT3IE = INTCON3.5 ' unimplemented - Read As 0
    Symbol INT1IP = INTCON3.6 ' INT1 External Interrupt Priority bit 1 = High priority PortB1
    Symbol INT2IP = INTCON3.7 ' INT2 External Interrupt Priority bit 1 = High priority PortB2


    ' Interrupt on port b 0 to 2 - we will use portb.1
    ' use pin b1 (we can set priorities on B1 and B2 , portB0 is ALWAYS high priority.)
    ' to set up the interrupt... for PortB.1 high priority..

    INT1IP = 1 ' High Priority interrupt on PortB.1
    INTEDG1 = 1 ' rising edge on PortB.1

    ' enable the Interrupt 1
    INT1IE = 1

    ' Flags for the interrupts are ...
    INT1IF = 0 ' make sure its clear to start with.

    'set up any variables

    ' If you use a Byte for the 2 items below, then the MAX count is 0 to 255, a word allows 0 to 65535
    ' with the conditioning in the Interrupt service routine you can limit the count to any vlaue within the range - see notes there.

    Dim Count_Encoder As Word ' encoder counter - use byte word or anything to suit your purpose. !
    Dim Encoder_Now As Word ' this is the interim storage for the value from the interrupt, must be same type as above

    ' We have hardware Interrupts

    ON_INTERRUPT GoTo ISR ' hardware interrupt

    ' Jump over the Interrupt Service Routine

    GoTo Over_Interrupt

    'Interrupt Routine

    ISR: ' Interrupt Service Routine

    Context SAVE ' we are using an 18 series. SOMETIMES you do not HAVE
    ' to use this command with 18, check the datasheet
    ' Here yo DO, not using it will cause GLCD corruption at high rotation speed

    ' check PortB1 INT1IF, if = 1 then THIS was the interrupt

    If INT1IF<>1 Then Get_Out ' it wasn't us ! so clear off
    If PORTB.3=1 Then Count_Up ' if the other pin is 1 then up, otherwise down....
    Dec Count_Encoder

    ' Here is some conditioning for the total value of the counter.
    ' lets say we want a maximum reading of 1024.... a Word is 0 to 65535
    ' so when re reach 65535 we have clocked down from 0, so reset to your desired max count
    ' this will act like a tuning knob, clocking over when the count is reached.
    ' in other circumstance you may want to use the encoder like a volume control
    ' where a stop is reached, so if its 0 and you continue turning anticlcokwise it stays at 0
    ' and when the count is reached, and you continue turning clockwise, it remains at that count.
    ' here it is set as an example for the volume control type with a range of 0 to 1024
    ' un rem the other lines to try the clockover type, and rem out the volume lines.

    'If Count_Encoder = 65535 Then Count_Encoder= 1024 ' clock over counter type
    If Count_Encoder <= 0 Then Count_Encoder= 0 ' volume control type of count
    GoTo End_Interrupt

    Count_Up:

    Inc Count_Encoder

    ' the other part of the conditioning, we can use any value from 0 to 65535
    ' make sure that both parts of the conditioning relate to the same value !!!!
    ' If you wanted a maximum count of 10000 then change the 1024 value to 10000

    'If Count_Encoder > 1024 Then Count_Encoder = 0 ' clock over counter type
    If Count_Encoder >=1024 Then Count_Encoder = 1024 ' volume control type
    End_Interrupt:

    INT1IF = 0 ' clear flag

    Get_Out:

    Context Restore

    Over_Interrupt: ' setup the ports, initialise variables and display, enable the interrupts.

    ' the encoder A - B output is A = PortB.1 , B = PortB.3
    ' we dont need a trisb becuase the startup is all input
    ' leaving the whole ports as inputs is safer so you dont accidently
    ' connect your encoder output to a PICŪ output !
    Count_Encoder = 0 ' reset the variable to zero to start with
    ' you can preset this to a value to use if you like, to form a starting point
    DelayMS 200 ' settle period
    Cls ' Clear the lcd display

    IPEN = 1 ' Enable Priorities for interrupts
    PEIE = 1 ' Enable Peripheral interrupts
    GIE = 1 ' Global Interrupt Enable

    GoTo Display_0_at_startup ' make sure we have something on the display at startup
    ' Main Program Loop
    While 1=1
    If Encoder_Now <> Count_Encoder Then ' no need to update the diplay unless anything changed
    Encoder_Now = Count_Encoder
    Display_0_at_startup:
    Print At 0,0,Dec,Encoder_Now," " ' Display the result
    EndIf

    ' Do other things as you require

    Wend
    ' End of main program loop

    End ' We should NEVER get here, but just in case

    Include "font.inc" ' Font for our GLCD, make it an internal font
    contributed by Joe Hanley.