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

  • For some applications it might be better to give an error rather than just ignore the matter when an “impossible” state is reached (e.g., 0011 or 0110) because this indicates that at least one and maybe more transitions have been missed and it’s not possible to determine the direction of the actual motion.

    E.g., I prototyped some similar Arduino code to track and control the position of an astronomical observatory dome. Rather than have a long sequence of unattended exposures ruined if, for any reason, the controller was not keeping up with changes reported by the encoder I thought it better for the controller to stop and raise the alarm.

  • The code above is just an example. If you want to process the output, you can modify the read function to return an index in lookup table instead of value. Or, you can modify the lookup table to return 0 if encoder hasn’t moved and 0xff for invalid code and send alarm if received 0xff.

  • Steve

    Best code I could find to give me a reliable output, cheers!

  • John E.

    Great code!

    I am interrested in a code to read two rotary encoders, and send only four different values(pot 1 up/down, pot 2 up/down) via Serial (TX/RX) to another arduino.

    The problem is that I am not very good in programming, so I was not able to modify this code the way i wanted.

    Any suggestions?

  • Larry Wade

    I am missing one vital part of your coding: what is the significance of the constant ENC_PORT and why does it have to be PINC when using pins 14 & 15? What makes this HAVE to be defined PINC or PINB when using pins 8 & 9? I am fairly new to programming but fairly computer knowledgeable, having worked in IT and/or electronics manufacturer’s customer support for the last 25 years or so.

    • PINB, PINC are port names containing pins to which encoder is connected. When reading encoder, you want to read both pins in one operation; therefore, you can’t use Digital.read as it reads one pin at a time.

      Hope this helps. Let me know if you need more detailed explanation.

      • Larry Wade

        So they are pre-programmed into the Arduino? And the pairing is therefor set for specific pin combinations? Are there only the three you listed, or are there others? Is this documented anywhere? Say I want to use encoders on all six analog pins. 14 and 15 work as PINC. What about a 16 & 17 pairing and an 18 & 19 pairing? Where does PINA occur? The pairs you listed all work fantastically, but I would hate to lose pins 0 & 1 to have a third encoder. And as the project I have been playing around with had three potentiometers on analog pins, using up to at least four the analog pins for encoders would be advantageous.

        • I believe port names are standard AVR mnemonic. Arduino doc is here -> http://www.arduino.cc/en/Reference/PortManipulation . If you want to generate lookup table index by shifting like in an example, you would want to leave empty space in a byte to accommodate those shifts. For instance, connect 2 encoders to bits ( pins ) 0,1 and 4,5 of a port. Then read a port, apply 0b00110011 ( i.e. 0x33 ) mask, OR with old_AB, use lower nibble as lookup table index for first encoder. Then shift the byte right 4 times, use as index for the second encoder. Don’t forget to save and shift current reading at some point to use as old_AB for the next reading.
          It can be complicated further. Connect 4 encoders to one port. Use above procedure for encoders 1 and 3. For encoders 2 and 4, use mask 0b11001100, reverse direction of shifts and create second lookup table for new indexes. You will also need two old_AB variables.

          • Larry Wade

            Thanks a lot for your help. The open source community is always great to work with.

          • Will

            Trying to get 2 encoders going at the same time connected to A0, A1 and A4, A5 per your instructions in 30 Apr 1540hrs post, but it’s not working properly. One encoder will work fine (tmpdataB) but tmpdataA freewheels away out of control. Any thoughts please?

            old_AB <>= 4; //shift upper nibble to lower nibble
            tmpDataB = enc_states[(old_AB & 0x0F)]; //get state of second encoder
            old_AB <<= 4; // Put old_AB back the way it was before

            Note: if i dont bitshift 4 bits Back to the right, nothing works.

          • Will

            Oleg-

            Having some difficulty reading two encoders on the same port (Pins A0,1,4,5).

            1. old_AB <>= 4; //bitshift four bits to the right, readings from encoder B shifted to lower nibble
            5. tmpdataB = enc_states[old_AB & 0x0F)]; //Get value for encoderB
            6. old_AB <<= 4; //Put old_AB back the way it was

            Without lines 4, 5 and 6 encoderA works fine. WITH lines 4,5 and 6 encoderB works fine, but encoderA is erratic.

          • When you are shifting oldAB, you are losing current encoder A information. You need to save it first, then process two separately. Or better yet, read encoder bits from the port to two different variables for encoder A and B and then process them.

          • Will

            Yup, got it working now – Thanks for the hint. Guess I spent too much time trying to make it sexy that I couldn’t even make it work!

        • do you have any success with multiple encoders and this code? can you show me how it`s done?

  • Paul

    Just an FYI, this sketch works fine without any changes with the $1 rotary encoders that Electronic Goldmine sells. Like the Sparkfun one, they also include a push-in switch. They have a somewhat longer knob, and are all-metal.

  • LEo

    I changed this to work on pins 2 and 3, so you can use interrupts to check when the encoder moves:

    Essentially I added this to the setup function:


    // encoder pin on interrupt 0 (pin 2)
    attachInterrupt(0, doEncoder, CHANGE);

    // encoder pin on interrupt 1 (pin 3)
    attachInterrupt(1, doEncoder, CHANGE);

    and then these function to deal with the interrupts, which is based on the setup() function of the article. Probably you want to make counter a global or something like that, so you can use it somewhere else.

    void doEncoder()
    {
    static uint8_t counter = 0;
    int8_t tmpdata;
    tmpdata = read_encoder();
    if( tmpdata )
    {
    counter += tmpdata;
    }
    }

    I tweaked the read_encoder function to use PIND instead of PINC, and then I made an admittedly horrible tweak to the shifts. This can be optimized, but I’m just too lazy 🙂

    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;
    uint8_t new_AB = ENC_PORT;

    old_AB <>= 2; //shift the values of pins 2 and 3 down to 0 and 1
    //so the rest of the logic works

    old_AB |= ( new_AB & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

    Just remember to define ENC_PORT as PIND;


    #define ENC_PORT PIND //put this somwhere on top of the function

    Hope that helps.

    LEo

  • LEo

    For the sake of clarity, here is the complete sketch for my previous post:

    #define ENC_A 2
    #define ENC_B 3
    #define ENC_PORT PIND

    volatile uint8_t encoderCounter = 0;

    void setup()
    {

    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);

    // encoder pin on interrupt 0 (pin 2)
    attachInterrupt(0, doEncoder, CHANGE);

    // encoder pin on interrupt 1 (pin 3)
    attachInterrupt(1, doEncoder, CHANGE);

    }

    void loop()
    {

    //Do your stuff here. The value of the encoder is in the variable encoderCounter

    }

    void doEncoder()
    {
    int8_t tmpdata;
    /**/
    tmpdata = read_encoder();
    if( tmpdata )
    {
    encoderCounter += tmpdata;
    }
    }

    /* returns change in encoder state (-1,0,1) Based on http://www.circuitsathome.com/mcu/programming/reading-rotary-encoder-on-arduino */
    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;
    uint8_t new_AB = ENC_PORT;

    old_AB <>= 2;
    //old_AB |= ( ENC_PORT & 0x12 ); //add current state

    old_AB |= ( new_AB & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

    Hope it helps.

    LEo

    • bikedude

      This is the correct read_encoder method for pins 2 and 3:

      /* returns change in encoder state (-1,0,1) Based on http://www.circuitsathome.com/mcu/programming/reading-rotary-encoder-on-arduino */
      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;
      uint8_t new_AB = ENC_PORT;

      new_AB >>= 2; //Shift the bits two positions to the right
      old_AB <<= 2;

      old_AB |= ( new_AB & 0x03 ); //add current state
      return ( enc_states[( old_AB & 0x0f )]);
      }

  • John Schisler

    Won’t compile on the line:

    old_AB = 2;

    Gives error:

    In function ‘int8_t read_encoder()’:
    error: expected primary-expression before ‘>=’ token

  • Don

    I’m afraid that this code has a bug in it, but I can’t see clearly how to fix it. As I turn my encoder in one direction, the counter goes up. But when I change directions, the counter goes up by one, and then begins to decrement. When I change directions again, the counter decrements by one before incrementing again. This is a problem for me, as I intend to use my encode for menu selection, which would involve small movements and many direction changes.

    • Leo

      Hi Don,

      I’m still in the process of understanding the sketch as I’m pretty new to Arduino.

      I’m surprised this “counter += tmpdata;” from line 26 isn’t before line 24 in the sketch.

      Did you figure it out in the meantime?

      Leo

  • Steffen

    Hello Oleg,

    I´m really happy that i found your example here, and i want to use it for an encoder for my midi-controller. For this i need the encoder to send a note on- and note off- command every click. When the encoder direction changes, it must send another note (on and off). I think this won´t be a problem with your code, i “just” need to change the count-function, but i don´t understand how.:(
    Can you give me an example how to send a command every click and another every click in the other direction? That would be so great, cause the encoder is the only thing why i can´t finish my project.

    Sorry for my bad english, greetings from germany!
    Steffen

    • Steffen,

      read_encoder() returns -1, 0, or 1, depending on what happened to the encoder between function calls. So all you need to do is change the loop starting at line 22:

      if( tmpdata ) {
         if( tmpdata == 1 ) {
             play_inc();
         }
         else {
           play_dec();
         }
      }
      
  • Steffen

    Hello Oleg,
    thank you very much for your fast help!
    I tested it with a serialPrint command (L for left and R for right).. the result is that i really often get something like RRRLRRLRRRR or LLLLRLLR and so on. You are writing about this when you explain the method, that the counter is “jumping” sometimes, i think.
    Now, i know that each click will give me four counts, but i only need one command per four counts / click, so i´m thinking about if it is possible to do semething like only sending a command when the input is four times the same.. maybe this will give me some “empty” clicks on the encoder, but deletes the incorrect combinations..
    Very theoretical, i think i have to do some more research about this.

  • What is your serial speed? If it’s set to default 9600bps, you will be blocking encoder polls. You need serial speed set as high as possible, 115200 being a good number.

  • Steffen

    I tested it with 115200 and 9600, with 9600 i got less wrong results. For my final programm i need to set the serial speed to 31250 because i´m sending midi-data. I´m really confused about that it is so difficult to setup an encoder.. maybe i can use interrupts, but then i have to resolder my chip:(

  • Hi,

    Thanks for posting your code here.

    One thing that I am not able to understand is – how did you derived the states in the enc_states array. Please explain…

    Gurb

  • Hi, Thanks for the reply.

    I now understand the 1,-1,0 but I still do not understand how do you get 16 states and more importantly in that order.
    (I read this link but could not understood).

    Gurb

    • You have 4 valid states for clockwise direction and 4 for counterclockwise, it gives you 8 states. In total, you have 16 possible states ( 4 for each position times 2 for the rotation times 2 for the direction ), half of them invalid. The program reads encoder, shifts the result and reads it again. The result – 4-bit number, is array index. That’s why there is 16 elements in the array.

      There are many ways to derive this array, the easiest to understand is just write down all valid combinations from tables above ( 0001, 0111, 1110, 1000, etc.), get array indexes by converting from binary to decimal, filling array elements with proper increment, and then fill the rest of the array with zeroes.

      • Meloman0001

        Hi,

        Sorry to ask for further clarification, but I understand why you would have 4 valid states for CW and 4 valid states for CCW direction, thus giving 8 valid states. But why did you multiply it by 2 again to get 16 valid states.

  • Steffen

    Hey Oleg,
    finally it´s working really good! I did something like this:

    tmpdata = read_encoder();
    if( tmpdata ) {
    if( tmpdata == 1 ) {
    count++;}
    if (count==4){
    Serial.println(“Left “);
    count =0;}

    if( tmpdata == -1 ) {
    count–;}
    if (count==-4){
    Serial.println(“Right “);
    count =0;}
    }

    Sure, not like a pro would do it, but in the end it gives me a command every click and in my software wich i want to control, the encoder works exactly like it should.

    Again, thank you very much, you helped me a lot!
    Steffen

  • One of the best starting points to rotary encoders. Here is a link to my personnal works on encoders http://didier.longueville.free.fr/arduinoos/?p=485

  • Hey Oleg! Very interessting code!
    im Using also an Arduino and lady ada motorshield for my follow focus. here is a smal video of the follow focus. http://vimeo.com/12331846 so far i use a normal potentiometer but the feedback is not so good. than i found this video on youtube: http://www.youtube.com/watch?v=AEdMgZGbc9Q this guy uses two rotary encoders one for the dc motor and one for the input…do you think this is possible with your code for reading this motor http://www.robotstorehk.com/motors/doc/emg30.pdf and use another encoder for the input?

    thx janosch 🙂

    • It will probably work fine if you don’t do anything which would slow down the Arduino too much (like serial output, for example). You may want to connect both encoders to speed things up.

      • nooooo i turn serial output off…but im not a programmer and im struggeling to put together the code but today i will try your code and just read out the encoder and than try to find an code that compares two encoders …if you have one drop me a line 🙂

        cheers janosch

  • Cleber

    HI!
    I have a encoder:
    http://cgi.ebay.com/12mm-Rotary-Encoder-Switch-Flat-Top-Knob-NEW-/220671209494?pt=LH_DefaultDomain_0&hash=item3361077016


    I'm trying read using a code :

    #define encoder0PinA 2
    #define encoder0PinB 3

    volatile unsigned int encoder0Pos = 0;

    void setup() {

    pinMode(encoder0PinA, INPUT);
    digitalWrite(encoder0PinA, HIGH); // turn on pullup resistor
    pinMode(encoder0PinB, INPUT);
    digitalWrite(encoder0PinB, HIGH); // turn on pullup resistor

    attachInterrupt(0, doEncoder, CHANGE); // encoder pin on interrupt 0 - pin 2
    Serial.begin (9600);
    Serial.println("start"); // a personal quirk

    }

    void loop(){
    // do some stuff here - the joy of interrupts is that they take care of themselves
    }

    void doEncoder() {
    /* If pinA and pinB are both high or both low, it is spinning
    * forward. If they're different, it's going backward.
    *
    * For more information on speeding up this process, see
    * [Reference/PortManipulation], specifically the PIND register.
    */
    if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
    } else {
    encoder0Pos--;
    }

    Serial.println (encoder0Pos, DEC);
    }

    /* See this expanded function to get a better understanding of the
    * meanings of the four possible (pinA, pinB) value pairs:
    */
    void doEncoder_Expanded(){
    if (digitalRead(encoder0PinA) == HIGH) { // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
    // encoder is turning
    encoder0Pos = encoder0Pos - 1; // CCW
    }
    else {
    encoder0Pos = encoder0Pos + 1; // CW
    }
    }
    else // found a high-to-low on channel A
    {
    if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
    // encoder is turning
    encoder0Pos = encoder0Pos + 1; // CW
    }
    else {
    encoder0Pos = encoder0Pos - 1; // CCW
    }

    }
    Serial.println (encoder0Pos); // debug - remember to comment out
    Serial.println ('/ '); // before final program run
    // you don't want serial slowing down your program if not needed
    }

    But not is working very well! to right its ok, and to back failed!
    See a return in serial monitor:

    start
    1
    2
    3
    4
    5
    6
    7
    8
    9
    8 ---> here start forward
    9
    8
    9
    8

    Some one can help me ?

  • Raja Selvan

    Dear oleg, Your code really helped me to sort out a big issue. You did a great job man. Thanks a lot…..

  • Hi, very nice code. I’ve referenced it in my blog. One question: is there a single valid state during the entire bouncing time?

  • Bryan

    I notice that using the Authors original example and the sparkfun encoder that if you turn the encoder half a click (you can feel the clicks) it will advance by 1, then if you turn back to the original position it it advances again. Why is this and how is this corrected?

  • Bryan

    Nothing wrong with the setup, it seems that it reads a forward movement if you move 1/2 forward (registers +2) then back to the starting position instead of completing the click (registers +1)

    You can try it. I’ve tested it with several of the encoders to make sure one wasn’t bad. I’t not a problem with the code, it’s bad design in the encoders sparkfun sells. I may be looking for some better encoders. An optical encoder would not do this for sure but those are a bit pricy.

    For now what Im doing to be able to easily move 1 for 1 click is myVal = Counter/4; to account for the encoders +4/click. I will have to modify that when I get some decent encoders.

    I’m just contributing the oddness to subpar encoders.

    For my first project Im just using the encoder to change the backlight color with an RGB backlit lcd using the push button to toggle R/G/B and the encoder to set the value.

    For my next project I need 16 encoders, I know there are numerous i/o expanders that I can use, do you have any suggestions for both expander and good encoders?

  • diecavallax

    Hey, all (sorry my poor English)

    My project is base on rotatory mechanical mouse, because save money, here my modifications for this;
    1- For problem on the counter 1,2,3,4,3,4,3,5, onto sequence increment not exactly, I add on int8_t read_encoder() function after declaration of uint8_t, the delay of 5 (delay(5)), for me solved question.

    2 – For case of the count two incrementes for “click”, I create “giros” variable for permit increment counter when giros%2===0;

    take look of my code:
    //arduino pino analogico 0
    #define ENC_A 14
    //arduino pino analogico 1
    #define ENC_B 15
    #define ENC_PORT PINC
    /* fim variaveis wheel*/

    int giros=2;
    void setup()
    {

    /*wheel*/
    /* Setup encoder pins as inputs */
    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH);
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH);
    /*fim wheel*/
    Serial.begin(19200);
    }
    void loop()
    {
    /*wheel*/
    static uint8_t counter = 0; //this variable will be changed by encoder input
    int8_t tmpdata;
    /**/
    tmpdata = read_encoder();

    if(tmpdata) {
    //para enviar somente um comando por giro da roda
    if(giros%2==0){
    counter+=tmpdata;
    //imprime o codigo ligo
    imprimeCodigo(counter);
    }
    giros++;
    }
    /*fim wheel*/
    }
    void imprimeCodigo(int wheelVal){
    Serial.println(wheelVal, DEC);}

    /* returns change in encoder state (-1,0,1) */
    int8_t read_encoder()
    {
    /*int8_t enc_states[] = {
    0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0 };*/
    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;
    delay(5);
    old_AB <<= 2; //remember previous state
    ab=old_AB;
    old_AB |= ( ENC_PORT & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

    Wait for this help someone;

  • diecavallax

    Sorry for error of start forward like of Cleber, I put “counter+=tmpdata;” before print this code of counter. look on my previous post.

    See you on 2011.

  • Hey Oleg! Small world…I was researching using an Arduino to count quad and I find you…good work!

    Grayson

  • That is the best code I have come across to read encoders. The proof is in the pudding: it works. Thanks very much.

  • You guys rock! Thank you so much oleg, also LEo and bikedude for the interrupt code. Works perfectly!

  • Rob Zeilinga

    fantastic – (helps if you read Oleg’s explanation of code properly!)
    also explained bitwise operations in a real world example
    this should be included in the ARDUINO examples !

  • Martin

    While this code is elegant, I think there is basic incorrect assumption you have made. Just because the encoders are quadrature does not mean debouncing is required. All encoders are basically constructed as a pair of physical switches that operate in a known sequence. However like any switch, these can bounce (momentarily change state before reaching their final resting point). As you don’t account for this you get the effect that a few people have already mentioned. For instance with the standard code provided, and if I turn my encoder in one direction, I get the following sequence.

    Start
    Counter value: 0
    Counter value: 1
    Counter value: 2
    Counter value: 3
    Counter value: 4
    Counter value: 5
    Counter value: 6
    Counter value: 7
    Counter value: 8
    Counter value: 9
    Counter value: 10
    Counter value: 11
    Counter value: 12
    Counter value: 11
    Counter value: 12
    Counter value: 13
    Counter value: 14
    Counter value: 15
    Counter value: 16
    Counter value: 17
    Counter value: 18
    Counter value: 19
    Counter value: 20
    Counter value: 21
    Counter value: 22
    Counter value: 23

    Clearly at the 11-12 transition there was a bounce. The code interprets it as a change in direction. Debouncing would negate this. Obviously this is fairly easily solved with adding the normal delays that are used in debouncing. (Yes these are cheap Sure Electronics encoders, but all mechanical encoders will suffer from this to some extent).

    Please don’t take this as a criticism, I like your code!

  • Aek

    Hello,

    Now, I try to program for count value from two encoders.
    I need to know how many value of each direction of encoder.
    That mean:
    [1st Encoder = CW and 2nd Encoder = CW]
    [1st Encoder = CW and 2nd Encoder = CCW]
    [1st Encoder = CCW and 2nd Encoder = CW]
    [1st Encoder = CCW and 2nd Encoder = CCW]

    However, Sometime the count value is overflow(count value is non stop) after try to run program.

    Could you please suggest some example concerned with my case or some guideline?
    Thank you for your kindness.

    My code is below.
    ******************************************************************
    int encoder0PinA = 3;
    int encoder0PinB = 4;
    int encoder1PinA = 5;
    int encoder1PinB = 6;

    int encoder0Pos = 0;
    int encoder1Pos = 0;
    int encoder2Pos = 0;
    int encoder3Pos = 0;

    void setup()
    {
    pinMode (encoder0PinA,INPUT);
    pinMode (encoder0PinB,INPUT);
    pinMode (encoder1PinA,INPUT);
    pinMode (encoder1PinB,INPUT);

    Serial.begin (9600);
    Serial.println (“Start”);
    }

    void loop()
    {

    if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder1PinA) == HIGH)
    {
    if (digitalRead(encoder0PinB) == HIGH && digitalRead(encoder1PinB) == HIGH)
    {
    encoder0Pos++;
    }

    else if (digitalRead(encoder0PinB) == HIGH && digitalRead(encoder1PinB) == LOW)
    {
    encoder1Pos++;
    }

    else if (digitalRead(encoder0PinB) == LOW && digitalRead(encoder1PinB) == HIGH)
    {
    encoder2Pos++;
    }

    else if (digitalRead(encoder0PinB) == LOW && digitalRead(encoder1PinB) == LOW)
    {
    encoder3Pos++;
    }

    Serial.print (“F–>”);
    Serial.print (encoder0Pos);
    Serial.print (” B–>”);
    Serial.print (encoder1Pos);
    Serial.print (” LT–>”);
    Serial.print (encoder2Pos);
    Serial.print (” RT–>”);
    Serial.print (encoder3Pos);
    Serial.print (“\n”);
    }
    }

    ******************************************************************

  • tangible

    this is maybe the best rotary encoder code around, many thanks for all explanations (reading out two pins at a time etc.).
    just great!

  • Don

    Oleg, could you help?
    On your great article: Reading rotary encoder on Arduino:
    Using 16 x 2 LCD. Removed Serial.print code. Put in lcd code. tried this:

    lcd.print (counter/22, DEC);

    and get nice smooth count from 0 to 11, but 6 clicks before advancing to next number. 11 to 0 the same way. Very new to programing and rotary encoders, and not sure what to change.

    Thanks, Don

    • It is not clear to me what you are trying to achieve. Could you please elaborate a little?

      • Don

        Hi Oleg,
        I have an RSS weather reader on Arduino Uno that requires a 4 character ICAO code for a particular station to stream out weather data to present on an 2 x 16 LCD. The project works fine. But would like to select 10 or more codes using a rotary encoder, without having to upload each time. I have a cheap 12 clicks/full rotation from Sparkfun, which has been very erratic using simple code. Then, I saw your design and tried it. Very smooth, almost never misses a click. Got to read the code over again to see how it works. Just not sure how to use it. I read closer and now dividing counter by 4 now and see 0 – 63. I want to use a case statement to select the dozen or so ICAO codes. I could put more codes in later. I’m not sure if I can use counter variable directly in the case statement. For if I try to Serial.print counter without DEC, I’m not sure what I’m seeing. And, dealing with 4 copies of each number. I tried to us a int variable num for counter, but didn’t work. I’m a real newbie in this area.

  • Don

    Oleg, the code I added: cd.print (counter/22, DEC); is probably wrong, for the clicks vary between 6 and 5 and at end, only 4. It does get to from 0 to 11 and back. Want to use encoder for a menu.
    Don

  • DJKrugger

    Thanks Oleg this is by far the best working encoder code out there, i used it as Steffen suggested with some modifications for my case and it works flawlessly, no step skips, no repeated steps, now i’m planning to use this code to read 14 encoders by means of 2 I2C I/O expanders (MAX7300), do you think is it possible? encoders rarely will be moved 2 at the same time.

  • Jay

    Oleg,

    Thanks for the code, I’m trying to hook up my encoder to analog pins 4 and 5 but when I suspect I’m gonna have to change the read_encoder() method. How would I go about that because when I change the defines to be 18 (analog 4) and 19 (analog 5) the code doesn’t work, it still reads from analog zero and one.

    Thanks for any help!

  • Jay

    Don’t know if anyone is paying attention to this thread of comments, but I’ve got code that I “think” should work logically when I have the encoder on analog pins 4 and 5. Unfortunately it’s not working in practice, I can turn the knob CCW and the values increase but when I turn the knob CW they alternate between one value higher and then one value lower…

    CODE:
    /* 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;
    uint8_t new_AB = ENC_PORT;

    new_AB >>=4; // shift our new encoder bits right 4 places to put them in the lowest two bit positions
    old_AB <<=2; // shift our bits left two places so we remember the current encoder values

    old_AB |= ( new_AB & 0x03); // add current readings and zero out all but bit 4 and 5

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

    Any help is much appreciated, I'm losing my mind here…

    • The code looks correct. What is initial encoder value ( at detent )? You can insert some Serial.print() calls here and there to see what gets written to new_AB and old_AB.

      • Jay

        Oleg,

        Thanks so much for your blog and helping me out. The code is/was right, the problem was the fact that I put my filtering caps/resistor combo on my Arduino shield and then proceeded to run about 10 inches of 22AWG wire to the encoder. Not a good idea! I had to place a .1uF cap between each encoder pin and ground (on the actual encoder). Once I did that, all my problems went away….Thank goodness for my trusty oscilloscope showing me the err of my ways!

  • Albatetra

    Hey, great piece of code! Thanks a lot and I will embed it in my project with credits to you. I have a question.
    I have to place the encoder 5 meter far from the Arduino. I’ll use an STP (shielded twisted pair) cable with ferrite core.
    Do you think this will be a big problem?

  • joon

    How should I modify the code if I want to use two encoders(adding one more encoder to the analog in) and print both of the data at the same time?
    I appreciate if anyone could help me out in any way.

  • Hey Oleg,
    I am porting this code into openFrameworks. However, i can’t seem to access the PINB function with the arduino addon in the framework. Is there a way around this? If i had to access the PINB and did not have this possibility, how would it be written in the code without the definition statement?
    thanks for sharing the code,
    cheers,

  • I have a bunch of projects I am working on using encoders and motors in various combinations. I’m just getting started with learning arduino and figuring things out.

    I have tried out your code (copied below to ensure no mistakes) using the following encoder http://www.amtencoder.com/Product/AMT102 connected to my arduino uno.

    In the serial monitor I get values showing up ok rising from zero at start and regressing if turned in the opposite direction. The issue I am having is that if I turn the encoder for example 3/4 turn clockwise then turn it the same distance anticlockwise the counter doesn’t return to the original value. if I repeat this,each time it gives different values. How do i adjust the code to give me accurate position counts? I am not seeing any “bounce” where values drop back and forward, all counts seem to progress correctly in the appropriate direction.

    Another thing I wish to have this do, once i have accurate , repeatable counting is create limits to the output from the encoder so that when it is driving a motor, once the encoder passes a certain point of rotation it ceases to send an output and hence the motor will stop. (i am using stepper motors.

    Any input would be great. Our project is pretty cool and we may end up looking to involve someone to help us with all our arduino code in the future.

    code I have been using to test:

    /* Rotary encoder read example */
    #define ENC_A 8
    #define ENC_B 9
    #define ENC_PORT PINB

    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 uint16_t counter = 0; //this variable will be changed by encoder input
    int16_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) */
    int16_t read_encoder()
    {
    static int16_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
    static uint16_t old_AB = 0;
    /**/
    old_AB <<= 2; //remember previous state
    old_AB |= ( ENC_PORT & 0x03 ); //add current state
    return ( enc_states[( old_AB & 0x0f )]);
    }

    • The encoder you are using looks quite sophisticated, there may be something in its mechanics which causes this. Thy your code with a simple encoder like one I’m using in my examples.

  • Rajan

    Hey,

    I am facing a problem with overburdening of Arduino Mega with 3 encoder motors with a resolution of 1024 Counts Per Revolution and a RPM of 740 (12646 Counts Per Second per Motor).

    Arduino is skipping lot of states in between, thereby downcounting instead of upcounting at higher speeds. Do you have an solution for this?

    Following is the code:

    #include

    #define outMotor0 2
    #define outMotor1 4
    #define outMotor2 6

    #define pinDir0 3
    #define pinDir1 5
    #define pinDir2 7

    int newPos0 = 0, oldPos0 = 0; // encoder position
    int newPos1 = 0, oldPos1 = 0; // encoder position
    int newPos2 = 0, oldPos2 = 0; // encoder position

    int dir0=0, dir1=0, dir2=0;
    unsigned long oldTime=0, newTime=0, dt;
    float vel0, vel1, vel2;

    void setup() {
    Serial.begin(9600); // set up Serial library at 9600 bps

    //PIN 18 (Interrupt 5), PIN 19 (Interrupt 4), PIN 20 (Interrupt 3)
    pinMode(18, INPUT);
    pinMode(19, INPUT);
    pinMode(20, INPUT);

    pinMode(pinDir0, INPUT);
    pinMode(pinDir1, INPUT);
    pinMode(pinDir2, INPUT);

    pinMode(outMotor0, OUTPUT);
    pinMode(outMotor1, OUTPUT);
    pinMode(outMotor2, OUTPUT);

    // encoder pin on interrupt 5 (pin 18 – Motor0), interrupt 4 (pin 19 – Motor1), interrupt 3 (pin 20 – Motor2)
    attachInterrupt(5, doEncoder0, RISING);
    attachInterrupt(4, doEncoder1, RISING);
    attachInterrupt(3, doEncoder2, RISING);

    oldTime = millis();
    }

    void loop() {
    newTime = millis(); // read the current time stamp
    dt = newTime – oldTime; // compute delta time
    oldTime = newTime;

    vel0 = (float) ((newPos0 – oldPos0)) / dt; // velocity estimate in ms
    oldPos0 = newPos0;

    vel1 = (float) (newPos1 – oldPos1) / dt; // velocity estimate in ms
    oldPos1 = newPos1;

    vel2 = (float) (newPos2 – oldPos2) / dt; // velocity estimate in ms
    oldPos2 = newPos2;

    Serial.println(vel0);
    }

    // Interrupt routine
    void doEncoder0(){
    if (digitalReadFast(pinDir0)) {newPos0–;} // read the direction of motor
    else {newPos0++;}
    }

    void doEncoder1(){
    if (digitalReadFast(pinDir1)) {newPos1–;} // read the direction of motor
    else {newPos1++;}
    }

    void doEncoder2(){
    if (digitalReadFast(pinDir2)) {newPos2–;} // read the direction of motor
    else {newPos2++;}
    }

  • David Letellier

    Hi Oleg,

    Your code looks great, i’d like to use it to read a two channel hall effect encoder from a linear actuator.
    I’m an artist working on this for a cinetic sculpture, and very new to arduino, so i’m probably making obvious mistakes.

    Here is your code integrated in mine, the motor control part works well, but i don’t get anything from the encoder.
    The serial connection works, at the right speed, i got some serial input with other codes, but nothing ever usable.

    I’ll need to read 6 encoders at the same time on a mega, that’s why i’m looking for a solution without interrupts, or with a maximum of one interrupt per encoder.

    I’d really appreciate if you could just have a look.

    thank you

    #define ENC_A 2
    #define ENC_B 22
    #define ENC_PORT PINC

    const int switchPin = 7; // switch input
    const int dirPin = 6; // direction input
    const int motor1Pin = 30; // motomama In1
    const int motor2Pin = 31; // motomama In2
    const int EnA = 8; // motomama enable pin

    void setup() {

    pinMode(ENC_A, INPUT);
    digitalWrite(ENC_A, HIGH); // turn on pullup resistor
    pinMode(ENC_B, INPUT);
    digitalWrite(ENC_B, HIGH); // turn on pullup resistor
    Serial.begin (115200);
    Serial.println(“start”);

    pinMode(switchPin, INPUT);
    digitalWrite(switchPin, HIGH);
    pinMode(dirPin, INPUT);
    digitalWrite(dirPin, HIGH);

    pinMode(motor1Pin, OUTPUT);
    digitalWrite(motor1Pin, HIGH);
    pinMode(motor2Pin, OUTPUT);
    digitalWrite(motor2Pin, HIGH);
    pinMode(EnA, OUTPUT);

    }

    void loop()
    {
    if (digitalRead(switchPin) == LOW) {
    analogWrite(EnA, 64); // send PWM duty cycle :
    }
    else {
    digitalWrite(EnA, LOW);
    digitalWrite(switchPin, HIGH); // turn on pullup resistors
    digitalWrite(dirPin, HIGH); // turn on pullup resistors
    }

    // if the switch is high, motor will turn on one direction:
    if (digitalRead(dirPin) == LOW) {
    digitalWrite(motor1Pin, HIGH); // set leg 1 of the H-bridge low
    digitalWrite(motor2Pin, LOW); // set leg 2 of the H-bridge high
    }
    // if the switch is low, motor will turn in the other direction:
    else {
    digitalWrite(motor1Pin, LOW); // set leg 1 of the H-bridge high
    digitalWrite(motor2Pin, HIGH); // set leg 2 of the H-bridge low
    }

    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 )]);
    }

  • Hi! I`m need to use 3 rotary encoders connected to analog pins on arduino uno.
    Can you help me with code?

  • Leo

    Hi Oleg,

    Can you please explain lines 30 to 39 to me/others who don’t understand yet? I don’t understand the explanation you have already written.

    I’m pretty new to Arduino and have never programmed before. How does the sketch use the look-up table??

    Thanks in advance,

    Leo

    • When I was little, we had a sine table to be used in trig class. Since we haven’t had trig-capable calculators calculating the (approximate) value of sine/cosine of a certain angle was somewhat tedious. Now, when you have a table you don’t need to calculate anything – you just look it up.

      For the encoder, a table consists of one row. Cell number is made of current and previous states of an encoder. It’s a four bit value, where two high bits is previous state and two low bits is current state. A cell contains an increment value, i.e., -1, 0, or 1 depending on what happened to the encoder between two readings. You read an encoder, get a cell number and add a value read from the table to the step counter.

  • Roberto Lombi (@icsnerdics)

    What a great code!
    Give me AND/OR op, and I shall move the world. 😀

    Thanks!

  • DexterIsMyHero

    I set up my ATmega 2560 with the above sketch. It will print “start” in the terminal window, so I know it’s finishing setup().

    Nothing else seems to be happening.

    I know my encoder works, because I’ve used it with another encoder reading sketch. And I’m pretty sure I’ve got it connected as per the instructions. Encoder A and B leads to Analog In A0 and A1, encoder C lead to GND.

    I’ve got pins numbered 14 & 15, (Communication, tx3 and rx3) but I’m pretty sure you didn’t mean those pins. Plus the fact the sketch doesn’t work with those pins either.

    What could be wrong? Please advise.

    • The sketch expects encoder on PORTC pins 0 and 1. For 2560 it is pins 37, 36 on Arduino connector. You need to move the encoder to these pins or change the sketch.

      • DexterIsMyHero

        Thanks Oleg. I figured it had to be hookup problems.

        So, it seems there are three different names for everything.

        Is there one document where all these various names are listed and cross-referenced?

      • DexterIsMyHero

        One thing I am learning is that these Arduino boards and ATmega processors are marvelously complex. Once again, I’m learning way more than I though I’d need to learn. I’ve spent some time familiarizing myself with the pinouts of the ATmega 2560 and the Arduino schematic for the ATmega 2560.

        But I’m not quite there yet…in the sketch, it says

        #define ENC_A 14
        #define ENC_B 15

        I see there is a Port C, processor pins 53 thru 60. Connected to the Arduino board’s JP1, pins 36 and 37, as you have stated.

        I still can’t see how 14 and 15 applies to this. Sorry to be so bone headed about this, but I’m stumped.

        • PORTC 0,1 correspond to pins 14,15 on the board used in the example as well as any other Atmega328-based Arduino. For 2560 you need to change 14,15 to 36,37.

  • DexterIsMyHero

    OK, sprinkling Serial.println statements for troubleshooting.

    It’s making it to loop(), and executing tmpdata = read_encoder()

    read_encoder is getting run endlessly.

    It must be the if statement in loop()

    if (tmpdata)

    no tmpdata being read? and no else, so the loop just….loops, reading nothing, doing nothing?