There's much information on the internet about this. I documented my approach in case someone is paddling these waters.

My application involves a New Haven Serial 4x20 LCD available from Digikey or Mouser. The focus here is interfacing a Bourns Rotary Encoder Switch which includes a push to select momentary N.O. contact. Three lead styles are available, I used the vertical style (leads exiting from the rear). 3 terminals for the encoder (GND, channel A and channel B) plus two terminals for the push to select switch. Study the datasheet for fine details. The encoder uses timed mechanical switches that simulate a quadrature output. Switches bounce and it's possible to get between detents which alters the output causing erratic signals. The filter circuit Bourns suggests didn't work very well either.

That said, with a proper interface circuit good results can be achieved and the encoder can function as a reliable simple input device. Menu selections can be made as well as numeric input. My SOC SBC was the Arduino NANO V3 which has INT0 and INT1 interrupt inputs. The NANO V3 uses an ATmega328P processor and the tiny board has enough HP for some robust applications involving LCDs, serial coms, etc. For more sophisticated applications the ATMEGA2560 board could be used. Or for that matter any board that provides 2 interrupt lines. Interrupts are the best way to read a rotary encoder which is why I went in this direction.

Given the switch characteristics I needed a 25mS de-bouncing circuit which was easily handled with 555 timers. The circuit diagram is HERE. This was done as a breadboard circuit and tested fine. You could use 556 dual timer if you want. The next trick was writing the ISRs to get a variable to increment/decrement according to which direction the dial was rotated.

You'll need to declare three variables:

volatile char menu_sel=0; // Menu selection number from encoder dial.
volatile unsigned char int0_f=0; // Direction flags for quadrature encoder dial.
volatile unsigned char int1_f=0;

Your ISRs will be:

ISR(INT0_vect) /* Triggered on NANO pin 5 (PD2). */
if (int1_f) {menu_sel--; int1_f = 0;}
else int0_f = 1;

ISR(INT1_vect) /* Triggered on NANO pin 6 (PD3). */
if (int0_f) {menu_sel++; int0_f = 0;}
else int1_f = 1;

Set your interrupt sense to trigger on a rising edge. I found the 25mS pulse width (interface circuit) worked well to suppress any contact bounce yet maintain good response time. The 555 timer RC time constant can be changed if/as required.

All this does is set a flag for the first rising edge seen by channel A or channel B interrupts keeping track of the previous event. Which allows two events to be compared to see which one occurred first. If the channel A interrupt sees the flag set by channel B it knows channel B triggered first. The same logic applies to the other direction. Thus menu_sel is either incremented or decremented according to the direction the dial has been turned. Once the flag is used, it is reset. The menu_sel variable can be used to move a pointer horizontally or vertically. A single DI input is required to monitor the push to select switch. Polling this works fine as a way to change screens or select input. You may find a delay of about 500mS is required after a switch closure to avoid the switch from being read multiple times.

As for menu_sel it will keep on incrementing/decrementing as the dial is rotated. You can use the following code to restrict its value (1 >= menu_sel >= 4) and create a wrapping effect.

if (menu_sel < 1) menu_sel = 1;
if (menu_sel > 4) menu_sel = 4;

This works well if you're moving a pointer in column 1 of your LCD up/down from line 1 to 4. As for setting up interrupts this is IDE and processor specific. I like going directly to the processor register bit positions for settings. Make sure your global interrupt bit is set and the specific interrupt enable bits set for the channels you are using. The processor's datasheet will have all these details. A compiler set for a specific processor type will support all the register names and bit definition names. Or you can use canned functions if available in the IDE.

Evolve and simplify!
Scott Bridgman, Why not join and post your own comments?? (email me)