Interfacing Incremental Rotary Encoder with Arduino

Tutorial on interfacing incremental mechanical rotary encoder with Arduino. Interrupt based example code is included.
Rotary encoder breakout module

A rotary encoder is a cleverly designed piece of hardware that finds use in every projects that require to sense the rotatory motion of some shaft, knob etc, to find the direction of rotation, angular velocity etc. There are many types of rotary encoders of which some are designed for specific advanced applications. But here we’ll be using one of the many types of rotary encoder called an incremental rotary encoder or quadrature encoder as it is sometimes called. It works by mechanical means.

If you want to know more about a rotary encoder and how it actually works, go to Dejan Nedelkovski‘s rotary encoder tutorial here. He has also published a well explained video tutorial of rotary encoder which can be found here on YouTube. Continue from here after you’ve done that because I’m not going to talk about how it works in detail.

Incremental rotary encoder breakout module – component side
Incremental rotary encoder breakout module pinout

Following is the schematic of my breakout board. Two 10K pull-up resistors are connected to pin A (CLK) and B (DT) and tied to +5V pin. The common pin of the encoder is connected to GND pin of the board. My breakout board is missing a 10K pull-up for the center switch which isn’t necessary if you have it in your controller already. The center push button is activated by pressing the pivot against the board. It could be used as a mode select switch. The output of the switch is available at pin SW. The encoder I have has 30 steps or resolution which yields 12° per step which is enough for control input.

Incremental rotary encoder breakout module Eagle schematic – View PDF

Simply connect the breakout board as per the schematic and pins A and B to digital Pin-6 and Pin-7 of Arduino Uno board respectively. Then upload the code from Dejan Nedelkovski to your Arduino board to test your encoder.

/*     Arduino Rotary Encoder Tutorial
 *  by Dejan Nedelkovski,

#define outputA 6
#define outputB 7
int counter = 0;
int aState;
int aLastState;

void setup() {
  pinMode(outputA, INPUT);
  pinMode(outputB, INPUT);

  // Reads the initial state of the outputA
  aLastState = digitalRead(outputA);

void loop() {
  aState = digitalRead(outputA); // Reads the "current" state of the outputA
  // If the previous and the current state of the outputA are different,
  //that means a Pulse has occured
  if (aState != aLastState) {
    // If the outputB state is different to the outputA state,
    //that means the encoder is rotating clockwise
    if (digitalRead(outputB) != aState) {
    else {
    Serial.print("Position: ");
  aLastState = aState; // Updates last state of outputA with current state

If everything is done correctly, when you open the serial monitor and rotate the encoder you’ll see the the rotation count printed there.

Rotary encoder position being printed on serial monitor

So far we’re good. But there’s a problem. Basically, the above code is just for checking the encoder and see how it works. But for practical applications, that’s not enough or it won’t work the way you want it. Because the above mentioned code uses “polling” to detect any change in the encoder position. That means the loop in the code just continuously checks the status of the encoder and updates the counter. But what if your code has to do more than that, and that adds a delay to it ? That’s the problem! You might miss some increments produced by the encoder. So how we’re going to overcome this ? There’s a way and that is to use interrupts in your code. By using this method, you can do anything with your code and still not miss any change from your encoder. The interrupt routines will only be called when there’s an actual change occurs to the encoder position when you rotate it.

Kevin Darrah has an encoder video tutorial and interrupt based interfacing code. But his code didn’t work for me. So I decided to write my own. Following is the encoder output waveform at clockwise and counter clockwise rotations. We need this to design our code.

Rotary encoder logic waveform

Note that one complete cycle or 360° cycle in the waveform is not equal to one step of the encoder. One step of the encoder rotation equals to 90° in the waveform and a step change occurs at the dotted line. The start and end positions of a step are the idle fixed states of A and B after a step rotation. Don’t get confused with that.

From the output wave diagram it’s obvious that pins A and B produce different output pairs for clockwise and counter clockwise rotations. We can use this to design an ISR (Interrupt Service Routine) to update a counter. We have to only use one pin to detect the rising edge and falling edge to run the ISR because the other pin’s state won’t change at that time. Here we’ll use pin A (whatever physical pin that is) to detect the rising and falling edge and thus run the ISR. When pin A changes its state by rising or falling, the state of B will be unchanged. That’s the idea. Here’s the code I wrote.

This code is written for Arduino Uno to use interrupts of it. Digital pins 2 and 3 are the interrupt enabled pins in Uno. So connect your encoder accordingly. If you’re using a different Arduino board, set the pins as per that.

//  Incremental Rotary Encoder Interfacing using Interrupts   //
//                                                            //
//  Author : Vishnu M Aiea                                    //
//  E-Mail :                            //
//  Date : 8:36 AM 18-09-2016, Sunday (IST)                   //
//  Web :                               //
//  Github :  //
//  See my project website on              //
//  Notes : The pins are configured for working with Arduino  //
//          Uno. Digital pin 2 and 3 are interrupt capable    //
//          pins in Uno. So connect your encoder outputs to   //
//          digital pins 2 and 3 of your Uno or any other     //
//          board accordingly. Also add RC filters to the     //
//          pins.                                             //

int pulseCount; //rotation step count
int SIG_A = 0; //pin A output
int SIG_B = 0; //pin B output
int lastSIG_A = 0; //last state of SIG_A
int lastSIG_B = 0; //last state of SIG_B
int Pin_A = 2; //interrupt pin (digital) for A (change your pins here)
int Pin_B = 3; //interrupt pin (digital) for B

void setup() {
  SIG_B = digitalRead(Pin_B); //current state of B
  SIG_A = SIG_B > 0 ? 0 : 1; //let them be different
  //attach iterrupt for state change, not rising or falling edges
  attachInterrupt(digitalPinToInterrupt(Pin_A), A_CHANGE, CHANGE);

void loop() {
  //does nothing here. Add your code here

void A_CHANGE() { //Interrupt Service Routine (ISR)
  detachInterrupt(0); //important
  SIG_A = digitalRead(Pin_A); //read state of A
  SIG_B = digitalRead(Pin_B); //read state of B
  if((SIG_B == SIG_A) && (lastSIG_B != SIG_B)) {
    pulseCount--; //anti-clockwise rotation
    lastSIG_B = SIG_B;
    Serial.println(" - Reverse");
  else if((SIG_B != SIG_A) && (lastSIG_B == SIG_B)) {
    pulseCount++; //clockwise rotation
    lastSIG_B = SIG_B > 0 ? 0 : 1; //save last state of B
    Serial.println(" - Forward");
  attachInterrupt(digitalPinToInterrupt(Pin_A), A_CHANGE, CHANGE);

Unlike many other interfacing codes you might find on the web, this code does not “miss” steps and doesn’t add any delays intentionally. It only uses a single pin to detect rising and falling edges (and thus any state changes) to trigger the ISR and therefore the code and its logic are less complex and easy to understand. I had spent a whole day on this.

When we rotate the encoder in clockwise direction, the output of A changes twice; one rising edge and one falling edge. In the waveform diagram, UP arrow indicates rising edge or positive going edge and DOWN arrow indicates falling edge or negative going edge. At the rising edge of A or when A becomes 1 (HIGH), the state of B would be opposite to that, ie 0 (LOW). This is same for falling edge of A. So for clockwise rotation, A and B will always be in opposite states.

For counter clockwise rotation, rising edge and falling edges of A will be equal to the state of B. You can see that in the waveform diagram. So we can just check if SIG_A equals SIG_B whenever a state change occurs to output A.

But there’s a small issue. Just checking if A equals B does not determine whether the encoder is rotated clockwise or counter clockwise. If you take a closer look at the waveform you’ll notice that the state change happens to A in the same part of the graph could be a rising or falling edge. As we’re not detecting rising or falling edges of A directly but just detecting any state change, we can not be sure about whether we had detected a rising edge or falling edge. To determine that, we need to know the previous state of A or B. Here I’ve used B’s current state and last state to determine the falling edge and rising edge of A, because it’d be more easy to interpret it from the waveform. So that’s the logic of the following lines,

if ((SIG_B == SIG_A) && (lastSIG_B != SIG_B)) 
else if ((SIG_B != SIG_A) && (lastSIG_B == SIG_B))

In the first if block, we save the current state of B to lastSIG_B. But in the next else if block, we save the lastSIG_B as the inverse of the current state of B. This is because we’re not detecting any change happens to output B. Such change to B happens halfway around when we rotate the shaft for one step. So there’s no way can detect it when it happens. But we could assume what would happen. Therefore we save the inverse of SIG_B to lastSIG_B so that next time we detect a clockwise operation, we won’t miss a step. Otherwise you’ll miss intermediate clockwise steps. Try disabling the line lastSIG_B = SIG_B > 0 ? 0 : 1; or replace it with lastSIG_B = SIG_B; to see what happens.




  1. Dejan Nedelkovski’s Rotary encoder tutorial
  2. Kevin Darrah’s rotary encoder tutorial
Share to your friends
Vishnu Mohanan

Vishnu Mohanan

Founder and CEO at CIRCUITSTATE Electronics

Articles: 83

Leave a Reply

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

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.