Als Inkrementalgeber (auch Drehgeber oder Encoder) werden Sensoren zur Erfassung von Lagerveränderungen genannt. Ein weit verbreitetes Beispiel ist der Lautstärkeregler am Autoradio. Hierbei ist jedoch zu beachten, dass Drehgeber zur Lautstärkeregelung erst bei neueren Radios eingesetzt werden, bei alten Autoradios wurden schlicht logarithmische Potentiometer verbaut, die zum Beispiel das Eingangssignal eines Verstärkers abschwächen. Drehgeber erkennt man daran, dass man sie unendlich lange drehen kann – sie haben keinen Anfang und kein Ende. In diesem Artikel möchte ich auf die Auswertung dieser kleinen, sehr nützlichen Sensoren etwas näher eingehen. Es sei noch darauf hingewiesen, dass in diesem Artikel handbetriebene Drehgeber behandelt werden.

Funktion

Ein handbetriebener Drehgeber hat in der Regel 4 bis 5 Anschlüsse:

  1. Vcc
  2. GND
  3. Kanal A
  4. Kanal B
  5. Taster (optional)


Teilweise sind Drehgeber mit einem Taster ausgestattet, der zum Beispiel für Eingaben verwendet werden kann. Dieser ist allerdings nicht immer vorhanden. Der Drehknopf springt beim Drehen immer in eine Rastung hinein, dies dient dem Anwender als haptisches Feedback der Bewegung. Beim Drehen entsteht an den Anschlüssen Kanal A und Kanal B ein sogenanntes Gray Code Signal, das vom Mikrocontroller ausgewertet werden muss. Dieser Code hat die Eigenschaft, dass sich jeweils zum nächsten Code nur ein Bit ändert, man spricht auch von einer einfachen Hamming Distanz. Das hat den Vorteil, dass ein Entprellen i.d.R. nicht notwendig ist. Betrachtet man das erzeugte Gray-Code-Signal an den Kanälen des Drehgebers einmal genauer, wird man feststellen, dass die Flanken nicht so schön sind wie es auf dem gezeigten Bild den Anschein hat. Ein Signal geht nicht einfach von 0 Volt auf 5 Volt – es pendelt erst einmal hin und her, bevor es endgültig auf 5 Volt verharrt, es prellt. Das interessante an dem Ausgangssignal ist nun, dass sich eine gewisse Abfolge, in Abhängigkeit der Richtung erkennen lässt. Diese Eigenschaft ist es, die man sich zu Nutze macht um das Signal auszuwerten. Man kann somit detektieren ob der Anwender den Drehgeber gerade im Uhrzeigersinn (z.B.: Lautstärker höher) oder gegen den Uhrzeigersinn (z.B.: Lautstärker runter) gedreht hat und kann entsprechend handeln. Wenn man diese Eigenschaft des Drehgebers kennt ist die Auswertung eigentlich recht einfach.

Betrachten wir als nächstes mal die Signale der Kanäle A und B des Drehgebers bei den verschiedenen Drehrichtungen.

Signalfolge im Uhrzeigersinn:

Kanal A: 0  0  1  1 ..
Kanal B: 1  0  0  1 ..

Gegen den Uhrzeigersinn:

Kanal A: 1  1  0  0 ..
Kanal B: 1  0  0  1 ..

Hierzu lässt sich anschaulich ein Automatengraph entwerfen, welcher die Signalfolgen bildlich darstellt. Ich habe mit Microsoft Visio einen solchen Automatengraphen entworfen und möchte diesen als PDF zur Verfügung stellen.

Software Lösung

Die hier beschriebene Softwarelösung zur Auswertung des Drehgebers ist für den STEC11B13 von Reichelt. Die Routinen lassen sich jedoch leicht auf andere Typen anpassen. Wichtig ist jeweils nur die Signalabfolge von A und B zwischen zwei Rastungen. Anders als es auf dem Anzeigebild bei Reichelt zu sehen ist, ist dieser Drehgeber in aufrechter Form ausgeführt. Hier seht Ihr ein Foto das einen Drehgeber zeigt, der ähnlich dem STEC11B13 ist.

von Links nach Rechts:: A, VCC oder GND, B, Taster, Taster

Zu beachten ist, dass bei den handelsüblichen handbetriebenen Drehgebern zwei Codepaare pro Rastung auftreten. Das ist für uns nur von Vorteil, denn so kann auf ein Tastenentprellen, welches aufgrund des mechanischen Aufbaus vorkommt, verzichtet werden. Nur wenn zwei aufeinanderfolgende Codepaare zusammengehörig sind, wird eine Drehung durchgeführt und der PIC kann den Auftrag verarbeiten. Um es kurz zu machen: Die Erfassung der Signale an den Pins des Mikrocontrollers wird mit einem Timer-Interrupt gelöst. Es gibt diverse andere Lösungen, diese sind aber stark fehleranfällig und belasten den Prozessor teilweise bis ins Äußerste. Die Frequenz in der der Timer-Interrupt auftritt und die Signale A und B abgetastet werden, muss so groß gewählt werden, dass wenn der Drehgeber mit der theoretisch schnellsten Geschwindigkeit gedreht wird, mindestens 1 mal öfter abgetastet wird, wie es verschiedene Codepaare pro Rastung gibt. Mit anderen Worten, wenn zwischen zwei Rastungen vier verschiedene Kombinationen von A und B auftreten, dann muss mindestens 5 mal abgetastet werden. Diese Anforderung leitet sich aus dem Nyquist-Abtasttheorem her, das besagt, die Abtastfrequenz mit größer sein als die höchste im Signal auftretende Einzelfrequenz:

\text{f}_{\text{}A} > 2 \cdot \text{f}_{\text{max}}

Nun könnte man fälschlicherweise denken, dass die höchste Frequenz, die in unserem Anwendungsfall auftritt die eines Kanals A oder B sei. Doch hier kommt es uns ja auch die Phasenverschiebung dieser beiden Signal an. Also ist die maximale Frequenz die auftritt nicht \text{f}_{\text{A}} oder \text{f}_{\text{B}} sondern 2 \cdot \text{f}{\text{A}} =  2 \cdot \text{f}_{\text{B}} und somit ergibt sich die Abtastfrequenz (entspricht der Frequenz mit der unser Timer Interrupts auslösen muss):

\text{f}_{\text{A}} > 2 \cdot 2 \cdot \text{f}_{\text{A}} = 4 \cdot \text{f}_{\text{A}}

Und in Worten bedeutet das, dass wir mehr als 4 mal pro Rastung abtasten müssen um jede Kombination erfassen zu können. Andernfalls kann es zum unterschlagen von Codepaaren führen. Die verwendeten Makros können selbst angelegt/definiert werden (PHI und PHI90 sind beliebige IOs des PIC). Wie die Routinen zu verwenden sind ist in der Funktionsbeschreibung (siehe Code) beschrieben. Weiterhin kann zum Verständnis der Auswerteroutine der Automatengraph heran gezogen werden.

Das generelle Prinzip ist es, dass sich die Routine immer den voran gegangenen Zustand des Drehgebers merkt. Somit weiß die Routine genau, welcher Code als nächstes notwendig wäre um in den nächsten Zustand zu gelangen. Sind alle notwendigen Zustände durchlaufen, so wurde ein kompletter Impuls (Rastung  Rastung) durchlaufen und der entsprechende Rückgabewert wird erteilt. Im Folgenden sind die Codefolgen pro Schritt (die Pfeile markieren die Rastung) dargestellt:

Uhrzeigersinn             Gegen den Uhrzeigersinn

State | PHI | PHI90       State | PHI | PHI90
------+-----+------       ------+-----+------
R00   | 0   | 0            V00  | 0   | 0
R10   | 1   | 0            V01  | 0   | 1
R11   | 1   | 1            V11  | 1   | 1
R01   | 0   | 1            V10  | 1   | 0
R00   | 0   | 0            V00  | 0   | 0

Anbei noch der Quellcode zur Auswerteroutine. Wenn Ihr Probleme beim Anwenden der Routinen habt, könnt Ihr euch auch sehr gerne jederzeit über die Kommentarfunktion melden.

// ...
#define NO_KEY 0
#define KEY_UP 1
#define KEY_DOWN 2
// ...

/*
* Die Funktion analysiert den Gray Code eines Drehgebers.
* Da es Drehgeber in verschiedensten Ausführen gibt ist beim Kauf
* genaustens auf die Ausführung zu achten! Diese Routine ist für
* den Drehgeber STEC11B13 von Reichelt geschrieben!
*
* Verwendung: Die Routine muss mit einem Timer Interrupt
* aufgerufen werden. Stabil getestet beim Aufruf
* alle 2ms. Eventuell geht es auch noch etwas
* seltener, aber dass muss jeder selber testen!
*
* Rückgabewert: Entweder KEY_UP bei Rechtsdrehung oder
* KEY_DOWN bei Linksdrehung
*/

uint8_t encoder (void)
{
   static uint8_t encState = 0x00;

   switch(encState)
   {
      /*V00*/
      case 0x00:  if( PHI && !PHI90 )
                     encState = 0x10;
                  if( !PHI && PHI90 )
                     encState = 0x45;
                  break;
      /*V10*/
      case 0x10:  if( PHI && PHI90 )
                     encState = 0x11;
                  break;
      /*V11*/
      case 0x11:  if(!PHI && PHI90 )
                     encState = 0x01;
                  break;
      /*V01*/
      case 0x01:  if(!PHI && !PHI90 )
                  {
                     encState = 0x00;
                     return KEY_UP;
                  }
                  break;

      /*R01*/
      case 0x45:  if( PHI && PHI90 )
                     encState = 0x55;
                  break;
      /*R11*/
      case 0x55:  if( PHI && !PHI90 )
                     encState = 0x54;
                  break;
      /*R10*/
      case 0x54:  if(!PHI && !PHI90 )
                  {
                     encState = 0x44;
                     return KEY_DOWN;
                  }
                  break;
      /*R00*/
      case 0x44:  if(!PHI && PHI90 )
                     encState = 0x45;
                  if( PHI && !PHI90 )
                     encState = 0x10;
                  break;
   }
   return NO_KEY;
}

Leave a Comment