Anleitung: Drehimpulsgeber (rotary encoder) KY-040 zur Eingabe

Vorgeschichte: Meine kleine Italienerin macht zwar leckeren Espresso, die Auswahl des Kaffees mit einem Drehimpulsgeber erinnert aber eher an ein Glücksspiel. Wenn Profis diese Technik nicht in den Griff bekommen, dann, so meine Schlußfolgerung, will ich das mit einem Arduino auch nicht probieren.

Eine Lösung, wie es scheint, habe ich nun in der c't 18/2020 in dem Artikel "Drehknopf bis 11" von Pia Merkert gefunden. Gerne würde ich aus dem Inhalt zitieren, aber da dürfte der Verlag wohl nicht einverstanden sein. Den Link zur Quelle darf ich hoffentlich ebenso wie den Link zum Repository weitergeben.

Meine Drehimpulsgeberplatinen schalten, anders als im Beitrag vorgeschlagen, nach GND. Zum Testen habe ich daher das Programm geringfügig verändert. Schon mal im Schwung habe ich dann noch Variablentypen angepaßt, enum verwendet, #define rausgeschmissen, den Taster entprellt und alles in eine Funktion mit Rückgabewerten verschoben:

const byte ENCODER_A_PIN = 3;
const byte ENCODER_B_PIN = 2;
const byte SWITCH_PIN = 4;

void setup() {
  Serial.begin(115200);
  Serial.println("\nStart");
  pinMode(ENCODER_A_PIN, INPUT);
  pinMode(ENCODER_B_PIN, INPUT);
  pinMode(SWITCH_PIN, INPUT);
}

void loop() {
  int8_t state = 0;
  if (rotaryEncoder(state)) {
    Serial.println("- SWITCH -");
  }
  if (state == -1) {
    Serial.println("<-- ");
  }
  if (state == 1) {
    Serial.println(" -->");
  }
}

bool rotaryEncoder(int8_t &delta) {
  delta = 0;
  enum {STATE_LOCKED, STATE_TURN_RIGHT_START, STATE_TURN_RIGHT_MIDDLE, STATE_TURN_RIGHT_END, STATE_TURN_LEFT_START, STATE_TURN_LEFT_MIDDLE, STATE_TURN_LEFT_END, STATE_UNDECIDED};
  static uint8_t encoderState = STATE_LOCKED;
  bool a = !digitalRead(ENCODER_A_PIN);
  bool b = !digitalRead(ENCODER_B_PIN);
  bool s = !digitalRead(SWITCH_PIN);
  static bool switchState = s;
  switch (encoderState) {
    case STATE_LOCKED:
      if (a && b) {
        encoderState = STATE_UNDECIDED;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_LEFT_START;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_RIGHT_START;
      }
      else {
        encoderState = STATE_LOCKED;
      };
      break;
    case STATE_TURN_RIGHT_START:
      if (a && b) {
        encoderState = STATE_TURN_RIGHT_MIDDLE;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_RIGHT_END;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_RIGHT_START;
      }
      else {
        encoderState = STATE_LOCKED;
      };
      break;
    case STATE_TURN_RIGHT_MIDDLE:
    case STATE_TURN_RIGHT_END:
      if (a && b) {
        encoderState = STATE_TURN_RIGHT_MIDDLE;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_RIGHT_END;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_RIGHT_START;
      }
      else {
        encoderState = STATE_LOCKED;
        delta = -1;
      };
      break;
    case STATE_TURN_LEFT_START:
      if (a && b) {
        encoderState = STATE_TURN_LEFT_MIDDLE;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_LEFT_START;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_LEFT_END;
      }
      else {
        encoderState = STATE_LOCKED;
      };
      break;
    case STATE_TURN_LEFT_MIDDLE:
    case STATE_TURN_LEFT_END:
      if (a && b) {
        encoderState = STATE_TURN_LEFT_MIDDLE;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_LEFT_START;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_LEFT_END;
      }
      else {
        encoderState = STATE_LOCKED;
        delta = 1;
      };
      break;
    case STATE_UNDECIDED:
      if (a && b) {
        encoderState = STATE_UNDECIDED;
      }
      else if (!a && b) {
        encoderState = STATE_TURN_RIGHT_END;
      }
      else if (a && !b) {
        encoderState = STATE_TURN_LEFT_END;
      }
      else {
        encoderState = STATE_LOCKED;
      };
      break;
  }

  uint32_t current_time = millis();
  static uint32_t switch_time = 0;
  const uint32_t bounce_time = 30;
  bool back = false;
  if (current_time - switch_time >= bounce_time) {
    if (switchState != s) {
      switch_time = current_time;
      back = s;
      switchState = s;
    }
  }
  return back;
}

Testergebnis: Mit einer Platine nur mit PullUp-Widerständen (Beispiel zur Veranschaulichung) erziele ich eine perfekte Funktionalität.

agmue:
#define rausgeschmissen

Aber nicht alle :wink:

Gruß Tommy

Tommy56:
Aber nicht alle :wink:

Danke, hatte eine alte Variante erwischt, ist korrigiert.

Hallo agmue,

coole Sache.

Testergebnis: Mit einer Platine nur mit PullUp-Widerständen (Beispiel zur Veranschaulichung) erziele ich eine perfekte Funktionalität.

wolltest du jetzt einfach ein Bild von den KY-040-Encodern verlinken oder einen Schaltplan wie du das nur mit PullUps verschaltet hast?

Ich habe mir welche von AZ-Delivery gekauft die auch KY-040 heißen und auch so rein äußerlich ähnlich aussehen.
Wenn ich die mit den Standard-Testprogrammen drehe springt der Wert öfter mal in die entgegengesetzte Richtung.
Ich habe dann zunächst mal mit 5nF-Kondensatoren elektrisch entprellt. Das gab einen brauchbaren Kompromiss zwischen Flankensteilheit und immer noch sicher entprellen.

Das zeitweise falsch herum zählen trat aber immer noch auf.

Tritt das jetzt durch den code der alle Zustände STATE_LOCKED, STATE_TURN_RIGHT_START, STATE_TURN_RIGHT_MIDDLE, STATE_TURN_RIGHT_END

usw. behandelt nicht mehr auf?

Dein Code setzt die Variable state auf +1 / -1 . Könnte man diesen Code auch als Interrupt-Service-Routine programmieren und dann eine int-Variable immer weiter rauf bzw. runterzählen lassen ?
viele Grüße Stefan

StefanL38:
wolltest du jetzt einfach ein Bild von den KY-040-Encodern verlinken oder einen Schaltplan wie du das nur mit PullUps verschaltet hast?

Nach meinem Verständmis beschreibt KY-040 den puren Encoder, den es mit unterschiedlichen Platinen gibt. Der Link zeigt die Platine, die ich zum Testen verwende, mit den drei PullUp-Widerständen. Diese Variante war bei einer Recherche die am häufigsten anzutreffende, zumindest mit meiner Suchmaschine mit meinen Suchbegriffen.

Darüberhinaus habe ich noch eine andere Platine mit fünf Widerständen und zwei Kondensatoren. Leider konnte ich sowas in keinem Laden finden. Meine Quelle für diese Platine hat sich leider auf meine Nachfrage noch nicht gemeldet. Diese Platine benötigt keine Entprellung des Tasters und hat sich mit anderen Programmen gut "vertragen".

StefanL38:
Das zeitweise falsch herum zählen trat aber immer noch auf.

Das hatte ich bislang auch beobachtet, bei diesem Programm aber überhaupt nicht. Die Begründung mit Grafik steht im Artikel, da halte ich mich aber aus den genannten Gründen zurück.

StefanL38:
Dein Code setzt die Variable state auf +1 / -1 . Könnte man diesen Code auch als Interrupt-Service-Routine programmieren und dann eine int-Variable immer weiter rauf bzw. runterzählen lassen ?

Interrupt-Service-Routine eher nicht. Das geht, allerdings kann ich keinen Sinn darin erkennen.

Im originalen Programm werden Funktionen zum Rauf- oder Runterzählen aufgerufen, das würde Deiner Idee nahekommen. Das geht aber nur mit globalen Variablen, die "böse" sind. Daher habe ich eine Variante mit lokalen Variablen und Parameterübergabe gewählt. Sowas hängt aber vom Gesamtzkonzept und persönlichen Vorlieben ab. Realisiere es anders, wenn Du magst, ich möchte nur Anregungen geben.