Rotary Encoder KY-040 mit Arduino auslesen: Drehimpulsgeber für Menüs & Lautstärkeregler

Ein Potentiometer verschleißt, driftet und liefert nie exakt reproduzierbare Werte. Der KY-040 Rotary Encoder löst das anders: Er zählt Drehschritte digital, dreht sich endlos in beide Richtungen und bringt zusätzlich einen eingebauten Taster mit. In diesem Tutorial baust du damit eine Menünavigation am OLED-Display und einen präzisen PWM-Dimmer.

Warum ein Rotary Encoder statt Potentiometer?

Ein Potentiometer misst eine absolute Position über einen Spannungsteiler – begrenzt auf einen Drehbereich von meist 270° und mit der Zeit anfällig für Kontaktabrieb und Messrauschen. Der KY-040 (mit EC11-Drehgeber) funktioniert komplett anders: Zwei versetzte Kontaktspuren (CLK und DT) erzeugen bei jeder Drehung eine Folge von Rechtecksignalen. Aus der Reihenfolge, in der CLK und DT wechseln, ließt sich sowohl die Drehrichtung als auch die Anzahl der Rastschritte exakt bestimmen – digital, verschleißarm und ohne Begrenzung der Drehrichtung.

Zusätzlich bringt das Modul einen dritten Pin (SW) mit: einen eingebauten Taster, der beim Drücken auf die Encoderachse auslöst. Das macht den KY-040 zur perfekten Eingabe für Menüs – drehen zum Navigieren, drücken zum Bestätigen – wie du es von Kfz-Infotainment-Systemen oder 3D-Drucker-Displays kennst.

Was du brauchst

Bauteil Menge ca. Preis Link
Rotary Encoder KY-040 (EC11) 1x ab 2,79 € Zum Produkt
UNO R3 Board 1x ab 4,99 € Zum Produkt
0.96 Zoll OLED Display SSD1306 1x (für Projekt 2) ab 2,49 € Zum Produkt
3mm LED Sortiment 1 Box (für Projekt 3) ab 4,49 € Zum Produkt
600x Widerstände-Sortiment 1 Set ab 5,99 € Zum Produkt
Breadboard 400 Kontakte 1x ab 3,49 € Zum Produkt

Schaltungsaufbau

KY-040 Pin UNO R3 Pin Funktion
CLK Pin 2 (Interrupt) Taktsignal Spur A
DT Pin 3 Taktsignal Spur B
SW Pin 4 Integrierter Taster (Druck auf Achse)
+ (VCC) 5V Spannungsversorgung
GND GND Masse

Das SW-Signal ist standardmäßig HIGH und wird beim Drücken auf LOW gezogen – achte darauf, in der Software mit INPUT_PULLUP zu arbeiten oder externe Pull-up-Widerstände aus dem Widerstände-Set zu verwenden, falls dein Modul keine eigenen besitzt.

Code 1: Drehrichtung per Interrupt erkennen

Der zuverlässigste Weg, keine Drehschritte zu verpassen, ist die Auswertung per Hardware-Interrupt auf dem CLK-Pin. Bei jedem Wechsel von CLK wird geprüft, in welchem Zustand DT gerade ist – daraus ergibt sich die Drehrichtung.

const int PIN_CLK = 2;
const int PIN_DT = 3;

volatile int position = 0;
volatile int letzterClkZustand;

void setup() {
  pinMode(PIN_CLK, INPUT);
  pinMode(PIN_DT, INPUT);
  Serial.begin(9600);

  letzterClkZustand = digitalRead(PIN_CLK);
  attachInterrupt(digitalPinToInterrupt(PIN_CLK), aufDrehungReagieren, CHANGE);
}

void aufDrehungReagieren() {
  int clkZustand = digitalRead(PIN_CLK);

  if (clkZustand != letzterClkZustand) {
    if (digitalRead(PIN_DT) != clkZustand) {
      position++;   // im Uhrzeigersinn
    } else {
      position--;   // gegen den Uhrzeigersinn
    }
  }
  letzterClkZustand = clkZustand;
}

void loop() {
  static int letztePosition = 0;
  if (position != letztePosition) {
    Serial.print("Position: ");
    Serial.println(position);
    letztePosition = position;
  }
}

So funktioniert's

Der Interrupt reagiert auf jede Flanke von CLK – egal wie schnell du drehst, es geht kein Schritt verloren, da loop() dabei nicht blockiert wird. Der Vergleich von CLK und DT im Moment der Änderung verrät die Reihenfolge der beiden Spuren und damit die Drehrichtung – das klassische Prinzip der Quadratur-Dekodierung.

Code 2: Menünavigation auf dem OLED-Display

Jetzt kombinieren wir den Encoder mit dem OLED-Display aus unserem SSD1306-Tutorial: Drehen bewegt eine Auswahlmarkierung durch ein Menü, ein Druck auf die Achse bestätigt die Auswahl.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

const int PIN_CLK = 2;
const int PIN_DT = 3;
const int PIN_SW = 4;

volatile int menuIndex = 0;
volatile int letzterClkZustand;
const char* menuEintraege[] = {"Sensor lesen", "WLAN Setup", "Helligkeit", "Ueber"};
const int ANZAHL_EINTRAEGE = 4;

void setup() {
  pinMode(PIN_CLK, INPUT);
  pinMode(PIN_DT, INPUT);
  pinMode(PIN_SW, INPUT_PULLUP);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();

  letzterClkZustand = digitalRead(PIN_CLK);
  attachInterrupt(digitalPinToInterrupt(PIN_CLK), aufDrehungReagieren, CHANGE);

  zeichneMenue();
}

void aufDrehungReagieren() {
  int clkZustand = digitalRead(PIN_CLK);
  if (clkZustand != letzterClkZustand) {
    if (digitalRead(PIN_DT) != clkZustand) {
      menuIndex = (menuIndex + 1) % ANZAHL_EINTRAEGE;
    } else {
      menuIndex = (menuIndex - 1 + ANZAHL_EINTRAEGE) % ANZAHL_EINTRAEGE;
    }
  }
  letzterClkZustand = clkZustand;
}

void zeichneMenue() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  for (int i = 0; i < ANZAHL_EINTRAEGE; i++) {
    display.setCursor(10, i * 14 + 2);
    if (i == menuIndex) {
      display.print("> ");
    } else {
      display.print("  ");
    }
    display.println(menuEintraege[i]);
  }
  display.display();
}

void loop() {
  static int letzterIndex = -1;
  if (menuIndex != letzterIndex) {
    zeichneMenue();
    letzterIndex = menuIndex;
  }

  if (digitalRead(PIN_SW) == LOW) {
    display.setCursor(10, 56);
    display.print("Ausgewaehlt: ");
    display.println(menuEintraege[menuIndex]);
    display.display();
    delay(400); // einfache Entprellung des Tasters
  }
}

Der Menüindex läuft dank %-Operator zyklisch von 0 bis 3 und wieder zurück – du kannst also endlos in beide Richtungen durch das Menü scrollen. Der SW-Taster nutzt INPUT_PULLUP, weshalb er im gedrückten Zustand LOW liefert.

Code 3: PWM-Dimmer mit Ein/Aus per Tastendruck

Als drittes Beispiel steuerst du die Helligkeit einer LED per Drehung stufenlos – und schaltest sie per Tastendruck auf dem Encoder komplett ein oder aus.

const int PIN_CLK = 2;
const int PIN_DT = 3;
const int PIN_SW = 4;
const int LED_PIN = 9; // muss PWM-faehig sein (~)

volatile int helligkeit = 128; // 0-255
volatile int letzterClkZustand;
bool eingeschaltet = true;

void setup() {
  pinMode(PIN_CLK, INPUT);
  pinMode(PIN_DT, INPUT);
  pinMode(PIN_SW, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);

  letzterClkZustand = digitalRead(PIN_CLK);
  attachInterrupt(digitalPinToInterrupt(PIN_CLK), aufDrehungReagieren, CHANGE);
}

void aufDrehungReagieren() {
  int clkZustand = digitalRead(PIN_CLK);
  if (clkZustand != letzterClkZustand) {
    if (digitalRead(PIN_DT) != clkZustand) {
      helligkeit = min(255, helligkeit + 8);
    } else {
      helligkeit = max(0, helligkeit - 8);
    }
  }
  letzterClkZustand = clkZustand;
}

void loop() {
  static bool letzterTasterZustand = HIGH;
  bool tasterZustand = digitalRead(PIN_SW);

  if (tasterZustand == LOW && letzterTasterZustand == HIGH) {
    eingeschaltet = !eingeschaltet;
    Serial.println(eingeschaltet ? "LED an" : "LED aus");
    delay(50);
  }
  letzterTasterZustand = tasterZustand;

  analogWrite(LED_PIN, eingeschaltet ? helligkeit : 0);
}

Die Helligkeit wird in 8er-Schritten pro Rastung angepasst und mit min()/max() auf den gültigen Bereich 0–255 begrenzt. Der Taster schaltet unabhängig davon die LED komplett ein oder aus, ohne den zuletzt eingestellten Helligkeitswert zu verlieren – genau wie bei einem guten Dimmer-Schalter.

Praxisprojekte

🎛️ Lautstärkeregler: Nutze das gleiche Prinzip wie im PWM-Dimmer, um die Lautstärke eines MP3-Player-Moduls stufenlos einzustellen.

📝 Werte-Eingabe ohne Tastatur: Kombiniere Encoder und OLED-Display zu einer kompakten Zahlen- oder Zeiteingabe – z. B. für einen selbstgebauten Timer oder Wecker.

🎚️ Schrittmotor-Steuerung: Verbinde den Encoder mit unserem 28BYJ-48-Schrittmotor-Tutorial, um Motorbewegungen manuell und präzise per Drehung zu steuern.

Häufige Fehler

⚠️ Encoder überspringt Schritte oder zählt rückwärts: Fast immer liegt es an mechanischem Prellen der Kontakte. Nutze wie in den Beispielen einen Interrupt auf CLK statt Polling in der loop(), und ergänze bei hartnäckigen Fällen einen kleinen 100nF-Kondensator zwischen CLK/DT und GND.

⚠️ Taster (SW) reagiert nicht: Prüfe, ob INPUT_PULLUP gesetzt ist – ohne internen Pull-up-Widerstand liegt der Pin in der Luft und liefert zufällige Werte.

⚠️ Drehrichtung ist vertauscht: Tausche einfach CLK und DT in der Verkabelung oder im Code – das ist normal und hängt von der Orientierung deines Moduls ab.

🔧 Baue deine eigene Drehsteuerung

Alle Bauteile für dieses Projekt findest du bei makeroo.

Kombiniere den Encoder mit unserem OLED-Display-Tutorial für eigene Menüsysteme.

Fazit

Der KY-040 Rotary Encoder liefert präzise, verschleißarme Dreheingaben ganz ohne die Nachteile eines klassischen Potentiometers. Von der reinen Richtungserkennung über eine vollständige Menünavigation bis zum PWM-Dimmer zeigen die drei Codebeispiele, wie du ihn in eigenen Projekten einsetzt.

Zurück zum Blog