Arduino: How to use KY-040 Rotary Encoder Module

Arduino Tutorial: KY-040 Rotary Encoder Module

Abstract

Learn how to use the KY-040 Rotary Encoder Module with Arduino. This device converts angular position into a series of digital pulses, allowing for precise control of menus, volume, or rotational position. This tutorial focuses on using digital input pins and hardware interrupts for accurate, high-speed detection of rotation and button presses.

1. Introduction

The KY-040 is an incremental rotary encoder. Unlike a potentiometer (which gives an absolute analog position), the encoder reports only the change in position. It features:

  1. Rotational Sensing: Two output pins (CLD and DT) provide quadrature-encoded signals (two square waves slightly out of phase). By observing the sequence of these signals, the Arduino determines the direction (clockwise or counter-clockwise) and the number of steps taken.
  2. Push Button: An integrated switch (SW) provides a simple digital input when the shaft is pressed.

In this episode, you’ll learn:

  • The quadrature encoding principle for direction detection.
  • The critical role of hardware interrupts for reliable encoder reading.
  • How to decode rotation to increment/decrement a counter.
  • How to read the integrated push button.

This project provides a robust, digital alternative to the potentiometer for user input.

2. Prerequisites

Make sure you have:

  • An Arduino Uno or compatible board.
  • One KY-040 Rotary Encoder Module.
  • Jumper Wires.
  • Arduino IDE

3. Wiring and Setup for Arduino

The KY-040 module has five key pins and requires specific connections for rotation.

Step 1 – Identify Pins

The module has five main pins: GND, VCC, SW (Switch/Button), DT (Data), and CLK (Clock).

Step 2 – Connect the Module (Using Interrupts)

To reliably catch every pulse, we must connect the rotational pins (CLK and DT) to the Arduino’s external interrupt pins.

  • Connect the GND pin of the KY-040 to the GND pin on the Arduino.
  • Connect the VCC pin of the KY-040 to the 5V pin on the Arduino.
  • Connect the CLK pin to Arduino Digital Pin 2 (Interrupt 0 on Uno).
  • Connect the DT pin to Arduino Digital Pin 3 (Interrupt 1 on Uno).
  • Connect the SW pin to Arduino Digital Pin 4 (Standard Digital Input).

This image was created with Fritzing

Step 3 – Initialize Pin Modes

The CLK and DT pins must be configured for input and attached to an interrupt.

4. Writing Interrupt-Driven Encoder Code

The best practice for reading a rotary encoder is to use a hardware interrupt on one channel (CLK or DT) and then check the state of the other channel to determine the direction.

Open main.ino and implement the following code.

				
					// Rotary Encoder Pins

const int CLK_PIN = 2; // Must be an Interrupt Pin (Interrupt 0)
const int DT_PIN = 3;  // Must be an Interrupt Pin (Interrupt 1)
const int SW_PIN = 4;  // Button Pin

volatile int counter = 0;
int encoderCLK_prev;
int encoderCLK_value;
unsigned char bool_CW;
int count = 0;

void setup() {
  Serial.begin(9600);
 
  // Set all encoder pins as INPUT
  pinMode(CLK_PIN, INPUT);
  pinMode(DT_PIN, INPUT);
  pinMode(SW_PIN, INPUT_PULLUP); // Use internal pull-up for the button

  // Read initial CLK state
  encoderCLK_prev = digitalRead(CLK_PIN);
   
  Serial.println("KY-040 Encoder Ready. Rotate or Press Button...");
}

void loop() {
  // Read the button state (Active LOW with PULLUP)
  if (digitalRead(SW_PIN) == LOW) {
    Serial.println("--- BUTTON PRESSED! ---");
    // Simple debounce: wait until released
    while (digitalRead(SW_PIN) == LOW);
  }

  encoderCLK_value = digitalRead(CLK_PIN);
  if (encoderCLK_value != encoderCLK_prev) { // check if knob is rotating
    // if pin A state changed before pin B, rotation is clockwise
    if (digitalRead(DT_PIN) != encoderCLK_value) {
    count ++;
    bool_CW = true;
    } else {
    // if pin B state changed before pin A, rotation is counter-clockwise
    bool_CW = false;
    count--;
   }
    if (bool_CW) {
      Serial.print("\r\nClockwise");
    } else {
      Serial.print("\r\nCounter-Clockwise");
    }
    Serial.print(count);
    Serial.print("\r\n");
  }
  encoderCLK_prev = encoderCLK_value;
}

				
			

Code Explanation

  • Direction Logic: When a change on the CLK pin is captured, the code checks the state of DT. Due to the quadrature nature, if DT is at a different state than CLK, it signifies one direction; if they are the same, it signifies the opposite direction.
  • Debouncing: The button (SW) requires a software debounce (while (digitalRead(SW_PIN) == LOW);) to prevent multiple press events from a single physical push.

5. Uploading and Running the Project

Step 1 – Build

Click the Verify button (checkmark icon) in the Arduino IDE to compile the sketch.

Step 2 – Upload

  1. Connect your Arduino board via USB.
  2. Select the correct board and COM port.
  3. Click the Upload (arrow icon) button.

Step 3 – Test

  1. Open the Serial Monitor (Tools > Serial Monitor).
  2. Rotate the shaft clockwise and counter-clockwise. The Encoder Position should reliably increase or decrease by 1 for each audible “click” (detent) of the encoder.
  3. Press the button (push down on the shaft). The Serial Monitor should display the “BUTTON PRESSED!” message once per push.

6. Hands-On Lab Recap

You’ve learned:

  • The principle of quadrature encoding for rotation.
  • The vital importance of hardware interrupts (attachInterrupt) for high-speed digital input like encoders.
  • How to implement state change detection to determine rotation direction.
  • How to handle the integrated push button input with software debouncing.

This completes your foundation in all three major I/O types: Digital Input, Digital Output (PWM), and Analog Input.

7. Common Issues & Fixes

Issue Cause Solution
Counter jumps wildly or counts only one direction. Signal noise or wrong interrupt edge. Try changing the interrupt from FALLING to CHANGE or RISING. Ensure CLK and DT are on correct Interrupt Pins (2/3 on Uno).
Button triggers multiple times on one press. Missing or ineffective debounce logic. Ensure the while (digitalRead(SW_PIN) == LOW); line is present after registering the button press.
Button doesn't work. Missing Internal Pull-up. Ensure pinMode(SW_PIN, INPUT_PULLUP); is used, as the button is typically wired to pull the pin LOW when pressed.

Leave a Comment

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

Scroll to Top