Rotary encoders and interrupts

I have been reading on the forum about rotary encoders recently, and just assumed that they looked like this:

After all, that is a rotary-dial phone, and presumably as you dial the numbers are encoded.

However I now realize that people are probably talking about these things:

These are rotary switches, which (unlike potentiometers) are not analogue, but digital. As you turn the knob pulses are generated by switching the center (C) pin to either of the outer pins (A and B) in such a way that you can tell which way it is being turned.

To test this, I wired up the switch like this:

It was very simple, here is a photo of it being connected to an Arduino Uno:

In the photo a couple of diodes are visible. This was part of a scheme to try to use only one interrupt, but it didn't work perfectly.

Now the center (common, or C) pin is grounded. The outer two pins are connected to pins 2 and 3 of the Arduino, which are then pulled high by setting pull-up resistors on them. In the code that is done like this:

  digitalWrite (2, HIGH); 
  digitalWrite (3, HIGH);

To detect when the knob is being turned we set up an interrupt handler which fires whenever either pin changes:

  attachInterrupt (0, isr, CHANGE);   // pin 2
  attachInterrupt (1, isr, CHANGE);   // pin 3

A bit of investigation shows that whenever you turn the encoder by one click you get one of these state changes (where H is high and L is low):

Forward direction: LH then HH, or HL then LL
Reverse direction: HL then HH, or LH then LL

So we can see that both pins the same (HH or LL) is the "resting" position. So we need to remember what preceded them. Thus we remember the previous switch state, and then when we get HH or LL, we look at what we had last time to see which way the switch must have been turned.

An extra test was needed to cater for switch bounce (sometimes we got HH followed by HH). I also added code to alter the "increment" amount. The idea was that if a human is turning the knob, if he or she turns it faster, then we increment by more than one. That is so if you want a big change, you turn the knob quickly, and if you want a small change, you turn it slowly.

The finished test program looks like this:

// Rotary encoder example.
// Author: Nick Gammon
// Date:   24th May 2011

// Wiring: Connect common pin of encoder to ground.
// Connect pins A and B (the outer ones) to pins 2 and 3 (which can generate interrupts)

volatile boolean fired = false;
volatile long rotaryCount = 0;

// Interrupt Service Routine
void isr ()
{
  
static boolean ready;
static unsigned long lastFiredTime;
static byte pinA, pinB;  

// wait for main program to process it
  if (fired)
    return;
    
  byte newPinA = digitalRead (2);
  byte newPinB = digitalRead (3);
  
  // Forward is: LH/HH or HL/LL
  // Reverse is: HL/HH or LH/LL
  
  // so we only record a turn on both the same (HH or LL)
  
  if (newPinA == newPinB)
    {
    if (ready)
      {
      long increment = 1;
        
      // if they turn the encoder faster, make the count go up more
      // (use for humans, not for measuring ticks on a machine)
      unsigned long now = millis ();
      unsigned long interval = now - lastFiredTime;
      lastFiredTime = now;
      
      if (interval < 10)
        increment = 5;
      else if (interval < 20)
        increment = 3;
      else if (interval < 50)
        increment = 2;
         
      if (newPinA == HIGH)  // must be HH now
        {
        if (pinA == LOW)
          rotaryCount += increment;
        else
          rotaryCount -= increment;
        }
      else
        {                  // must be LL now
        if (pinA == LOW)  
          rotaryCount -= increment;
        else
          rotaryCount += increment;        
        }
      fired = true;
      ready = false;
      }  // end of being ready
    }  // end of completed click
  else
    ready = true;
    
  pinA = newPinA;
  pinB = newPinB;
}  // end of isr


void setup ()
{
  digitalWrite (2, HIGH);   // activate pull-up resistors
  digitalWrite (3, HIGH); 
  
  attachInterrupt (0, isr, CHANGE);   // pin 2
  attachInterrupt (1, isr, CHANGE);   // pin 3

  Serial.begin (115200);
}  // end of setup

void loop ()
{

  if (fired)
    {
    Serial.print ("Count = ");  
    Serial.println (rotaryCount);
    fired = false;
  }  // end if fired

}  // end of loop

(edit) See below for improved version that only requires a single interrupt and is shorter.

I have been reading on the forum about rotary encoders recently, and just assumed that they looked like this:

Haha made me smile :slight_smile:

Since you are effectively only using half the resolution of your quadrature encoder, you can significantly simplify your code.

  1. You only need to setup your interrupt on one of the pins, eg pinA.

Then HH = increment count.
HL = decrement count.
LH = increment count.
LL = decrement count.

No need to remember previous state information. You have the same resolution, simpler code.

jraskell:
Since you are effectively only using half the resolution of your quadrature encoder, you can significantly simplify your code.

  1. You only need to setup your interrupt on one of the pins, eg pinA.

Then HH = increment count.
HL = decrement count.
LH = increment count.
LL = decrement count.

No need to remember previous state information. You have the same resolution, simpler code.

I agree that the code is much simpler when using only one interrupt but what kind of trigger do you use for your interrupt when you use those rules?
When I've used rotary encoders with only one interrupt I've triggered on CHANGE on either of the pins and the rules have been
HH or LL = one direction
HL or LH = the other direction

so something like this has worked fine for me when attaching an interrupt to pinA, change:

if (pinA != pinB)
{
      //clockwise
}
else
{
      //counterclockwise
}

jraskell:
Since you are effectively only using half the resolution of your quadrature encoder, you can significantly simplify your code.

I'm not totally sure what you mean by using half the resolution. The two state changes (eg. LH then HH) represent a single "click" of the knob against the detent. It isn't possible to leave the knob between detent positions. Thus I only "recorded" a change after two state changes. Otherwise (say if it was a thermostat) you could only set it to 20, 22, 24 etc. and not the positions inbetween.

perhof:
When I've used rotary encoders with only one interrupt I've triggered on CHANGE on either of the pins and the rules have been
HH or LL = one direction
HL or LH = the other direction

Yes, well I initially had one interrupt, and I agree that in the cases you quote you probably don't need both pins. Except, and this is a big except, if you change directions. My testing showed, and you can see why, if you change directions the first move in the opposite direction is incorrectly recorded. Here, say we look at Pin A:

H -> L -> H -> L  (forwards)
H -> H -> L -> L  (reverse)

Now consider what happens if we reverse in the middle:

(forwards) H -> L -> H -> L  (reverse) H -> H -> L -> L

The first transition in the reverse direction is still L -> H which looks like a forwards movement. So we add one to the counter, when we should subtract one. And if you are tweaking your thermostat, you might dial up (say) 24 degrees, decide that is too high, and turn it one click to the left, but it shows 25! Not good enough. That's why I said above:

This was part of a scheme to try to use only one interrupt, but it didn't work perfectly.

(edit) I agree however that if the encoder is specified to move in one direction only, then the simplified code is much better, it uses less pins, less interrupts, and is cleaner.

Trigger the interrupt on PinA change only.

In the interrupt, read both pinA and pinB.

if pinA is High, you know you've just had a Low to High transition on pinA.
If pinB is also High, you know this transition was in the positive direction.
If pinB is Low, you know this transition was in the negative direction.

If pinA is Low, you know you've just had a High to Low transition on pinA.
If pinB is also Low, you again know this transition was in the positive direction.
If pinB is High, you know this transition was in the negative direction.

Changing direction isn't a problem. Let's say you receive a Low to High interrupt on pinA. pinA is in a High state. You read pinB, it's also in a high state, so you know you are moving in a positive direction. Now you reverse direction and the next interrupt is a High to Low transition. Now pinA is in a Low state when you read it (which means you had a High to Low transition), but pinB is still in a High state. As per the rules I listed above, this indicates you are moving in the negative direction. No problem changing direction.

With a bit of tweaking of my design, I got your suggestions to work perfectly, and it is indeed shorter and simpler. Thanks for that!

// Rotary encoder example.
// Author: Nick Gammon
// Date:   25th May 2011

// Thanks for jraskell for helpful suggestions.

// Wiring: Connect common pin of encoder to ground.
// Connect pin A (one of the outer ones) to a pin that can generate interrupts (eg. D2)
// Connect pin B (the other outer one) to another free pin (eg. D5)

volatile boolean fired;
volatile boolean up;

#define PINA 2
#define PINB 3
#define INTERRUPT 0  // that is, pin 2

// Interrupt Service Routine for a change to encoder pin A
void isr ()
{
  if (digitalRead (PINA))
    up = digitalRead (PINB);
  else
    up = !digitalRead (PINB);
  fired = true;
}  // end of isr


void setup ()
{
  digitalWrite (PINA, HIGH);     // enable pull-ups
  digitalWrite (PINB, HIGH); 
  attachInterrupt (INTERRUPT, isr, CHANGE);   // interrupt 0 is pin 2, interrupt 1 is pin 3

  Serial.begin (115200);
}  // end of setup

void loop ()
{
static long rotaryCount = 0;

  if (fired)
    {
    if (up)
      rotaryCount++;
    else
      rotaryCount--;
    fired = false;
        
    Serial.print ("Count = ");  
    Serial.println (rotaryCount);
    }  // end if fired

}  // end of loop

This version only requires one interrupt, which frees up the other one for some other use (eg. a second rotary encoder). The pins are now defines so you can easily change where you connect the encoder.

Nicely done Nick. Glad I could help out.

Nice tutorial!

One issue with "cheap" rotary encoders is contact bounce and in this case the approach suggested above may have issues. Thanks to the protocol (Manchester encoding) however it is possible to effectively eliminate all noise.

A rotary encoder has two signal pins (A and B) and so we have four possible states (0 0, 0 1, 1 0, and 1 1). In addition to this we must track previous state (additional two bits required) and so we get four bits in total. These four bits represent all of 16 possible transitions that are either invalid (due to contact bounce) or represent a valid increment/decrement. If we account for all 16 states in software, contact bounce will no longer be an issue. Fortunately this is also quite easy to implement in software as we can combine previous state and current state (four bits total) into a value in the 0 to 15 range. This value can then be used as an index into a table holding values 1 for increment, -1 for decrement and zero for invalid state changes.

This approach will also account for intermediate steps (half-way points) and work equally well with smooth or notched encoders.

An implementation of this approach is available from the following link:

http://www.circuitsathome.com/mcu/programming/reading-rotary-encoder-on-arduino

This implementation is polled, but can easily be modified to use a single pin-change interrupt if so desired.

Ah, a state machine. Very nice.

I think I'll leave my sketch above as "one possible way" for now. The state machine sounds like it is somewhat more resistant to errors, however for the switch I had to hand the improved sketch worked pretty well.

The initial state machine sketch had the drawback that it was polled, which may or may not be suitable for every application. It linked to another sketch which uses interrupts, however then that requires two interrupts, like my earlier sketch (although it would be more resistant to "bad states").

However I have picked up on the idea of the hardware debouncing, and amended the schematic above accordingly (you may need to refresh the page to see it).

It seems to me that a combination of one interrupt, hardware debouncing, and simplicity, may well meet the needs of at least some users.

I should also point out here that if you require multiple encoders (and thus multiple interrupts) a port expander like the MCP23017 (link below to example) could fit the bill.

Another interesting aspect is that some (more expensive) rotary coders are optical. I presume they don't suffer from switch bounce.

BenF:
This implementation is polled, but can easily be modified to use a single pin-change interrupt if so desired.

Actually I don't see how that can be modified to use one interrupt, because you will lose some transitions if you do that. The links I followed showed two interrupts to preserve the state machine information.

But thanks very much for the ideas, it is great to merge ideas together like that, we will end up with some great results this way.

The pin-change interrupt works at the port level and so if you connect A and B to pins on the same 8-bit port (such as digital pin 5 and 6) a single interrupt will suffice. You could even support a second encoder from the same pin-change interrupt (4 pins) and two regular switches (if the encoders include a push switch).

Here's my implementation to serve two encoders with push buttons from a single ISR.

ISR(PCINT2_vect)
{
  static const int8_t rot_states[] =   {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
  static uint8_t AB[2] = {0x03, 0x03};
  static uint8_t btn = _BV(PIND4) | _BV(PIND7);
  uint8_t t = PIND;  // read port status

  // check for rotary state change button1
  AB[0] <<= 2;                  // save previous state
  AB[0] |= (t >> 2) & 0x03;     // add current state
  rot[0] += rot_states[AB[0] & 0x0f];

  // check for rotary state change button2
  AB[1] <<= 2;                  // save previous state
  AB[1] |= (t >> 5) & 0x03;     // add current state
  rot[1] += rot_states[AB[1] & 0x0f];

  // check if buttons are pushed
  t &= _BV(PIND4) | _BV(PIND7);
  if (t != btn) {  // we're only interested in high to low transitions
    if (!t_rotary) {  
      push[0] |= !(t & _BV(PIND4));
      push[1] |= !(t & _BV(PIND7));
    }
    btn = t;
    t_rotary = DEBOUNCE_TIME;  // start/restart bounce guard timer (covers make and break)
 }
}

And here is the low level code to set up the interrupt:

  // enable pullup for encoder pins
  PORTD |= _BV(PORTD7) | _BV(PORTD6) | _BV(PORTD5) | _BV(PORTD4) | _BV(PORTD3) | _BV(PORTD2);

  // enable button pin change interrupt
  PCMSK2 = _BV(PCINT18) | _BV(PCINT19) | _BV(PCINT20) | _BV(PCINT21) | _BV(PCINT22) | _BV(PCINT23);
  PCICR = _BV(PCIE2);  // D-port interrupt enable

Did anyone perhaps tried an optical mouse to read a shaft? I.e. just mount the sensor part of the mouse above the shaft, and as it turn, use the output to determine its direction and speed? Can one actually make the Arduino to read a mouse? (I'm toying with the idea to read the shaft of my telescope, which is about 20mm diameter.)

Nick
Is that an English phone, since the 9 is at the bottom?

Bergie
There is a sketch for reading PS2 mice.
Some of them include a chip that does the quadrature encoding with 12ms debounce included.
The ones I looked at had 3 quadrature inputs plus three buttons.

I rescued them from the big garbage desposal, to use in a better home and reduce the pin count.

Mark

markB:
Nick
Is that an English phone, since the 9 is at the bottom?

I bought it in Australia, but it probably an English design. I wasn't sure what you meant by "9 at the bottom" but some checking of Google Images seems to show that some phones have the numbers going further around the dial. Is that what you meant?

Nick
The ones in NZ (and Aust) went 0 to 9.
Thats why we dialled 111 for an emergency, because it represented more pulses (9).

Apparently you guys use 000 for an emergency.

I do note the mindkits site has these portable phones (known as a Type 100)
http://www.mindkits.co.nz/store/communication/portable-rotary-phone-black

Kind of reminds me of the first cellphone we had while testing thr cell phone network (of one site) in chch way back in the 80's, although this is a bit smaller.

This link talks about dismantling one.
http://www.porticus.org/bell/telephones-technical_dials-rotary.html

Mark

In the UK we use 999

We use 000 in Australia. As you can see from the picture it was exactly at the bottom of the phone, and you are less likely to dial it by tapping the cradle.

I have to say though, that the decision of various countries to use different systems hasn't helped people learn how to dial "emergency". We use 000, NZ 111, USA 911, UK 999. And then mobile phone companies tell users to dial 112.

Is this some ploy to confuse users? So they die?

You see this when we watch a video at school telling students how to "dial emergency" when the actors earnestly tell the kids if they see a fire to dial "911" and then the teacher has to stand up and say, "well, actually it is 000".

Nick
You're not wrong.

In NZ they had to add the 911 capability, because we imported so much american produced programs...featuring real life emergencies ...
Our mobiles have 111 but also *555, you just have to remember to tell them where you are.

I'm not sure tapping the cradle was the issue, but more any line breaks (which amounts to the same).

That old phone reminds me of the first 'mobile phone' we had at the radio depot in telecom.
Back then we didn't have cellphones or pagers, so if you were on call you told them where you would be, and the phone number to call.

We used to install what was known as country sets, which were a radio link, that did everything include ring the bells on the normal phone.
Someone had the bright idea of putitng one in his car, and the other end on the local hills, so he had coverage all over christchurch.

Apparently he got funny looks while driving a Humber Super Snip and talking on this normal phone.
You should have seen the looks from the picnicers when it rung while we were at some park one weekend.

Many years later, we got pagers, then cellular phones.
mark

Hi Nick, I was really glad to find this tutorial because I am wanting to use 2 encoders on interrupts with my Uno. You said :

This version only requires one interrupt, which frees up the other one for some other use (eg. a second rotary encoder). The pins are now defines so you can easily change where you connect the encoder.

I copied that code only changing the baud rate to 9600 and with both wires connected (pins 2 & 3) it works perfectly but when I disconnect pin 3 it counts 0,1,0,1,0,1 etc. I'm new at this and may be making a simple mistake, did I miss something? :~