KY-040 Rotary Encoder Module

The KY-040 is a rotary encoder module that provides digital signals corresponding to the rotational position and direction. It features continuous 360-degree rotation and includes a built-in push-button switch, making it suitable for various control applications.

Digital KY-0xx module Rotary_encoder
KY-040 Rotary Encoder Module image
KY-040 · Digital
Digital
Interface
5pins
Connections
3.3-5V
Supply
$2
Typical price
On this page

KY-040 pinout

5 pins · Digital

The KY-040 is a 5-pin rotary encoder module with integrated push button:

View:
KY-040 Rotary Encoder Module pinout
PinTypeDescriptionNotes
CLKCommunicationClock signal outputPulses on rotation
DTCommunicationData signal outputPhase-shifted pulses for direction detection
SWControlSwitch/button signalActive low when pressed
VCCPowerPower supply3.3V or 5V
GNDPowerGround connection
  • Interface: Digital quadrature encoder (2-phase)

  • Rotation: Continuous 360° rotation (no limits)

  • Direction: CLK and DT phase relationship determines rotation direction

  • Button: Built-in push-button switch (active low)

  • Power: 3.3V or 5V operation

  • Debouncing: Software debouncing recommended for stable readings

Wiring the KY-040 to ESP32

5 connections · 1 optional

To interface the KY-040 with an ESP32 for rotary input:

Wiring diagram coming soon
The pin-to-pin table covers every connection.
KY-040 pinESP32 pinPurpose
VCC3.3VPower supply
GNDGNDGround
CLKGPIO18Clock input (any GPIO)
DTGPIO19Data input (any GPIO)
SWGPIO21Button input (any GPIO) · optional
  • Interrupt Pins: Use interrupt-capable GPIO pins for best response

  • GPIO Selection: Any digital GPIO pins work, shown pins are examples

  • Voltage: Use 3.3V to avoid level shifting

  • Software: Implement debouncing and state machine for direction detection

  • Button Optional: SW pin not required if button functionality not needed

KY-040 code examples

5 platforms
Platform:

KY-040 Arduino example

Copy
#define CLK_PIN 18 // GPIO18, matches the wiring above
#define DT_PIN 19  // GPIO19
#define SW_PIN 21  // GPIO21

int counter = 0;
int currentStateCLK;
int lastStateCLK;
bool currentStateSW;
bool lastStateSW;

void setup() {
    pinMode(CLK_PIN, INPUT);
    pinMode(DT_PIN, INPUT);
    pinMode(SW_PIN, INPUT_PULLUP);
    lastStateCLK = digitalRead(CLK_PIN);
    lastStateSW = digitalRead(SW_PIN);
    Serial.begin(115200);
    Serial.println("KY-040 Rotary Encoder Test");
}

void loop() {
    currentStateCLK = digitalRead(CLK_PIN);
    if (currentStateCLK != lastStateCLK) {
        if (digitalRead(DT_PIN) != currentStateCLK) {
            counter++;
        } else {
            counter--;
        }
        Serial.print("Position: ");
        Serial.println(counter);
    }
    lastStateCLK = currentStateCLK;

    currentStateSW = digitalRead(SW_PIN);
    if (currentStateSW == LOW && lastStateSW == HIGH) {
        Serial.println("Button pressed");
        delay(50); // debounce
    }
    lastStateSW = currentStateSW;
}

This Arduino code sets up the KY-040 rotary encoder using three pins: CLK (Clock), DT (Data), and SW (Switch). It initializes these pins as inputs and continuously monitors the rotation direction and button presses.

When the encoder knob is turned, the CLK and DT pins generate pulses. By comparing the sequence of these pulses, the code determines whether the encoder is rotating clockwise or counterclockwise:

  • If the DT signal is opposite to the CLK signal, the counter increments (clockwise rotation).
  • If the DT signal matches the CLK signal, the counter decrements (counterclockwise rotation).

The position counter is updated accordingly and printed to the serial monitor.

The built-in push-button switch (SW) is also monitored. If pressed, a message is printed to the serial monitor.

A delay(50) is used to debounce the encoder readings, ensuring stable detection of rotation and button presses.

KY-040 ESP-IDF example

Copy
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#define CLK_PIN GPIO_NUM_18
#define DT_PIN GPIO_NUM_19
#define SW_PIN GPIO_NUM_21

volatile int counter = 0;
volatile int lastStateCLK;

void IRAM_ATTR encoder_isr_handler(void* arg) {
    int currentStateCLK = gpio_get_level(CLK_PIN);
    if (currentStateCLK != lastStateCLK) {
        if (gpio_get_level(DT_PIN) != currentStateCLK) {
            counter++;
        } else {
            counter--;
        }
        printf("Position: %d\n", counter);
    }
    lastStateCLK = currentStateCLK;
}

void app_main(void) {
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_ANYEDGE,
        .mode = GPIO_MODE_INPUT,
        .pin_bit_mask = (1ULL << CLK_PIN) | (1ULL << DT_PIN) | (1ULL << SW_PIN),
        .pull_up_en = GPIO_PULLUP_ENABLE
    };
    gpio_config(&io_conf);

    lastStateCLK = gpio_get_level(CLK_PIN);
    gpio_install_isr_service(0);
    gpio_isr_handler_add(CLK_PIN, encoder_isr_handler, NULL);

    printf("KY-040 Rotary Encoder Test\n");
    while (1) {
        if (gpio_get_level(SW_PIN) == 0) {
            printf("Button Pressed\n");
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

This ESP-IDF code configures the KY-040 rotary encoder on GPIO18 (CLK), GPIO19 (DT), and GPIO21 (SW). It uses an interrupt service routine (ISR) to detect changes in the encoder's rotation and update a counter. Additionally, it checks for button presses in the main loop and prints the status to the console.

KY-040 ESPHome example

Copy
sensor:
  - platform: rotary_encoder
    pin_a: GPIO18
    pin_b: GPIO19
    name: "KY-040 Rotary Encoder"
    min_value: -1000
    max_value: 1000
    resolution: 1

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO21
      mode: INPUT_PULLUP
    name: "KY-040 Button"

This ESPHome configuration sets up the KY-040 rotary encoder with GPIO18 and GPIO19 for rotation detection and GPIO21 for the push button. The encoder is configured with a minimum and maximum value range, and its resolution is set to 1 step per increment.

KY-040 PlatformIO example

Copy
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
src/main.cppCopy
#include <Arduino.h>

#define CLK_PIN 18
#define DT_PIN 19
#define SW_PIN 21

volatile int counter = 0;
int lastStateCLK;

void IRAM_ATTR rotary_encoder() {
    int currentStateCLK = digitalRead(CLK_PIN);
    if (currentStateCLK != lastStateCLK) {
        if (digitalRead(DT_PIN) != currentStateCLK) {
            counter++;
        } else {
            counter--;
        }
        Serial.print("Position: ");
        Serial.println(counter);
    }
    lastStateCLK = currentStateCLK;
}

void setup() {
    pinMode(CLK_PIN, INPUT);
    pinMode(DT_PIN, INPUT);
    pinMode(SW_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(CLK_PIN), rotary_encoder, CHANGE);
    Serial.begin(115200);
    Serial.println("KY-040 Rotary Encoder Test");
}

void loop() {
    if (digitalRead(SW_PIN) == LOW) {
        Serial.println("Button Pressed");
        delay(100);
    }
}

This PlatformIO code configures GPIO18 (CLK), GPIO19 (DT), and GPIO21 (SW) for the KY-040 rotary encoder. It uses an interrupt to detect changes in rotation and updates a counter accordingly. The push button state is also monitored and printed to the serial monitor when pressed.

KY-040 MicroPython example

Copy
import machine
import time

CLK_PIN = machine.Pin(18, machine.Pin.IN, machine.Pin.PULL_UP)
DT_PIN = machine.Pin(19, machine.Pin.IN, machine.Pin.PULL_UP)
SW_PIN = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_UP)

counter = 0
last_state_clk = CLK_PIN.value()

while True:
    current_state_clk = CLK_PIN.value()
    if current_state_clk != last_state_clk:
        if DT_PIN.value() != current_state_clk:
            counter += 1
        else:
            counter -= 1
        print("Position:", counter)
    last_state_clk = current_state_clk

    if SW_PIN.value() == 0:
        print("Button Pressed")
        time.sleep(0.1)
    time.sleep(0.05)

This MicroPython script configures GPIO18 (CLK), GPIO19 (DT), and GPIO21 (SW) for the KY-040 rotary encoder. It continuously monitors the encoder rotation and updates a counter while also detecting button presses.

KY-040 specifications

From the datasheet
Operating Voltage
3.3V to 5V
Pulses per Revolution
20
Output
2-bit Gray Code
Mechanical Angle
360° Continuous
Built-in Switch
Yes (Push-to-Operate)
Dimensions
30mm x 18mm x 30mm

About the KY-040

The KY-040 is built around an EC11-style incremental rotary encoder: a rotating shaft drags a metal wiper across two offset contact tracks, producing the CLK and DT quadrature pulses that let software work out both how far and which way the knob turned. Twenty detents per revolution, matching the module’s own pulses-per-revolution spec, is typical for this contact-based design, and the board carries its own 10 kOhm pull-up resistors on all three signal lines (CLK, DT and SW) so it drops onto an ESP32 without any external resistors. Because the signal comes from a physical wiper scraping metal rather than an optical slot, contact bounce shows up on every click and every rotation step, not just the push-button - a debounced read (or a short settle delay after each edge) matters more here than “read two digital pins” makes it sound.

On the ESP32 side CLK and DT are best read with an interrupt on the CLK pin rather than pure polling, since a fast spin can produce edges quicker than a loop() running other work will notice; the direction then comes from comparing DT’s level at the moment CLK changes, exactly as shown in the wiring above. For a finished product built on the same idea rather than a bare module, the CrowPanel 2.1" rotary display wires its knob to ESPHome’s rotary_encoder platform the same way this module does, just packaged behind a screen instead of a breakout board.

KY-040 troubleshooting

2 common issues

Unexpected Behavior or No Response

Issue: The module does not respond or behaves erratically when the knob is rotated.

Solutions:

  • Ensure all connections are secure and correctly wired according to the pinout diagram.
  • Verify that the microcontroller's input pins are properly configured as inputs in the code.
  • Check for proper power supply voltage (3.3V or 5V) to the module.
  • Implement software debouncing to account for mechanical switch noise.

Incorrect Direction Detection

Issue: The detected rotation direction is opposite to the actual rotation.

Solutions:

  • Swap the connections of the CLK and DT pins to correct the direction detection.
  • Ensure that the code logic correctly interprets the sequence of pulses from the CLK and DT pins.

Where to buy the KY-040

KY-040 Rotary Encoder Module
KY-040 Rotary Encoder Module
$2per unit, typical
Amazon and AliExpress links are affiliate links - buying through them supports the site at no extra cost to you.

Resources