Youtube channel

Check out my youtube channel!

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.


Download RotaryLib.zip (Version 1.1)


58 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
  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
  3. It would be great if this was updated--I agree that all the other examples suck. Thanks in advance-

    ReplyDelete
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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