Arduino: Using a rotary encoder

As explained in this post, rotary encoders are notoriously unreliable. Well, the cheap ones are. Why bother?

But wait, could they be used at all? Most of the problems people complain about have to do with bouncing. But is bouncing as important if someone is turning a rotary encoder manually to increment a counter? Also, debouncing often slows down the response speed of the Arduino code enough to miss a few clicks. Is missing a few clicks important?

What I’m trying to implement is a simple way of incrementing or decrementing a counter to choose a stored patch value in a pedal board. Practically, this is done by manually turning a knob attached to a rotary encoder. Software will read the encoder and increment (or decrement) the value of a counter associated with a particular patch, or memory value.

The rotary encoder that I’m using came with a knob:Typical rotary encoder

As you can see, the knob is huge! The speed at which one can twist a knob between one’s thumb and index finger is linked directly to the size of the knob, within limits. The bigger the knob, the slower the turning speed. This knob’s diameter is 25 mm (1 inch). The maximum speed that I can achieve is about one revolution per second, in two steps. The clicking speed is then about 40 clicks per second, or 25 ms per click. (I’m guessing the designers knew this)

Let’s see how that affects the debouncing that we might want to do. If we have 25 ms per click, we have to be pretty reasonable about the delay that we think is necessary waiting for the switch to stop bouncing. Actually, even this assumption is wrong. Referring to the previous post, each pin goes through a 0 to 1 then 1 to 0 transition between two clicks. Effectively, our debouncing has to happen twice per click per pin to be able to register every state change. Again, if we have 25 ms per click, there is not much time left to do anything! What to do? As you will see in my solutions, I’m only interested in a transition of one of the pins from 0 to 1. This means that I only have to debounce once per click.

The maximum speed that I could impart on that knob is one revolution per second. I can see myself reaching that speed if I want to jump between two values of my counter as fast as possible. So what if I miss a few clicks? The numbers would be going up fast enough that I couldn’t even see the missing steps (I tried). In practice, as I get close to the chosen value, I would slow down enough to be able to read each number and mentally process its value. As slow as 1/4 of a second per click, or 250 ms! Plenty of time to do a little debouncing!!

After much testing, reading and breadboarding, I chose to explore 3 methods that work well for what I’m trying to achieve.

rotary encoder grey code

Before presenting the solutions, I have to state that I rely on one assumption. With a rotary encoder, a transition on pin1 (A above) from 0 to 1 while pin2 (B above)= 1 means that a counter is increased by one. Otherwise, the counter decreases by 1. This is based on the graphic above (thanks to sagsaw). It is in line with the testing done in this post.  In Arduino code, this translates to:

 

if (pin1 == HIGH)
  if (pin2 == HIGH)
    counter++;
    else
    counter--;

In fact, what I want to catch is the moment when pin1 becomes HIGH. There are a few programming techniques available to achieve this.

Method 1: Using the Arduino Bounce Library

#include <Bounce.h>

// This code increments or decrements a counter based on
// the status of a rotaty encoder

#define pin1 2
#define pin2 3
#define LED 13
int counter = 0;

// Instantiate a Bounce object with a 5 millisecond debounce time
// Only pin1 needs to be debounced. It is assumed that pin2
// will be stable when reading pin1
Bounce bouncer1 = Bounce( pin1,5 ); 

void setup() {
  pinMode(pin1,INPUT);
  pinMode(pin2,INPUT);
  pinMode(LED,OUTPUT);
  Serial.begin(9600);
}

void loop() {
 // Update the debouncer
  bouncer1.update ( );

 // Turn on or off the LED and
 // increment or decrement the counter
 if ( bouncer1.risingEdge()) {
   if (digitalRead(pin2)){
     digitalWrite(LED, HIGH );
     counter++;
     Serial.println(counter);
   }else{
     counter--;
     Serial.println(counter);
   }
 } else {
    digitalWrite(LED, LOW );
 }

}

This sketch is based on the sample code of the new Arduino “Bounce” library, that is replacing the old “Debounce” library. I’m reading the rising edge value on pin1. I’m only interested in a pin1 transition from 0 to 1 (LOW to HIGH). The LED and the Serial commands are for testing only.

I am able to overflow this code if I remove the knob from the rotary encoder and twist the shaft as fast as I can, snapping it between my thumb and index finger. What happens then is that the counter might increase or decrease by one, randomly, until the rotational speed slows down a bit. If you read the counter values in the Arduino IDE, you can spot the artifact. But driving a real life counter, like a 3 digit LED display, you will not be aware of the incrementation skips.

This code will start misbehaving as the Arduino code gets more complex. If I add a delay in the main loop, say delay(100), the encoder readings becomes unreliable.With this in mind, I decided to try a version with an interrupt handler.

Method 2: External Interrupt Handler

The Arduino can accept external interrupts on some of its pins. The goal is again to catch the rising or falling edge on pin1.

/*  Digital Pin 2 accepts external interrupts. Pin1 of a rotary encoder
    is attached to DigitalPin2. An interrupt routine will be called
    when pin1 changes state, including noise.
    This will be made more efficient with hardware debouncing.
    */
int pin1 = 2;
int pin2 = 3;
int counter;
boolean goingUp = false;
boolean goingDown = false;
void setup()
{
  counter = 0;
  //Serial prints for debugging and testing
  Serial.begin(9600);

/* Setup encoder pins as inputs */
    pinMode(pin1, INPUT); // Pin 2
    pinMode(pin2, INPUT); // Pin 4 

// encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, decoder, FALLING);

}

void loop()
{
//using while statement to stay in the loop for continuous
//interrupts
while(goingUp==1) // CW motion in the rotary encoder
{
goingUp=0; // Reset the flag
counter ++;
Serial.println(counter);
}

while(goingDown==1) // CCW motion in rotary encoder
{
goingDown=0; // clear the flag
counter --;
Serial.println(counter);
}
}

void decoder()
//very short interrupt routine 
//Remember that the routine is only called when pin1
//changes state, so it's the value of pin2 that we're
//interrested in here
{
if (digitalRead(pin1) == digitalRead(pin2))
{
goingUp = 1; //if encoder channels are the same, direction is CW
}
else
{
goingDown = 1; //if they are not the same, direction is CCW
}
}

This sketch works well because the interrupt routine is very short. I works even better if a bit of hardware debouncing is forced on the rotary encoder. With two 0.1 uF capacitors soldered to the encoder pins, the number of calls to the interrupt routine is dramatically reduced. This is important because too many calls to the interrupt routine will rob computing cycles from the main routine, negating the effects of using interrupts to save computing power. It has been recommended to never connect a bouncy switch directly to a controllers interrupt pins.

Method 3: Using a Timer Interrupt

 

/* 
 * Example on how to configure the periodical execution of a user 
 * defined function (Interrupt service routine) using Timer2. This 
 * example will run the function every 1ms.  
 */

#include <Bounce.h> 

/* Timer2 reload value, globally available */
unsigned int tcnt2;  

int pin1 = 2;
int pin2 = 3;

// Instantiate a Bounce object with a 5 millisecond debounce time
// Only pin1 needs to be debounced. It is assumed that pin2
// will be stable when reading pin1
Bounce bouncer1 = Bounce( pin1,5 );

/* Setup phase: configure and enable timer2 overflow interrupt */
void setup() {  

  pinMode(pin1, INPUT);
  pinMode(pin2, INPUT);
  Serial.begin(9600); 

   /* First disable the timer overflow interrupt while we're configuring */
  TIMSK2 &= ~(1<<TOIE2);  

  /* Configure timer2 in normal mode (pure counting, no PWM etc.) */
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));  
  TCCR2B &= ~(1<<WGM22);  

  /* Select clock source: internal I/O clock */
  ASSR &= ~(1<<AS2);  

  /* Disable Compare Match A interrupt enable (only want overflow) */
  TIMSK2 &= ~(1<<OCIE2A);  

  /* Now configure the prescaler to CPU clock divided by 128 */
  TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits  
  TCCR2B &= ~(1<<CS21);             // Clear bit  

  /* We need to calculate a proper value to load the timer counter. 
   * The following loads the value 131 into the Timer 2 counter register 
   * The math behind this is: 
   * (CPU frequency) / (prescaler value) = 125000 Hz = 8us. 
   * (desired period) / 8us = 125. 
   * MAX(uint8) + 1 - 125 = 131; 
   */
  /* Save value globally for later reload in ISR */
  tcnt2 = 131;   

  /* Finally load end enable the timer */
  TCNT2 = tcnt2;  
  TIMSK2 |= (1<<TOIE2);  
}  

/* 
 * Install the Interrupt Service Routine (ISR) for Timer2 overflow. 
 * This is normally done by writing the address of the ISR in the 
 * interrupt vector table but conveniently done by using ISR()  */
ISR(TIMER2_OVF_vect) {  
  /* Reload the timer */
  TCNT2 = tcnt2;  

  bouncer1.update();
  if(bouncer1.risingEdge()){
    if (digitalRead(pin2))
      Serial.println("CW");
      else
      Serial.println("CCW");
    }
}  

void loop() {
  delay(100);
//  Serial.println(millis());
}

I am still working on this version. Ideally, I would like to remove the call to the Bounce Library. But the timer interrupt subroutine is still extremely fast (tested at 12usec). The results are also unaffected by the length of the main loop. I tested it with a delay of 100 ms and the rotary switch was still decoded precisely.

Another version of the Timer function could be:

ISR(TIMER2_OVF_vect) {  
  /* Reload the timer */
  TCNT2 = tcnt2;  

  state=(state<<1) | !digitalRead(pin1) | 0xe000;
//  Serial.println(state,HEX);
  if (state==0xf000){
    state=0x0000;
    if(digitalRead(pin2))
      counter++;
    else
      counter--;
  }
}

This will increase or decrease the counter if 12 consecutive reads agree with our condition, but my switch seems to be too unreliable to constantly produce interesting results. I would save 4 usec using this routine.

__________________________________

So, the Timer Interrupt method is the best one. Studying the code of the new Arduino Bounce library (and testing with micros() ) confirmed that the library doesn’t rely on delay() for debouncing, effectively adding very little overhead to method 1 or 3.

This solution will be perfect for my initial version of the Rotary Encoder Arduino sub-project.

If you have read this so far, please keep in mind that your results may vary. I AM ASSUMING  that pin2 is stable when I accept the debounced status on pin1. Otherwise, the switch is totally unreliable.

Also, if your application needs 100% confidence that the encoder will never miss a state change, or produce false positive, just buy a better encoder. I definitely would not trust my little encoder to drive a robot or a high precision machine. Take a look at this page and you will realise that my little encoder, at $1, really is at the bottom end of the quality scale. It was definitely made for manual operation, as a volume control or, like I’m doing here, to increase and decrease a counter.

This entry was posted in Arduino, Pedal board and tagged , . Bookmark the permalink.

27 Responses to Arduino: Using a rotary encoder

  1. Pingback: Linkdump: Arduino and JeeLabs « EdVoncken.NET

  2. Chingkhei Nganba Sapam says:

    Thanks, for the quality information. This is indeed one of the best explanation on using a rotary encoder, especially for people who cant afford to spend too much on highly precise ones.

  3. Chingkhei Nganba Sapam says:

    Hi, I tried using your technique and am getting good encoder response. I added 0.1uF capacitor to each of A and B to the Ground.
    The encoder is not skipping nor reversing the action, even at fast rotation speed. But i do notice one thing,
    I am using the Timer Interrupt Technique. I am using pull up resistor type connection for the A, and B on the encoder to the pin 2 and 3 on Arduino UNO.

    Just for test I have a Green and Red LED which Blinks alternately every 1 second.
    Without the timer script the LEDs lit brightly as expected. But with the Timer Interrupt Code added to my Green and Red LED sketch, The LEDs still blinks alternately but the intensity is very low. Any reason why?
    I have also tried running the same sketch with the encoder disconnected. Still the LEDs are dimly lit.

    Any pointer or advice will be highly appreciated.
    Thanks.

    • rt says:

      Remember that changing Timer2 will change the behavior of pins 3 and 13 on an arduino Uno or older ones, pins 9 and 10 on a Mega. What pins are your LEDs tied to?

      • chingkhei nganba sapam says:

        I am using pin 8 and 9 for the LED and pin 2 and 3 for the rotary encoder.

        Thanks.

      • chingkhei nganba sapam says:

        Also what should be the tcnt2 value for my arduino uno?

      • Chingkhei Nganba Sapam says:

        Are these values for TCCR2A and TCCR2B right for my Aruduino UNO?
        Or do i have to generate the correct values for them and others.

        I am using the exact code provided on your blog.

        Thanks.

        • rt says:

          The Uno uses the same processor (AtMega328) as the older Arduino. So yes, this is the right code.
          Can you post the code that you are using for the LED blinking? There is something that I don’t understand.

          • Chingkhei Nganba Sapam says:

            Here is my code:

            /*
            * Example on how to configure the periodical execution of a user
            * defined function (Interrupt service routine) using Timer2. This
            * example will run the function every 1ms.
            */

            #include

            /* Timer2 reload value, globally available */
            unsigned int tcnt2;

            int pin1 = 2;
            int pin2 = 3;

            int counter = 0;

            // Instantiate a Bounce object with a 5 millisecond debounce time
            // Only pin1 needs to be debounced. It is assumed that pin2
            // will be stable when reading pin1
            Bounce bouncer1 = Bounce( pin1,5 );

            /* Setup phase: configure and enable timer2 overflow interrupt */
            void setup() {

            pinMode(pin1, INPUT);
            pinMode(pin2, INPUT);
            digitalWrite(pin1, HIGH);
            digitalWrite(pin2, HIGH);
            Serial.begin(9600);

            /* First disable the timer overflow interrupt while we're configuring */
            TIMSK2 &= ~(1<<TOIE2);

            /* Configure timer2 in normal mode (pure counting, no PWM etc.) */
            TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
            TCCR2B &= ~(1<<WGM22);

            /* Select clock source: internal I/O clock */
            ASSR &= ~(1<<AS2);

            /* Disable Compare Match A interrupt enable (only want overflow) */
            TIMSK2 &= ~(1<<OCIE2A);

            /* Now configure the prescaler to CPU clock divided by 128 */
            TCCR2B |= (1<<CS22) | (1<<CS20); // Set bits
            TCCR2B &= ~(1<<CS21); // Clear bit

            /* We need to calculate a proper value to load the timer counter.
            * The following loads the value 131 into the Timer 2 counter register
            * The math behind this is:
            * (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
            * (desired period) / 8us = 125.
            * MAX(uint8) + 1 - 125 = 131;
            */
            /* Save value globally for later reload in ISR */
            tcnt2 = 131;

            /* Finally load end enable the timer */
            TCNT2 = tcnt2;
            TIMSK2 |= (1<<TOIE2);
            }

            /*
            * Install the Interrupt Service Routine (ISR) for Timer2 overflow.
            * This is normally done by writing the address of the ISR in the
            * interrupt vector table but conveniently done by using ISR() */
            ISR(TIMER2_OVF_vect) {
            /* Reload the timer */
            TCNT2 = tcnt2;

            bouncer1.update();
            if(bouncer1.risingEdge()){
            if (digitalRead(pin2)){
            counter++;
            //Serial.println("CW");
            Serial.println(counter);
            }
            else {
            counter--;
            //Serial.println("CCW");
            Serial.println(counter);
            }
            }
            }

            void loop() {
            delay(100);
            // Serial.println(millis());
            digitalWrite(9, LOW);
            digitalWrite(8, HIGH); // set the LED on
            delay(1000); // wait for a second
            digitalWrite(8, LOW); // set the LED off
            digitalWrite(9, HIGH);
            delay(1000);
            }

          • rt says:

            Add these 2 lines at the beginning of you setup():
            pinMode(8, OUTPUT);
            pinMode(9, OUTPUT);

            and try the code again. Let me know if it changes the brightness of the LEDs.

  4. Chingkhei Nganba Sapam says:

    Your are spot on… :).

    While copying the code into your script, I forgot to copy the two initializing lines for the LEDs. A blunder from my side.

    Your script is working perfectly. LEDs are also working as expected.

    Thanks a lot for all the help.

    Regards,
    Sapam

  5. Ignacio Herrero Iriarte says:

    Hi, great work yours… I have two 2 bit gray encoders setup, but I have trouble and they don´t work properly. I try alot of scripts to understand my trouble, but not whith debounce… I have arduino 2009 and when I compile bounce fails… I don´t know why does not work have you the same trouble…?
    https://dl.dropbox.com/u/58726056/sketch_sep06c/panel_inigo_02/panel_inigo_02.ino here is my script and the idea it´s to increase or decrease frecs for each encoder, in amounts of 10 each click. Thx for your attention

    • rt says:

      Hello Ignacio,
      I never had problems with the Bounce library. Make sure that the version that you use is compatible with the arduino IDE that you use. Current IDE version is 1.01 as of today. Maybe your library is for IDE 0.23?
      Without debouncing, you might try using a delay, like 5 ms, in your loop, and see if the readings are better. That’s how I started to use the decoders. Keep in mind that they bounce A LOT! Try turning the encoder at different speeds to see if the readings make sense. I will look at your code later. I see that you put plenty of comments in there!

  6. crob09 says:

    I’m using an encoder from Ebay: here are the details:
    400 p/r (Single-phase 400 pulses /R,Two phase 4 frequency doubling to 1600 pulses)

    Power source: 7-24 VDC
    Shaft: 6mm Dia, 13mm Length

    Size: 38mm Dia, 35.5mm Length

    Output :AB 2phase output rectangular orthogonal pulse circuit, the output for the NPN open collector output type
    Maximum mechanical speed: 6000 R / min
    Response frequency: 0-20KHz

    connection:
    Yellow = A phase, blue = B phase, red = Vcc power supply, black = neg

    I’ve tried all examples but still have a little problem, the encoder just keeps counting up when I open the Serial-Monitor. How do I get the RPM of the shaft it’s connected to?

    Thanks for any and all help!

    • rt says:

      Rectangular Orthogonal Pulse Circuit (ROPC) is quite different from the grey-code behaviour of the encoder I use.For example, some require a special circuit (i.e. http://www.melexis.com/Hall-Effect-Sensor-ICs/Triaxis®-Hall-ICs/MLX90316-566.aspx) for the decoding. Others have the circuit integrated.
      Can you measure stable pulses on the output (A or B) of your encoder (try using a logic detector with a simple circuit)? Before looking at the program, you will have to determine the exact type of pulse at the output of your decoder. Since the specs specify a power source AND 4 pins, I suspect some type of circuit is integrated. So what kind of signal (or voltage) shows up on pins A and B?
      Also, at 400 pulses per rev, unless the encoder sits very still, the output pulse will alternate with less than 1 degree of motion!
      Also, and maybe more importantly, at 400 pulses per revolution, I’m not sure the Arduino will be able to keep up. The program that I use creates a timer interrupt every millisecond or so, which is enough for (relatively) slow counting with a 16-20 pulses/rev cheap rotary encoder (although if I twist the shaft fast enough, it will skip pulses). At 400 pulses/rev (1600 using A-B differentiation), you will have to try using hardware interrupt instead. At 400 p/r, turning very slowly at 1 rev/second will generate a pulse every 2 millisecond or so. The timer interrupt will have to react faster than once every ms. With a timer interrupt, the code in the interrupt routine will have to be very short (a few instructions) otherwise, the arduino won’t keep up.

  7. Ferry says:

    I have a rotary encoder omron E6B2 ( http://www.omron.co.id/product_info/E6B2/index.asp ) and arduino uno.
    Whether the program you can be used in the Arduino Uno to read a rotary encoder that type?
    Thank you for your attention.

  8. zahaskay says:

    Hello everyone
    i am having problem to program my encoder dc gear motor (http://www.cytron.com.my/viewProduct.php?pcode=SPG30E-300K&name=DC%20Geared%20Motor%20with%20Encoder )
    it is not working. i try sample coding online.
    and did my circuit. even that not working.
    y
    any1 can advice please

  9. Donald says:

    Hello everyone,
    i used your code with Method 2 (external Interrupt) to control my encoder . I have a good answer with a slow rotation speed, but when i accelerate the movement of rotation, it does not work any more. I have several same results at several places like
    1 2 3 4 5 6 7 7 7 7 7 7 8 8 9 10 11 12 13 14 15 16 16 16 16 16 16 16 17 18 19…
    help me please.

    • rt says:

      Your code is probably too slow to process the incoming interrupt. Can you send me your code?

      • Donald says:

        of course,

        int pin_A= 3;// Signal A
        int pin_B= 2; // Signal B
        int pinStatus_A;
        int pinStatus_B;
        volatile double z=0;
        volatile boolean goingUp=false;
        volatile boolean goingDown=false;
        double a;
        String wert = “”;
        char zeichen =0;

        void setup()
        {
        Serial.begin(115200);
        pinMode(pin_A,INPUT);
        pinMode(pin_B,INPUT);

        attachInterrupt(0,zaehlen_A,FALLING);
        }
        void loop()
        {
        pinStatus_A = digitalRead(pin_A);
        pinStatus_B = digitalRead(pin_B);
        while(goingUp == 1)
        {
        goingUp = 0;
        z++;
        a=(z*200)/1024; //Umwandlung in mm Serial.println(a,3);
        }
        while(goingDown == 1)
        {
        goingDown = 0;
        z–;
        a=(z*200)/1024; // in mm
        Serial.println(a,3);
        }

        while (Serial.available()>0) // reset function
        {
        zeichen = Serial.read();
        wert += zeichen;
        if(wert==”reset”)
        {
        z=0 ;
        Serial.flush();
        }
        }
        wert =””;

        }
        void zaehlen_A()
        {

        if( pinStatus_A != pinStatus_B)
        goingUp = 1;
        else
        goingDown = 1;
        }

        • rt says:

          I remember having this problem when I tested the code. The problem is that the rotary encoder is generating interrupts too fast, especially if it bounces a lot. Do you use capacitors on the pins to reduce the bouncing a bit?
          Also, I think that the pins change status fast enough that the two “while” are executed in the same loop, causing the counter to go UP then DOWN in the same loop. Also, Serial.println is very slow, so the interrupt routine gets called while the Arduino is printing.
          Try to change the code in the interrupt routine to do the counting there. It will slow down the interrupt routine, but will count better. This will work but there is still the bouncing problem.
          Take a look at this:

          “void doEncoder()
          {
          if (digitalRead(encoderPinA) == digitalRead(encoderPinB))
          encoderPos++; // count up if both encoder pins are the same
          else
          encoderPos–; // count down if pins are different
          }”

          Excerpt From: Michael Margolis. “Arduino Cookbook.” iBooks.

  10. Donald says:

    I have had to do the routine interruption as you do, but I had the same problem before i think to do as I made up. I have observed the signal with a oszilloscope and it does not bounce even when I turn quickly. I use Arduino Leonardo , it is not slow enough to make this work?

  11. Simon Merrett says:

    Hi rt, your code inspired me to write some which is based on pin change interrupts.

    Please take a look and let me know what you think: http://m.instructables.com/id/Improved-Arduino-Rotary-Encoder-Reading/

    I limited myself to the hardware interrupt pins so that I could take advantage of the RISING flavour of interrupt in an attempt to reduce the calls to the ISR. I added cli() and sei() to avoid mid-ISR triggering of the other ISR.

    I particularly drew inspiration from your empirical approach to select what actually worked best for your application, rather than what looks best in theory.

  12. Seb says:

    Very very nice!
    Love it! It’s the only script for me that works. Other interrupt scripts (not containing digital read) required 2 clicks for one pulse (even when changing to RISING, FALLING, CHANGE, LOW). By changing your interrupt to CHANGE I got it exactly the way I wanted. Love it! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *