Posts

Reading rotary encoder on Arduino

Rotary encoder connected to Arduino

Rotary encoder connected to Arduino


Quadrature rotary encoders, also known as rotary pulse generators, are popular input devices for embedded platforms, including Arduino. Several rotary encoder code examples are posted on Arduino site and elsewhere, however, they treat encoder as a pair of switches, adding decoding/debouncing overhead. For many years, I used an algorithm based on the fact that quadrature encoder is a Gray code generator and if treated as such, can be read reliably in 3 straight step without need for debouncing. As a result, the code I’m using is very fast and simple, works very well with cheap low-quality encoders, but is somewhat cryptic and difficult to understand. Soon after posting one of my projects where I used rotary encoder to set motor speed i started receiving e-mails asking to explain the code. This article is a summary of my replies – I’m presenting small example written for the purpose of illustrating my method. I’m also going through the code highlighting important parts.

The hardware setup can be seen on title picture. The encoder from Sparkfun is connected to a vintage Atmega168-based Arduino Pro. Common pin of the encoder is connected to ground, pins A and B are connected to pins 14 and 15, AKA Analog pins 0 and 1, configured as digital inputs. We also need a serial connection to the PC to receive power, program the Arduino, and send program output to the terminal. For this purpose, Sparkfun FTDI basic breakout is used.

Connecting encoder pins to pins 0 and 1 of 8-bit MCU port makes encoder reading code very simple. If analog pins are needed for something else, it is possible to move encoder to digital pins 8,9 or 0,1 (losing serial port) with no modification of code logic. While technically using any two consecutive port pins is possible with a bit of tweaking, using non-consecutive pins for encoder input with this method is not recommended. Lastly, it sometimes hard to determine which encoder pin is A and which is B; it is easier to connect them at random and if direction is wrong, swap the pins.

Example code is posted below. It is complete sketch – you can copy it from this page and paste into Arduino IDE window, compile, upload and run. The result of rotating the encoder can be seen in terminal window. Note that serial speed in the sketch is set to 115200, you will need to set your PC terminal to that speed as well. The explanation of the code is given after the listing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Rotary encoder read example */
#define ENC_A 14
#define ENC_B 15
#define ENC_PORT PINC
 
void setup()
{
  /* Setup encoder pins as inputs */
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);
  Serial.begin (115200);
  Serial.println("Start");
}
 
void loop()
{
 static uint8_t counter = 0;      //this variable will be changed by encoder input
 int8_t tmpdata;
 /**/
  tmpdata = read_encoder();
  if( tmpdata ) {
    Serial.print("Counter value: ");
    Serial.println(counter, DEC);
    counter += tmpdata;
  }
}
 
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
  static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  /**/
  old_AB <<= 2;                   //remember previous state
  old_AB |= ( ENC_PORT & 0x03 );  //add current state
  return ( enc_states[( old_AB & 0x0f )]);
}

First three #defines set names for port pins. This way, if you want to use different pins for your encoder, you can do it here without sifting through code looking for pin names. If you’d like to move encoder pins to Arduino pins 8 and 9, ENC_PORT shall be defined as PINB, and for pins 0,1 as PIND.

In setup() function hardware is initialized. pinMode sets the pin as input so we can read it and digitalWrite turns on pull-up resistor on the pin so it won’t dangle in the air when switch is open. Finally, the serial port is initialized and message is printed as an indicator that port is working – if you can’t see “Start” on your terminal screen, check PC port settings.

Let’s go down to line 30, where read_encoder() function starts. enc_states[] array is a look-up table; it is pre-filled with encoder states, with “-1” or “1” being valid states and “0” being invalid. We know that there can be only two valid combination of previous and current readings of the encoder – one for the step in a clockwise direction, another one for counterclockwise. Anything else, whether it’s encoder that didn’t move between reads or an incorrect combination due to switch bouncing, is reported as zero.

Note that old_AB is declared static. This means that it’s value will be retained between function calls therefore previous encoder reading will be preserved. When function is called, old_AB gets shifted left two times (line 36) saving previous reading and setting two lower bits to “0” so the current reading can be correctly ORed here. Then ENC_PORT & 0x03 reads the port to which encoder is connected and sets all but two lower bits to zero so when you OR it with old_AB bits 2-7 would stay intact. Then it gets ORed with old_AB (line 37, old_AB |= ( ENC_PORT & 0x03 )). At this point, we have previous reading of encoder pins in bits 2,3 of old_AB, current readings in bits 0,1, and together they form index of (AKA pointer to) enc_states[] array element containing current state – either increment, decrement, or no change. All that is left to do is return this state to the calling function, and line 38 does just that. Upper half of old_AB gets zeroed by old_AB & 0x0f – if we don’t do this, we will be reading memory past enc_states[] array.

The above paragraph was pretty long explanation for mere 5 lines of code (counting two declarations); if you are reading this, 98% of work is done. The rest of the code is very easy. We are now moving up to line 17, where loop() function is residing. counter variable is the one which is modified by encoder and tmpdata is used to hold the reading. On line 22, encoder is read. If encoder state has changed, i.e., read_encoder() returned non-zero, current value of counter variable is printed and then counter is updated with tmpdata. This is done within conditional statement on lines 22-27. The loop then ends and starts again.

Now let’s briefly talk about real-life applications of this method. If you’re using Sparkfun encoder, you’ll notice that it gives 4 increments per click and it’s impossible to make it hold position between clicks. Therefore, to count clicks you will need to divide counter variable by 4. Also, if counter larger than 255 is necessary, the declaration for it would have to be changed to uint16_t or longer. Another nice modification, left out to keep code simple, would be to move enc_states[] array to program memory using PROGMEM type saving several bytes of precious RAM.

Rotary encoder singing out of tune

Rotary encoder singing out of tune


In order for this method to work well, read_encoder() function needs to be called fairly often . To see what happens when loop is slow, lower serial port speed – go to line 13, change “115200” to “9600”, recompile the sketch and run (don’t forget to reconfigure the terminal on PC side). Picture on the left shows the result. Note that encoder goes down from 237 to 229, then jumps up to 230, then continues going down. Sometimes it counts correctly but gives 2 or 3 states per click instead of 4. Instabilities like this are good indication of slow loop. If encoder is used to things like setting motor speed or sound volume, this behavior won’t do much harm because encoder will never skip back more than one state and overall counting would still be in the right direction. However, if encoder is used for interfacing with display, glitches like that are very annoying. Therefore, for user interface applications, loop time should be kept short and in some cases it may be even necessary to convert encoder reading function into interrupt service routine. Given the size and speed of encoder read function, such conversion can be done without much difficulty.

To summarize: I presented a method to convert quadrature rotary encoder output into direction information using look-up table. The method makes debouncing unnecessary, and as a result, conversion function implemented with this method is very short, fast, and works very well on small 8-bit MCU platforms, such as Arduino.

Oleg.
[EDIT] I posted a new article describing reading an encoder from an ISR.

241 comments to Reading rotary encoder on Arduino

  • Ok thanks a lot – that is really helpful. But as soon as I integrated this code with some more complex code I have using pulseIn() function to understand the input of a standard RC receiver, and a motor driver controlling two motors, the encoder seems totally incorrect: When I had the the same functions working on the encoder, I was getting super clean values. But when I had more code and the encoder stuff running side by side, my counter values are totally off (see below, when spinning the encoder round and round) . So it seems like the timing is totally not working –
    I’m wondering what I might be doing wrong because nothing too “high level” is happening in my code, and I took out all my “println”s.

    I can attach my code if that’d be helpful, but was wondering your thoughts / experience (re using 2 encoders with some other processes going on in Arduino UNO)
    Thank you so so much again for all the help and explanation.

    ¾ÖŸReady
    Counter value2: 0
    Counter value2: 255
    Counter value2: 0
    Counter value2: 255
    Counter value2: 0
    Counter value2: 255
    Counter value2: 0
    Counter value2: 255
    Counter value2: 0
    Counter value2: 255
    Counter value2: 254
    Counter value2: 253
    Counter value2: 254
    Counter value2: 253
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 253
    Counter value2: 252
    Counter value2: 251
    Counter value2: 252
    Counter value2: 251
    Counter value2: 250

  • Hm. Yea I commented everything out and the encoders work great. When I add back in stuff to read the receiver , that’s the point at which the encoders seem to stop working:

    Channel2Value = pulseIn (RX1, HIGH, 20000); //read RC channel 2

    Channel1Value = pulseIn (RX3, HIGH, 20000); //read RC channel 2
    // Serial.print(” motor_body : “);

    I’m wondering what you mean by the code is blocking somewhere… or what a “fix” would be ? ?

    Thanks a lot again

    • Blocking is when a piece of code is waiting for some event to happen, interrupting the flow of execution of the main loop. For example, delay() is blocking; the program won’t continue until time specified as delay has expired. If you have a delay of one second in your main loop, you can rotate your encoder full circle and miss every pulse.

      Fixing means finding blocking code and writing it differently. What does ‘20000’ mean in pulseIn?

  • Ok I guess I have to use an interrupt? I wanted to be able to use Arduino UNO with the two encoders. Would there be a way to use two encoders with Arduino UNO , even if the two default interupt pins are 2 and 3?
    the third parameter (20000) is the time out in microseconds.

    just fyi, PulseIN() : Reads a pulse (either HIGH or LOW) on a pin. For example, if value is HIGH, pulseIn() waits for the pin to go HIGH, starts timing, then waits for the pin to go LOW and stops timing. Returns the length of the pulse in microseconds. Gives up and returns 0 if no pulse starts within a specified time out. so it seems the problem is it “delays” and waits (ie blocking)

  • Hi Oleg

    I really appreciate your tutorials, they are great and am wondering if you could put me in the right direction as I am building a bi-directional people sensor and counter in a room using two infrared sensors (S1 and S2).What I want is the first sequence on breaking S1 and S2 the change should signal entry and increment a counter whilst breaking the beam in the second sequence S2 first and S1 will signal exit and decrement a counter and display the count value on a 3 digit 7 segment display.
    I have thought of using the idea of reading two rotary encoder using external interrupts on an arduino. Is this the right way to go??

    Much Thanks.

    Steffano.

  • Hi Oleg,

    Please could you kindly elaborate on what you mean by coding the sensor states directly?since it’s a sequence.
    am using IR sensors with make and break beam.I will appreciate any more information.

    Thank you.
    Staffano.

  • Hi Oleg,

    This is the far have come so far and am serial printing the counts. Any idea how I could hold old value and update this old value with the current val count to a 3 digit seven segment display? depending on how the sensors are tripped? Any correction or suggestions am very willing to learn

    int counter, S1_SIG=0, S2_SIG=1;

    void setup(){
    attachInterrupt(0, S1_RISE, RISING);//hooked to pin2 on mega 2560
    attachInterrupt(1, S2_RISE, RISING);//hooked to pin3
    Serial.begin(115200);
    }//setup

    void loop(){

    }

    void S1_RISE(){
    detachInterrupt(0);
    S1_SIG=1;

    if(S2_SIG==0)
    counter++;//entry
    if(S2_SIG==1)
    counter–;//exit
    Serial.println(counter);
    attachInterrupt(0, S1_FALL, FALLING);
    }

    void S1_FALL(){
    detachInterrupt(0);
    S1_SIG=0;

    if(S2_SIG==1)
    counter++;//entry
    if(S2_SIG==0)
    counter–;//exit
    Serial.println(counter);
    attachInterrupt(0, S1_RISE, RISING);
    }

    void S2_RISE(){
    detachInterrupt(1);
    S2_SIG=1;

    if(S1_SIG==1)
    counter++;//entry
    if(S1_SIG==0)
    counter–;//exit
    Serial.println(counter);
    attachInterrupt(1, S2_FALL, FALLING);
    }

    void S2_FALL(){
    detachInterrupt(1);
    S2_SIG=0;

    if(S1_SIG==0)
    counter++;//entry
    if(S1_SIG==1)
    counter–;//exit
    Serial.println(counter);
    attachInterrupt(1, S2_RISE, RISING);
    }

    Steffano

  • steffano

    I managed to do my 2 ISR for the two sensors and the 3 digit seven segment display, however the only problem have got is when my sensors are broken at first the count goes negative for 2 or 3 cycles then comes to positive(normal) does anyone have an idea how to correct this??please. I have declared static int i in loop.

  • Windsor

    Hi, thanks for the great tutorial. I adapted your code (with minor changes) to run on an STM32F100B (ARM) micro. Works flawlessly with my encoder without the need for any hardware debouncing circuitry!

  • Josh

    Hi Mr. Oleg,
    Good day to you and thanks for the great effort helping a rookie like me to start with the arduino, I have tried your code using Uno and it works flawlessly but when i tried it using the Leonardo, it didn’t work, wandering what could be the problem.

  • Josh

    Hi Mr. Oleg,
    Below is the code I used with Uno and Leonardo, it works with Uno but not with Leonardo.

    /* Rotary encoder read example */
    #define ENC_A 14
    #define ENC_B 15
    #define ENC_PORT PINC
    // constants won’t change. They’re used here to
    // set pin numbers:
    const int buttonPin = 2; // the number of the pushbutton pin
    const int ledPin = 13; // the number of the LED pin

    // variables will change:
    int buttonState = 0; // variable for reading the pushbutton status
    int switch0=0;
    void setup() {
    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);

    // initialize serial communication at 9600 bits per second:
    Serial.begin(115200);
    Serial.println(“Start”);
    delay(1);
    // initialize the LED pin as an output:
    pinMode(ledPin, OUTPUT);
    // initialize the pushbutton pin as an input:
    pinMode(buttonPin, INPUT);
    }

    void loop(){
    static uint8_t counter = 0; //this variable will be changed by encoder input
    int8_t tmpdata;
    /**/
    tmpdata = read_encoder();
    if( tmpdata ) {
    Serial.print(“Counter value: “);
    Serial.println(counter, DEC);
    counter += tmpdata;
    }
    // read the state of the pushbutton value:
    buttonState = digitalRead(buttonPin);

    // check if the pushbutton is pressed.
    // if it is, the buttonState is HIGH:
    if (buttonState == LOW) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
    // print out the state of the button:
    if (switch0 == 0){
    Serial.print(“Pinindot”);
    delay(1);
    Serial.println(buttonState);
    delay(1); // delay in between reads for stability
    switch0=1;
    }
    }
    else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
    if (switch0==1){
    Serial.print(“Pinindot”);
    delay(1);
    Serial.println(buttonState);
    delay(1); // delay in between reads for stability
    switch0=0;
    }
    }
    }

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder()
    {
    static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
    static uint8_t old_AB = 0;
    /**/
    old_AB <<= 2; //remember previous state
    old_AB |= ( ENC_PORT & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

  • Josh

    The debounce did not work for me but instead I used the switch which is perfect. The only output shown in the Serial Monitor is when the switch is being pressed but not the encoder, I am using an encoder with a switch.

  • Mike

    Ok, I get it and I like it a lot. The only thing that I cannot figure out is how to keep it from going from 255 to 0 or from 0 to 255. Is there a way that I can stop it from overflowing the register and just park it at max or min until the user decreases the value?

  • alwerner

    Using two or more encoder with the Arduino ?

    It all depends on the total of tasks being performed by the Arduino, and the number of
    pulses received by the processor.

    Say for example you have a small slow moving xy table, with 2 rotary US Digital encoders generating say 4000 pulses per second and driving two stepper motors with an “Easydriver” board you would soon be running out of head room on your project.

    My suggestions would be to use, two Tiny13 chips from Atmel to encode the rotary encoders, and than feed the resultant signal into your Arduino, where it will give an excellent performance. In my humble opnion it may even support three channels, XY and Z.

    You would need some machine code experience to do this, but this would be worth learning.

    Good luck.

  • Seif

    Hi,

    Thank you for this great useful article… I’m wondering if I can use you idea with Arduino Uno?? I mean to use the rotary encoder with Arduino Uno in order to select the power of my RC car which I’m building…

    thanx again

  • Salvatore Aiello

    Hi,
    Sweet bit of code! I am using it in a project at work, using the encoder to display a motor speed selection on a seven segment display and it works very nicely indeed. Once you remove the serial.print commands it is difficult to spin the bare encoder shaft quickly enough to generate a misread. With a suitably sized knob on the shaft it is going to slow rotation speed and make it virtually impossible for the casual operator to get an erroneous reading… 😉

    Thanks!

  • Marco

    Hi, very nice job
    my R2D2 Dome’s encoder sensor has 3 output, one of them reads just one special dent on the motor gear, like a ZERO POSITION.
    How can I implement it in the code?
    Also How can I make encoders count to 36 or +18 to -18?
    Thanks just learning here

  • Alex

    Here is the code with zero position and 3 outputs.

    int counter, S1_SIG=0, S2_SIG=1, S3_SIG=1;
    bool firstinit = false;

    void setup()
    {
    attachInterrupt(0, S1_RISE, RISING);//hooked to pin2 on mega 2560
    attachInterrupt(1, S2_RISE, RISING);//hooked to pin3
    attachInterrupt(2, S3_RISE, RISING);//hooked to pin21
    Serial.begin(115200);
    }//setup

    void loop()
    {

    }

    void S1_RISE()
    {
    detachInterrupt(0);
    S1_SIG=1;

    if(S2_SIG==0)
    counter++; //entry
    if(S2_SIG==1)
    counter–; //exit
    Serial.println(counter);
    attachInterrupt(0, S1_FALL, FALLING);
    }

    void S1_FALL()
    {
    detachInterrupt(0);
    S1_SIG=0;

    if(S2_SIG==1)
    counter++; //entry
    if(S2_SIG==0)
    counter–; //exit
    Serial.println(counter);
    attachInterrupt(0, S1_RISE, RISING);
    }

    void S2_RISE()
    {
    detachInterrupt(1);
    S2_SIG=1;

    if(S1_SIG==1)
    counter++;//entry
    if(S1_SIG==0)
    counter–;//exit
    // Serial.println(counter);
    attachInterrupt(1, S2_FALL, FALLING);
    }

    void S2_FALL()
    {
    detachInterrupt(1);
    S2_SIG=0;

    if(S1_SIG==0)
    counter++; //entry
    if(S1_SIG==1)
    counter–; //exit
    Serial.println(counter);
    attachInterrupt(1, S2_RISE, RISING);
    }

    void S3_RISE()
    {
    S3_SIG = 1;
    detachInterrupt(2);

    if (!firstinit && (( S2_SIG==1 && S1_SIG==1) || (S1_SIG==0 && S2_SIG==1)))
    {
    counter=0;
    firstinit = true;
    }

    attachInterrupt(2, S3_FALL, FALLING);
    }

    void S3_FALL()
    {
    S3_SIG = 0;
    detachInterrupt(2);

    if ( !firstinit && (( S2_SIG==1 && S1_SIG==1) || (S1_SIG==1 && S2_SIG==0)))
    {
    counter=0;
    firstinit = true;
    }

    attachInterrupt(2, S3_RISE, RISING);
    }

  • Marty

    I am new to Arduinos and am starting to play with them. I have seen many examples of rotary encoder code. But non that use the Leonardo to output key press commands when rotated. My application is a small CNC vertical machining center. The control itself can not accept a rotary encoder (manual pulse generator) The control is PC based. It CAN accept key presses, for example, to move the table in these directions:
    X+ Ctrl + Right Arrow Key
    X- Ctrl + Left Arrow Key
    Y+ Ctrl + Up Arrow Key
    Y- Ctrl + Down Arrow Key
    Z+ Ctrl + Page Up Key
    Z- Ctrl + Page Down Key
    A+ Ctrl + Plus (+) Key
    A- Ctrl + Minus (-) Key

    I can make it work with out a Ctrl key press, but better to have the combination if possible. I was thinking of using a 4 position rotary switch. Each position would be for each axis, X,Y,Z and A. When the switch is in the proper position, then depending on which direction the MPG handwheel was turned, Leonardo would send the appropriate keyboard character via the PC’s USB port. One keypress for each pulse of the MPG hand wheel. It is a 100PPR hand wheel.

    Is this doable and might someone be able to help cut my learning curve down with an example of the code? Thanks!

  • faultymonk

    Just as a note to people trying to use a Leonardo (or Micro), the code example will work if you use:

    #define ENC_A A4
    #define ENC_B A5
    #define ENC_PORT PINF

  • Ron

    You mention this:
    “… Sparkfun encoder, you’ll notice that it gives 4 increments per click and it’s impossible to make it hold position between clicks. Therefore, to count clicks you will need to divide counter variable by 4”

    My Quadrature encoders are giving 2 counts per click. Would you mind a quick lesson on just how to do this, since the variable isn’t a decimal number?

    Many thanks! Your code is forcing me to “dig in” and learn more than just the “simple” programming, as given on the Arduino site examples… which is what “teaching” is all about!

  • Ron

    I got it… one of those “doh!” moments… sorry!

  • Ron

    (with regard to the solution for 24-pos encoder which gives 2 pulses per “click”)

    What happens when I bitwise shift(1bit) right a negative number, as when I’m turning CCW?
    Does the ‘sign’ bit stay where it’s supposed to?

    I’m getting very different results CW from CCW.

    • You don’t shift numbers, you shift bytes. When you right shift the leftmost bit becomes zero. If this bit represented a sign, it will be gone.

    • If your encoder clicks every second pulse you can determine a click position without dividing. Even increments will have zero in bit 0 and odd increments will have one. This is true for both signed and unsigned representations. Also, checking single bit, like if( count & 0x01 ) { ..., could be faster than shifting.

  • fbonan

    Thanks Oleg, this sample is great and save me years : )
    Many requested a complete working example with 2 encoders, here it is:

    #define ENC_1_A A0
    #define ENC_1_B A1

    #define ENC_2_A A2
    #define ENC_2_B A3

    int encoder_1_value = 0;
    int encoder_2_value = 0;

    static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};

    void setup() {
    /* Setup encoder pins as inputs */
    pinMode(ENC_1_A, INPUT);
    digitalWrite(ENC_1_A, HIGH);
    pinMode(ENC_1_B, INPUT);
    digitalWrite(ENC_1_B, HIGH);

    /* Setup encoder pins as inputs */
    pinMode(ENC_2_A, INPUT);
    digitalWrite(ENC_2_A, HIGH);
    pinMode(ENC_2_B, INPUT);
    digitalWrite(ENC_2_B, HIGH);

    Serial.begin (115200);
    Serial.println(“************ Start *************”);
    }

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder1() {
    static uint8_t old_AB = 0;

    old_AB <<= 2;
    old_AB |= ((digitalRead(ENC_1_B))?(1<<1):0) | ((digitalRead(ENC_1_A))?(1<<0):0);
    return ( enc_states[( old_AB & 0x0f )]);
    }

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder2() {
    static uint8_t old_AB = 0;

    old_AB <<= 2;
    old_AB |= ((digitalRead(ENC_2_B))?(1<<1):0) | ((digitalRead(ENC_2_A))?(1<<0):0);
    return ( enc_states[( old_AB & 0x0f )]);
    }

    void loop() {
    int8_t encoderdata1;
    int8_t encoderdata2;

    encoderdata1 = read_encoder1();
    if ( encoderdata1 ) {
    encoder_1_value += encoderdata1;
    Serial.print("Encoder 1: ");
    Serial.println(encoder_1_value);
    }

    encoderdata2 = read_encoder2();
    if ( encoderdata2 ) {
    encoder_2_value += encoderdata2;
    Serial.print("Encoder 2: ");
    Serial.println(encoder_2_value);
    }

    }

    —————-
    Fabrice

  • prins

    great tutorial.

    I found HID board using PIC18F4550 chip capable of reading 16 rotary encoders (all types of rotary) in real-time, and no additional chip. anyone knows how to do that in arduino. what method is it implementing, timer or interrupt?

  • mark

    how can I use this code on mega 2560. I get serial to print “start” but that’s it. What pine do I use?

    • For 2560, you don’t need to do anything, the pins are switched in the code during compile.

      • mark

        Thanks,

        So I’m using analog pins 14 and 15 but get no response from the encoder. Tried 14 and 15 Digital, no response.

        “If you’d like to move encoder pins to Arduino pins 8 and 9, ENC_PORT shall be defined as PINB, and for pins 0,1 as PIND.”

        why does ENC_PORT change when changing pins, is this unique to the board your using?

        whats ENC_PORT do?

        • ENC_PORT is a #define for a port name. Does it work if you’re using the same pins as I?

          • Jay

            Hi Oleg, I also have the same problem with a MEGA256, I don’t get response from the encoder.
            Tried changing ENC_PORT to PINB PINC and PIND, none of them work.
            Any idea?

          • Make sure your encoder is connected to the port your sketch is reading, port bits 0 and 1.

          • Jay

            Yes, it’s properly wired and connected to the right pins

          • It should work then, but it doesn’t. Maybe your testing needs to be changed? Include the Arduino itself. For example, make sure you can read a singe pin. Connect a pushbutton to a data pin and write a sketch which reads the state of this pin and prints it out. There are many examples on the net doing similar things. Then move the button to one of the pins intended for the encoder and make it work there, then move to the other pin. Then connect the encoder, it’s really a pair of pushbuttons with some clever sequencer. Then modify my sketch with what you have learned – it will work.

            Post a good picture of your setup to the accompanying G+ community so that other people can see it and help you too -> https://plus.google.com/u/0/communities/113627505540902841340

  • Allan

    is it possoble to measure wheel speed using a rotary encoder incremental with output A, B an Z

  • aditya

    I have 12V , 500 RPM, 15 Kgcm DC geared motor and encoder. How could I control the DC motor to rotate in forward, Reverse direction and No of rotations of DC motor shaft.

  • Hi Oleg,

    I adapted this code to a PIC chip project I was/am working on many months ago. It made my rotary knob functionality perfectly clean for the first time. The code was more compact as well. I never did mention it, but this was really helpful to me. It works very very well on a rotary encoder with absolutely no hardware debouncing. The signal is really ugly.

    Just wanted to say thanks. I’ve tried lots of debounce code. This is not only the best I’ve found, but the best by far.

  • Liam Goudge

    Hi Oleg.
    What an elegant algorithm; read_encoder is a very nice way to implement the state machine.
    Thanks for publishing and sharing your ideas.
    Very simple port to the Freescale platform on ARM”s MBED.
    Worked very well but I did need 100nF of anti-bounce on my Panasonic EVE-KE2F2024B encoder, perhaps because this CPU is clocked so much faster than the ATMEGA and AVRs.

  • Jeff Beougher

    I am attempting to connect 6 encoders to an Arduino Leonardo with a MCP23017 I2c Port Expander. I have been able to use one encoder on the Arduino directly. What I am attempting to do is depending if the encoder is turned up or down to send a keyboard scan key to to the computer for a project. I was hoping to have some type of multiplexing to be able to read each encoder and know if they have been turned. The MCP23017 has not helped. I know it works because I have been able to use the example codes and blink an led and see button press input.

    So my question is how can I multiplex 6 rotary encoders. Either using the MCP 23017 or a different input expander of some type.

    Links to the encoder and MCP.

    http://www.adafruit.com/products/377
    http://www.adafruit.com/products/732

  • Neelam

    Hi,

    My project is to control the speed of the dc motor(Hardware) using PID controller in LabVIEW. For interfacing Labview with dc motor, I am using Arduino Uno. For encoder, I am using Optocoupler and a schmitt trigger. The encoder has three terminals. Red brown & black. red and black are for +5v and ground respectively and brown will read the value (i.e no.of rotations) from the dc motor. This output should be connected to arduino which in turn will pass this value to labview. Please tell me on which pin of arduino I should connect that brown wire.

    From digital pin 3 of arduino, I am passing PWM signal to the dc motor. I tried to connect that brown wire of encoder to pin 5 but I am getting an unknown error.

    Please help me in this.

  • peter

    Hi Oleg.
    thanks for your demonstration.

  • Robert

    Thanks Oleg, your code works great on the Sparkfun illuminated RGB encoders.

    To get it to work on my MEGA 2560, I used A0 and A1, changing the definitions to:
    #define ENC_A A0
    #define ENC_B A1
    #define ENC_PORT PINF

  • Herman

    I am new at Arduino (Uno) and writing code so this is a good example for me. Thanks Oleg!
    I have a bag of encoders that seem to work different. I put some LEDs on the terminals to see how they work. When turning clockwise both outputs become high similtanious, then one goes low and then the other goes low. When turning counter/anti clockwise the first one becomes high, then the second one and then they both get low at the same time. Like this:
    010 010 010
    011 011 011

    Is there an easy way I can change this code so my rotary encoders will work? I hope you can help me.

    • I’m not sure about easy way. The fundamental property of Grey code encoder is that only one output is changing at a time so that when both has changed simultaneously you would know that the outputs are wrong somehow. You can change the lookup table to give proper increment/decrement for your sequence but then you’d have to add a debounce.

      • Herman

        Thanks Oleg! The encoders were extremely cheap on Ebay. Now I know why 😉 I will try playing with the code myself. If it works I will post the solution here.

        • sofad

          hi herman,

          i´m interested in a solution for this too, since i want to use a special encoder with an integrated 4 position hat. this encoder works like yours, with changing outputs.

  • Hi Oleg

    Hi, Oleg.
    I connect my encoder to pins 10 and 11 on my Uno board, and code did notwork, even i define ENC_A and ENC_B to those pins.
    How to implement this code to pins 10 and 11.
    I feel i should change left shift operator and enc_states pattern. But how?

    • Hi Oleg

      Let me answer to myself.
      To use pin 10 and 11 in PINB, we need to correct string number 37 to

      old_AB |= ( ENC_PORT>>2 & 0x03 ); //add current state for pin 10 and 11. We need to shift right on two bites

  • Bill

    Hi Oleg,
    I am using your code on a Arduino mega 2560 board. I have a rotary encoder(incremental) 1024 p/r hooked up using pins 36 & 37. Theverything seems to work okay but the counter will only go up to 255 and then it starts back at 0 after not even a quarter turn. Is there a way to change the code so I can read a full revolution of the encoder (1024)
    Thanks
    Bill

  • Bill

    Hi Oleg,
    Thanks for getting back to me. I did change the counter size too uint16_t. In 1 CW rotation the counter showed 926. I reset the board and tried another CW rotation and the counter showed 680 If I then turn CCW the counter will go back to 0 then display 65535, the max number for uint16_t.
    I tried again using int8_t and I am getting weird readouts in the serial monitor. I turned CW from 0 and at 127 the next number was -128 as shown below.
    Counter value: 116
    Counter value: 117
    Counter value: 118
    Counter value: 119
    Counter value: 120
    Counter value: 121
    Counter value: 122
    Counter value: 123
    Counter value: 124
    Counter value: 125
    Counter value: 126
    Counter value: 127
    Counter value: -128
    Counter value: -127
    Counter value: -126
    Counter value: -125
    Counter value: -124
    Counter value: -123
    Counter value: -122
    Counter value: -121
    Counter value: -120
    Counter value: -119
    Counter value: -118
    Counter value: -117
    Counter value: -116
    Counter value: -115
    Counter value: -114
    Counter value: -113
    Counter value: -112
    Counter value: -111
    Counter value: -110
    Counter value: -109
    Counter value: -108
    I don’t know what to think here. I am running a arduino mega 2560 board with a dfRobot lcd keypad shield mounted to it. i have a yumo E6B2-CWZ3E Rotor Encoder(Incremental) 1024-P/R. I have the A-B wires plugged into 36 -37 of the mega board, the 5v lead to the 5v connection on the board and the common lead hooked up top ground. I want to be able to have the counter display The total number of pulses for 1 CW rotation 1024? and when rotated CCW 1 rotation the counter should display -1024. or what ever the total number of pulses for 1 rotation. The shaft would only be turned by hand. Is this possible with this code? I very new to arduino and your comments are greatly appreciated.
    Bill

    • You are mixing ‘int_n*’ and ‘uint_n’. ‘u’ means unsigned, it can’t hold negative values. Also, if you kept initialization of ‘counter’ as in the example, you would only get ‘1’ or ‘65535’ on the first reading. If you’re getting random values after reset check if ‘counter’ was initialized.

  • Bill

    Not sure whats going on with the code Oleg, I am missing something.

    /* Rotary encoder read example */
    // include the library code:
    #include
    #define ENC_A 14
    #define ENC_B 15
    #define ENC_PORT PINC
    // these constants won’t change. But you can change the size of
    // your LCD using them:
    const int numRows = 2;
    const int numCols = 16;
    // initialize the library with the numbers of the interface pins
    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
    void setup()
    {
    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);
    Serial.begin (115200);
    Serial.println(“Start”);
    // set up the LCD’s number of columns and rows:
    lcd.begin(numCols, numRows);

    }

    void loop()
    {
    static uint8_t counter = 0; //this variable will be changed by encoder input
    int8_t tmpdata;
    /**/
    tmpdata = read_encoder();
    if( tmpdata ) {
    Serial.print(“Counter value: “);
    Serial.println(counter, DEC);
    counter += tmpdata;

    lcd.setCursor(0, 0);
    lcd.print(“Counter Value: “);
    lcd.setCursor(0, 1);
    lcd.print (counter, DEC);
    }
    }

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder()
    {
    static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
    static uint8_t old_AB = 0;
    /**/
    old_AB <<= 2; //remember previous state
    old_AB |= ( ENC_PORT & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

    To change the counter where do I have to change to uint16_t, where ever there is a unit8_t?

    Is it possible to get this to count to 1024 for 1 CW rotation or should I be looking at different code. I want to read the position of the encoder as if it was a high res tuning knob.
    Bill

  • Ilpg

    Hi Oleg!
    I am still pretty new to arduino and i have a problem adapting your code. is it possible to increase the value by 0.1?

    I tried to modify the values in the array (see outcommented line below)but it didnt work…

    or is it possible to get the 1/10th steps increasing/decreasing value by a simple division by 10. but i got no clue where to place this division.
    or do i have to use your code in a subfunction to use the value set by the rotary encoder in a second program part that will substract values from the value set in the first part.

    hope you can help me to get me on my way…
    thanks a lot!
    best regards
    martin

    /* Rotary encoder read example */
    #define ENC_A 14
    #define ENC_B 15
    #define ENC_PORT PINC

    void setup()
    {
    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);
    Serial.begin (115200);
    Serial.println(“Start”);

    }

    void loop()
    {
    static uint8_t benzin = 0; //this variable will be changed by encoder input
    int8_t tmpdata;

    /**/
    tmpdata = read_encoder();
    if( tmpdata ) {
    Serial.print(“Benzin: “);
    Serial.println(benzin);
    benzin += tmpdata;

    benzin = constrain(benzin,0.1,30);

    }
    }

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder()

    {
    static int8_t enc_states[] = {0, -1, 1,0, 1,0,0, -1, -1,0,0, 1,0, 1, -1,0};
    // static int8_t enc_states[] = {0,-0.1,0.1,0,0.1,0,0,-0.1,-0.1,0,0,0.1,0,0.1,-0.1,0};
    static uint8_t old_AB = 0;

    /**/
    old_AB <<= 2; //remember previous state
    old_AB |= ( ENC_PORT & 0x03 ); //add current state

    return ( enc_states[( old_AB & 0x0f )]);
    }

    • Encoder always increments/decrements by 1. You can always add/subtract 0.1 to the variable that hold count but you need to either make it float or use fixed-point arithmetic.

  • James

    Hi Oleg,
    I want to do interrupt with your code,
    Is this right?

    /* Rotary encoder read example */
    #define ENC_A 2
    #define ENC_B 3
    #define ENC_PORT PIND

    void setup()
    {
    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);
    Serial.begin (115200);
    Serial.println("Start");
    attachInterrupt(0, doEncoder, CHANGE);
    }

    void loop()
    {
    static uint8_t counter = 0; //this variable will be changed by encoder input
    int8_t tmpdata;
    /**/
    tmpdata = doEncoder();
    if( tmpdata ) {
    Serial.print("Counter value: ");
    Serial.println(counter, DEC);
    counter += tmpdata;
    }
    }

    /* returns change in encoder state (-1,0,1) */
    void doEncoder()
    {
    static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
    static uint8_t old_AB = 0;
    /**/
    old_AB <<= 2; //remember previous state
    old_AB |= ( ENC_PORT & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

  • Paul R. Potts

    Thanks for posting this very useful algorithm.

    I am experimenting with some Atmel microcontrollers that have very small amounts of SRAM, such as the ATtiny104. That micro has only 32 bytes of SRAM, so I wanted to try implementing your algorithm without using that array of 16 unsigned chars to hold the +1, 0, and -1 values.

    I thought maybe I could do it with less memory, and it turns out that I can. Since there are only three possible return values, one easy way is to just make two integer constants and access them to decide what to return. The compiler (in my case, GCC in Atmel Studio) will generate code that puts these values in registers so they don’t need to use SRAM for the table. It will also inline the function and put the return value in a register, and the locals too, so it uses, basically, no SRAM at all except one byte for the history!

    Initially, I tried to make one 32-bit constant and access it two bits at a time, but GCC appeared to generate incorrect code for that version.

    Defining binary constants is a GCC extension. For another compiler they could be turned into hex, 0x4182 and 0x6996.

    #define ENCODER_DIR_SIGNBITS ( 0b0100000110000010U )
    #define ENCODER_DIR_ONEBITS ( 0b0110100110010110U )

    static int8_t read_encoder(void)
    {
    static uint8_t AB_history = 0;
    uint16_t mask;
    uint16_t shift;
    AB_history <> ENCODER_A_B_SHIFT );
    shift = ( AB_history & 0xF );
    mask = 0x1 <> shift );
    }
    }

    Other encoding schemes are possible and would use less storage; for example, I could have used run-length encoding allowing variable-width bit fields, which would make the table 24 bits wide, but make it so the table had to be scanned from bit zero each time. Or it is possible to encode compactly using fractional bits, but for this tiny amount of data that also seems like overkill.

    Anyway, I thought you or your readers might find this amusing and maybe useful, as a way to do the same algorithm in even less memory…

  • Paul R. Potts

    Wow, that really butchered my code block somehow. After setting the mask it is supposed to read

    if ( 0 != ( ENCODER_DIR_SIGNBITS & mask ) )
    {
    return -1;
    }
    else
    {
    return ( int8_t )( ( ENCODER_DIR_ONEBITS & mask ) >> shift );

    }

  • Paul R. Potts

    I have made a gist here, if that is more readable: https://gist.github.com/paulrpotts/3cea24b7e003fe00c63ff467a6f567ff

  • Paul R. Potts

    (Please delete my previous comments — they are not formatted correctly).

    Thanks for posting this very useful algorithm.

    I am experimenting with some Atmel microcontrollers that have very small amounts of SRAM, such as the ATtiny104. That micro has only 32 bytes of SRAM, so I wanted to try implementing your algorithm without using that array of 16 unsigned chars to hold the +1, 0, and -1 values.

    I thought maybe I could do it with less memory, and it turns out that I can. Since there are only three possible return values, one easy way is to just make two integer constants and access them to decide what to return. The compiler (in my case, GCC in Atmel Studio) will generate code that puts these values in registers so they don’t need to use SRAM for the table. It will also inline the function and put the return value in a register, and the locals too, so it uses, basically, no SRAM at all except one byte for the history!

    Initially, I tried to make one 32-bit constant and access it two bits at a time, but GCC appeared to generate incorrect code for that version.

    Defining binary constants is a GCC extension. For another compiler they could be turned into hex, 0×4182 and 0×6996.

    #define ENCODER_DIR_SIGNBITS ( 0b0100000110000010U )
    #define ENCODER_DIR_ONEBITS ( 0b0110100110010110U )

    I had some trouble getting code to show up properly formatted, even when I wrapped it with code tags, so I have put my sample function in a gist here:

    https://gist.github.com/paulrpotts/3cea24b7e003fe00c63ff467a6f567ff

    I thought you or your readers might find this amusing and maybe useful, as a way to do the same algorithm in even less memory…