Wednesday 5 October 2011

Rotary encoders, done properly

Rotary encoders, in the most recognisable form, are mechanical devices that look a bit like potentiometers, and are often used in their place. Some audio gear uses them, and are recognisable as volume controls with regular bumps or clicks as they turn. They are also used in mechanical trackballs and mice, as well as lots of other applications that require accurate rotational sensing.

In hobbyist applications, they are handy for all sorts of things - potentiometer replacements, up/down switching, etc. Using them with AVRs, PICs and Arduinos is quite common and they are easily available.




Their operation is quite simple, using a careful arrangement of conductive tracks. There are 3 pins on the most common ones - two for the "bit" outputs, and a common (wiper). As the encoder turns, the bit pins change according to a Gray Code sequence as follows:

Position Bit A Bit B
000
1/410
1/211
3/401
100

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 video below shows these changes:



I looked around for an AVR/Arduino decoder implementation for an application I had in mind. There were a lot of code samples around, and even the official Arduino wiki has a lot of code options.

Unfortunately, most of them suck.

The vast majority of them suffer from one or more of the following flaws:
  • Debounce handling. Mechanical switches are imperfect, and bounce on and off during transitions, over a few milliseconds, which is enough to be sensed as discrete changes by the MCU. Many implementations simply detect a change from 00 to 01 and signal it. But with switch bounce, it could be detected as many events. Thus, implementations often include debounce routines. but these add additional code, and also at high rotational rates, they filter out the events and break.
  • Direction changes. Some implementations use algorithms that expect direction changes to occur at the '00' bit position. If a change occurs mid-step, they get confused and return too many events, or a spurious one in the wrong direction.
  • Complexity. Many implementations are plagued by complicated conditionals and loops. They have unwieldly long if-then statements that are impossible to debug, yet can still suffer from the above problems. Complex code also leads to consumption of valuable code space.
  • Weak algorithms. Far too many implementations simply look for a single transition from one state to another, rather than following and checking for valid states. This leads to odd quirks and bugs.
Annoyed by the abundance of buggy and poor routines, I set about writing a decoder that would not be subject to these problems. I looked carefully at the bit transitions of the encoder, and built an algorithm that followed these accurately, yet with simple code.

The Gray code actually follows a simple state machine, and at any given state, it can only change to one of two other values. Let's take a look at the state table again:

Position Bit A Bit B
000
1/410
1/211
3/401
100

For example at position 1/4, the only valid next states are either 00 at position 0, or 11 at position 1/2. Any other state is invalid and should be ignored. The algorithm should know this.
Next, when there is switch bounce, the switch output will alternate many times between states, eg from 1/4 to 1/2 to 1/4 to 1/2 to 1/4 to finally settling on 1/2. The algorithm should not generate spurious events in this case, and must also still recognise the final 1/2 state.

The code I wrote honours all of these. It enforces valid state changes, deals with switch bounce, handles direction change, yes the entire logic is four lines of code. The rest of the code is port setup, and a static array holding the state machine table.

My code also includes a #define, which sets it to either emit a turn event after a full step (ie complete position changes between 00-10-11-01-00), or to support 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.

Unnecessary code is excluded. For example, its up to you to trigger a poll of the encoder input pins. This is done with a single call, which returns a code indicating a clockwise, anticlockwise, or no change. A change is triggered when the state goes back to 00 (with full step), or at both 00 and 11 (half step).
You can trigger the poll either from a loop, or even from a pin change interrupt handler (sample code included).

As you can see in the video below, the algorithm works reliably at both slow and high rotation speeds.


Get it from the Github repository.



141 comments:

  1. This is the best rotary encoder solution. It uses the same idea as this post: http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino.

    Here's a bit of code that utilizes your library to enable the Arduino to read two rotary encoders:

    Rotary r = Rotary(2, 3);
    Rotary r2 = Rotary(4, 5);

    void setup() {
    Serial.begin(115200);
    PCICR |= (1 << PCIE2) | (1 << PCIE1);
    PCMSK2 |= (1 << PCINT18) | (1 << PCINT19) | (1 << PCINT20) | (1 << PCINT21);
    sei();
    }

    void loop() {

    }

    ISR(PCINT2_vect) {
    char result = r.process();
    char result2 = r2.process();
    if (result) {
    Serial.println(result == DIR_CW ? "1Right" : "1Left");
    }
    if (result2) {
    Serial.println(result2 == DIR_CW ? "2Right" : "2Left");
    }
    }

    I've just expanded the AVR native interrupt code because the Arduino only supports two interrupt pins.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  2. Hi, Great thanks this looks so much strait forward.
    But i have trouble with the sample3 in the lib folder.

    If I #define FULL_STEP or comment the #define HALF_STEP out I can not compile any more - get funny error I don't understand. I use Arduino 1.0.

    could You pls check and also upgrade your lib Rotary to Arduino 1.0.

    thanks in advance ( I go in circles ;-) )

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  3. It would be great if this was updated--I agree that all the other examples suck. Thanks in advance-

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
    2. Dear Marco, I am Unknown. No one wants to talk to you. Thank you.

      Delete
  4. Defintively the best code for encoders:

    I have just a problem with transfering the result in a variable. Which are the two possible outcomes? if (result ==0x40) is left, but what is right?

    My Code just won't work

    void loop() {
    char result = rotary_process();
    if (result ==0x40) {{
    x++;};

    if (x==40) {
    x=0;};
    if (result !=0x80) {
    x--;};

    if (x==-1) {
    x=39;};void loop() {
    char result = rotary_process();
    if (result ==0x40) {{
    x++;};

    if (x==40) {
    x=0;};
    if (result !=0x80) {
    x--;};

    if (x==-1) {
    x=39;};

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  5. There is a slight problem due to the rotary_process returning a char, when in fact it should be unsigned.

    I will post a fix shortly, but in the meantime, just change all instances of "char" in the library files to "unsigned char".

    For the outcomes, you should compare against the constants DIR_CW and DIR_CCW.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  6. Hi Ben,

    I just did he changes (in rotary.h and rotary.cpp and in the sketch itself) - but it didn't do the trick. I can still only count clockwise. Any suggestions what I'm doing wrong? (PS: In your example with the serial output it works perfectly.)

    Sascha

    void loop() {

    }

    ISR(PCINT2_vect) {
    unsigned char result = r.process();
    if (result) {
    if (result == DIR_CW) {{
    x++;};
    if (x==40) {
    x=0;};

    if (result == DIR_CCW) {
    x--;};
    if (x==-1) {
    x=39;};

    ReplyDelete
  7. I've updated the library here to Arduino 1.0, as well as fixed a couple of problems relating to signed variables. Please give it a go!

    ReplyDelete
  8. Thank you very much. WIth the new Version and a programming mistake I made fixed it works now.

    Just another question. Did you also make some changes in the HALF_STEP programming?

    I ask because i noticed, that it counts with the libray just every other step ( if i insert "#define HALF_STEP" or not makes no difference.

    However, if I use the code without the library, it works just perfect.

    ReplyDelete
  9. Ok, my mistake.
    I got it. I didn't think, that i had to activate the feature WITHIN the library ;-)

    Just forget my last comment. Everything but the big, fat THANK YOU, of course

    ReplyDelete
  10. I'm using this library, but having some strangeness when I try to use the rotary3 example without half stepping. Whenever I comment out the #define HALF_STEP I can't compile my sketch anymore. Any insight on how to use the rotary3 sketch without half stepping?

    ReplyDelete
    Replies
    1. additionally, when I do get it to compile and upload without using half step (don't know why one sketch compiles and the other doesn't), but when I do get it uploaded then turning the knob in either direction on registers as LEFT, though it is doing single steps.

      Delete
    2. This is with the latest version?

      Delete
    3. rotary3 contains a typo in the table for FULL_STEP operation.

      To get it to compile under 1.0.1, without HALF_STEP defined, I found I had to remove all references to HALF_STEP but then found it only went LEFT.

      If you look at the defs, CCW = 0x10 and CW = 0x20, but the table for FULL_STEP has both entered as 0x10.

      Changing the table to as shown below means the code does FULL_STEP correctly in both directions:

      const unsigned char ttable[7][4] = {
      {0x0, 0x2, 0x4, 0x0}, {0x3, 0x0, 0x1, 0x10},
      {0x3, 0x2, 0x0, 0x0}, {0x3, 0x2, 0x1, 0x0},
      {0x6, 0x0, 0x4, 0x0}, {0x6, 0x5, 0x0, 0x20},
      {0x6, 0x5, 0x4, 0x0},
      };

      Delete
    4. I'm Marco from a rotary encoder factory in China, it would be great if we can have a talk thank you.

      Delete
  11. I was having trouble with rotary encoders as UI input in my project and this library sorted everything out. This should be the first listing under http://arduino.cc/playground/Main/RotaryEncoders as the other libs there don't work. Thanks for taking the time and effort to make this library!

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  12. Regards from Spain, great job yours.. I have one project with two rotary encoders but they don´t work properly, can you see the arduino sketch to test those troubles??? and xplain me how solve it???
    https://dl.dropbox.com/u/58726056/sketch_sep06c/panel_inigo_02/panel_inigo_02.ino
    The idea it´s increase Frecz when turn right or decrease when turn left, amounts of 10 mhz each pulse, and when we turns faster increase 100 Mhz units till 999,999 limits.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  13. Sorry my encoders are 2-bit gray code, perhaps there is the trouble???

    ReplyDelete
  14. Regards I have installed into arduino rotary3.ino file but each click gives me via serial port two signals right or when turn left two signals left... I think there is the trouble perhaps my encoders have different steep table??? in the script you indicate there is two tipes half and full steep tables isn´t??? Thx for your support.



    ReplyDelete
    Replies
    1. Getting two signals per click is pretty normal. If setting it to full step is giving this, then your program will probably have to only take actions on two steps, rather than one.

      Delete
  15. Hello Ben

    i am new at programming , i bought an arduino a few months ago and i have done amazing projects so far. my next challange involves a rotary encoder as you demostrated. your code is exactly what i ma looking for . however may you please suggest a way on how i can get a variable accordingly to the direction . for example , count the number of left and right turns or switch on a led if the encoder only turns right . thank you for all your help. i really appricate and admire your work.

    michael

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  16. please ignore my last comment, i had managed it rather well. although my biggest issue with a rotary encoder is that i cannot count once per direction. i would like to measure oscillations so the rotary encoder will change dirctions to the oscilattion. i have managed to count the half pulses in one direction and reverse the count if the encoder rotated in the opposite direction. this is fairly simple but now i battling , please help with any suggestions and help?

    ReplyDelete
  17. Reorganized for Arduino 1.x IDE, added syntax highlighting, added polling example.

    https://github.com/brianlow/Rotary

    ReplyDelete
  18. I love your explanation but I'm having trouble following your code. I'm in the process of learning ASM for the AVR 8-bit microcontroller and haven't quite deciphered the C language yet. Do you have any suggestions or links that could help me learn or translate your code to ASM?

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  19. What really confuses me are the hexadecimal values.. Where do you get some of the values in the table? On the Gray chart above, I see that the highest value in the table is 0x03? In your .imo document, I see hexadicimal values going up to 0x20!

    ReplyDelete
    Replies
    1. The values in the code go higher as I return not just the next state for the state machine, but also a couple of the high nibble bits are used to indicate whether a clockwise or anticlockwise rotation is signalled. This is more efficient than maintaining separate variables.

      Delete
  20. Your algorithm is excellent. I have spent the afternoon trying to have more complex libraries work with no success. It took me some time to have yours running as I'm still beginner, and my lcd has some serious problems displaying informations, but it now does work pretty fine, no servo jittering at all. Thank you very much.

    ReplyDelete
    Replies
    1. I would like to see the code for running a servo. I have been hunting everywhere for some samples of rotary encoders and servos.

      Thanks,

      Delete
    2. This comment has been removed by a blog administrator.

      Delete
  21. Hi, I tried your algorithm, but in my case it doesn't work as expected.

    When I look at the table (below), I think it outputs a result at 11 (R_START | DIR_CW) in stead of 00 (second row). Am I correct or am i overlooking something?

    Kind regards,
    Piet

    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},
    };

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  22. I'll try turning the table around.

    @Ben Buxton: Is it possible to post the rc5/manchester algorithm? Would really be appreciated.

    Kind regards,

    Piet

    ReplyDelete
    Replies
    1. The algorithm I used for RC5 is http://www.clearwater.com.au/rc5 and I used that as a starting point for the rotary decoder. A read through that page should clarify things somewhat.

      Delete
  23. would it be possible to identify the rotation direction for a simple analog pot .

    ReplyDelete
  24. Hi Ben, Thanks for this great info! I'm a neophyte and got a single encoder working immediately. I, however am looking to have two encoders in my project, and am quickly floundering. Is it possible two get two working? The first post implies it can be done but jumps into code that is over my head. Any hints?

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  25. What is your application? the example in the first response is using interrupts, like the rotary sketch in the zip. Doing it like rotary3 does, with polling, may be responsive enough and easier to get your head around. I'm going to be playing with multiple encoders tomorrow, I started modifying the code to support more encoders. I'm working on a 5 zone/room audio amplifier so each zone needs its own volume control, etc. thus five rotary encoders with push buttons for mute.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  26. Excellent, thank you very much for this! Really high quality stuff, works absolutely reliable without any hardware debouncing, I didn't think, that this could be done. I have had quite a hard time to port it to basic because C code is so incomprehensible to me, but it definitely worth the time. I reworked the library to very general state and now I have few encoders running at the same time and still have damned effective and small code. Much better than my simple software solution with heavy hardware filtering and still not 100% reliable. Thank you one more time.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  27. Would you mind sharing your multi encoder implementation? seems that a lot of people (myself included) are interested in doing that.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Idea is quite simple, I have excluded data collecting and processing outside the library towards user responsibility and also variable holding last encoder state. Routine in library just take actual encoder state (pre-processed to 000000XX binary form by user) and last encoder state as parameters and return event as byte - CCW, CW or nothing. So user can define and use whatever number of encoders, he just need to create parameters for each one and pass them to library routine for processing. Here is the code:


      module Rotary_encoder

      dim encoder_state as byte[2] volatile

      'Values returned by 'function Rotary'
      ' No complete step yet.
      const DIR_NONE = 0x0
      ' Clockwise step.
      const DIR_CW = 0x10
      ' Anti-clockwise step.
      const DIR_CCW = 0x20

      sub function Rotary(dim pinstate, encoder_number as byte) as byte

      implements

      const R_START = 0x0
      const R_CW_FINAL = 0x1
      const R_CW_BEGIN = 0x2
      const R_CW_NEXT = 0x3
      const R_CCW_BEGIN = 0x4
      const R_CCW_FINAL = 0x5
      const R_CCW_NEXT = 0x6


      const ttable as byte[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 OR 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 OR DIR_CCW),
      ' R_CCW_NEXT
      (R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START)
      )

      sub function Rotary(dim pinstate, encoder_number as byte) as byte
      ' Determine new state from the pins and state table.
      encoder_state[encoder_number] = ttable[encoder_state[encoder_number] AND 0xf][pinstate]
      ' Return emit bits, ie the generated event.
      result = encoder_state[encoder_number] AND 0x30
      end sub

      end.

      So far tested on two encoders at once served in single interrupt routine - uses almost no resources, but works like a charm. Here is my interrupt routine:

      sub procedure PinChangeInterrupt() iv IVT_ADDR_PCINT1 ics ICS_AUTO
      dim i_, temp, output, current_state as byte
      temp = PINB
      for i_ = 0 to 1
      current_state = temp AND 000011
      output = Rotary(current_state, i_)
      if output = DIR_CW then
      if selector[i_] = 0 then
      Inc(setpointV[i_])
      else
      Inc(setpointA[i_])
      end if
      else
      if output = DIR_CCW then
      if selector[i_] = 0 then
      if setpointV[i_] > 0 then
      Dec(setpointV[i_])
      end if
      else
      if setpointA[i_] > 0 then
      Dec(setpointA[i_])
      end if
      end if
      end if
      end if
      temp = temp >> 2
      next i_
      end sub

      It is a part of bigger project, so some things may not give sense (this is only small snippet) but it should give you a small look how it works if you are able to read mikroBasic code.

      Delete
  28. In the full step code, in the 3rd row in the table:

    R_CW_BEGIN
    (R_CW_NEXT, R_CW_BEGIN, R_START, R_START),

    I don't understand why if we receive the 0-0 encoder output, we have to go to R_CW_NEXT. As far as i can understand the code, the previous state was 1-0, so we only can go to 1-1 (NEXT) or 0-0. 0-0 should mean START... but the table has R_CW_NEXT.

    could you explain why these values are so?

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  29. Hi Ben!

    I have used your algorithm in a project: http://atoomnet.net/countdown-timer-with-random-intervals/. It works beautifully.

    At first I did not understand how it worked, but later I did and it is really elegant.

    The rotary encoder is the only input. You can use is to configure settings on the lcd screen for the countdown timer program.

    Thanks.

    ReplyDelete
    Replies
    1. Nice project! Glad the code works well - this one-knob input is exactly what I intended it to be for.

      Delete
  30. Hi Ben,

    Afther 2 weeks trying to get the Rotary working in my project i have stopped it and try to write a help signal to u.

    I need some little help with your amazing Rotary project.

    I have written a project to control a television/radio tuner with I2C. To control (select) the frequency i use a PCF with buttons Up and Down. this project is working exelent.

    i Have find on the www your lib & example code. I want to inplement your Rotary code that i have a choice to set the frequency or with the buttons, or with the Rotary. just both wanted to be active in the project.

    This is the PCF button part of the code,and below that the control for the Rorary.

    {
    Wire.requestFrom(PCF1_BUTTON, 1);
    if(Wire.available()) // If the request is available from the PCF1_BUTTON
    {
    PCF_1=Wire.read(); // Receive the data from the PCF1_BUTTON
    }
    if(PCF_1<255) // If the button data is less than 255
    {
    if (PCF_1==254) { StepDR2--; }; // P0 = 0xFE Decrease DR2 with 1 step
    if (PCF_1==253) { StepDR2++; }; // P1 = 0xFD Increase DR2 with 1 step
    if (PCF_1==251) { Xtal=Xtal_a; }; // P2 = 0xFB Xtal 4.0 MHz (depending device = stepsize = 62.5 kHz)
    if (PCF_1==247) { Xtal=Xtal_b; }; // P3 = 0xE7 Xtal 3.2 MHz (depending device = stepsize = 50 kHz)
    if (PCF_1==239) { Band=HB; }; // P4 =
    if (PCF_1==223) { RELAY_Output(Relay_2, HIGH); }; //Mono
    if (PCF_1==191) { RELAY_Output(Relay_2, LOW); }; //Stereo
    if (PCF_1==127) { RELAY_Output(Relay_1, HIGH); }; // P7 Spare (for testing)

    }
    {
    unsigned char result = r.process();
    if (result == DIR_NONE)
    {
    }
    else if (result == DIR_CW)
    {
    StepDR2++;
    }
    else if (result == DIR_CCW)
    {
    StepDR2--;
    {

    The Problem i have in my project, it stuck now for more that 2 weeks :(
    When i put this (above) or the complete Rotary example in my code, is dont react at all.

    what i am doing wrong??
    The complete project code i have written is to long to put here, if u want to have it, i can send u, no problem.

    hopefully u can help me.

    Best regard's

    Edwin
    pd2ebh@hotmail.com
    the Netherlands

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  31. When i put this code in the project:

    {
    Wire.requestFrom(PCF1_BUTTON, 1);
    if(Wire.available()) // If the request is available from the PCF1_BUTTON
    {
    PCF_1=Wire.read(); // Receive the data from the PCF1_BUTTON
    }
    if(PCF_1<255) // If the button data is less than 255
    {
    if (PCF_1==254) { StepDR2--; }; // P0 = 0xFE Decrease DR2 with 1 step
    if (PCF_1==253) { StepDR2++; }; // P1 = 0xFD Increase DR2 with 1 step
    if (PCF_1==251) { Xtal=Xtal_a; }; // P2 = 0xFB Xtal 4.0 MHz (depending device = stepsize = 62.5 kHz)
    if (PCF_1==247) { Xtal=Xtal_b; }; // P3 = 0xE7 Xtal 3.2 MHz (depending device = stepsize = 50 kHz)
    if (PCF_1==239) { Band=HB; }; // P4 = 0xEF Low band = 0xA1
    if (PCF_1==223) { RELAY_Output(Relay_2, HIGH); };
    if (PCF_1==191) { RELAY_Output(Relay_2, LOW); };
    if (PCF_1==127) { RELAY_Output(Relay_1, HIGH); }; // P7 Spare (for testing)

    }
    ISR(PCINT2_vect)
    {
    unsigned char result = r.process();
    if (result == DIR_NONE) {
    // do nothing
    }
    else if (result == DIR_CW) {
    Serial.println("ClockWise");
    }
    else if (result == DIR_CCW) {
    Serial.println("CounterClockWise");
    }
    }

    i get this problem;

    Universal_controller_met_Rotary_test:228: error: expected unqualified-id before string constant
    Universal_controller_met_Rotary_test:229: error: a function-definition is not allowed here before '{' token

    line 228 >> ISR(PCINT2_vect)


    any help will be appreciate.

    Edwin

    ReplyDelete
    Replies
    1. Could it be that your microcontroller doesnt support PCINT2? I get the impression that the compiler is not finding its definition. try changing the PCINT to one supported by your micro, or use polling mode instead.

      Delete
  32. If I comment out the #define HALF_STEP in the rotary3 example, I get a bunch of CRAZY compiler errors but why!

    rotary3.ino: In function 'void rotary_init()':
    rotary3:49: error: 'INPUT' was not declared in this scope
    rotary3:49: error: 'pinMode' was not declared in this scope
    rotary3:52: error: 'HIGH' was not declared in this scope
    rotary3:52: error: 'digitalWrite' was not declared in this scope
    rotary3.ino: In function 'unsigned char rotary_process()':
    rotary3:63: error: 'digitalRead' was not declared in this scope
    rotary3.ino: In function 'void setup()':
    rotary3:69: error: 'Serial' was not declared in this scope
    rotary3.ino: In function 'void loop()':
    rotary3:76: error: 'Serial' was not declared in this scope

    If, instead I delete all the code setting up the half step table, it compiles ok but then it only goes in one direction, as Adrian found out a while back, due to a typo in the full step table


    Regards,

    Philip

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  33. Hi Ben,

    Thanks for providing the rotary library. My project uses a rotary encoder on pins D2 and D3 of a Uno and I can get the 'rotary' example to work fine. However I also need to use the SoftwareSerial library to implement a MIDI in and Out on pins D11 and D12. When I add this I get a compiler error:

    SoftwareSerial\SoftwareSerial.cpp.o: In function `__vector_5':
    C:\Documents and Settings\Philip\My Documents\Arduino\libraries\SoftwareSerial/SoftwareSerial.cpp:319: multiple definition of `__vector_5'
    rotary_demo.cpp.o:C:\Program Files\Arduino/rotary_demo.ino:35: first defined here

    The __vector_5 error seems to be related to the PCINT2_vect.

    A simple sketch (extension of the 'rotary' example) to show this problem is:

    // Comment out the next line to remove the MIDI handling code
    #define _MIDI

    #ifdef _MIDI
    // Handling the MIDI stuff

    #include

    #define MIDI_RXpin 12
    #define MIDI_TXpin 11

    SoftwareSerial mySerial(MIDI_RXpin,MIDI_TXpin); // RX, TX

    byte incomingByte;
    byte note;
    byte chan;
    byte command;
    #endif

    #include

    Rotary r = Rotary(2, 3);

    void setup() {
    Serial.begin(9600);
    PCICR |= (1 << PCIE2);
    PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
    sei();
    }

    void loop() {

    }

    ISR(PCINT2_vect) {
    unsigned char result = r.process();
    if (result) {
    Serial.println(result == DIR_CW ? "Right" : "Left");
    }
    }

    Any suggestions or is it impossible to use rotary lib and Software Serial lib together?

    Thanks,

    Philip

    ReplyDelete
    Replies
    1. It's possible that the SoftwareSerial library also has a handler for PCINT2, which would give the conflict here.

      Ways to address it:

      - Remove the handler from SoftwareSerial.
      - Use a different pin (and thus PCINT) for rotary.
      - Use polling for rotary instead.

      I cant give exact details, but that should give a starting point.

      Delete
    2. Thanks for your reply Ben. I'll have a look in SoftwareSerial. I know already that it does have a handler for PCINT2 (line 319 in the compiler error message). I will try if I can remove that line and see if it works. I am running the MIDI on pins 11 and 12 so maybe SoftwareSerial doesn't need PCINT2??

      Also I have managed to poll the rotary but it misses if the rotary is spun too fast but I may be able to get away with it as is.

      Will let you know how I get on :-)

      Phil.

      Delete
    3. in SoftwareSerial.cpp there is a line that looks like this:

      ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));

      comment that out. wOOt!

      Delete
  34. Oops - for some reason the #includes didn't paste??

    the first is
    #include '<'SoftwareSerial.h'>'

    and the second is
    #include '<'rotary.h'>'

    (without the quotes)

    ReplyDelete
  35. Good news!

    By commenting out lines 318-323 in SoftwareSerial.cpp my script now compiles and, thankfully the MIDI input (which uses SoftwareSerial) still works :-)

    Thanks for your help Ben and hopefully this fix may help others who need to use your rotary lib and the SoftwareSerial lib.

    Phil.

    ReplyDelete
    Replies
    1. this is really old, I know, like me, but Phil or anyone, I need the SoftwareSerial.cpp that you used as I too need it, but can't compile with the standard SoftwareSerial.cpp. Thanks!

      Delete
  36. Can this code be adapted to Arduino 0023 - if so, how? (with 1.0.x I get very poor performance when I use pulseIn() )

    For people like me, that know little to nothing about software I changed the code from displaying 'left' & 'right' to showing numbers 0-10 and counting up or down, depending on the direction. If you go over 10 it starts at 0, and below 0 will jump to 10.

    int x;

    void setup() {
    /*
    code from example here, unchanged
    */

    int x = 5;
    }

    void loop() {

    }

    ISR(PCINT2_vect) {
    unsigned char result = r.process();
    if(result) {
    if(result == 16) {x--; }
    if(result == 32) {x++; }

    if(x < 0 ) {x = 10; }
    if(x > 10) {x = 0 ; }

    Serial.println(x);

    }
    }

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  37. //this called from external interrupt for falling and
    //rising edges
    void Rotary_InterrupCallback(void)
    {
    static uint8_t state_left=0,state_right=0;
    BOOL a,b;
    uint8_t data;

    a = GPIO_ReadInputDataBit(ROTARY_A_PORT, ROTARY_A_PIN);
    b = GPIO_ReadInputDataBit(ROTARY_B_PORT, ROTARY_B_PIN);

    data = (a << 1) | b;

    switch(data)
    {
    case 0x00:
    state_left = 0;
    break;
    case 0x02:
    if(state_left == 0)
    {
    state_left=1;
    }
    break;
    case 0x03:
    if(state_left == 1)
    {
    state_left=2;
    Rotary_Decrement();
    return;
    }
    break;
    case 0x01:
    if(state_left == 2)
    {
    state_left=3;
    Rotary_Decrement();
    return;
    }
    break;
    }

    switch(data)
    {
    case 0x00:
    state_right = 0;
    break;
    case 0x01:
    if(state_right == 0)
    {
    state_right=1;
    }
    break;
    case 0x03:
    if(state_right == 1)
    {
    state_right=2;
    Rotary_Increment();
    }
    break;
    case 0x02:
    if(state_right == 2)
    {
    state_right=3;
    Rotary_Increment();
    }
    break;
    }

    ReplyDelete
  38. Thanks for this. I've adapated your code to work with a couple of encoders on a Raspberry Pi wifi radio project (flickr : http://www.flickr.com/photos/limpfish/sets/72157634474295977/with/9208712190/ ) Code I had been using previously was awful. Thanks again.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  39. I just started using Arduino and I am not thrilled with much of the stuff it trows out as errors.
    I have given up on using the Rotary header / library for few reasons.

    This snippet throws out something "does not name type"
    So what does that means? t
    #include
    Rotary r = Rotary(2,3); .

    Second - the rotary.cpp constructor is
    Rotary(char,char)
    Should that be int's?

    Third
    How does one keep track of verisons? How do I know I AM using the latest / gratest?

    I guess I use your code and forget to rotary library for now.
    73 Vaclav AA7EJ




    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  40. Hello, I have some issue with this code (1.1). Namely, when I turn rotary encoder left-right-left-right...etc.it does not emit event, and I have to turn two click, and after that it emits events conitunuosly(turnig encoder in one direction), when I change direction again it emits event after second click.

    Here is my simple code:

    unsigned char rot_state = 0;
    int8_t i=5;

    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},
    };

    while(1){


    //Rotary encoder, PC4, PC5.
    unsigned char pinstate = (PINC & _BV(PC5)) >> 5 | (PINC & _BV(PC4)) >> 3; //PC5 op bit0, PC4 op bit1
    rot_state = ttable[rot_state & 0x0f][pinstate];


    if ((rot_state & 0x30) == DIR_CW ) {i++; if (i>9) i=9;}



    if ((rot_state & 0x30) == DIR_CCW ) {i--; if (i<0) i=0;}


    PORTB=x[i];



    }

    ReplyDelete
    Replies
    1. I solved this issue.
      Rotary encoder I bought have different sequence(encoder is http://www.alps.com/WebObjects/catalog.woa/E/HTML/Encoder/Incremental/EC12E/EC12E1220405.html). it starts(when no finger action applied) with 10,11,01,00,10. It starts and ends with 10, and your code starts with 00 and ends with 00. Because I dont have enough a programmer/digital skills to understand this code(state machine), I bought another encoder (http://www.song-huei.com.tw/pdfimages/ED1112.pdf) to try, and it has sequence you wrote this table(00,10,11,01,00).

      Delete
  41. Some tips to use this library on MEGA2560?
    Thanks in advance,
    Mimmo

    ReplyDelete
    Replies
    1. Hi Erminio I'm Marco from a rotary encoder factory in China, it would be great if we can have a talk thank you.

      Delete
  42. Where is the power coming from to light up the leds??? is the centre pin on 5v?

    ReplyDelete
    Replies
    1. I assume you mean for the first video? The LEDS are on +5v, then the center of the encoder is at GND.

      Delete
    2. This comment has been removed by a blog administrator.

      Delete
  43. how does ths work can someone please help me understand why pin2 has a left shift of 1 bit and then ORed with pin one.

    unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. It's so they can be joined in a single variable, pinstate, for further manipulation. For example, If pin 1 is low and pin 2 is high, pinstate will be 10. Pin 1 high and pin 2 low, pinstate is 01, etc.

      This is an awesome library. Worked with attiny85 without changing a single line.

      Delete
  44. thanks ben for this code!!! i hope it works for what im trying to do..
    i cant try the code that i have edited at the moment but i have added a 2nd encoder and have it output as button presses on midi. (using teensy 3.1 so it is usbMIDI)
    i have edited out all the parts to the half parts as i kept getting errors

    [CODE]
    /* Rotary encoder handler for arduino.
    *
    * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
    * Contact: bb@cactii.net
    *
    * Quick implementation of rotary encoder routine.
    *
    * More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
    *
    */

    const byte ROTARY0_PIN1 = 2;
    const byte ROTARY0_PIN2 = 3;
    const byte ROTARY1_PIN1 = 4;
    const byte ROTARY1_PIN2 = 5;

    int DIR_CCW0 = 0x10;
    int DIR_CW0 = 0x20;
    int DIR_CCW1 = 0x10;
    int DIR_CW1 = 0x20;

    const unsigned char ttable[7][4] = {
    {0x0, 0x2, 0x4, 0x0}, {0x3, 0x0, 0x1, 0x10},
    {0x3, 0x2, 0x0, 0x0}, {0x3, 0x2, 0x1, 0x0},
    {0x6, 0x0, 0x4, 0x0}, {0x6, 0x5, 0x0, 0x10},
    {0x6, 0x5, 0x4, 0x0},
    };

    volatile unsigned char state0 = 0;
    volatile unsigned char state1 = 0;

    /* Call this once in setup(). */
    void rotary0_init() {
    pinMode(ROTARY0_PIN1, INPUT);
    pinMode(ROTARY0_PIN2, INPUT);
    }
    void rotary1_init(){
    pinMode(ROTARY1_PIN1, INPUT);
    pinMode(ROTARY1_PIN2, INPUT);
    }

    /* Read input pins and process for events. Call this either from a
    * loop or an interrupt (eg pin change or timer).
    *
    * Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
    */
    unsigned char rotary0_process() {
    unsigned char pinstate = (digitalRead(ROTARY0_PIN2) << 1) | digitalRead(ROTARY0_PIN1);
    state0 = ttable[state0 & 0xf][pinstate];
    return (state0 & 0x30);
    }
    unsigned char rotary1_process() {
    unsigned char pinstate = (digitalRead(ROTARY1_PIN2) << 1) | digitalRead(ROTARY1_PIN1);
    state1 = ttable[state1 & 0xf][pinstate];
    return (state1 & 0x30);
    }

    void setup() {
    rotary0_init();
    rotary1_init();
    }

    void loop() {
    unsigned char result0 = rotary0_process();
    if (result0) {
    result0 == DIR_CCW0 ? "LEFT" : "RIGHT";
    if("LEFT"){
    usbMIDI.sendNoteOn(1,127,5);
    delayMicroseconds(5);
    usbMIDI.sendNoteOff(1,0,5);
    }
    if("RIGHT"){
    usbMIDI.sendNoteOn(2,127,5);
    delayMicroseconds(5);
    usbMIDI.sendNoteOff(2,0,5);
    }
    }
    unsigned char result1 = rotary1_process();
    if (result1) {
    Serial.println(result1 == DIR_CCW1 ? "LEFT" : "RIGHT");
    if("LEFT"){
    usbMIDI.sendNoteOn(3,127,5);
    delayMicroseconds(5);
    usbMIDI.sendNoteOff(3,0,5);
    }
    if("RIGHT"){
    usbMIDI.sendNoteOn(4,127,5);
    delayMicroseconds(5);
    usbMIDI.sendNoteOff(4,0,5);
    }
    }
    }

    [/CODE]

    again thanks heaps

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  45. Is there any explanation how to fill lookup table for diferent sequence ? For this one 10,11,01,00,10 for example? I am not sure why some rotary encoders have diferent sequences.

    ReplyDelete
  46. Hi Ben,
    I used your library for a program that uses an si5351 as a vfo/bfo for ham radio purposes.
    Thanks.
    http://ak2b.blogspot.com/

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  47. Hi Ben,

    I'm loving this code. Have you ever considered adding a velocity section to switch between 1/4, HALF and FULL mode based on how fast the encoder is rotated? I'm just getting started and am trying to build something like that based on your superior code. Any hints would be most appreciated!

    thanks,

    Daniel

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  48. I'm trying to compile the original file ( RotaryLib.zip ) , but every time I try , it returns me the same error:

    Arduino: 1.0.6 (Windows 7), Board: "Arduino Leonardo"

    rotary.ino: In function 'void setup()':
    rotary:7: error: 'PCIE2' was not declared in this scope
    rotary:8: error: 'PCINT18' was not declared in this scope
    rotary:8: error: 'PCINT19' was not declared in this scope

    What i´m doing wrong?

    ReplyDelete
  49. Hi Ben!

    Love this library - exactly what I was looking for. I wanted something that didn't require interrupts, and I didn't want to re-write the example from Adafruit or try to adapt something from the Arduino playground.

    I made some modifications to the library to support push-button encoders, and posted the changes up on Bitbucket. Please feel free to grab it, and let me know what you think!

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

    ReplyDelete
  50. This comment has been removed by a blog administrator.

    ReplyDelete
  51. Hello, Ben... Thank you for posting this code! I am a complete Noob. I would like to use digital encoders to control digital resistors in a DIY power supply. I am using a Microchip brand PIC18F45K50 and their MCP4361-103 digital resistor. I am using MPLAB X for coding in C, not C++. Will your C++ code compile as C code? If not, where could I learn how to port it to C? What else might need to be changed to use the 18F chip, instead of the Arduino. Thank you for your help!

    ReplyDelete
  52. Was there a fix for when you remm out the half step define like this:

    // #define HALF_STEP

    So you can run the code in full step?

    ReplyDelete
  53. BTW I remmed out all of the half step lines to get it to run in full step and no matter which way I turn the knob it displays Left Left Left !!

    ReplyDelete
  54. This comment has been removed by the author.

    ReplyDelete
  55. This comment has been removed by the author.

    ReplyDelete
  56. Adrian's post from Aug 2012 still applies: there is a bug in the FULL_STEP table that causes the code to only see turning left. Change the 10 to a 20 as indicated and the problem is solved.

    ReplyDelete
  57. hi ben & brianlow. thx a lot for your tutorial. your libs help me a lot.

    ReplyDelete
  58. Thanks for the library, an elegant solution!

    ReplyDelete
  59. After all these years, still one of the better solutions. Any chance I could get some help with 3 encoders on a Mega? Ports 2,3,18,19,20,21.

    ReplyDelete
  60. Hi Ben,

    Thanks for this solution - certainly elegant :)
    I have a question about the likelihood of multiple debounces - not sure
    if this is seen in real life (I guess I should experiment...), but the state machine only handles a single debounce, right?
    What happens if you've got flaky hardware and the below is the signal
    coming from the pins:
    00000101010111111131313133333333232322222020200000
    ?

    I guess it could also depends on the polling speed, but have you seen
    something like this happen?


    Cheers,

    Bonny

    ReplyDelete
    Replies
    1. Bonny,

      Cheap encoders are miserable to work with. I bought 5 and managed to get 3 good ones that don't go crazy just by touching them. I imagine there are "good" encoders out there, but I don't know where to get them.

      Delete
  61. Hello everyone,
    Someone knows how to change the Rotary library so you can use with the Mega 2560?
    I need more pins than the UNO RV3. so I need to use the Mega.
    Thank you.
    Daniel

    ReplyDelete
    Replies
    1. Daniel,

      I was in the same boat. I needed three encoders. I ended up doing away with the Rotary library and just using attachInterrupt like so:

      attachInterrupt(digitalPinToInterrupt(2), interrupt, CHANGE);
      attachInterrupt(digitalPinToInterrupt(3), interrupt, CHANGE);
      attachInterrupt(digitalPinToInterrupt(20), interrupt, CHANGE);
      attachInterrupt(digitalPinToInterrupt(21), interrupt, CHANGE);
      attachInterrupt(digitalPinToInterrupt(18), interrupt, CHANGE);
      attachInterrupt(digitalPinToInterrupt(19), interrupt, CHANGE);

      Delete
    2. All the interrupts dump into this function:


      void interrupt()
      {
      encode = 1;
      }


      Once encode is set to 1, the rest is handled in the main loop.

      Delete
  62. Thanks for the info, I need one encoder, the digital pins are for control relays.
    The problem with the proposed program at the beginning, is I can not recall the contents of result.

    I need :

    ISR(PCINT0_vect)
    {

    unsigned char result = r.process();

    if (result) {
    if (result == DIR_CW){rx=rx+increment;}
    else {rx=rx-increment;};



    }
    }

    ReplyDelete
  63. about to get started with rotary encoders, had no idea about Direction change issues

    ReplyDelete
  64. This comment has been removed by the author.

    ReplyDelete
  65. Hi, excelent blog. How about 1/4 cycle rotary?. would you write code for this rotary. Thanks.
    I am arduino newbee.

    ReplyDelete
  66. Ben, I ported your code to a PIC18F1330 and I have it working nicely to handle four encoders with switches. There is a bug in your code which prevents it from working correctly in full step mode. The file is rotary3.ino, on line 33 you need to change the 0x10 to 0x20. People using your Arduino library will not see this- the problem is in the code in the 'rotary3' folder which does not use the library.

    ReplyDelete
  67. THANK YOU!!! Finally a library that works. Much appreciated.

    ReplyDelete
  68. Hello Ben,
    Thanks to your article, I could correct errors in a Proton PIC Basic program that reads a 600 ppr optical rotary encoder. Below you can see a part of the program. Thanks for your help. Regards, Franz
    Device 16F874A 'The PIC is PIC16F874A.
    Xtal = 16 'I am Using a 16 MHz Xtal
    Config HS_OSC, WDTE_OFF , PWRTE_ON , BOREN_OFF ,_
    CP_OFF
    All_Digital = true 'All I/O are digital
    On_Hardware_Interrupt Encoder 'On falling edge of A goto Encoder:
    TRISB = %10000011
    OPTION_REG.7 = 0 'Enable pull-up resistors
    OPTION_REG.6 = 0 'Interruption at falling edge
    INTCON.7 = 1 'Enable global interrutions
    INTCON.4 =1 'Enable RB0/INT interruptions
    Dim Counter As Word
    Dim OldA As Bit 'To be used in the interruption routine
    Dim OldB As Bit
    PORTB = 0
    Symbol A = PORTB.0 'Encoder's signal A
    Symbol B = PORTB.1 'Encoder's signal B
    '*********** INTERRUPTION *************
    Encoder:
    Context Save 'Save registry contents
    'Checking the Gray code sequence mentioned by Ben:
    'If A and B change at the same time, Counter is not affected.
    If A <> OldA And B <> OldB Then NoChange
    If INTCON.1 = 1 Then
    If B = 1 Then Inc Counter
    If B = 0 Then Dec Counter
    OldA = A
    OldB = B
    EndIf
    NoChange:
    INTCON.1 = 0 'Clear RB0/INT flag
    Context Restore 'Restore former registy values.

    ReplyDelete
  69. Hi Ben, Thanks for library. After many years it is still my favourite.
    Is it possible to add (or)change code for "quad step"? (4x mode like in "pjrc" library).

    MAc

    ReplyDelete
  70. ok - I've spent too many hours trying to understand this state table and finally I am giving up and asking for help. I looked back through all the comments and there was one fellow that asked pretty much the same thing as I am trying to figure out without getting a response so I will copy his question here -
    -----
    In the full step code, in the 3rd row in the table:

    R_CW_BEGIN
    (R_CW_NEXT, R_CW_BEGIN, R_START, R_START),

    I don't understand why if we receive the 0-0 encoder output, we have to go to R_CW_NEXT. As far as i can understand the code, the previous state was 1-0, so we only can go to 1-1 (NEXT) or 0-0. 0-0 should mean START... but the table has R_CW_NEXT.

    could you explain why these values are so?
    --------------------------------

    ReplyDelete
    Replies
    1. Hold the press ! I figured it out with the help of a youtube video where a fellow implements a similar approach and in fact references this site. The key to understanding this (at least for me) was the "state diagram" that he draws out on the whiteboard. You can see the paths much easier and prevent confusion.
      https://www.youtube.com/watch?v=BJHftzjNjkw

      Delete
    2. @Lewis: I have seen the youtube and understood the state diagram, however, I'm still confused about having an output (from the encoder) of 00 and state jumping to 11 (NEXT). Any hint will be greatly appreciated...

      Delete
    3. Now I understand…

      I was puzzled as of why, in the 3rd line (R_CW_BEGIN), an output of 00, rather than 11, took us to state R_CW_NEXT. If you follow the states, you will find that, for a CW, the sequence is 00>01>00>10>11>00….so it still works! Although, through a less efficient route…

      I have re written the state table so that it follows the required sequence: 00>10>11>01>00

      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},
      };

      I have tested it and it works like a charm! I hope it helps others...

      Delete
  71. Thank you very much for your work, and for sharing!!!

    ReplyDelete
  72. With your code, my encoder is running smoothly. Thank you very much!

    My encoder has half-turn detents, in 11 and 00, then I have to use the half step table.

    I added some code to activate acceleration. Although it is running right until now, I am relatively new to these things, and I don't know yet if the code is well writen or if the code is bullet proff.

    The acceleration depends of maximum multiplier and the gap time. Bigger multiplier permits bigger increments, while bigger gap time, permits activate the acceleration with less turning speed.

    In practice it works like this: on slowly turning the encoder, each step will change the value 1 by 1, as the speed of rotation increases, it will change the value by 2 in 2, more speed by 3 in 3, and so, until the maximum set in the multiplier was hit. How quickly the acceleration will be active depends on the gap time.



    unsigned char Rotary::process()
    {
    unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);
    state = ttable[state & 0xf][pinstate];

    //instead returning directly, the result is passed to variable "direction"
    int direction = encoderState_ & 0x30;

    int factor = 1; //factor used to multiple to output, default is 1
    if ((direction != 0) && accIsOn_) //if direction has changed and acc is on (accIsOn_ is a bool flag to acceleration)
    {
    //elapsed time between actual turning and last turning
    unsigned long turnInterval = (millis() - lastReadTimeMs_);
    if (turnInterval < accGapTimeMs_) //if elapsed time is smaller than gap time
    {
    // Here the turn interval will be subtracted from the acceleration gap time
    // This returns a value that will be larger as the rotation is faster
    int z = (accGapTimeMs_ - turnInterval);

    // And here the value will be remaped from: minimum = 1, maximum: acc gap time
    // to: minimum = 1, maximum = (acc multiplier +1). This +1 is because without it
    // the acceleration, even at the maximum, will reach only the maximum - 1.
    factor = map(z, 1, accGapTimeMs_, 1, (accMaxMultiplier_ + 1));
    }
    lastReadTimeMs_ = millis(); // store when the last turn happened
    }

    // The return of function will be
    // 0 = no movement
    // positive number = clock wise turning
    // negative number = counter clock wise turning
    if (direction == DIR_CW) //if direction is CW
    {
    int x = (1 * factor); //multiply the factor by 1
    return x; //return the value
    }
    else if (direction == DIR_CCW) //if direction is CCW
    {
    int x = (-1 * factor); //multiply the factor by -1
    return x; //return the value
    }
    else
    // if has no movement
    return 0; //return 0
    }

    ReplyDelete
    Replies
    1. I have been unsuccessful using your code. Can you help by putting it in an .ino for me.

      Delete
    2. Someone did: https://forum.arduino.cc/index.php?topic=242356.msg2308019#msg2308019

      Delete
  73. Hello and thank you for the elegantly simple code to bypass all the nasty debounce routines that I have been seeing all over the web. I am interested in modifying the .h file and .cpp files so that I can use an encoder that was a third input, the SW button when pushing down on the shaft. Would this even be possible?

    I want to control and navigate a menu on an LCD screen using a single rotary encoder and its push button functionality.

    Thank you in advance for taking the time to read and respond.

    ReplyDelete
    Replies
    1. Hi there...I have included in the following link the rotary.h and .cpp libraries with a brief explanation of a couple of modifications that I've made. I hope it helps!

      https://carlossilesblog.wordpress.com/2019/07/27/rotary-encoder/

      Delete
  74. I need a rotary encoder just as good as this one but in Python (M5Stack|Core). Anyone that has one?

    ReplyDelete
  75. Hey, I just wanted to thank you for writing and sharing this library.

    ReplyDelete
  76. hi.
    i'm trying to use one RE for many jobs. is this the right way to do it?

    Rotary Step1Rotary = Rotary(rotarypin1, rotarypin2);
    Rotary Step2Rotary = Rotary(rotarypin1, rotarypin2);
    Rotary Step3Rotary = Rotary(rotarypin1, rotarypin2);
    Rotary Step4Rotary = Rotary(rotarypin1, rotarypin2);

    RotaryProcessThing = &Step1Rotary;

    Then change what RotaryProcessThing refers to each time I change what I'm using the encoder for?

    I'm assuming the last state is then preserved for each?

    ReplyDelete
    Replies
    1. i'm calling RotaryProcessThing.process() in a later function to increment or reduce variables

      Delete
  77. The example doesn't compile on windows 10 with arduino 1.8.11.
    So far I only spent countless hours getting a decent rotary encoder routine to work. This project highlights the issues I've had, so I would hope it would get working.

    C:\Users\me\AppData\Local\Temp\cc8tAXOq.ltrans0.ltrans.o: In function `rotate':
    D:\buxtronix-encoder\interrupt/interrupt.ino:29: undefined reference to `Rotary::process()'
    C:\Users\me\AppData\Local\Temp\cc8tAXOq.ltrans0.ltrans.o: In function `global constructors keyed to 65535_0_interrupt.ino.cpp.o.1755':
    :(.text.startup+0x60): undefined reference to `Rotary::Rotary(char, char)'
    collect2.exe: error: ld returned 1 exit status
    exit status 1
    Error compiling for board Arduino Uno.

    ReplyDelete
  78. https://getdailybook.com/ for top selling books

    ReplyDelete
  79. http://alltopc.com/ for softwares

    ReplyDelete
  80. https://hashmiipc.com/adobe-premiere-pro-cc-2015-free-download

    ReplyDelete
  81. Simple, fast and easy to port to my PIC-controller, using MPLAB IDE with CX8 C-compiler.

    Before, I lost several days trying to solve some strange behavior of my Encoders. It took me some while to realize that it was caused by bouncing. Realizing that using a state-machine was the way to go, this post crossed my path, and made me win back several days instead of re-inventing the wheel!

    Thanks a lot!!!

    ReplyDelete
  82. FYI Ben, as of June 2021: This is still the nutz. Thanks for sharing.

    ReplyDelete