Rotary Encoder How To

Rotary Encoder How To

Rotary Encoders
Rotary encoders present several challenges:

Contact bounce is a nuisance both for the button and the rotary encoder switches. We will deal with contact bounce for buttons and the rotary encoder using two different methods.

Sampling speed is important so button presses are not missed and rotary encoder operation is smooth and responsive.

Contact Bounce

Shown below is a basic switch connection schematic where the switch can be a pushbutton, toggle, rotary encoder, etc. When the switch is open a small current flows through the internal pullup resistor, resulting in 5V at the Arduino input pin. When the switch is closed all the current flows to ground, pulling the Arduino input pin to 0V.

Switch Open Switch Closed

Mechanically, when a switch is closed the switch contacts scrape a little bit. This is by design and helps keep the switch contacts clean. However while doing this it makes intermittent contact and the resulting voltage that appears at the Arduino input pin looks sort of like this...

Graph
The glitchy area starting at 0 milliseconds is "bounce" that occurs as the contacts move against each other. Eventually the contacts settle and constant contact is made.

Bad Debouncing

One common hardware approach to debouncing is to add a low pass filter (a resistor and capacitor combination). Although this will filter out some high frequency noise it is ineffective at removing all of the contact bounce.

Stupid
Low Pass Filter is a Poor Debounce Strategy

Basic Software Debouncing

What the software needs to do is ignore the noise but still respond to the constant contact when a button is pressed. This is done by watching the input signal until a minimum period of constant signal is seen.

Graph

This can be accomplished using a software delay loop as shown in this code...

An improvement is to use the timer function millis() for the timing instead of counting CPU cycles. This allows the code to be non-blocking, as shown in this code...

Both of the examples above do the debouncing on the front end of the press, which delays the detection of the button by a fraction of a second. We can make a further improvement to the code so that the debouncing is done on the release of the button instead, so that the button is detected right when it's pressed.

All of the previous examples rely on the loop() running quite fast in order to sample the button input quickly. We can make a further improvement by putting the button sampling into a periodic interrupt, which removes the requirement that loop() must run fast. Now loop() can perform tasks that take a significant amount of time and button presses will not be missed in the meantime.

Rotary Encoder Debouncing and Decoding

A rotary encoder has two rotor contact switch outputs, designated A and B. The rotary encoder may also have a push switch contact output. The A and B signals are subject to contact bounce noise that's similar to other types of switches. However because of the way the A and B signals interact we can deal with contact bounce using a different method than the pushbutton.

Beware that the A and B rotation convention can vary depending on the rotary encoder model. Some models reverse A and B; the code must take this into account or the rotation direction will be backwards.

The way the encoder is constructed only one of the A or B signals can change state at a time. Each rotation detent is divided up into 4 phases defined by the A and B signal states. This obviously leads to 4 possible states. We will add a fifth state to act as a ratchet. The software implements the state machine as shown below. Notice that the spin count state change occurs in one direction only; this is important as the state change edge can be noisy and bounce back and forth between states. But since the detection occurs in one direction it latches in that direction and as a result any noise is ignored.
State Diagram

An "edge" is formed by concatenating two states; for example state 00 to 01 the edge is 0001. Below is the pseudocode for the core logic. The code is optimized by reduction so that it doesn't have to track the state exactly; rather it looks at the relevant edge transitions and uses two flag bits, CW_INHIBIT and CCW_INHIBIT, to differentiate the alternate 00 state for CW and CCW.

An improvement to basic encoder decoding is dynamic speed boost, or velocirotor. The logic for this is to measure the time between rotary encoder movements and multiply the movement by a function that's inversely proportional to the time. So the shorter the time between movements the faster the spin counts.

Breadboard Example
Here we have a 4 encoder breadboard example, plus an array of MAX7219 LED displays. The LED display is optional though; you can watch the output on the Serial Monitor. You also don't have to build up all 4 rotary encoders.

Breadboard Picture Breadboard Picture

Wiring Schematic Diagram
Schematic Diagram

Download Code
Breadboard Example Source CodeArduino.zip