Rotary Encoder

I’ve searched throughout the internet for an efficient way to use a Rotary Encoder. As usual, I’ve found tons of information: with and without libraries, with and without debounce subroutines…but finally I’ve ended up with Buxtronix rotary encoder

This site gives us a library that uses a Finite State Machine (FSM) which eliminates the debounce problems, accurately takes care of direction changes and can even handle bad states due to interferences from EMI. So thank a lot Buxtronix!

So what’s new here?

Two modifications: 1) modified the State tables so that it follows, correctly, the Gray Code sequence and 2) I have included in the library the push button code (total credit to Phill Fisk).

Gray code changes ONE bit at a time, hence 01 to 11…not to 10. I won’t get into much detail about Gray Code, since there is a lot of information in the WEB. However, I will do my best in explaining the State Table sequence.

A rotary encoder is a position sensor that is used to determine angular position of the rotating shaft. Usually, a rotary encoder has 5 pins: 5V, GND, push button, DT and CLK that are used for Bit A and Bit B. The encoder uses Gray Code sequence which is as follows:

Position Bit A Bit B
0 0 0
1/4 1 0
1/2 1 1
3/4 0 1
1 0 0

The basic way of decoding these is to watch for which bits change. For example, a change from “00” to “10” indicates one direction, whereas a change from “00” to “01” indicates the other direction.

The suggested library uses an algorithm of a FSM in order to avoid unwanted debounces. It uses a Static State Table for a full step mode (i.e. complete position changes between 00-10-11-01-00) and another for a half-step mode, where it emits an event at both 00 and 11 positions. Both are equally useful, but the full-step code is handy for devices that give a physical ‘bump’ only at the 00 positions.

Let me explain the full-step state table (Original version from Buxtronix): In ttable[7][4], Rows describe States while Columns describe input coming from pins DT and  CLK. Two bits can output 00-01-10-11, hence four columns.

#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

//original code from Buxtronixs
const unsigned char ttable[7][4] = {
// R_START
{R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CW_FINAL
{R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
// R_CW_BEGIN
{R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
// R_CW_NEXT
{R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
// R_CCW_BEGIN
{R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
// R_CCW_FINAL
{R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
// R_CCW_NEXT
{R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};

Let me convert to “0” and “1s”:

#define R_CW_FINAL 0x1    01
#define R_CW_BEGIN 0x2    10
#define R_CW_NEXT 0x3     11
#define R_CCW_BEGIN 0x4  100 
#define R_CCW_FINAL 0x5  101 
#define R_CCW_NEXT 0x6   110

If I want to follow a complete Clockwise (CW) sequence, then my objective is a Gray Code or: 00-10-11-01-00. Start at state 00 and jump to 10 (R_CCW_BEGIN in 1st line) which is state 100; then to 11 (R_START in 5th line) which is state 00; then to 01 (R_CW_BEGIN in 1st line) which is state 10 and finally to 00 (R_CW_NEXT in 3rd line) which is state 11.

So the actual sequence is: 00-100-00-10-11. It still works since the sequence that follows is 01-00-10-11. Although it works I decided to re rewrite the table in order to make it more understandable in terms of the initial sequence. Following the new table:

const unsigned char ttable[7][4] = {
// R_START
{R_START,           R_CCW_BEGIN, R_CW_BEGIN,   R_START},
  // R_CW_FINAL
{R_START | DIR_CW,  R_CW_FINAL,  R_START,      R_CW_NEXT},
  // R_CW_BEGIN
{R_START,           R_START,     R_CW_BEGIN,   R_CW_NEXT},
  // R_CW_NEXT
{R_START,           R_CW_FINAL,  R_CW_BEGIN,   R_CW_NEXT},
  // R_CCW_BEGIN
{R_START,           R_CCW_BEGIN, R_START,      R_CCW_NEXT},
  // R_CCW_FINAL
{R_START | DIR_CCW, R_START,     R_CCW_FINAL,  R_CCW_NEXT},
  // R_CCW_NEXT
{R_START,           R_CCW_BEGIN, R_CCW_FINAL,  R_CCW_NEXT},
};

Following the Finite State Machine diagram for the full step case:

Full Step FSM

In the above diagram, outer flow describes the path for Clockwise (CW) direction, while inner path refers to Counter Clockwise (CCW). I haven’t included all the arrows but you get the idea. Green and blue arrows refer to paths in both directions while red ones are unidirectional. That is because we don’t want the States to go back and forth and mark each path as a complete sequence. Please take a look to the following great video:

https://www.youtube.com/watch?v=BJHftzjNjkw

Let’s try again with the sequence: 00-10-11-01-00.

Start at state 00 and jump to 10 (R_CW_BEGIN in 1st line) which is state 10; then to 11 (R_CW_Next in 4th line) which is state 11; then to 01 (R_CW_FINAL in 2nd row) which is state 01 and finally to 00 (R_START|DIR_CW in 2nd row) which is state 00.

So actual sequence is: 00-10-11-01-00 which is our intended sequence!

Same idea for the Half step table, which I include below as a reference:

const unsigned char ttable[6][4] = {
// R_START (00)
{R_START, R_CCW_BEGIN, R_CW_BEGIN, R_START_M},
// R_CCW_BEGIN
{R_START, R_CCW_BEGIN, R_START, R_START_M | DIR_CCW},
// R_CW_BEGIN
{ R_START, R_START, R_CW_BEGIN , R_START_M | DIR_CW},
// R_START_M (11)
{R_START, R_CW_BEGIN_M, R_CCW_BEGIN_M, R_START_M},
// R_CW_BEGIN_M
{R_START | DIR_CW, R_CW_BEGIN_M, R_START_M, R_START_M},
// R_CCW_BEGIN_M
{R_START | DIR_CCW, R_START_M, R_CCW_BEGIN_M, R_START_M},
};

And the Finite State Machine Diagram for the half step case:

Half Step FSM

The analysis is similar to the full step case. However, in this case, we will get to state R_START when receiving input of 00 and 11.

Original library comes from Buxtronix, which has been modified by Phill Fisk incorporating push button features:

https://bitbucket.org/Dershum/rotary_button/src/master/

I have modified the tables in order to follow the above FSM sequence and you can go to my GitHub and download the library from there: https://github.com/CarlosSiles67/Rotary

In the following example you will be able to see the rotation and push button in the serial monitor. Use 57600 for baud rate and you should leave uncommented the #define HALF_STEP in rotary.h file.
/*
 * Example using the Rotary library, dumping integers to the serial
 * port. The integers increment or decrement depending on the direction
 * of rotation.
 *
 * This example uses polling rather than interrupts.
 */

#include <rotary.h>

// Rotary encoder is wired with the common to ground and the three
// outputs to pins A0, A1 and A2.
Rotary rotary = Rotary(A0,A1, A2);

// Counter that will be incremented or decremented by rotation.
int counter = 0;

void setup() {
  Serial.begin(57600);
}

void loop() {
  unsigned char result = rotary.process();
  if (result == DIR_CW) {
    counter++;
    Serial.println(counter);
  } else if (result == DIR_CCW) {
    counter--;
    Serial.println(counter);
  }

   if (rotary.buttonPressedReleased(30)) {
    Serial.println("Push button");
  }//endif buttonPressedReleased
}//End Loop

I hope it helps….happy hacking!!

21 comments

  1. Hello Carlos,

    Along Ben Buxton’s encoder I came on your blog. I got interested in this subject.

    You added a button. Maybe it’s a good idea to add some examples for your version, in particular for a demo of the pushbutton. See also the examples in the Buxton-library.

    I adapted my example (from Ben Buxton) and I did notice that when reversing direction of turning the shaft, there will be no output of a position for the 1st click. Maybe I did not understand your explanation.

    Regards,

    Jop

    Like

    1. Hi Jop,
      Absolutelly! I just posted a simple example that uses polling. Be sure to replace the rotary.h and rotary.cpp that I have uploaded in your arduino library sketch folder. Also, in the rotary.h be sure to leave uncommented the line #define HALF_STEP so that you get an output at every click. Let me know how it goes.

      Like

  2. Hi Carlos,

    I get compile error, at end of input a missing “}”. I transferred your code with copy/paste.
    Isn’t it better to put your code on GitHub to prevent this kind of troubles? Or make available a downloadable .ZIP on your blog.
    And maybe it is a good suggestion to name you library a bit different as the one of Ben Buxton. I don’t plan to abandon this good library as long as it is not clear what I win with your library.

    One question: why you change now to a polling-version? Isn’t it better to use interrupts?
    It is not clear why you implemented code for the Push-button? Wat’s your aim to use this pushbutton?

    Regards,

    Jop

    Like

    1. Hi Jop,

      I’m sorry I do not owe a Github account at this moment…but maybe in the near future (thanks for you kind suggestion). If you double click inside the code window, it will select all the code and you will be able to copy/paste into your arduino IDE.

      I use the polling just as an example but you can sure use the interrupt method. You are right, in more complex examples, it is better to use interrupts so that you free your code and do other stuff. I have used the polling method for a LCD menu project with no problem whatsoever. Instead of using push buttons, I just used the rotary encoder to navigate through a menu and click to choose an option.

      I will soon post an example on how to use this Library with LCD menus. Just to be clear, the person that added the push button code to this library is Phill Fisk, and I give full credit to him in the library.

      Hope it helps!

      Like

  3. K Missioni · · Reply

    Carlos, excellent information.
    Other than the code having some errors, does it speed up the code?
    Just learning and I am curious.

    Like

    1. Hi Klarson,

      The code is very light, hence it is fast. Also, you have the option of using interrupts which gives you more freedom and should run even faster. Nice thing about this code is that it doesn’t skip states, so it runs smoothly. What errors have you found in the code? Please let me know. Thanks!

      Like

      1. When i stated “code had some errors”, I meant the code from the buxtronic page.
        I would be interested in a faster version. Do you have code for the interrupts? I am not a programmer. Thanks for the response!

        Like

      2. Hi Klarson,
        Please take a look in the example folder:
        https://github.com/CarlosSiles67/Rotary

        Like

  4. Hello,

    Thanks for explaining in more detail what’s going on. I feel I have a much better idea how the state machine works now. As well has how the look up uses current state (Rows) and pin state (Column) to look up the next state and whether a CW/CCW movement was made.

    The HALF_STEP still confuses me a bit. In my head the state diagram should be the same just with more “| CW/CCW” checks.

    I have a number of Rotary Encoders that are 4 Pulses Per Revolution (PPR) and have 16 detents. I’d like to use them on an Arduino based project and have one CCW or CW event per detent movement.

    If I use the Buxtronix code in FULL_STEP I get 1 CCW/CW event every 4 detents in single direction.

    If I use HALF_STEP I get 1 event every 2 detents. I was wondering if you could help me design and modify the state machine and table to support this?

    Thanks in advance for any help you can offer.

    Like

    1. Hi Brian,
      I’m glad you found this site useful.

      Regarding your concern about: The HALF_STEP still confuses me a bit. In my head the state diagram should be the same just with more “| CW/CCW” checks…..beware that we need to output BOTH at 00 and 11. And once we get to 11(from either direction) you cannot go back to previous STATE (since this arrow is unidirectional). Try it out and you will see that you need different STATES.

      The State Table that you want for “QUARTER STEP” is a rather difficult one because every single click gives you an OUTPUT of either DIR_CW or DIR_CCW. Unfortunately I do not owe a rotary encoder 4PPR and 16 detents so I cannot test any table. Here my best guess of how it should look like but you will have to work with this TABLE and debug it (this needs to go in Rotary.cpp library). Good luck!

      #define R_CW_FINAL 0x1
      #define R_CW_BEGIN 0x2
      #define R_CW_NEXT 0x3
      #define R_START_M 0x4
      #define R_CCW_BEGIN 0x5
      #define R_CCW_NEXT 0x6
      #define R_CCW_FINAL 0x7

      const unsigned char ttable[8][4] = {
      //R_START (00)
      {R_START | DIR_CW, R_START | DIR_CW, R_CW_BEGIN | DIR_CW, R_START | DIR_CW},
      // R_CW_BEGIN
      {R_START_M | DIR_CW, R_START | DIR_CW, R_CW_BEGIN | DIR_CW, R_CW_NEXT | DIR_CW},
      // R_CW_NEXT
      {R_START | DIR_CW, R_CW_FINAL | DIR_CW, R_CCW_FINAL | DIR_CW, R_CW_NEXT | DIR_CW},
      // R_CW_FINAL
      {R_START | DIR_CW, R_CW_FINAL | DIR_CW, R_START | DIR_CW, R_CCW_NEXT | DIR_CW},
      // R_START_M
      {R_START | DIR_CCW, R_CCW_BEGIN | DIR_CCW, R_CW_BEGIN | DIR_CCW, R_START | DIR_CCW},
      // R_CCW_BEGIN
      {R_START_M | DIR_CCW, R_CCW_BEGIN | DIR_CCW, R_START_M | DIR_CCW, R_CCW_NEXT | DIR_CCW},
      //R_CCW_NEXT
      {R_START_M | DIR_CCW, R_CW_FINAL | DIR_CCW, R_CCW_FINAL | DIR_CCW, R_CCW_NEXT | DIR_CCW},
      // R_CCW_FINAL
      {R_START_M | DIR_CCW, R_START_M | DIR_CCW, R_CCW_FINAL | DIR_CCW, R_CW_NEXT | DIR_CCW},
      };

      Like

  5. Hi Carlos, thank you very much for posting this. Ben Buxton’s library has been extremely useful, so I was really excited to see a simple implementation of the push button. I have one problem, however. I have rotary.h and rotary.cpp in the same folder as the .ino sketch, and when I run the interrupt example I only see the button press confirmation in the serial monitor (i.e. there is no output when turning the encoder CW or CCW).

    To solve this I attempted to put the turning methods into a void function and attached the void function as an interrupt, but this didn’t work either. I ensured that the CLK and DT pins were properly numbered in the sketch as well, and even switched them around, but I still got no response when turning the encoder. Do you happen to have any ideas why this might be happening? Thanks in advance, and thank you again for this blog post!

    Like

    1. Hi Ami!
      I’m guessing you are using the rotary.h and rotary.cpp from the modified https://github.com/CarlosSiles67/Rotary. Correct?
      When using #include , i.e. when the angle brackets syntax is used, the libraries paths will be searched for the file.
      try using #include “rotary.h”. When the double quotes syntax is used, the folder of the file using the #include directive will be searched for the specified file, then the libraries paths if it was not found in the local path. Use this syntax for header files in the sketch’s folder.
      Also, ensure the baud rate in Serial.begin(57600); matches the one used in your serial monitor.
      Finally, try changing your encoder in order to verify that your encoder itself is not the problem.
      Let me know how it goes.

      Like

      1. Hey Carlos! Thank you for the fast response. I was using the double quotes in the include. I solved the issue, and it turned out that my encoder was the problem 🙂 I should have tried switching it before posting. Your code works wonderfully.

        I have modified it slightly so that the Data and Clock pins on the encoder are monitored in an interrupt routine, and then the button press/hold is polled for continuously in the loop() function. Thank you again for this blog post- it really helped me understand the theory behind the algorithm

        Liked by 1 person

  6. Hi Ami, I’m glad it is working!. No worries about not switching your encoder before posting, I’ve been through that path many..many times 😉

    Like

  7. Hi great job. But I wondering from what you get the value in the rows eg
    R_START, R_CCW_BEGIN, R_CW_BEGIN, R_START_M . does this result from the state machine transition graph?

    Like

    1. Hi Mirek,

      Yes. You are correct, it follows the table of the FSM.
      Let’s take the example you mentioned, i.e,
      // R_START (00)
      {R_START, R_CCW_BEGIN, R_CW_BEGIN, R_START_M},

      You have 4 columns (think 2-bit logic table), which are: 00-01-10-11.
      You are standing in R_START and next sequence is 00, you will stay in same state R_START
      You are standing in R_START and next in sequence is 01, you will go to state R_CCW_BEGIN
      You are standing in R_START and next in sequence is 10, you will go to state R_CW_BEGIN
      You are standing in R_START and next in sequence is 11, it means BOTH digits are changing, which is forbidden, then needs to start another sequence (R_START_M).

      Remember it is a gray code. A Gray code is an encoding of numbers so that adjacent numbers have a single digit differing by 1.

      Hope it answers your question.

      Like

  8. Hi Carlos,
    thank you very much for this in depth explanation of how code for rotary encoders works!

    After I was happy to understand a bit more due to your publication, I got attracted by the “Quarter-Step” mode code snippet you provided in response to Brian (July 13, 2020).

    I started to draw (by hand and ugly) a FSM diagram for this mode. In the process I added some question marks to transitions to “R_CCW_NEXT” and to “R_CCW_FINAL” in the diagram because they made no sense to me.

    The table seems to be correct, but I think the following two definitions should read:

    #define R_CCW_FINAL 0x6
    #define R_CCW_NEXT 0x7

    and NOT

    #define R_CCW_NEXT 0x6
    #define R_CCW_FINAL 0x7

    as in the code snippet.

    Example:
    Let’s look at row 7 (R_CCW_NEXT), last column (“11”) of the table for the “Quarter-Step” mode. If the state is R_CCW_NEXT and the next sequence is “10” it makes (to me) sense to keep the R_CCW_NEXT state only, if R_CCW_NEXT is defined as “110” (0x6) …and NOT if it is defined as “111” (0x7).

    After swapping the definitions of the two states, my diagram makes sense to me.

    HNY and Thanks again!

    Like

  9. Sorry – there is an an error in my message…it should read:

    “Example:
    Let’s look at row 7 (R_CCW_NEXT), last column (“11”) of the table for the “Quarter-Step” mode. If the state is R_CCW_NEXT and the next sequence is “11” it makes (to me) sense to keep the R_CCW_NEXT state only, if R_CCW_NEXT is defined as “111” (0x7) …and NOT if it is defined as “110” (0x6).”

    Sorry for that …

    Like

  10. Hello again Carlos,

    After some redrawing and correcting…

    Click to access df7tv-rotary-enc-fsm.pdf

    shows what I think should work as fsm / transition table for the “Quarter-Step” mode of a standard rotary quadrature 2-bit encoder.

    Greetings!

    Like

  11. Hello,
    my present version for the “Quarter-Step Mode” is now shown by the document:

    Click to access Quarter-Step_State_Transition_Table_for_Standard_Quadrature_2-Bit_Gray_Code_Rotary_Encoders.pdf

    Greetings!
    Tom DF7TV

    Liked by 1 person

    1. Thanks Tom for working Quarter-Step_State version and sharing!!

      Like

Leave a comment