Bauteile · 13 Min. Lesezeit

Drehencoder Arduino: KY-040 auslesen mit Interrupt und Menü

Drehencoder am Arduino: KY-040 anschließen, A/B-Quadratursignal auswerten, per Interrupt ohne Schrittverlust zählen, entprellen, LCD-Menü, ESP32 PCNT, Pico.

Ein Drehencoder (Rotary Encoder, auch Drehgeber) ist ein digitaler Einstellknopf für Arduino-Projekte, der sich endlos drehen lässt, pro Rastung einen Schritt zählt und beim Drücken der Achse wie ein Taster funktioniert. Am Arduino verhält sich ein Drehencoder wie der Menüknopf eines 3D-Druckers, der Einstellregler eines Oszilloskops oder der Lautstärkeknopf im Auto: Er weiß, um wie viele Klicks und in welche Richtung du gedreht hast, und stößt nie an einen Anschlag. Das Modell, das dir in Hobby-Sets am häufigsten begegnet, ist das fünfpolige Modul KY-040.

In dieser Anleitung lernst du, wie sich der KY-040 von einem Potentiometer unterscheidet, wie die Drehrichtung aus den Phasen A und B (Quadratur) bestimmt wird, wie du das Kontaktprellen per Hardware und Software filterst und wie du mit Interrupts jeden einzelnen Klick zählst. Danach bauen wir Schritt für Schritt die Abkürzung mit der Encoder-Bibliothek, die Menüsteuerung auf LCD und OLED, den Hardware-Zähler PCNT des ESP32 und MicroPython-Code für den Raspberry Pi Pico.

Was ist ein Drehencoder und wie funktioniert er?

In einem mechanischen Encoder wie dem KY-040 sitzt eine gezahnte Kontaktscheibe, die sich mit der Achse dreht, und zwei kleine Schalter, die über sie gleiten. Beim Drehen öffnen und schließen die beiden Schalter nacheinander, und an den Pins CLK (A) und DT (B) entstehen Rechteckimpulse. Die beiden Signale sind um eine Viertelperiode, also 90 Grad, gegeneinander verschoben; man spricht von einem Quadratursignal. Der Mikrocontroller erkennt die Richtung daran, welches Signal sich zuerst ändert, und die Drehmenge an der Zahl der Änderungen. Der Encoder merkt sich seine Position nicht, er meldet nur Bewegung. Deshalb ist der KY-040 ein inkrementeller Drehgeber: Beim Einschalten ist die Position null, und das Zählen übernimmt komplett dein Code.

EigenschaftWert beim KY-040
TypMechanisch, inkrementell, Ausgänge A/B
Rastungen20 Klicks pro Umdrehung (bei manchen Encodern 30)
Impulse20 volle A/B-Zyklen pro Umdrehung, also 80 Flanken
Winkelauflösung18 Grad pro Klick
PinsCLK (A), DT (B), SW (Taster), + (Versorgung), GND
Versorgung3,3–5 V (speist nur die Pull-up-Widerstände)
Eingebaute Widerstände10-kΩ-Pull-ups an CLK und DT; der SW-Widerstand ist nicht auf jedem Modul bestückt
TasterSchließer; beim Drücken der Achse wird SW mit GND verbunden (aktiv LOW)
DrehbereichUnbegrenzt, kein Anschlag

Drehencoder oder Potentiometer: Was ist der Unterschied?

Auf den ersten Blick sieht ein Encoder einem Potentiometer sehr ähnlich: Beide haben eine drehbare Achse und beide dienen zum Einstellen eines Wertes. Ihre Funktionsweise ist aber völlig verschieden. Ein Potentiometer ist ein Spannungsteiler; es liefert je nach Stellung der Achse eine analoge Spannung zwischen 0 und 5 V, die du mit analogRead einliest. Ein Encoder erzeugt dagegen Impulse statt einer Spannung; er meldet nicht die Position, sondern die Änderung.

Potentiometer zum Vergleich mit einem Drehencoder
Potentiometer: Der Drehbereich ist auf etwa 270–300 Grad begrenzt, die Stellung wird als analoge Spannung ausgegeben. Ein Drehencoder dreht sich endlos und erzeugt digitale Impulse.
EigenschaftPotentiometerDrehencoder (KY-040)
DrehbereichEtwa 270–300 Grad, begrenztUnbegrenzt, durchgehend 360 Grad
AusgangAnaloge Spannung (0–5 V)Digitale Impulse (A und B)
AuslesenanalogRead (ADC)digitalRead oder Zählen per Interrupt
PositionsangabeAbsolut; die Achsstellung liefert den Wert auch nach einem StromausfallRelativ; die Position ist beim Einschalten unbekannt
RastgefühlKeins, dreht gleichmäßigJa, meist 20 Klicks pro Umdrehung
TasterNeinJa (Achse drücken)
Typisches ProblemMit der Zeit Kratzen und schwankende WerteKontaktprellen (per Software lösbar)
Typischer EinsatzLautstärke, Servowinkel, analoge EinstellungMenüsteuerung, schrittweise Zahleneinstellung, Gerätebedienfelder

Kurz gesagt: Wenn der Wert eins zu eins der physischen Achsstellung entsprechen soll (etwa um einen Servoarm von Hand zu führen oder für eine einfache Lautstärkeregelung), ist ein Potentiometer einfacher. Willst du durch ein Menü blättern, eine Zahl in genauen Schritten erhöhen und verringern, mit einem Knopf mehrere Einstellungen ändern oder den Wert auch per Software verändern, ist ein Encoder viel flexibler. Wird zum Beispiel ein Wert von 50 per Handy auf 80 gesetzt, machst du beim Drehen des Encoders einfach bei 80 weiter; beim Potentiometer passen Knopfstellung und angezeigter Wert dann nicht mehr zusammen.

Phasen A und B: So funktioniert die Quadratur-Auswertung

Steht der KY-040 in einer Rastung, sind beide Schalter offen, und dank der Pull-up-Widerstände lesen CLK und DT beide HIGH (1). Drehst du den Knopf um einen Klick, fallen die beiden Leitungen nacheinander auf LOW und steigen nacheinander wieder auf HIGH. Im Uhrzeigersinn fällt zuerst CLK, gegen den Uhrzeigersinn zuerst DT. Diese Folge aus vier Zuständen ist ein Gray-Code: Bei jedem Schritt ändert sich nur ein Bit. Die Tabelle zeigt, was die beiden Pins während eines Klicks lesen:

SchrittIm Uhrzeigersinn (CLK, DT)Gegen den Uhrzeigersinn (CLK, DT)
In der Rastung1, 11, 1
1. Übergang0, 11, 0
2. Übergang0, 00, 0
3. Übergang1, 00, 1
4. Übergang (nächster Klick)1, 11, 1

Aus der Tabelle ergeben sich zwei einfache Regeln. Erstens: Ist DT noch HIGH, wenn CLK fällt, dreht der Knopf im Uhrzeigersinn; ist DT schon LOW, dreht er gegen den Uhrzeigersinn. Zweitens: Ein Klick hat vier Übergänge, 20 Klicks pro Umdrehung bedeuten also 80 Übergänge. Einfacher Code nutzt nur die erste Regel und zählt einmal pro Klick. Robuster Code verfolgt alle vier Übergänge und kann so prellende oder halb ausgeführte Bewegungen aussortieren. Welche Richtung als Uhrzeigersinn gilt, kann je nach Modul und Verdrahtung variieren; zählt dein Zähler falsch herum, tausche einfach die Pins CLK und DT im Code.

KY-040 Pinbelegung und Anschluss

KY-040-PinFunktionArduino UnoESP32Raspberry Pi Pico
CLKPhase AD2GPIO 25GP16
DTPhase BD3GPIO 26GP17
SWTaster (gedrückt LOW)D4GPIO 27GP18
+Versorgung5V3V33V3(OUT)
GNDMasseGNDGNDGND
Schließe beim ESP32 und Raspberry Pi Pico den +-Pin des Moduls unbedingt an 3,3 V an. Die Pull-up-Widerstände des KY-040 hängen am +-Pin; versorgst du das Modul mit 5 V, werden CLK und DT auf 5 V gezogen und die Grenze der 3,3-V-Eingänge wird überschritten. Da der Encoder nur aus Schaltern besteht, funktioniert er mit 3,3 V einwandfrei.

Beim Arduino Uno und Nano gibt es Hardware-Interrupts nur an den Pins D2 und D3, daher schließt du CLK und DT am besten dort an. Beim Mega sind 2, 3, 18, 19, 20 und 21 Interrupt-Pins, beim Leonardo 0, 1, 2, 3 und 7. Beim ESP32 und Pico kann fast jeder GPIO einen Interrupt auslösen. Die Pinbelegung deines Boards findest du im Tool Pinout. Verwende für den SW-Pin im Code INPUT_PULLUP: Manche Module haben an dieser Leitung keinen Widerstand, und ein offener Pin liest sich, als würde zufällig gedrückt. Beim ESP32 sind GPIO 34–39 reine Eingänge ohne internen Pull-up; nimm sie nicht für SW.

Erster Test Schritt für Schritt: Auslesen per Polling

  1. Verbinde CLK des KY-040 mit D2, DT mit D3 und SW mit D4.
  2. Verbinde + mit 5V und GND mit GND des Arduino.
  3. Füge den folgenden Code in die Arduino IDE ein und lade ihn auf das Board.
  4. Öffne den seriellen Monitor und stelle die Geschwindigkeit auf 115200 Baud.
  5. Drehe den Knopf ein paar Klicks in beide Richtungen und drücke dann die Achse, um den Zähler zurückzusetzen.
Arduino (C++)
1// KY-040: CLK -> D2, DT -> D3, SW -> D4, + -> 5V, GND -> GND
2const byte PIN_CLK = 2;
3const byte PIN_DT  = 3;
4const byte PIN_SW  = 4;
5
6int counter = 0;
7int lastClk;
8
9void setup() {
10  pinMode(PIN_CLK, INPUT_PULLUP);   // modülde 10k pull-up zaten var / the module already has 10k pull-ups
11  pinMode(PIN_DT, INPUT_PULLUP);
12  pinMode(PIN_SW, INPUT_PULLUP);    // basınca LOW okunur / reads LOW when pressed
13  Serial.begin(115200);
14  lastClk = digitalRead(PIN_CLK);
15}
16
17void loop() {
18  int clk = digitalRead(PIN_CLK);
19  if (clk != lastClk && clk == LOW) {      // CLK düşen kenar / CLK falling edge
20    if (digitalRead(PIN_DT) == HIGH) {     // DT hâlâ HIGH: saat yönü / DT still HIGH: clockwise
21      counter++;
22      Serial.print("CW  ");
23    } else {                               // DT zaten LOW: ters yön / DT already LOW: counter-clockwise
24      counter--;
25      Serial.print("CCW ");
26    }
27    Serial.println(counter);
28  }
29  lastClk = clk;
30
31  if (digitalRead(PIN_SW) == LOW) {        // mile basıldı / shaft pressed
32    delay(20);                             // buton sıçramasını bekle / wait out the button bounce
33    if (digitalRead(PIN_SW) == LOW) {
34      counter = 0;
35      Serial.println("Sifirlandi / reset");
36      while (digitalRead(PIN_SW) == LOW) {}   // bırakılmasını bekle / wait for release
37    }
38  }
39}

Der Code liest CLK bei jedem loop-Durchlauf und erkennt nur den Moment, in dem CLK von HIGH auf LOW fällt. Ist DT in diesem Moment noch HIGH, wurde im Uhrzeigersinn gedreht, ist es LOW, gegen den Uhrzeigersinn. Diese Methode ist am leichtesten zu verstehen, hat aber zwei Schwachstellen. Erstens gehen Impulse verloren, die ankommen, während loop mit anderen Aufgaben beschäftigt ist (Display aktualisieren, delay, Sensoren lesen), und bei schnellem Drehen hinkt der Zähler hinterher. Zweitens prellen mechanische Kontakte: CLK kann beim einmaligen Fallen mehrmals LOW-HIGH wechseln, und der Code zählt einen Klick womöglich doppelt oder dreifach. Die nächsten beiden Abschnitte lösen beide Probleme.

Kontaktprellen: Entprellen per Hardware und Software

Die Metallfedern im Encoder springen beim Berühren des Kontakts ein paarmal zurück, bevor sie aufliegen. In Herstellerdatenblättern wird diese Zeit meist mit wenigen Millisekunden angegeben (zum Beispiel höchstens 2 ms). Bei Tastern löst du das Prellen mit "nach der letzten Änderung 20–50 ms warten", beim Encoder funktioniert das aber nicht: Wird er schnell gedreht, liegen zwischen zwei Flanken nur wenige Millisekunden, und eine lange Wartezeit verschluckt auch echte Schritte. Stattdessen nutzt man diese Methoden:

  • Hardware-Filter: Setze je einen 10–100-nF-Kondensator zwischen CLK und GND sowie zwischen DT und GND. Zusammen mit den 10-kΩ-Pull-ups des Moduls entsteht ein RC-Filter; die Zeitkonstante liegt zwischen 10 kΩ × 10 nF = 0,1 ms und 10 kΩ × 100 nF = 1 ms und schluckt kurze Preller. Berechnen kannst du sie mit dem Tool RC-Zeitkonstante, und einen Kondensator mit dem Aufdruck 103 oder 104 entschlüsselst du mit dem Tool Kondensator-Code.
  • Zustandstabelle: Bei jeder Flanke beider Leitungen werden alter und neuer AB-Zustand verglichen. Prellen bedeutet Hin- und Herspringen zwischen denselben zwei Zuständen; das ergibt abwechselnd +1 und −1, was sich aufhebt. Übergänge, bei denen sich beide Bits gleichzeitig ändern, gelten als ungültig und werden übersprungen.
  • Nur in der Rastung entscheiden: Die Viertelschritte werden gesammelt, und der Zähler wird erst erhöht oder verringert, wenn der Encoder in eine Rastung einrastet (beide Leitungen HIGH). So lässt ein auf der Rastung zitternder Kontakt den Menücursor nicht springen.
  • Sperrzeit für den Taster: Der SW-Pin verhält sich wie ein gewöhnlicher Taster; eine Sperrzeit von 30 ms mit millis() genügt.

Mit Interrupts auslesen und keinen Schritt verpassen

Bei einem Interrupt unterbricht der Prozessor sofort seine Arbeit, wenn sich ein Pin ändert, und führt eine kurze Funktion (ISR) aus. Hängen die Encoder-Leitungen an Interrupts, geht keine Flanke verloren, selbst wenn in loop ein delay von 50 ms steht oder das Display langsam aktualisiert wird. Der folgende Code läuft an beiden Flanken von CLK und DT, nutzt die oben beschriebene Zustandstabelle und zählt erst, wenn der Knopf in eine Rastung einrastet. Die Grundlagen findest du auch im Artikel Arduino Interrupt.

Arduino (C++)
1// KY-040: CLK -> D2 (INT0), DT -> D3 (INT1), SW -> D4, + -> 5V
2const byte PIN_CLK = 2;
3const byte PIN_DT  = 3;
4const byte PIN_SW  = 4;
5
6// İndeks: (eski AB << 2) | yeni AB / Index: (old AB << 2) | new AB
7const int8_t QEM[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
8
9volatile long position = 0;   // tık sayısı, ISR ile loop paylaşır / detent count shared by ISR and loop
10uint8_t lastAB = 3;           // son durum, 3 = ikisi de HIGH / last state, 3 = both HIGH
11int8_t quarter = 0;           // iki tık arası çeyrek adımlar / quarter steps between detents
12
13void readEncoder() {
14  uint8_t ab = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
15  quarter += QEM[(lastAB << 2) | ab];
16  lastAB = ab;
17  if (ab == 3) {                          // tıka oturdu / settled in a detent
18    if (quarter > 1) position++;          // saat yönü / clockwise
19    else if (quarter < -1) position--;    // ters yön / counter-clockwise
20    quarter = 0;
21  }
22}
23
24bool buttonPressed() {
25  static bool lastState = HIGH;
26  static unsigned long lastChange = 0;
27  bool state = digitalRead(PIN_SW);
28  if (state != lastState && millis() - lastChange > 30) {   // 30 ms kilit / 30 ms lockout
29    lastChange = millis();
30    lastState = state;
31    return state == LOW;                  // yalnızca basış anı / only the moment of pressing
32  }
33  return false;
34}
35
36void setup() {
37  pinMode(PIN_CLK, INPUT_PULLUP);
38  pinMode(PIN_DT, INPUT_PULLUP);
39  pinMode(PIN_SW, INPUT_PULLUP);
40  Serial.begin(115200);
41  lastAB = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
42  attachInterrupt(digitalPinToInterrupt(PIN_CLK), readEncoder, CHANGE);   // her iki kenar / both edges
43  attachInterrupt(digitalPinToInterrupt(PIN_DT), readEncoder, CHANGE);
44}
45
46long lastPosition = 0;
47
48void loop() {
49  noInterrupts();                         // 4 baytı güvenle kopyala / copy the 4 bytes safely
50  long pos = position;
51  interrupts();
52
53  if (pos != lastPosition) {
54    Serial.print(pos > lastPosition ? "CW  " : "CCW ");
55    Serial.println(pos);
56    lastPosition = pos;
57  }
58
59  if (buttonPressed()) {
60    noInterrupts();
61    position = 0;
62    interrupts();
63    Serial.println("Sifirlandi / reset");
64  }
65
66  delay(50);   // loop yavaş olsa da tık kaçmaz / no detent is lost even with a slow loop
67}
CodeteilWas macht er?
Tabelle QEM[16]Macht aus altem und neuem AB-Zustand +1, −1 oder 0; ungültige Übergänge, bei denen sich beide Leitungen gleichzeitig ändern, zählen als 0
attachInterrupt(..., CHANGE)Ruft readEncoder() bei steigender und fallender Flanke von CLK und DT auf
quarterSammelt die Viertelschritte zwischen zwei Klicks; durch Prellen entstehende +1/−1-Paare heben sich auf
if (ab == 3)Entschieden wird nur, wenn der Encoder in einer Rastung steht (beide Leitungen HIGH); eine Summe von +2 oder mehr zählt als ein Klick
volatile long positionDie von ISR und loop gemeinsam genutzte Variable; volatile verhindert, dass der Compiler eine veraltete Kopie verwendet
noInterrupts() / interrupts()Verhindert, dass die ISR dazwischenfunkt, während der 4 Byte lange long-Wert kopiert wird
buttonPressed()Entprellt SW mit 30 ms Sperrzeit und liefert nur einmal, im Moment des Drückens, true
Halte die ISR so kurz wie möglich: Verwende darin kein Serial.print, kein delay und keine Display-Befehle; aktualisiere nur den Zähler und überlass die eigentliche Arbeit loop. Deklariere jede von ISR und loop gemeinsam genutzte Variable als volatile. Da der Uno einen 8-Bit-Prozessor hat, verhindert ein kurzes Abschalten der Interrupts beim Lesen einer 2- oder 4-Byte-Variable, dass du einen halb aktualisierten Wert liest.

Die Abkürzung: die Encoder-Bibliothek

Wenn du keine eigene ISR schreiben willst, erledigt die Encoder-Bibliothek von Paul Stoffregen dieselbe Aufgabe mit optimiertem Code. Öffne in der Arduino IDE den Bibliotheksverwalter, suche nach "Encoder" und installiere die Bibliothek von Paul Stoffregen; die einzelnen Schritte zeigt die Anleitung Arduino-Bibliothek hinzufügen. Die Bibliothek arbeitet am besten, wenn beide Pins Interrupt-Pins sind, gut, wenn nur der zuerst an den Konstruktor übergebene Pin einer ist, und mit geringer Leistung, wenn keiner es ist. Da sie jede Flanke zählt, ändert sich der Wert pro Klick um 4; teile durch 4, um die Klicks zu erhalten.

Arduino (C++)
1#include <Encoder.h>
2
3// Önce DT (3), sonra CLK (2): saat yönü artı sayılır / DT (3) first, then CLK (2): clockwise counts up
4Encoder knob(3, 2);
5const byte PIN_SW = 4;
6long lastDetent = 0;
7
8void setup() {
9  pinMode(PIN_SW, INPUT_PULLUP);
10  Serial.begin(115200);
11}
12
13void loop() {
14  long detent = knob.read() / 4;          // 4 kenar = 1 tık / 4 edges = 1 detent
15  if (detent != lastDetent) {
16    lastDetent = detent;
17    Serial.println(detent);
18  }
19  if (digitalRead(PIN_SW) == LOW) {       // basınca sıfırla / reset on press
20    knob.write(0);
21  }
22}

read() liefert die bisherige Gesamtzahl der Flanken, write(0) setzt den Zähler zurück, und readAndReset() liest und setzt in einem Schritt zurück. Dem Konstruktor übergeben wir zuerst DT und dann CLK wegen des Vorzeichens der Drehrichtung: In dieser Reihenfolge zählt der Uhrzeigersinn positiv. Ist es bei dir umgekehrt, tausche 2 und 3. Die internen Pull-up-Widerstände der Pins schaltet die Bibliothek selbst ein.

Menüsteuerung mit Drehencoder auf einem LCD

I2C 16x2 LCD-Display
I2C 16x2 LCD-Display: Es wird nur über die Leitungen SDA und SCL angeschlossen; die Einstellungen des Menübeispiels erscheinen auf diesem Display.

Am häufigsten werden Encoder für Menüs verwendet: Du drehst den Knopf, um zwischen den Zeilen zu wechseln, drückst ihn, um die ausgewählte Einstellung in den Bearbeitungsmodus zu setzen, drehst, um den Wert zu ändern, und drückst noch einmal, um ins Menü zurückzukehren. Das folgende Beispiel stellt den roten, grünen und blauen Kanal einer RGB-LED zwischen 0 und 255 ein und zeigt die Werte auf einem I2C-LCD an. Schließe das LCD an A4 (SDA) und A5 (SCL) an und die Farbbeinchen einer RGB-LED mit gemeinsamer Kathode über 220-Ω-Widerstände an D9, D10 und D11.

Arduino (C++)
1#include <Wire.h>
2#include <LiquidCrystal_I2C.h>
3
4LiquidCrystal_I2C lcd(0x27, 16, 2);           // adres 0x27 ya da 0x3F / address 0x27 or 0x3F
5
6const byte PIN_CLK = 2, PIN_DT = 3, PIN_SW = 4;
7const byte COUNT = 3;
8const byte LED_PINS[COUNT] = {9, 10, 11};     // RGB LED, PWM pinleri / PWM pins
9const char* NAMES[COUNT] = {"Kirmizi/Red", "Yesil/Green", "Mavi/Blue"};
10int values[COUNT] = {0, 0, 0};                // 0-255 parlaklık / brightness
11
12const int8_t QEM[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
13volatile int steps = 0;                       // loop'un işlemediği tıklar / detents not yet handled by loop
14uint8_t lastAB = 3;
15int8_t quarter = 0;
16
17int selected = 0;                             // seçili satır / selected item
18bool editing = false;                         // değer düzenleme modu / value edit mode
19
20void readEncoder() {
21  uint8_t ab = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
22  quarter += QEM[(lastAB << 2) | ab];
23  lastAB = ab;
24  if (ab == 3) {
25    if (quarter > 1) steps++;
26    else if (quarter < -1) steps--;
27    quarter = 0;
28  }
29}
30
31bool buttonPressed() {
32  static bool lastState = HIGH;
33  static unsigned long lastChange = 0;
34  bool state = digitalRead(PIN_SW);
35  if (state != lastState && millis() - lastChange > 30) {
36    lastChange = millis();
37    lastState = state;
38    return state == LOW;
39  }
40  return false;
41}
42
43void drawMenu() {
44  int top = selected > COUNT - 2 ? COUNT - 2 : selected;   // görünen ilk satır / first visible row
45  for (byte row = 0; row < 2; row++) {
46    int i = top + row;
47    char mark = ' ';
48    if (i == selected) mark = editing ? '*' : '>';
49    char line[17];
50    snprintf(line, sizeof(line), "%c%-11s%4d", mark, NAMES[i], values[i]);
51    lcd.setCursor(0, row);
52    lcd.print(line);
53  }
54}
55
56void setup() {
57  pinMode(PIN_CLK, INPUT_PULLUP);
58  pinMode(PIN_DT, INPUT_PULLUP);
59  pinMode(PIN_SW, INPUT_PULLUP);
60  for (byte i = 0; i < COUNT; i++) pinMode(LED_PINS[i], OUTPUT);
61  lcd.init();
62  lcd.backlight();
63  lastAB = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
64  attachInterrupt(digitalPinToInterrupt(PIN_CLK), readEncoder, CHANGE);
65  attachInterrupt(digitalPinToInterrupt(PIN_DT), readEncoder, CHANGE);
66  drawMenu();
67}
68
69void loop() {
70  noInterrupts();
71  int s = steps;                              // biriken tıkları al / take the pending detents
72  steps = 0;
73  interrupts();
74
75  if (s != 0) {
76    if (editing) {
77      values[selected] = constrain(values[selected] + s * 5, 0, 255);   // tık başına 5 / 5 per detent
78      analogWrite(LED_PINS[selected], values[selected]);
79    } else {
80      selected = constrain(selected + s, 0, COUNT - 1);
81    }
82    drawMenu();
83  }
84
85  if (buttonPressed()) {                      // bas: düzenle ya da bitir / press: edit or finish
86    editing = !editing;
87    drawMenu();
88  }
89}

Am Anfang der ausgewählten Zeile erscheint ein >, im Bearbeitungsmodus ein *. Der Encoder-Code ist derselbe wie im vorigen Beispiel; der einzige Unterschied ist, dass die ISR statt einer Position die Zahl der noch nicht verarbeiteten Klicks (steps) sammelt und loop sie ausliest und zurücksetzt. So geht kein Klick verloren, auch wenn loop eine Weile beschäftigt ist; die gesammelten Klicks werden im nächsten Durchlauf zusammen verarbeitet. Im Bearbeitungsmodus ändert jeder Klick den Wert um 5; für feinere Einstellungen kannst du den Schritt auf 1 senken oder eine Beschleunigung einbauen, die den Schritt bei schnellem Drehen vergrößert.

Um dasselbe Menü auf einem OLED-Display anzuzeigen, musst du nur die Funktion drawMenu() austauschen. Die folgende Version zeichnet die ausgewählte Zeile invertiert auf einem weißen Balken. Binde statt der LCD-Bibliothek die Bibliotheken Adafruit SSD1306 und Adafruit GFX ein, lösche das lcd-Objekt und rufe in setup() display.begin() statt lcd.init() auf.

Arduino (C++)
1// LCD menü kodunda değişecek kısımlar / parts to change in the LCD menu sketch
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5Adafruit_SSD1306 display(128, 64, &Wire, -1);
6// setup() içinde lcd.init() ve lcd.backlight() yerine / in setup() instead of lcd.init() and lcd.backlight():
7//   display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
8
9void drawMenu() {
10  display.clearDisplay();
11  display.setTextSize(1);
12  for (byte i = 0; i < COUNT; i++) {
13    int y = i * 16;
14    if (i == selected) {
15      display.fillRect(0, y, 128, 14, SSD1306_WHITE);   // seçili satır çubuğu / highlight bar
16      display.setTextColor(SSD1306_BLACK);
17    } else {
18      display.setTextColor(SSD1306_WHITE);
19    }
20    display.setCursor(4, y + 3);
21    display.print(NAMES[i]);
22    display.setCursor(98, y + 3);
23    display.print(values[i]);
24    if (i == selected && editing) display.print('*');   // düzenleme işareti / edit marker
25  }
26  display.display();
27}
Sollen die Einstellungen bei Stromausfall nicht verloren gehen, schreibe den Wert beim Verlassen des Bearbeitungsmodus ins EEPROM und lies ihn beim Start wieder ein. Das EEPROM verträgt nur eine begrenzte Zahl von Schreibvorgängen, speichere also nur beim Tastendruck und nicht bei jedem Klick. Einen langen Tastendruck als "Zurück"-Befehl zu nutzen, ist zudem ein einfacher Weg zu verschachtelten Menüs.

Drehencoder am ESP32: der PCNT-Hardware-Zähler

Der ESP32 besitzt einen Hardware-Block namens PCNT (Pulse Counter), der Impulse zählt, ohne den Prozessor zu belasten. Jede Einheit hat zwei Kanäle und kann das A/B-Signal daher direkt auswerten; der klassische ESP32 hat 8 PCNT-Einheiten, ESP32-S2 und S3 haben 4. Der Zähler ist 16 Bit breit und enthält einen Glitch-Filter gegen kurze Störungen. Dieser Filter lässt sich auf höchstens etwa 12,8 Mikrosekunden einstellen und schluckt daher nur sehr kurze Spitzen, nicht das gesamte Prellen. Den Rest gleicht die Vierflanken-Zählung von selbst aus: Hin- und Herpreller heben sich als +1 und −1 auf. Der folgende Code nutzt den ESP-IDF-Treiber, der mit Version 3.x des Arduino-ESP32-Cores mitkommt.

Arduino (C++)
1#include "driver/pulse_cnt.h"
2#include "driver/gpio.h"
3
4// KY-040: CLK -> GPIO 25, DT -> GPIO 26, SW -> GPIO 27, + -> 3V3
5const int PIN_CLK = 25;
6const int PIN_DT  = 26;
7const int PIN_SW  = 27;
8
9pcnt_unit_handle_t unit = NULL;
10
11void setup() {
12  Serial.begin(115200);
13  pinMode(PIN_SW, INPUT_PULLUP);
14
15  pcnt_unit_config_t unitCfg = {};
16  unitCfg.low_limit = -10000;                 // sınıra varınca sayaç 0'a döner / the counter returns to 0 at a limit
17  unitCfg.high_limit = 10000;
18  ESP_ERROR_CHECK(pcnt_new_unit(&unitCfg, &unit));
19
20  pcnt_glitch_filter_config_t filterCfg = {};
21  filterCfg.max_glitch_ns = 1000;             // 1 µs'den kısa iğneleri yok say / ignore spikes shorter than 1 µs
22  ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(unit, &filterCfg));
23
24  pcnt_chan_config_t aCfg = {};
25  aCfg.edge_gpio_num = PIN_CLK;               // A kanalı: CLK kenarları, DT seviyesi / channel A: CLK edges, DT level
26  aCfg.level_gpio_num = PIN_DT;
27  pcnt_channel_handle_t chA = NULL;
28  ESP_ERROR_CHECK(pcnt_new_channel(unit, &aCfg, &chA));
29
30  pcnt_chan_config_t bCfg = {};
31  bCfg.edge_gpio_num = PIN_DT;                // B kanalı: DT kenarları, CLK seviyesi / channel B: DT edges, CLK level
32  bCfg.level_gpio_num = PIN_CLK;
33  pcnt_channel_handle_t chB = NULL;
34  ESP_ERROR_CHECK(pcnt_new_channel(unit, &bCfg, &chB));
35
36  // 4x çözme: iki hattın tüm kenarları sayılır / 4x decoding: every edge of both lines is counted
37  ESP_ERROR_CHECK(pcnt_channel_set_edge_action(chA, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
38  ESP_ERROR_CHECK(pcnt_channel_set_level_action(chA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
39  ESP_ERROR_CHECK(pcnt_channel_set_edge_action(chB, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
40  ESP_ERROR_CHECK(pcnt_channel_set_level_action(chB, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
41
42  gpio_pullup_en((gpio_num_t)PIN_CLK);        // çıplak encoder için iç pull-up / internal pull-up for a bare encoder
43  gpio_pullup_en((gpio_num_t)PIN_DT);
44
45  ESP_ERROR_CHECK(pcnt_unit_enable(unit));
46  ESP_ERROR_CHECK(pcnt_unit_clear_count(unit));
47  ESP_ERROR_CHECK(pcnt_unit_start(unit));
48}
49
50int lastDetent = 0;
51
52void loop() {
53  int count = 0;
54  pcnt_unit_get_count(unit, &count);
55  int detent = count / 4;                     // 4 kenar = 1 tık / 4 edges = 1 detent
56  if (detent != lastDetent) {
57    lastDetent = detent;
58    Serial.printf("Tik / detent: %d  (ham / raw: %d)\n", detent, count);
59  }
60  if (digitalRead(PIN_SW) == LOW) {           // basınca sıfırla / reset on press
61    pcnt_unit_clear_count(unit);
62  }
63  delay(20);
64}

Die beiden Kanäle werden spiegelbildlich eingerichtet: Kanal A zählt die Flanken von CLK und kehrt die Richtung je nach Pegel von DT um, Kanal B zählt die Flanken von DT bezogen auf CLK. Dadurch ändert sich der Zähler pro Klick um 4. Erreicht er die Grenze von ±10000, springt er auf null zurück; brauchst du einen größeren Bereich, kannst du Überläufe mit der Option flags.accum_count und Watch Points aufsummieren. Der ESP32-C3 hat keine PCNT-Einheit; nutze auf diesem Board den Arduino-Code mit Interrupts. Arbeitest du mit Version 2.x des Arduino-ESP32-Cores oder willst du kürzeren Code, erledigt die Bibliothek ESP32Encoder mit derselben Hardware die Aufgabe über attachFullQuad() und getCount(). Nutzt du MicroPython auf dem ESP32, verwendet auch die Klasse machine.Encoder aktueller Versionen (z. B. 1.27) im Hintergrund PCNT. Bist du neu beim ESP32, lies zuerst Was ist der ESP32.

Drehencoder mit dem Raspberry Pi Pico und MicroPython

Jeder GPIO-Pin des Pico kann einen Interrupt auslösen, daher ist das Auslesen eines Encoders in MicroPython mit Pin.irq() einfach. Der folgende Code nutzt genau dieselbe Zustandstabelle wie das Arduino-Beispiel und macht aus dem Encoder einen LED-Dimmer: Jeder Klick ändert die Helligkeit um 5 Prozent, ein Druck auf die Achse schaltet die LED ein und aus. Verbinde CLK mit GP16, DT mit GP17, SW mit GP18 und eine LED über einen 220-Ω-Widerstand mit GP15. Wie du MicroPython einrichtest, erfährst du im Artikel Was ist der Raspberry Pi Pico.

MicroPython
1from machine import Pin, PWM
2import time
3
4clk = Pin(16, Pin.IN, Pin.PULL_UP)     # A fazı / phase A
5dt = Pin(17, Pin.IN, Pin.PULL_UP)      # B fazı / phase B
6sw = Pin(18, Pin.IN, Pin.PULL_UP)      # buton, basınca 0 / button, 0 when pressed
7led = PWM(Pin(15))                     # LED + 220 ohm
8led.freq(1000)
9
10# İndeks: (eski AB << 2) | yeni AB / Index: (old AB << 2) | new AB
11QEM = (0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0)
12last_ab = (clk.value() << 1) | dt.value()
13quarter = 0
14position = 10                          # 0-20 tık, başlangıç %50 / 0-20 detents, start at 50 %
15
16def on_edge(pin):
17    global last_ab, quarter, position
18    ab = (clk.value() << 1) | dt.value()
19    quarter += QEM[(last_ab << 2) | ab]
20    last_ab = ab
21    if ab == 3:                        # tıka oturdu / settled in a detent
22        if quarter > 1 and position < 20:
23            position += 1
24        elif quarter < -1 and position > 0:
25            position -= 1
26        quarter = 0
27
28clk.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=on_edge)
29dt.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=on_edge)
30
31on = True
32shown = -1
33while True:
34    if sw.value() == 0:                # mile basıldı / shaft pressed
35        time.sleep_ms(20)              # sıçramayı bekle / wait out the bounce
36        if sw.value() == 0:
37            on = not on
38            shown = -1                 # yeniden yaz / force an update
39            while sw.value() == 0:     # bırakılmasını bekle / wait for release
40                time.sleep_ms(10)
41    if position != shown:
42        shown = position
43        led.duty_u16(shown * 65535 // 20 if on else 0)
44        print("Parlaklik / brightness: %d%%" % (shown * 5), "acik / on" if on else "kapali / off")
45    time.sleep_ms(10)

Die Grenzen des Wertes (0–20 Klicks) prüfen wir in der Interrupt-Funktion; so liest die Hauptschleife die Variable nur, und es entsteht keine Race Condition, bei der beide Seiten gleichzeitig schreiben. Wie die LED-Helligkeit gesteuert wird, erklärt der Artikel Was ist PWM. Möchtest du eine fertige Lösung, bringt das Modul rotary_irq_rp2 aus dem Projekt micropython-rotary Grenzen, Überlauf (Wrap) und Richtungsumkehr mit. Für sehr schnelle Motor-Encoder kann der PIO-Block des Pico zählen, ohne den Prozessor zu belasten. Bist du neu bei MicroPython, ist der Artikel MicroPython-Grundlagen ein guter Einstieg.

Arten von Drehencodern: mechanisch, optisch und magnetisch

TypWie funktioniert er?Typische AuflösungWo wird er eingesetzt?
Mechanisch (KY-040, EC11)Kontaktscheibe und zwei Schalter12–30 Klicks pro UmdrehungMenüknöpfe, Lautstärkeregler, Gerätefrontplatten
OptischSchlitzscheibe, LED und FototransistorHunderte bis Tausende Impulse pro UmdrehungCNC-Maschinen, Robotergelenke, Industriemotoren
Magnetisch (Hall-Effekt)Magnetscheibe und Hall-SensorenWenige Impulse pro Umdrehung der Motorwelle (z. B. 7 oder 11)N20- und Getriebe-DC-Motoren mit Encoder
AbsolutGibt für jede Position einen eigenen Code ausZum Beispiel 12 Bit (4096 Positionen) beim AS5600Roboterarme, Winkelmessung; kennt die Position beim Einschalten

Der Code in diesem Artikel funktioniert auch mit anderen inkrementellen Encodern mit A/B-Ausgängen. Bei einem Getriebemotor mit Encoder ergeben zum Beispiel ein Hall-Encoder mit 11 Impulsen pro Umdrehung der Motorwelle, Vierflanken-Zählung und ein Getriebe von 1:30 genau 11 × 4 × 30 = 1320 Zählschritte pro Umdrehung der Abtriebswelle. Steigt die Impulsrate auf Tausende pro Sekunde, beansprucht Interrupt-Code einen spürbaren Teil der Prozessorzeit, während die PCNT-Einheit des ESP32 das mühelos zählt. Industrielle optische Encoder arbeiten meist mit 5–24 V und haben Open-Collector- oder Differenzausgänge; prüfe Ausgangstyp und Spannung vor dem Anschließen unbedingt im Datenblatt.

Projektideen mit Drehencoder

  • Digitaler Lautstärkeregler: Stelle die Lautstärke der Musik eines DFPlayer Mini mit dem Encoder ein und springe per Achsendruck zum nächsten Titel.
  • Farbwähler-Lampe: Drehe den Farbton eines WS2812B-LED-Rings mit dem Encoder und wechsle per Druck in den Helligkeitsmodus.
  • Präzise Positionierung: Lass jeden Klick einen Schrittmotor um eine feste Schrittzahl bewegen; baue einen Kamera-Slider oder einen Drehteller.
  • Küchentimer: Stelle die Zeit mit dem Encoder ein, starte den Countdown per Druck und lass am Ende einen Summer ertönen.
  • Spielcontroller: Nutze den Encoder als Schläger in Pong; bei Arduino-Spieleprojekten erlaubt er feinere Steuerung als ein Joystick.
  • Uhrzeit und Wecker stellen: Stelle Uhrzeit und Alarm eines RTC-Uhrmoduls mit einem einzigen Knopf ein.
  • Bedienfeld für Messgeräte: Ändere eine PWM-Frequenz, den Wert eines Signalgenerators oder die Solltemperatur einer Lötstation in feinen Schritten.

Häufige Fehler und ihre Lösungen

ProblemWahrscheinliche UrsacheLösung
Zählt 2 oder 4 pro KlickJede Flanke wird einzeln gezählt oder die Kontakte prellenFlankenzahl durch 4 teilen, Zustandstabelle verwenden, 10–100-nF-Kondensatoren an CLK und DT
Zählt nur jeden zweiten KlickEncoder mit 30 Klicks / 15 Impulsen; jeder Klick ist ein halber ZyklusBibliothekswert durch 2 statt 4 teilen; im Tabellencode if (ab == 3) zu if (ab == 3 || ab == 0) ändern
Richtung vertauschtCLK und DT vertauscht oder anderes ModulPinnummern von CLK und DT im Code tauschen
Bei schnellem Drehen gehen Schritte verlorenPolling, delay in loopInterrupts oder ESP32 PCNT nutzen, ISR kurz halten
Taster löst von selbst ausSW-Pin offen, kein Pull-uppinMode(SW, INPUT_PULLUP) verwenden
Zähler springt zufälligKeine gemeinsame Masse, lange oder lose KabelGND verbinden, Kabel kürzen, Steckbrett-Kontakte prüfen
fatal error: driver/pulse_cnt.h: No such file or directory / undefined reference to 'pcnt_new_unit'Arduino-ESP32-Core 2.x (erster Fehler) oder ESP32-C3 ohne PCNT-Einheit (zweiter Fehler)Core im Boardverwalter auf 3.x aktualisieren oder die Bibliothek ESP32Encoder nutzen; beim C3 den Interrupt-Code nutzen
LCD zeigt nur KästchenKontrast oder I2C-Adresse falschTrimmer auf der Rückseite drehen, Adresse 0x3F statt 0x27 probieren
Möchtest du den Code ausprobieren, bevor du die Schaltung aufbaust: Der Simulator Wokwi hat ein fertiges KY-040-Bauteil. Nach einem Klick auf den Encoder drehst du ihn mit den Pfeiltasten Klick für Klick und drückst den Taster mit der Leertaste. Wie du startest, zeigt die Anleitung Wokwi-Simulation.

Ein Drehencoder ist eine der günstigsten Möglichkeiten, einem Projekt eine professionelle Bedienoberfläche zu geben: Ein einziger Knopf und ein Display verwalten Dutzende Einstellungen. Verstehe zuerst mit dem Polling-Code die Logik, wechsle dann zur Interrupt- oder PCNT-Version und passe das Menübeispiel an dein eigenes Projekt an. In der BlueGrays-App findest du Projekte zu Servosteuerung per Potentiometer, I2C-LCD, Taster und 7-Segment-Zähler mit Schaltplänen und Schritt-für-Schritt-Code und kannst sie leicht erweitern, indem du Potentiometer oder Taster durch einen Encoder ersetzt.

Häufige Fragen

Was ist der Unterschied zwischen Drehencoder und Potentiometer?

Ein Potentiometer liefert eine analoge Spannung abhängig von der Achsstellung, sein Drehbereich ist auf etwa 270–300 Grad begrenzt und es wird mit analogRead gelesen. Ein Drehencoder dreht sich endlos und erzeugt bei jedem Klick digitale Impulse; er meldet Richtung und Umfang der Bewegung, nicht die Position. Für Menüs und schrittweise Einstellungen eignet sich der Encoder, für einfache positionsabhängige Einstellungen das Potentiometer.

Warum zählt mein Drehencoder zwei Schritte pro Klick oder überspringt Schritte?

Zu viele Zählschritte entstehen meist durch Kontaktprellen oder weil jede Flanke einzeln gezählt wird; die 4 Flanken pro Klick durch 4 zu teilen und eine Zustandstabelle zu verwenden, behebt das. Übersprungene Schritte entstehen, wenn Impulse ankommen, während loop beschäftigt ist. Schließe CLK und DT an Interrupt-Pins an, lies sie per Interrupt und ergänze bei Bedarf 10–100-nF-Kondensatoren, dann ist das Problem gelöst.

Funktioniert der Drehencoder KY-040 mit 3,3 V?

Ja. Der KY-040 besteht nur aus mechanischen Schaltern und Pull-up-Widerständen und funktioniert daher auch mit 3,3 V einwandfrei. Beim ESP32 und Raspberry Pi Pico schließt du den +-Pin unbedingt an 3V3 an; bei 5 V ziehen die Pull-ups CLK und DT auf 5 V, und die Grenze der 3,3-V-Eingänge wird überschritten.

Wie viele Impulse pro Umdrehung liefert der KY-040?

Übliche KY-040-Module haben 20 Klicks pro Umdrehung, und die Leitungen A und B durchlaufen pro Klick einen vollen Zyklus. Jeder Zyklus hat vier Flanken, also zählst du 80 Flanken pro Umdrehung, und ein Klick entspricht 18 Grad. Manche Encoder haben 30 Klicks und 15 Impulse; dort entspricht jeder Klick einem halben Zyklus.

Kann man einen Drehencoder ohne Interrupt auslesen?

Ja, per Polling, also durch ständiges Prüfen des CLK-Pins in loop, und für einen langsam gedrehten Menüknopf reicht das oft aus. Enthält loop aber delay, Display-Aktualisierungen oder lange Berechnungen, gehen schnell gedrehte Klicks verloren. Für zuverlässige Ergebnisse empfehlen sich Interrupts, beim ESP32 der PCNT-Hardware-Zähler.

Kann ich mehrere Drehencoder an einen Arduino anschließen?

Ja. Da beim Uno nur D2 und D3 Hardware-Interrupts bieten, kannst du mit der Encoder-Bibliothek den zuerst an den Konstruktor übergebenen Pin jedes Encoders an einen Interrupt-Pin und den anderen an einen normalen Pin anschließen oder Pin-Change-Interrupts verwenden. Der Mega hat sechs Interrupt-Pins, der klassische ESP32 kann mit seinen 8 PCNT-Einheiten 8 Encoder in Hardware zählen, und beim Pico kann jeder GPIO einen Interrupt auslösen.

Alle Projekte ansehen
LED mit Taster ein- und ausschalten
#10Anfänger

LED mit Taster ein- und ausschalten

Einmal drücken und das Licht geht an, noch einmal drücken und es geht aus, genau wie der Lichtschalter in deinem Zimmer. Der Arduino bemerkt jeden Druck und schaltet die LED in den jeweils anderen Zus

4.8(100)
162
ESP32 LED Steuerung mit Taster (Toggle)
#105Anfänger

ESP32 LED Steuerung mit Taster (Toggle)

Jedes Mal, wenn die Taste gedrückt wird, ändert sich der Zustand der LED: ist sie an, schaltet sie aus, ist sie aus, schaltet sie ein. Klassisches Toggle-Muster. Mit der Entprellung wird das mechanische Geräusch des Tasters beseitigt, um ein einmaliges Umschalten mit einem einzigen Druck zu gewährleisten.

4.6(39)
57
Raspberry Pi Pico Taster Debounce: IRQ vs Software Polling (GP5 + GP4)
#159Mittel
Premium

Raspberry Pi Pico Taster Debounce: IRQ vs Software Polling (GP5 + GP4)

Einmal Taster gedrückt, Code zählt 3-4? Das ist Contact Bounce! 🔁 Vergleiche 2 Zählmethoden in Serial: RAW Polling (fängt Bounce, zählt falsch) vs IRQ + Software Debounce (genau). Lerne Contact Bounce Physik, Software Debounce, Pin.irq() Interrupt, IRQ_FALLING Trigger, ISR Handler, global Variablen ISR-Sicherheit.

0.0(0)
12
Servomotorbetrieb mit Potentiometer
#36Anfänger
Premium

Servomotorbetrieb mit Potentiometer

Während du am Knopf des Potentiometers drehst, dreht sich der Arm des Servomotors einfach mit: Das eine Ende des Knopfes bedeutet null Grad, das andere eine halbe Umdrehung. Der Arduino liest die Stel

4.6(37)
52
ESP32: LED-Helligkeit mit Potentiometer (Analog → PWM)
#115Mittel
Premium

ESP32: LED-Helligkeit mit Potentiometer (Analog → PWM)

In diesem Projekt steuern wir die Helligkeit einer LED analog über ein Potentiometer. Der ADC-Pin des ESP32 liest die vom Potentiometer kommende Spannung von 0-3,3 V aus, woraufhin dieser Wert in ein PWM-Signal (Pulsweitenmodulation) umgewandelt und an die LED gesendet wird. So leuchtet die LED beim Drehen des Potentiometers stufenweise auf und erlischt wieder. Mit Arduino Core 3.0+ kann `analogWrite()` direkt verwendet werden; bei ESP32-S2/S3 ist die ADC-Auflösung auf 12 Bit festgelegt.

4.5(19)
32
Zähler mit 7-Segment-Anzeige und Tastern
#43Fortgeschritten
Premium

Zähler mit 7-Segment-Anzeige und Tastern

Ein einfacher Zähler mit drei Knöpfen: Mit dem einen zählt die Ziffer auf der Anzeige um eins hoch, mit dem anderen um eins runter, und der dritte setzt sie auf null zurück. Die einstellige 7-Segment-

4.6(35)
47
Verwendung des LCD-Bildschirms mit I2C
#32Anfänger
Premium

Verwendung des LCD-Bildschirms mit I2C

Gib deinem Arduino ein eigenes Display und lass es seine erste Nachricht schreiben. Das zweizeilige LCD wird mit nur wenigen Kabeln angeschlossen; oben erscheint ein Gruß, dann unten eine zweite Zeile

4.8(53)
70
Paddle-Ball-Spiel - SSD1306
#86Mittel
Premium

Paddle-Ball-Spiel - SSD1306

Ein Ball, zwei Schläger und du: Du spielst Tischtennis, eines der allerersten Videospiele, auf einem kleinen OLED-Display. Mit zwei Knöpfen bewegst du deinen Schläger rechts nach oben und unten, den l

4.5(47)
65