Rotary Encoder made easy (Single Wire!) and debounced at the same time

Hello all,

Update: It is now a Single Wire solution.

there are many pieces of sample code available for using these cheap, Chinese Rotary Encoders, often used for Volume settings or Menu selections.
However, I am under the impression not many are really happy with them, at least I was not.

They either needed many input ports, or using them took both available interrupts, and debouncing was always a PITA.

In my sample code I use just one Analog port and one Digital port, and I use only one interrupt.
This way even a Nano or Mini has several ports free to do something else.

What I think could be an advantage as well, is the fact my code does not need 'polling', so it will not slow down your program.

Basically my 'trick' consists of connecting both the Digital port AND the Analog port to the same input-signal from the Rotary Encoder.

In the new version these functions are combined in just using A0 for both interrupt and signal reading.

The switches inside the encoder are read in a similar way as the 5-button analog keyboard demo.
However, I use the signal, changing when activating the encoder, at the same time to trigger a ISR routine, and to read the voltage at the analog input.

Looking forward to feedback about how to improve this,

Un saludo from Spain,

Satbeginner

P.S. here a link to this type of encoders: http://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20160721235103&SearchText=rotary+encoder

Rotary_Analog.ino (11.3 KB)

Rotary_Encoder_Single_Wire_LK.ino (11.4 KB)

Please give a link to such strange encoders.

Your circuit and code may be sufficient for manually rotated encoders, but not for precise high resolution position or motor speed feedback.

Have a look at Ben Buxton's rotary library.
It doesn't require any interrupt pins, detects the turn of a rotary precisely, at low and at high speeds and - with really cheap rotary knobs.

For projects with menus it's my all time favorite and I checked almost a dozen different approaches, libraries, sketches etc.

You can download it here.

Hi, thanks for showing me that.

I too found a different article here ( http://www.allaboutcircuits.com/projects/how-to-use-a-rotary-encoder-in-a-mcu-based-project/ ) that used Interrupts on the Analog line.

Basically similar use of the Analog line as in the article you pointed out to me.

In my demo I now use only one wire, both for interrupt AND reading the value.

I love minimalism!

Un saludo,

Satbeginner

Rotary_Encoder_Single_Wire_LK.ino (11.4 KB)

I will give your sketch a try.
Is it absolutely needed to work with the resistors or would it work without?

Hi rpt007,

No, you definitely need the resistors, because they form a divider 'per encoder action'.

So, 'Left' will give a different voltage value than 'Right', and 'Button Pressed' will give a third value.

It is important that the three resistors coming from the +5V are significantly different, so the MCU can differentiate between the three actions.

Un saludo,

Satbeginner

Thanks. Understood - I forgot about the analogue input.

Hey Satbeginner, nice job on the one wire encoder. Very Clever.

Thank you Satbeginner.

And I was just looking for a way to use one less pin on an attiny85 (only 5 usable i/o pins plus RST).
Lets see if I can port this, I don't need interrupts so it looks possible, the difficult part will be calibration, no easy way to do a Serial.print()...

Hi,
Can you post a picture of your encoder please?
Encoders have A and B outputs, not Left Turn and Right Turn switches.
It is HIGH and LOW COMBINATIONS of these two outputs that indicate count and direction.

Thanks.. Tom... :slight_smile:

TomGeorge:
Hi,
Can you post a picture of your encoder please?
Encoders have A and B outputs, not Left Turn and Right Turn switches.
It is HIGH and LOW COMBINATIONS of these two outputs that indicate count and direction.

Thanks.. Tom... :slight_smile:

They are the normal cheap rotary encoders.
The HIGH and LOW are DIGITAL readings, this approach adds one resistor to each pin so the ANALOG value that's read can be matched to one pin.
The idea is similar to analog keyboard matrices. Clever, very clever... :wink:

Such an approach is very limited in speed. During one ADC sample time no more than a single signal transition is allowed. And if the samples are delayed further by other activities, the risk of lost pulses increases dramatically.

Hi,
But Encoders DO NOT HAVE OUTPUTS LABELLED LEFT TURN and RIGHT TURN.

When you turn an encoder clockwise or counter clockwise you get a stream of A and B outputs, these outputs are phased so as to show the direction of rotation.

Looking at the code, all it does is check if U, D or M are high, not if there is a combination of two signals that an Encoder would output.

U and D and M buttons , yes, but not from what I can see a proper Quadrature Encoder would supply.

Tom... :slight_smile:

TomGeorge:
Hi,
But Encoders DO NOT HAVE OUTPUTS LABELLED LEFT TURN and RIGHT TURN.

When you turn an encoder clockwise or counter clockwise you get a stream of A and B outputs, these outputs are phased so as to show the direction of rotation.

Looking at the code, all it does is check if U, D or M are high, not if there is a combination of two signals that an Encoder would output.

U and D and M buttons , yes, but not from what I can see a proper Quadrature Encoder would supply.

Tom... :slight_smile:

The reading is done by the ISR

ISR (PCINT1_vect)                                                                                      // Interrupt Service Routine (ISR) to read the encoder functions  
{                
    // Using an interrupt routine avoid slowing down the main program, since no polling is neccesary 
    Result = (' ');                                                                                    // Clear the old result, let's see if we can get a new one
    ReadValue = (analogRead(AnalogInput) + analogRead(AnalogInput) + analogRead(AnalogInput)+ analogRead(AnalogInput))/4;        // Read the average value from the Analog Input A0
    if ( ReadValue >  581 && ReadValue <  620 )   Result = ('M');                                      // The value-windows are not adjecent, there are gap's between the 'windows' to avoid incidental mis-reading of the encoder functions.
    if ( ReadValue >  651 && ReadValue <  740 )   Result = ('U');                                      // So, P=581-620, and then U=651-740, and then D=771-860
    if ( ReadValue >  771 && ReadValue <  860 )   Result = ('D');                                      // The 'window' values depend on the resistors used, and can be determined by the debug line in the show_encoder() routine
  
    if ( Result == (' ') ) ReadValue = 0;  
  // If no valid value is found, we clear the measured average and do nothing
}

It produces 'M', 'U' or 'D' in a global char var named Result. This variable is then processed.
I don't think this is the best way to handle it and I'm sure a lot of events are beeing lost, but "it's not stupid if it works", it might be suitable to some situations.

However, what I think it's really important in this post is the way to wire the rotary encoder, if we can use only one pin instead of 3, then it's a major WIN !!!

It will not be fast, possible not fast enough to use it on a game console, but it might be just what we need 99% of the times we decide to use a rotary encoder...