We are surrounded by rotary encoders without even realizing it, as they are used in so many everyday items, from printers and cameras to CNC machines and robots. The most common application of a rotary encoder is the volume knob on a car radio.

A rotary encoder is a type of position sensor that converts the angular position (rotation) of a knob into an output signal that can be used to determine which direction the knob is turned.

Rotary encoders are classified into two types: absolute and incremental. The absolute encoder reports the exact position of the knob in degrees, whereas the incremental encoder reports the number of increments the shaft has moved.

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

Rotary Encoders Vs Potentiometers

Rotary encoders are the modern digital equivalent of potentiometers and are more versatile.

Rotary encoders can rotate 360° without stopping, whereas potentiometers can only rotate 3/4 of the circle.

Potentiometers are used in situations where you need to know the exact position of the knob. Rotary encoders, on the other hand, are used in situations where you need to know the change in position rather than the exact position.

How Rotary Encoders Work?

Inside the encoder is a slotted disc that is connected to pin C, the common ground. It also has two contact pins A and B, as shown below.

rotary encoder internal structure

When you turn the knob, A and B make contact with the common ground pin C in a specific order depending on which direction you turn the knob.

When they make contact with common ground, two signals are generated. These signals are 90° out of phase because one pin makes contact with common ground before the other. It is referred to as quadrature encoding.

rotary encoder working animation

When the knob is turned clockwise, the A pin connects to ground before the B pin. When the knob is turned anticlockwise, the B pin connects to ground before the A pin.

By monitoring when each pin connects or disconnects from ground, we can determine the direction in which the knob is being rotated. This can be accomplished by simply observing the state of B when A’s state changes.

When A changes state:

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

Rotary Encoder Pinout

The pinout of the rotary encoder module is as follows:

rotary encoder module pinout

GND is the ground connection.

VCC is the positive supply voltage, which is typically between 3.3 and 5 volts.

SW is the output of the push button switch (active low). When the knob is depressed, the voltage goes LOW.

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

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

Wiring a Rotary Encoder to an Arduino

Now that we understand how the rotary encoder works, it’s time to put it to use!

Let’s hook up the rotary encoder to the Arduino. The connections are quite simple. Begin by connecting the module’s +V pin to the Arduino’s 5V output and the GND pin to ground.

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

The following image shows the wiring.

wiring rotary encoder with arduino uno

Arduino Example Code 1 – Reading Rotary Encoders

Our first example is very straightforward; it simply detects the rotation direction of the encoder and when the button is pressed.

First, try out the sketch, and then we’ll go over it in more 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);
}

You should see similar output in the serial monitor.

rotary encoder output on serial monitor

If the reported rotation is the opposite of what you expect, try swapping the CLK (Output A) and DT (Output B) pins.

Code Explanation:

The sketch begins by declaring the Arduino pins to which the encoder’s CLK, DT, and SW pins are connected.

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

Following that, some variables are defined.

  • The counter variable will increment each time the knob is rotated one detent (click).
  • The variables currentStateCLK and lastStateCLK store the state of the CLK output and are used to calculate the amount of rotation.
  • A string named currentDir will be used to display the current rotational direction on the serial monitor.
  • The variable lastButtonPress is used to debounce a switch.
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;

In the setup section, we first configure the rotary encoder connections as inputs, and then we enable the input pull-up on the SW pin. We also set up the serial monitor.

Finally, we read the current value of the CLK pin and store it in the variable lastStateCLK.

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 differ, it indicates that the knob has been turned. We also check to see if currentStateCLK is 1 in order to react to only one state change and avoid double counting.

currentStateCLK = digitalRead(CLK);

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

Within the if statement, the direction of rotation is determined. To accomplish this, we simply read the DT pin and compare it to the current state of the CLK pin.

  • If these two values differ, it indicates that the knob is turned anticlockwise. The counter is then decremented, and the currentDir is set to “CCW”.
  • If these two values are identical, it indicates that the knob is turned clockwise. The counter is then incremented, and the currentDir is set to “CW”.
if (digitalRead(DT) != currentStateCLK) {
    counter --;
    currentDir ="CCW";
} else {
    counter ++;
    currentDir ="CW";
}

The results are then printed to the serial monitor.

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

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

lastStateCLK = currentStateCLK;

The next step involves reading and debouncing the push button switch. We first read the current button state, and when it changes to LOW, we wait 50 ms for the push button to debounce.

If the button remains LOW for more than 50ms, it indicates that it has really been pressed. As a result, we print “Button pressed!” to the serial monitor.

int btnState = digitalRead(SW);

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

Then we repeat the process.

Arduino Example Code 2 – Using Interrupts

To read the rotary encoder, we must constantly monitor changes in the DT and CLK signals.

One way to detect these changes is to poll them continuously, as we did in our previous sketch. It is, however, not the best solution for the following reasons.

  • We must perform frequent checks to see if the value has changed. If the signal level does not change, there will be a waste of cycles.
  • There is a possibility of latency between the time the event occurs and the time we check. If we need to react quickly, we will be delayed due to this latency.
  • If the duration of the change is short, it is possible to completely miss the signal change.

One way to deal with this is to use interrupts.

With interrupts, there is no need to continuously poll for a specific event. This frees up the Arduino to perform other tasks without missing an event.

Wiring

Because most Arduino boards (including the Arduino UNO) only have two external interrupts, we can only monitor changes in the DT and CLK signals. Therefore, we will remove the SW pin connection.

Some boards (such as the Arduino Mega 2560) have more external interrupts than others. If you have one of these, you can keep the SW pin connection and modify the sketch below to include the push button code.

The updated layout of the wiring is as follows:

control rotary encoder using interrupts with arduino uno

Arduino Code

Here’s an example of how to read a rotary encoder with interrupts.

// 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;
}

Observe that the main loop of this program is left empty, so Arduino will be busy doing nothing.

Now, as you turn the knob, you should see similar output on the serial monitor.

rotary encoder interrupt output on serial monitor

Code Explanation:

This sketch simply monitors digital pins 2 (corresponding to interrupt 0) and 3 (corresponding to interrupt 1) for signal changes. In other words, it detects when the voltage changes from HIGH to LOW or LOW to HIGH as a result of turning the knob.

When a change occurs, the Arduino immediately detects it, saves its execution state, executes the function updateEncoder() (also known as the Interrupt Service Routine or simply ISR), and then returns to whatever it was doing before.

The following two lines configure the interrupts. The attachInterrupt() function instructs the Arduino which pin to monitor, which ISR to execute when the interrupt is triggered, and what type of trigger to look for.

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

Arduino Example Code 3 – Controlling Servo Motor with Rotary Encoder

In the following example, we will use a rotary encoder to control the position of a servo motor.

This project can be very useful in a variety of situations. For example, if you want to operate a robotic arm, it can help you position the arm and its grip accurately.

If you are unfamiliar with servo motors, please read the following tutorial.

Wiring

Let’s include a servo motor in our project. 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 PWM enabled digital pin 9.

You can, of course, use the Arduino’s 5V output, but keep in mind that the servo may induce electrical noise on the 5V supply line, which could damage your Arduino. Therefore, it is advised that you use an external power supply.

wiring for controlling servo motor with rotary encoder

Arduino Code

Here is the code for using the rotary encoder to precisely control the servo motor. Each time the knob is rotated one detent (click), the position of the servo arm changes 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;
}

Code Explanation:

If you compare this sketch to our very first sketch, you will notice that, with a few exceptions, they are quite similar.

In the beginning, 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 the range 0 to 179 because servo motors only accept values within 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);


Login
ADS CODE