Getting input from Rotary Encoders

Hi all! :smiley:
I'm making a simple button box with switches, pushbuttons and rotary encoders. I have gotten the switches and buttons working fine, but I find these rotary encoders notoriously difficult to wrap my brain around.

To try to understand I thought I would do a simple setup. I have a rotary encoder hooked up to a Pro Micro like this: (right pin (green wire) to A2, middle pin to ground, left pin (purple wire) to A3)

The code: (Note! It's not my code. I found this code on the web and modified it a bit to suit my setup.)

int clockP = A3;
int data = A2;
int count = 0;
int c = HIGH;
int cLast = HIGH;
int d = HIGH;

void setup() {
 pinMode (clockP,INPUT_PULLUP);
 pinMode (data,INPUT_PULLUP);
 Serial.begin (9600);
}

void loop() {
 c = digitalRead(clockP); // read pin A as clock
 d = digitalRead(data);  // read pin B as data

 if (c != cLast) {       // clock pin has changed value
   d = c^d;              // work out direction using an XOR
   if ( d ) {
     count++;
     Serial.print ("Right: ");
     Serial.println(count);
     delay(20);
   }else{
     count--;
     Serial.print ("Left: ");
     Serial.println(count);
     delay(20);
   }
   cLast = c;
 }
}

This is the output when I run the code and first rotate the encoder one dent/position to the right followed by one dent/position to the left:

Right: 1
Right: 2
Left: 1
Left: 0

As you can see I get two inputs every time I turn just one dent.

I've been reading a bit about encoders and pulses and realize that they are somewhat complex in nature. I found one Youtuber explaining them more in detail: Video
He uses interrupts to handle the inputs from the encoder. Here is the code he links to.

I tried to modify that code to suit my setup but just couldn't get it to work.

Desired functionality
I'm using the joystick library to get button presses from my box. What I want to happen with the rotary encoders is that when I rotate one dent to the right then it sets a button to on then off shortly afterwards. Same for rotating to the left, only a different button obviously.

So the end code would be something like this:

#include <Joystick.h>
Joystick_ Joystick;

int clockP = A3;
int data = A2;
int count = 0;
int c = HIGH;
int cLast = HIGH;
int d = HIGH;

void setup() {
 pinMode (clockP,INPUT_PULLUP);
 pinMode (data,INPUT_PULLUP);
 Joystick.begin();
}

void loop() {
 c = digitalRead(clockP); // read pin A as clock
 d = digitalRead(data);  // read pin B as data

 if (c != cLast) {       // clock pin has changed value
   d = c^d;              // work out direction using an XOR
   if ( d ) {
     count++;
     Joystick.setButton(1, 1);
     delay(50);
     Joystick.setButton(1, 0);
   }else{
     count--;
     Joystick.setButton(0, 1);
     delay(50);
     Joystick.setButton(0, 0);
   }
   cLast = c;
 }
}

(the "count" variable isn't really needed in this example but I kept them just to show what's happening)

That sort of works, but the behaviour is somewhat unpredictable – sometimes two button presses are registered, and sometimes both buttons are pressed when rotating only one way.

Would be great if someone could help me with this. It's the only code left to finish before my button box is complete.

Any suggestions/solutions will be appreciated. :slight_smile:

I think it's an inherent feature of your encoder hardware. I found the same with my encoder and the code you gave.

If I turn the knob veeeerrrrryyyyy slowly I see it counts once as it comes out of the current detent, and counts again as it enters the next one.

So to me the simple solution might be to count only every second one...

I think what's actually happening is that since you're only looking for c to change, regardless of direction, you're counting on both of the falling and rising edges. There's obviously one of each per detent.

So you could try:

if (c != cLast && c == HIGH) //or c == LOW

... to catch it on only one of the edges.

Or just do it the way you already have it and divide by 2 :wink:

sayHovis:
I think what's actually happening is that since you're only looking for c to change, regardless of direction, you're counting on both of the falling and rising edges. There's obviously one of each per detent.

So you could try:

if (c != cLast && c == HIGH) //or c == LOW

... to catch it on only one of the edges.

Ah yes, I think you may be on to something here. Will test this when I get home. I'll have to somehow bake the joystick button press into it too - not quite sure how I'll do that. But anyway, one step at a time.

Thanks for helping out. Stay tuned :smiley:

There's also a library called Encoder: it's installed from the IDE, at Sketch > Include library > Manage libraries.

Put "encoder" in the filter and when it's finished finding them, scroll down to the one called "encoder".

There are two types of rotary encoders (at least that I've encountered):

EDIT:
This is a really good article on programming encoders: Buxtronix: Rotary encoders, done properly

sayHovis:
There's also a library called Encoder: it's installed from the IDE, at Sketch > Include library > Manage libraries.

Put "encoder" in the filter and when it's finished finding them, scroll down to the one called "encoder".

gfvalvo:
This is a really good article on programming encoders: Buxtronix: Rotary encoders, done properly

I'll chech these out! Thanks a lot guys! 8)

Just to clarify the hardware issue, when you order an encoder you have the option of selecting the number of pulses per revolution and the number of detents per revolution. If the two are the same, then you cycle through a complete sequence on both pins from one detent to the next. But if there are half as many pulses as detents, then you only get a half-cycle between detents.

If you get two steps per detent, then you have the first type of encoder, but are using code written for the second type. And if it takes two detents to register a step, you have the opposite situation.

Of course you can write code to make either of them produce one step per detent, but the first type has the advantage that both switches are always open at a detent, which means no current is ever flowing through the pullup resistors at detents. With the second type, there's a 50/50 chance the encoder will come to rest at a point where both switches are closed. On the other hand, the second type in theory should be a bit more reliable and less bouncy because the transitions are occurring half as often per a given twist of the knob.

(If you are polling, you can use the second type, but enable the pullup resistors only immediately before you read the pins, then disable them immediately after, and thus pretty much eliminate the 50/50 current loss. But that doesn't work with interrupts.)

ShermanP:
Just to clarify the hardware issue...

Very clear, thanks for that. Take a pound out the till...

I prefer to talk in terms of complete quadrature cycles rather than number of “pulses”. There are multiple “pulses” per quadrature cycle and they occur on both decoder outputs. So, the important question is how many detents must be traveled in order to output a complete quadrature cycle. As I mentioned, on the decoders that I’ve seen, that number is either 1 or 2 detents per cycle. Equivalently that’s 1 or 1/2 cycle per detent.

If you’re interested, you can take a look at some encoder code that I’ve written here: GitHub - gfvalvo/NewEncoder: Rotary Encoder Library. It allows you to specify which type of encoder you have, so you’ll always get one increment of the counter per detent. It uses the state table approach described the article I linked in Reply #5, so it automatically handles contact de-bouncing.

My code does require 2 pins that support external interrupts per decoder. I think the pro-micro has 5 such pins.

gfvalvo:
I prefer to talk in terms of complete quadrature cycles rather than number of “pulses”. There are multiple “pulses” per quadrature cycle and they occur on both decoder outputs. So, the important question is how many detents must be traveled in order to output a complete quadrature cycle. As I mentioned, on the decoders that I’ve seen, that number is either 1 or 2 detents per cycle. Equivalently that’s 1 or 1/2 cycle per detent.

If you’re interested, you can take a look at some encoder code that I’ve written here: GitHub - gfvalvo/NewEncoder: Rotary Encoder Library. It allows you to specify which type of encoder you have, so you’ll always get one increment of the counter per detent. It uses the state table approach described the article I linked in Reply #5, so it automatically handles contact de-bouncing.

My code does require 2 pins that support external interrupts per decoder. I think the pro-micro has 5 such pins.

Thank you so much for this library! Brilliant stuff 8)
I installed your NewEncoder library and modified your example code to suit my setup. That is, I had to move the connectors from A2 and A3 to pins 2 and 3, since they are interrupt pins.

That worked perfectly! :smiley:

The if-clause that checks whether the encoder was rotated now looks like this:

   if (up || down) { //**************************************** Encoder left/right
     currentValue = encoder1;
     if (currentValue != prevEncoderValue) {
         prevEncoderValue = currentValue;
         if (up) {
             Joystick.setButton(enc1CWNum, ON);
             delay(50);
             Joystick.setButton(enc1CWNum, OFF);
         } else {
             Joystick.setButton(enc1CCWNum, ON);
             delay(50);
             Joystick.setButton(enc1CCWNum, OFF);
         }
     }
   }//********************************************************** Encoder left/right

When I rotate the encoder one dent left or right it "blips" the corresponding button, i.e. turns it briefly on then off again. Which is exactly the functionality I wanted.

I'm waiting for my other encoder to finish the button box. When I receive it I'll just use this code on that too.

According to this page the Pro Micro has 5 interrupt pins:

pin 3 maps to interrupt 0 (INT0)
pin 2 maps to interrupt 1 (INT1)
pin 0 maps to interrupt 2 (INT2)
pin 1 maps to interrupt 3 (INT3)
pin 7 maps to interrupt 4 (INT6)

So I thought I'd hook up the second encoder to pins TX0 and RX1. I suppose that should be fine?

If all you care about is knowing if there was a rotation or not (and which direction) rather than the count value, then you can make it simpler:

if (encoder1.upClick()) {
  Joystick.setButton(enc1CWNum, ON);
  delay(50);
  Joystick.setButton(enc1CWNum, OFF);
 } else if (encoder1.downClick()) {
  Joystick.setButton(enc1CCWNum, ON);
  delay(50);
  Joystick.setButton(enc1CCWNum, OFF);
}

gfvalvo:
If all you care about is knowing if there was a rotation or not (and which direction) rather than the count value, then you can make it simpler:

Good point, I'll clean it up.

Btw, you said your code requires two interrupt pins per encoder, correct? So how exactly does it handle things when one has say 4 encoders, since I assume there is no Arduino board with that many interrupt pins?

Also, can I actually use the TX and RX pins on my Pro Micro? I read something about issues when using those, such as maybe not being able to upload sketches?

If I can't use those two pins (and therefore don't have enough interrupt pins), then what are my options for connecting more encoders to my Pro Micro?

whitestar:
Btw, you said your code requires two interrupt pins per encoder, correct? So how exactly does it handle things when one has say 4 encoders, since I assume there is no Arduino board with that many interrupt pins?

That would be a poor assumption. For example, many of the ARM-based boards support external interrupts on all pins. I frequently use the Teensy 3.x Family.

Also, can I actually use the TX and RX pins on my Pro Micro? I read something about issues when using those, such as maybe not being able to upload sketches?

Never used a Pro Micro

If I can't use those two pins (and therefore don't have enough interrupt pins), then what are my options for connecting more encoders to my Pro Micro?

You could rewrite the code to use either the AVR Pin Change Interrupts (read about them in the ATMega 32U4 datasheet) or polling.

EDIT:
You could also give the PRJC Encoder Library a try. It's available from the Library Manager.

Yes, use the proper library. It will use interrupts when available but will work fine without sacrificing those precious interrupt pins.

I usually let the library keep the "real" count but divide by 2 for my count of clicks.

MorganS:
Yes, use the proper library.

Although, my experience is that the standard Encoder library is much more susceptible to contact bounce than using the state table approach. In fact, the later is more or less totally immune to it.

If it were my project and I had to have more encoders and I had to use a Pro Micro (32u4) then I'd implement the state table approach using Pin Change interrupts.

Thanks guys, I will continue with gfvalvo's code for now and test using the two first pins, TX and RX on the Pro Micro and see how that goes.

Side-question: Why does it say TX1, RX0 on the pinout description and TX0, RX1 on the board itself?
Second image here.

Using the TX and RX pins worked, but I had to unplug them before uploading the sketch and then reconnect them afterwards.
So I'll be hooking my second encoder to those pins when it arrives and make that my final version of the box (yay!).

Pin change interrupt seems at first glance to be a bit above my skill level to be honest. I'd rather use polling, although I'm not even sure how to go about doing that. Maybe this code by buxtronix will work?

Or maybe I'll just use a Teensy 3.5 for my next project involving encoders. Can it be programmed using the same Arduino code as for my Pro Micro?

Yes. Pretty much everything works the same on the Teensy.

Thank you :slight_smile: