How Rotary Encoder Works and Interface It with Arduino

A rotary encoder is a type of position sensor that converts the angular position (rotation) of a knob into an output signal that is used to determine what direction the knob is being rotated.

Due to their robustness and fine digital control; they are used in many applications including robotics, CNC machines and printers.

There are two types of rotary encoder – absolute and incremental. The absolute encoder gives us the exact position of the knob in degrees while the incremental encoder reports how many increments the shaft has moved.

The rotary encoder used in this tutorial is of an incremental type.

Rotary Encoders Vs Potentiometers

Rotary Encoders are the modern digital equivalent of the potentiometer and are more versatile than a potentiometer.

They can fully rotate without end stops while a potentiometer can rotate only about 3/4 of the circle.

Potentiometers are best in situations where you need to know the exact position of the knob. However, rotary encoders are best in situations where you need to know the change in position instead of the exact position.

How Rotary Encoders Work

Inside the encoder is a slotted disk connected to the common ground pin C, and two contact pins A and B, as illustrated below.

rotary encoder internal structure

When you turn the knob, A and B come in contact with the common ground pin C, in a particular order according to the direction in which you are turning the knob.

When they come in contact with common ground they produce signals. These signals are shifted 90° out of phase with each other as one pin comes in contact before the other pin. This is called quadrature encoding.

rotary encoder working animation.gif

When you turn the knob clockwise, the A pin connects first, followed by the B pin. When you turn the knob counterclockwise, the B pin connects first, followed by the A pin.

By tracking when each pin connects to and disconnects from the ground, we can use these signal changes to determine in which direction the knob is being rotated. You can do this by simply observing the state of B when A changes state.

When the A changes state:

  • if B != A, then the knob was turned clockwise.
    rotary encoder output pulses in clockwise rotation
  • if B = A, then the knob was turned counterclockwise.
    rotary encoder output pulses in anticlockwise rotation

Rotary Encoder Pinout

The pinouts of the rotary encoder are as follows:

rotary encoder module pinout

GND is the Ground connection.

VCC is the positive supply voltage, usually 3.3 or 5 Volts.

SW is the active low push button switch output. When the knob is pushed, the voltage goes LOW.

DT (Output B) is the same as the CLK output, but it lags the CLK by a 90° phase shift. This output can be used to determine the direction of rotation.

CLK (Output A) is the primary output pulse for determining the amount of rotation. Each time the knob is rotated by one detent (click) in either direction, the ‘CLK’ output goes through one cycle of going HIGH and then LOW.

Wiring – Connecting Rotary Encoder to Arduino

Now that we know everything about the rotary encoder it is time to put it to use!

Let’s connect Rotary Encoder to Arduino. Connections are fairly simple. Start by connecting +V pin on the module to 5V on the Arduino and GND pin to ground.

Now connect the CLK and DT pins to digital pin#2 and #3 respectively. Finally, connect the SW pin to a digital pin #4.

The following illustration shows the wiring.

wiring rotary encoder with arduino uno

Arduino Code – Reading Rotary Encoders

Now that you have your encoder hooked up you’ll need a sketch to make it all work.

The following sketch detects when the encoder is being rotated, determines which direction it is being rotated and whether or not the button is being pushed.

Try the sketch out; and then we will dissect it in some detail.

// Rotary Encoder Inputs
#define CLK 2
#define DT 3
#define SW 4

int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;

void setup() {
	
	// Set encoder pins as inputs
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);
	pinMode(SW, INPUT_PULLUP);

	// Setup Serial Monitor
	Serial.begin(9600);

	// Read the initial state of CLK
	lastStateCLK = digitalRead(CLK);
}

void loop() {
	
	// Read the current state of CLK
	currentStateCLK = digitalRead(CLK);

	// If last and current state of CLK are different, then pulse occurred
	// React to only 1 state change to avoid double count
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

		// If the DT state is different than the CLK state then
		// the encoder is rotating CCW so decrement
		if (digitalRead(DT) != currentStateCLK) {
			counter --;
			currentDir ="CCW";
		} else {
			// Encoder is rotating CW so increment
			counter ++;
			currentDir ="CW";
		}

		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
	}

	// Remember last CLK state
	lastStateCLK = currentStateCLK;

	// Read the button state
	int btnState = digitalRead(SW);

	//If we detect LOW signal, button is pressed
	if (btnState == LOW) {
		//if 50ms have passed since last LOW pulse, it means that the
		//button has been pressed, released and pressed again
		if (millis() - lastButtonPress > 50) {
			Serial.println("Button pressed!");
		}

		// Remember last button press event
		lastButtonPress = millis();
	}

	// Put in a slight delay to help debounce the reading
	delay(1);
}

If everything is fine, you should see below output on serial monitor.

rotary encoder output on serial monitor

If the rotation being reported is the opposite of what you expect, try swapping the CLK and DT lines.

Code Explanation:

The sketch begins with the declaration of the Arduino pins to which the encoder’s CLK, DT and SW pins are connected.

#define CLK 2
#define DT 3
#define SW 4

Next, a few integers are defined. The counter variable represents the count that will be modified each time that the knob is rotated one detent (click).

The currentStateCLK and lastStateCLK variables hold the state of the CLK output and are are used for determining the amount of rotation.

A string called currentDir will be used when printing the current direction of rotation on the serial monitor.

The lastButtonPress variable is used to debounce a switch.

int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;

Now in the the Setup section, we first define the connections to the encoder as inputs, then we enable the input pullup resistor on SW pin. We also setup the serial monitor.

At the end we read the current value of the CLK pin and store it in the lastStateCLK variable.

pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);

Serial.begin(9600);

lastStateCLK = digitalRead(CLK);

In the loop section, we check the CLK state again and compare it to the lastStateCLK value. If they are different then it means that the knob has turned and a pulse has occurred. We also check if the value of currentStateCLK is 1 in order to react to only one state change to avoid double count.

currentStateCLK = digitalRead(CLK);

if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

Inside the if statement we determine the direction of rotation. To do this we simply read the DT pin on the encoder module and compare it to the current state of the CLK pin.

If they are different, it means that the knob is rotated counterclockwise. We then decrement the counter and set currentDir to “CCW”.

If the two values are the same, it means that the knob is rotated clockwise. We then increment the counter and set currentDir to “CW”.

if (digitalRead(DT) != currentStateCLK) {
    counter --;
    currentDir ="CCW";
} else {
    counter ++;
    currentDir ="CW";
}

We then print our results on the serial monitor.

Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);

Outside the if statement we update lastStateCLK with the current state of CLK.

lastStateCLK = currentStateCLK;

Next comes the logic to read and debounce the push button switch. We first read the current button state, if it’s LOW, we wait for 50ms to debounce the push button.

If the button stays LOW for more than 50ms, we print “Button pressed!” message on the serial monitor.

int btnState = digitalRead(SW);

if (btnState == LOW) {
    if (millis() - lastButtonPress > 50) {
        Serial.println("Button pressed!");
    }
    lastButtonPress = millis();
}

Then we do it all over again.

Arduino Code – Using Interrupts

In order for rotary encoder to work, we need to continuously monitor changes in DT and CLK signals.

To determine when such changes occur, we can continuously poll them (like we did in our previous sketch). However, this in not the best solution for below reasons.

  • We have to busily perform checking to see whether a value has changed. There will be a waste of cycles if signal level does not change.
  • There will be a latency from the time the event happens to the time when we check. If we need to react immediately, we will be delayed by this latency.
  • It is possible to completely miss a signal change if the duration of the change is short.

A solution widely adopted is the use of an interrupt.

With interrupts you don’t need to constantly poll the specific event. This frees the Arduino to get some other work done while not missing the event.

Wiring

As most Arduinos (including Arduino UNO) have only two external interrupts, we can only monitor changes in the DT and CLK signals. That’s why we removed the connection of the SW pin from the previous wiring diagram.

So now the wiring looks like this:

control rotary encoder using interrupts with arduino uno

Some boards (like the Arduino Mega 2560) have more external interrupts. If you have one of them, you can keep the connection for SW pin and extend below sketch to include code for the button.

Arduino Code

Here’s the sketch that demonstrates the use of the interrupts while reading a rotary encoder.

// Rotary Encoder Inputs
#define CLK 2
#define DT 3

int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";

void setup() {
	
	// Set encoder pins as inputs
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);

	// Setup Serial Monitor
	Serial.begin(9600);

	// Read the initial state of CLK
	lastStateCLK = digitalRead(CLK);
	
	// Call updateEncoder() when any high/low changed seen
	// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
	attachInterrupt(0, updateEncoder, CHANGE);
	attachInterrupt(1, updateEncoder, CHANGE);
}

void loop() {
	//Do some useful stuff here
}

void updateEncoder(){
	// Read the current state of CLK
	currentStateCLK = digitalRead(CLK);

	// If last and current state of CLK are different, then pulse occurred
	// React to only 1 state change to avoid double count
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

		// If the DT state is different than the CLK state then
		// the encoder is rotating CCW so decrement
		if (digitalRead(DT) != currentStateCLK) {
			counter --;
			currentDir ="CCW";
		} else {
			// Encoder is rotating CW so increment
			counter ++;
			currentDir ="CW";
		}

		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
	}

	// Remember last CLK state
	lastStateCLK = currentStateCLK;
}

Notice that the main loop of this program is kept empty so Arduino will be busy doing nothing.

Meanwhile, this program watches digital pin 2 (corresponds to interrupt 0) and digital pin 3 (corresponds to interrupt 1) for a change in value. In other words, it looks for a voltage change going from HIGH to LOW or LOW to HIGH, which happens when you turn the knob.

When this happens the function updateEncoder (often called the interrupt service routine or just ISR) is called. The code within this function is executed and then the program returns back to whatever it was doing before.

Below two lines are responsible for all this. The function attachInterrupt() tells the Arduino which pin to monitor, which ISR to execute if the interrupt is triggered and what type of trigger to look for.

attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);

Control Servo Motor with Rotary Encoder

For our next project we will use a rotary encoder to control the position of a servo motor.

This project can be very useful in many situations, for example, when you want to operate a robot arm, as it would let you precisely position the arm and its grip.

In case you are not familiar with servo motor, consider reading (at least skimming) below tutorial.

SUGGESTED READING

Tutorial for Interfacing Servo Motor with Arduino
How Servo Motor Works & Interface It With Arduino
Want to add motion to your next Arduino project without building a motor controller? Then servo motors might be the solid launching point for you....

Wiring

As the wiring diagram shows you’ll need a servo motor. Connect the Red wire of the servo motor to the external 5V supply, the Black/Brown wire to ground and the Orange/Yellow wire to the PWM enabled pin 9.

Of course you can use the Arduino 5V output but keep in mind that the servo can induce electrical noise onto the 5V line that the Arduino uses, which may not what you want.

Therefore it is recommended that you use an external power supply.

wiring for controlling servo motor with rotary encoder

Arduino Code

Here’s the sketch to precisely control the servo motor with the rotary encoder. Each time the knob is rotated one detent (click), the position of the servo arm will be changed by one degree.

// Include the Servo Library
#include <Servo.h>

// Rotary Encoder Inputs
#define CLK 2
#define DT 3

Servo servo;
int counter = 0;
int currentStateCLK;
int lastStateCLK;

void setup() {
	
	// Set encoder pins as inputs
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);
	
	// Setup Serial Monitor
	Serial.begin(9600);
	
	// Attach servo on pin 9 to the servo object
	servo.attach(9);
	servo.write(counter);
	
	// Read the initial state of CLK
	lastStateCLK = digitalRead(CLK);
}

void loop() {
	
	// Read the current state of CLK
	currentStateCLK = digitalRead(CLK);
	
	// If last and current state of CLK are different, then pulse occurred
	// React to only 1 state change to avoid double count
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){
		
		// If the DT state is different than the CLK state then
		// the encoder is rotating CCW so decrement
		if (digitalRead(DT) != currentStateCLK) {
			counter --;
			if (counter<0)
				counter=0;
		} else {
			// Encoder is rotating CW so increment
			counter ++;
			if (counter>179)
				counter=179;
		}
		// Move the servo
		servo.write(counter);
		Serial.print("Position: ");
		Serial.println(counter);
	}
	
	// Remember last CLK state
	lastStateCLK = currentStateCLK;
}

If you compare this sketch with our first one you’ll notice many similarities, except few things.

At the start we include the built-in Arduino Servo library and create a servo object to represent our servo motor.

#include <Servo.h>

Servo servo;

In the Setup, we attach the servo object to pin 9 (to which the control pin of the servo motor is connected).

servo.attach(9);

In the loop, we limit the counter to have a range 0 to 179, because a servo motor only accepts a value between this range.

if (digitalRead(DT) != currentStateCLK) {
    counter --;
    if (counter<0)
        counter=0;
} else {
    counter ++;
    if (counter>179)
        counter=179;
}

Finally the counter value is used to position the servo motor.

servo.write(counter);