Einlesen eines Incremental Encoder

(auch als Drehgeber, Drehwinkelgeber, Rotary Encoder, Inkrementalgeber, etc. bezeichnet)

Incremental Encoder sind in verschiedensten Ausführungen erhältlich und werden zur Erfassung von Messgrößen wie Drehwinkel, Drehzahl oder Wegstrecke eingesetzt. Sie liefern - in der einfachsten Bauform nach Beschaltung mit Pullup-Widerständen (Bild 2) - eine bestimmte Anzahl von Impulsen pro Umdrehung der Welle an ihren Anschlüssen A und B, die um ca. 90 Grad gegeneinander versetzt sind (Bild 3). Durch die Erfassung bzw. Zählung der angelaufenen Impulse - absolut oder pro Zeiteinheit - kann die gewünschte Messgröße ermittelt und durch die zweikanalige Erfassung die Drehrichtung der Welle erkannt werden.

                    

                      

Bild 1: Abbildung und Anschlussbelegung des von mir zum Testen verwendeten Incremental Encoders, der z.B. bei der Firma Pollin Electronic günstig zu erwerben ist.

Bild 2: Beschaltung eines Incremental Encoders mit Pullup-Widerständen

Bild 3: Um ca. 90° versetze Ausgangssignale eine Inkremental Encoders

Eine Anwendung ist auch die Vorgabe von Analogwerten, wie sie z.B. zur Einstellung eines Regler-Sollwertes oder von Datum und Uhrzeit - ohne Tastatur - erfolgen kann. Mit einem Taster, der bei manchen Encodern integriert ist und durch Drücken der Welle betätigt wird, kann dann der eingestellte Wert übernommen oder auf einen definierten Wert zurückgesetzt werden.

Bei dem von mir verwendeten Incremental Encoder mit ca. 100 Impulsflanken pro Umdrehung (pro Kanal), der z.B. bei Pollin Elektronic erhältlich ist (Preis ca. 1 EUR), ist dieser Taster nicht integriert (siehe: www.pollin.de/p/digital-encoder-bourns-ecw0j-r35-se0051-240708). Allerdings konnte ich die dort angegebene Typenbezeichnung "BOURNS ECW0J-R35-SE0051" auf der BOURNS-Homepage, bei der Suche nach einem Datenblatt, nicht ausfindig machen.


Testaufbau:

Verwendete Bauteile:

  • 1 Arudino Uno oder Nano
  • 1 Incremental Encoder
  • 1 Taster (falls nicht im Incremental Encoder integriert)
  • 3 Widerstände 10 kOhm



Testprogramm:

Funktion:

Mit dem Testprogramm wird in Abhängigkeit der Drehrichtung ein Zählerstand (Variable "zaehler") mit jeder Impulsflanke des Kanal A um 1 erhöht oder erniedrigt, sofern sich der Zählerstand innerhalb von vorgegebenen Grenzen (Variable "oGrenze" bzw. "uGrenze") befindet. Wird eine Grenze erreicht, verbleibt der Zählerstand auf diesem Wert, bis die Drehrichtung wieder geändert wird. Durch Drücken des Tasters wird der Zählerstand auf 0 gesetzt.


Drehrichtungserkennung:

Jeder Flanke des Kanal A löst einen Pin Change Interrupt (PCI) aus. In der Pin Change Interrupt Service-Routine wird anschließend ermittelt, ob der PCI durch eine steigende oder fallende Flanke ausgelöst wurde und welcher Zustand der Kanal B zu diesem Zeitpunkt hat. Im Bild 4 ist die Drehrichtungserkennung durch die Flankenauswertung Kanal A und durch die Zustandsauswertung Kanal B dargestellt:


Bild 4: Drehrichtungserkennung durch Flankenauswertung von Kanal A und Zustandsauswertung von Kanal B


Nachfolgend ist die Drehrichtungserkennung nochmals tabellarisch dargestellt:

    Kanal A    Kanal B          Drehrichtung

     0 -> 1         0        =>        1

     1 -> 0         0        =>        2

     0 -> 1         1        =>        2

     1 -> 0         1        =>        1


Verwendete Library:

Metro: Mit Metro steuere ich die zyklische Wiederholung von Vorgängen, hier im Programm die Abfrage des Tasters. Einen Link zu Metro findet ihr unter Fremd-Libraries.


Hinweis:

Wer mit Registerprogrammierung und Pin Change Interrupt nicht vertraut ist, sollte sich zum Verständnis des Programms folgende Seiten ansehen: Bitmanipulation, Ein-Ausgangsports und Pin Change Interrupt


//Incremental Encoder mit Taster
//Code fuer Arduino
//Author Retian
//Version 1.0


#include <Metro.h>

Metro tastZyklus = 150; //Taster-Abfrage-Zyklus = 150 ms


volatile bool altEncoderKanalA;
volatile bool encoderKanalA, encoderKanalB;
volatile int16_t zaehler = 0; //Startwert des Zaehler
volatile bool pciFlag = false;
const int16_t oGrenze = 100; //Encoder Obergrenze
const int16_t uGrenze = -100; //Encoder Untergrenze
const uint16_t debounceTime = 2000; //Encoder Entprellzeit in us


void setup() {
  Serial.begin(250000);

  //Digitalpins D2 - D4 sind Eingaenge
  DDRD &= ~(1 << DDD2) & ~(1 << DDD3) & ~(1 << DDD4);

  altEncoderKanalA = (PIND & (1 << PIND2)) >> PIND2;

  //Pinchange-Interrupt für Digitaleingang D2 festlegen
  cli();
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18);
  sei();
}


void loop() {
  if (tastZyklus.check())
  {
    //Taster am Digitalpin D4 abfragen
    if ((PIND & (1 << PIND4)) >> PIND4)
    {
      zaehler = 0;
      Serial.println(zaehler);
    }
  }

  if (pciFlag)
  {
    pciFlag = false;
    Serial.println(zaehler);
  }
}


//Pin Change Interrupt Service-Routine
ISR(PCINT2_vect)
{
  encoderKanalB = (PIND & (1 << PIND3)) >> PIND3;
  delayMicroseconds(debounceTime);
  encoderKanalA = (PIND & (1 << PIND2)) >> PIND2;

  if (altEncoderKanalA != encoderKanalA)
  {
    //Fallende Flanke hat PCI ausgeloest
    if (altEncoderKanalA == HIGH && encoderKanalA == LOW)
    {
      if (encoderKanalB == HIGH) zaehler++;
      else if (encoderKanalB == LOW) zaehler--;
    }
    //Steigende Flanke hat PCI ausgeloest
    else if (altEncoderKanalA == LOW && encoderKanalA == HIGH)
    {
      if (encoderKanalB == LOW) zaehler++;
      else if (encoderKanalB == HIGH) zaehler--;
    }
    //Zaehler mit Obergrenze und Untergrenze begrenzen
    if (zaehler > oGrenze) zaehler = oGrenze;
    else if (zaehler < uGrenze) zaehler = uGrenze;
   
    altEncoderKanalA = encoderKanalA;

    pciFlag = true;
  }
}

Ausgabe des Zählerstandes am Seriellen Plotter der IDE:

Achtung: Die Baudrate der seriellen Übertragung ist auf 250000 Baud eingestellt!

Bild 5: Ausgabe des Zählerstandes am Seriellen Plotter der IDE