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.