Rotary Encoders – The Complete Tutorial – Part 3

Previously, the operation of a rotary encoder and how to read one was explained. The output of the signal pins of the encoder form a Gray Code bit pattern. The speed and direction of rotation of the encoder knob is found by the output Gray Code pattern. Armed with this information, an Arduino board, and a rotary encoder we can write a test program to interface the encoder to an Arduino.


The Wiring


rotary encoder wiring diagram


The Arduino Uno R3 board is used in this example only because it was within arms reach when I started this write-up. The above diagram shows the wiring to interface the rotary encoder to the Arduino board. Pins 2/3 of the Arduino connect the signal A/B pins from the encoder. External interrupts are available on these two pins of the Uno which will be utilised in our code.

This fancy rotary encoder has a built-in push button and has been wired to digital pin 4 of the Arduino. An external pull-up resistor has been used with the pushbutton, but it is also acceptable to enable the internal pull-up instead.


The Rotary Encoder Algorithm


A rotary encoders output will follow a 2-bit Gray Code pattern. When rotating the knob of the encoder clockwise the bit pattern on the output will be:

00 — 01 — 11 — 10 — 00 …etc

Alternately, when  rotating the knob of the encoder counter-clockwise the bit pattern on the output will be:

00 — 10 — 11 — 01 — 00 …etc

The only difference is the pattern reverses depending on the direction of rotation. Comparing the previous state of the encoder to the new one will make the direction of rotation known. So, as an example:

Previous state: 01        New state: 11

This progression matches the pattern for and confirms clockwise rotation.

Gray Code has four unique states, so there will only be four different transitions when rotating clockwise before the pattern repeats. The same goes with counter-clockwise rotation. To cover all transitions, in all directions of rotation, there are only 8 transitions to test for. The code reading the rotary encoder and determining direction of rotation simply becomes two IF statements testing for the four clockwise and counter clockwise transitions.




The variables in our code are pretty straightforward. The first three define the pins used on the arduino for each rotary encoder output.


Next we have our storage variables for volume. This example is assuming our rotary encoder is acting as a volume knob. We need to store the current volume as well as the last volume. As we rotate the knob our code will be increasing or decreasing the volume variable. The lastVolume variable is used to determine a change since we last checked the volume. In a real application the volume value sets the gain in an amplifier for an audio output stage of a circuit. Adjusting the gain should only occur if the volume changes, this is done by tracking the previous volume.

The same current/previous storage variable concept applies to lastEncoded. This is our previous value read from the rotary encoder which we compare to the value we read directly.

The last set of variables are for detecting a button press of the rotary encoder. If we remember the previous state of the button, we avoid registering multiple continuous button presses in the case we hold down the button.


Setup Code


Setup involves starting our serial monitor which we are using to visualise inputs through the rotary encoder. The three rotary encoder pins are set as inputs, with the signal A/B inputs having the internal pull-ups enabled (we already had one wired on the third button input). Finally we need to activate the two external interrupts. When an interrupt occurs the code now knows what our handling function is and runs it.


The Loop Code


The main loop starts with checking if our volume has changed and dealing with that. Next, the code not only needs to check if the button is pressed but it needs to know if it was pressed last time we checked. This is where we avoid registering multiple button presses when you hold down the button. This example is effectively treating the button as a mute button by setting the volume variable to 0 when pressed.

Setting a delay at the end creates enough time between loops checking the button state for the contact bounce of the button to settle. This debouncing was necessary as the code would occasionally register multiple button presses at once. The code is fast enough to check the button state again before the contact bounce of a button press dissipates.


Interrupt Handler


serviceEncoderInterrupt is the function attached to our two interrupts as in the setup code. Whenever an interrupt occurs on either pin 2 or 3 this function will immediately run. The algorithm for testing clockwise or counter clockwise rotation is utilised here. The previous encoder value is combined with the current reading on signal A/B pins to create a binary string. This binary string then takes the form:

previous signal B, previous signal A, signal B, signal A

The two if statements test if we either have a forward or reverse Gray Code pattern and determine the direction of rotation from the outcome. Finally, we need to save our current state as the previous state for the next time we have an interrupt.


Code Download


Here is the complete code from above. It is in the form of an Arduino project .ino file.

Click the button to download: Download



Video Demo


The result of the code above running on the Arduino can be seen in the below video. I also show a few bugs that can pop up with a rotary encoder and what the symptoms look like.



This article was part 3 of the rotary encoder tutorial series, see the other entries below:

Rotary Encoders – The Complete Tutorial – Part 1

Rotary Encoders – The Complete Tutorial – Part 2




  1. I see you don’t monetize your site, don’t waste your traffic, you can earn extra bucks every month because you’ve got high quality content.
    If you want to know how to make extra money, search for: Boorfe’s tips best adsense alternative

Leave a Reply

Your email address will not be published. Required fields are marked *