home
erste Version am 12.11.2016
letzte Änderung am 13.11.2016

Drehgeber auswerten


Heute sind 10 Drehgeber aus China angekommen. Einen konkreten Einsatzzweck habe ich derzeit zwar noch nicht, aber bei 4,06€ für 10 Stück habe ich sie einfach mal auf Vorrat bestellt...
Das war jetzt eine Lieferzeit von 18 Tagen und die könnte schon mal ziemlich unerfreulich sein, wenn man die Dinger eigentlich sofort für ein Projekt braucht.

Es gibt dazu schon reichlich Doku im Playground, aber was ich dort mittels Quer-Lesen gesehen habe, gefiel mir alles nicht.
Außerdem ist es ohnehin viel spannender, sowas selbst zu bauen.



An dem Bauteil befinden sich fünf Kontakte.
Zwei Kontakte sind für den Drucktaster.
An den drei Kontakten auf der gegenüber liegenden Seite werden die beiden versetzten Signale für die Drehrichtung abgegriffen.
Bei folgender Benennung
  A B
 C D E

also A/B für den Taster und C/D/E für die Drehrichtung.
B und D gehen an Masse, A/C/E an je einen digital-Eingang des Arduino mit eingeschaltetem Pullup-Widerstand.
Weil mechanische Taster quasi immer prellen, braucht es einen Entprell-Mechanismus. Vorzugsweise in Software.

Ein prellender Taster liefert ein Signal wie dieses:
    ___   _   _                            _   _   ____
       |_| |_| |__________________________| |_| |_|


Ein Drehgeber hat zwei versetzt angebrachte Taster.
Abhängig davon, welcher Taster zuerst geschaltet wird, lässt sich die Drehrichtung erkennen.

Ohne Prellung sähe das etwa so aus:
    __                  _________
      |________________|
    ______                  _____
          |________________|
     1 0   0
     1 1   0

Oder für die andere Richtung:
    ______                  _____
          |________________|
    __                  _________
      |________________|
     1 1   0
     1 0   0

Bei 0/0 wird der Schritt erkannt. Abhängig davon, ob vorher 0/1 oder 1/0 anlag, wird die Drehrichtung bestimmt.
Mit Prellung sehen die Signale eher so aus, wie im folgenden Programm-Header.



Hier der Programm-Kode:
/*
 *  Auswertung eines Drehgebers mit "digitaler" Entprellung
 *  ... also ohne Kondensator oder delay(1)
 *  Der gemeinsame Kontakt am Drehgeber ist an Masse zu legen.
 *
 *  Rückwärts
 *    ___   _   _                            _   _   __________________________
 *       |_| |_| |__________________________| |_| |_|
 *    ____________________   _   _                                  _   _   ___
 *                        |_| |_| |________________________________| |_| |_|
 *    1   0 1 0 1 0        0 0 0 0 0         1 0 1 0 1              1 1 1 1 1
 *    1   1 1 1 1 1        0 1 0 1 0         0 0 0 0 0              1 0 1 0 1
 *    #   # # # # #        #                                        # # # # #
 *    3   1 3 1 3 1        0                                        3 2 3 2 3
 *
 *  Vorwärts
 *    ____________________   _   _                                  _   _   ___
 *                        |_| |_| |________________________________| |_| |_|
 *    ___   _   _                            _   _   __________________________
 *       |_| |_| |__________________________| |_| |_|
 *    1   1 1 1 1 1        0 1 0 1 0         0 0 0 0 0              1 0 1 0 1
 *    1   0 1 0 1 0        0 0 0 0 0         1 0 1 0 1              1 1 1 1 1
 *    #   # # # # #        #                                        # # # # #
 *    3   2 3 2 3 2        0                                        3 1 3 1 3
 *
 *
 * D.Ahlgrimm  12.11.2016
 */

#define DR_INT0   2   // Pin2 für Interrupt 0
#define DR_INT1   3   // Pin3 für Interrupt 1
#define DR_BTN    4

// 0=00 (Schritt), 1=01 (rückwärts), 2=10 (vorwärts), 3=11 (Grundstellung)
volatile unsigned char dr_t=0;

volatile int dr_counter=0;


void dr(void) {
  static unsigned char p;
  switch(p=digitalRead(DR_INT0) | digitalRead(DR_INT1)<<1) {
    case 0:
      if(dr_t==1) {
        dr_t=p;
        dr_counter--;
      } else if(dr_t==2) {
        dr_t=p;
        dr_counter++;
      }
      break;
    case 1:
    case 2:
      if(dr_t==3) dr_t=p;
      break;
    case 3:
      dr_t=p;
  }
}


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

  pinMode(DR_INT0, INPUT_PULLUP);
  pinMode(DR_INT1, INPUT_PULLUP);
  pinMode(DR_BTN,  INPUT_PULLUP);

  attachInterrupt(0, dr, CHANGE);
  attachInterrupt(1, dr, CHANGE);
}


void loop() {
  static int cnt_old=-1, but_old=1;

  if(digitalRead(DR_BTN)!=but_old) {
    but_old=digitalRead(DR_BTN);
    delay(2);   // der Taster wird dann doch mittels Wartezeit entprellt
    if(but_old==LOW) {
      Serial.println("DN");
    } else {
      Serial.println("UP");
    }
  }
 
  if(dr_counter!=cnt_old) {
    cnt_old=dr_counter;

    Serial.println(dr_counter, DEC);
  }
}

Und noch ein Foto vom Test-Aufbau:
Foto vom Testaufbau mit dem
        Drehgeber



Falls die zwei wertvollen Interrupt-Pins anderweitig benötigt werden, lässt sich das ganze auch via Timer-Interrupt realisieren.
Dafür wird dann allerdings eine entsprechende Library aus dem Playground benötigt.
/*
  Auswertung eines Drehgebers mit "digitaler" Entprellung, Version 2
  ... also ohne Kondensator oder delay(1)
  Der gemeinsame Kontakt am Drehgeber ist an Masse zu legen.

  Rückwärts
    ___   _   _                            _   _   __________________________
       |_| |_| |__________________________| |_| |_|
    ____________________   _   _                                  _   _   ___
                        |_| |_| |________________________________| |_| |_|
    1   0 1 0 1 0        0 0 0 0 0         1 0 1 0 1              1 1 1 1 1
    1   1 1 1 1 1        0 1 0 1 0         0 0 0 0 0              1 0 1 0 1
    #   # # # # #        #                                        # # # # #
    3   1 3 1 3 1        0                                        3 2 3 2 3

  Vorwärts
    ____________________   _   _                                  _   _   ___
                        |_| |_| |________________________________| |_| |_|
    ___   _   _                            _   _   __________________________
       |_| |_| |__________________________| |_| |_|
    1   1 1 1 1 1        0 1 0 1 0         0 0 0 0 0              1 0 1 0 1
    1   0 1 0 1 0        0 0 0 0 0         1 0 1 0 1              1 1 1 1 1
    #   # # # # #        #                                        # # # # #
    3   2 3 2 3 2        0                                        3 1 3 1 3

   D.Ahlgrimm  13.11.2016
*/

#include <TimerOne.h>

#define DR_BTN    4
#define DR_P1     5
#define DR_P2     6

// 0=00 (Schritt), 1=01 (rückwärts), 2=10 (vorwärts), 3=11 (Grundstellung)
volatile unsigned char dr_t=0;

// Die Position des Drehgebers
volatile int dr_counter=0;

/* -----------------------------------------------------------------
   Inkrementiert "dr_counter" bei der Folge 11-10-00 bzw.
   dekrementiert "dr_counter" bei der Folge 11-01-00
   an den Pins "DR_P1" und "DR_P2".
*/
void dr(void) {
  static unsigned char p;
  switch(p=digitalRead(DR_P1) | digitalRead(DR_P2)<<1) {
    case 0:                 // Schritt erkannt
      if(dr_t==1) {         //   wenn zuvor Rückwärts-Schritt
        dr_t=p;             //     merken
        dr_counter--;       //     und Schritt ausführen
      } else if(dr_t==2) {  //   wenn zuvor Vorwärts-Schritt
        dr_t=p;             //     merken
        dr_counter++;       //     und Schritt ausführen
      }                     //   sonst nix bzw. ignorieren
      break;
    case 1:                 // rückwärts
    case 2:                 // vorwärts
      if(dr_t==3) dr_t=p;   //   wenn zuvor Grundstellung -> Richtung merken
      break;
    case 3:                 // Grundstellung
      dr_t=p;               //   unbedingt merken
  }
}

/* -----------------------------------------------------------------
   Liefert den entprellten Zustand des Tasters "DR_BTN".
*/
char buttonDebounce(void) {
  char last_but=digitalRead(DR_BTN), cnt=0;

  // Schleife erst verlassen, wenn 10x in Folge der selbe Wert gelesen wurde
  while(cnt<10) {
    if(last_but==digitalRead(DR_BTN)) {
      cnt++;
    } else {
      cnt=0;
      last_but=digitalRead(DR_BTN);
    }
    delayMicroseconds(10);
  }
  return(last_but);
}


/* -----------------------------------------------------------------

*/
void setup() {
  Serial.begin(115200);

  pinMode(DR_BTN, INPUT_PULLUP);
  pinMode(DR_P1,  INPUT_PULLUP);
  pinMode(DR_P2,  INPUT_PULLUP);

  Timer1.initialize(1000);  // 1x pro Millisekunde
  Timer1.attachInterrupt(dr);
  Timer1.stop();
}


/* -----------------------------------------------------------------
   Main
*/
void loop() {
  static int cnt_old=0;
  static char but_old=1, but_new;

  but_new=buttonDebounce();
  if(but_old!=but_new) {
    but_old=but_new;
    if(but_old==LOW) {
      Serial.println("DN");
      Timer1.start();
    } else {
      Serial.println("UP");
      Timer1.stop();
    }
  }

  if(dr_counter!=cnt_old) {
    cnt_old=dr_counter;
    Serial.println(dr_counter, DEC);
  }
}
Bei dieser Version wird der Drehgeber nur abgefragt, so lange der Taster am Drehgeber gedrückt gehalten wird.
Die Konsolen-Ausgabe für zwei kurze Hin- und Her-Dreher sieht etwa so aus:
DN
1
2
3
4
5
6
5
4
3
2
1
0
-1
UP
DN
-2
-3
-4
-5
-4
-3
-2
-1
0
1
2
UP



Lägen die Wechsel so nah aneinander, dass das erste Signal noch prellt, wenn das zweite kommt, ergäben sich folgende Flanken-Beziehungen:
_____________   _   _
             |_| |_| |__
___   _   _
   |_| |_| |____________
  1 1 1 1 1 1 0
  1 0 1 0 1 0 0
  3 2 3 2 3 2 0 -> Normal-Fall ohne Überlappung

_____________   _   _
             |_| |_| |__
_____   _   _
     |_| |_| |____________
    1 1 1 1 1 0 1 0
    1 0 1 0 1 0 0 0
    3 2 3 1 3 0 2 0 -> direkter Übergang

_____________   _   _
             |_| |_| |__
_______   _   _
       |_| |_| |____________
      1 1 1 1 0 1 0
      1 0 1 0 1 0 0
      3 2 3 2 1 2 0 -> halbe Überlappung

_____________   _   _
             |_| |_| |__
_________   _   _
         |_| |_| |____________
        1 1 1 0 1 0 1 0
        1 0 1 0 1 0 0 0
        3 2 3 0 3 0 2 0 -> volle Überlappung

Schief gehen könnte was, wenn zusätzlich noch die Anzahl der Prellungen oder deren einzelne Dauer bei den Signalen unterschiedlich ausfiele:
_______   _
       |_| |______
___   _   _
   |_| |_| |______
  1 1 1 0 1 0
  1 0 1 0 1 0
  3 2 3 0 3 0 -> kein Schritt erkannt

_________     _
         |___| |__
___     ___
   |___|   |______
  1 1 1 1 0 0
  1 0 0 1 1 0
  3 2 2 3 1 0 -> falsche Richtung erkannt

Allerdings müsste man den Drehgeber dazu wahrscheinlich schon mit einem schnellen Akkuschrauber drehen.
Mein Modell liefert 20 Schritte für eine komplette Umdrehung.
Wenn ich von einer definierten Position starte, so schnell wie (von Hand) möglich mehrere Umdrehungen in eine Richtung drehe und danach wieder mit gesitteter Geschwindigkeit bis zum Wert 0 zurückdrehe, steht die Markierung des Drehknopfes zuverlässig auf der Ausgangs-Position.
Will heißen: es wurde in beide Richtungen sauber gezählt und dabei nix übersprungen oder gar falsch herum gezählt.
Beim zweiten Sketch langt es für solche Hektik-Aktionen allerdings nicht, Timer1 mit 1000 zu initialisieren.
Auch bei 500 konnte ich noch Ausgangs-Positions-Abweichungen schaffen - bei 100 (=0,1ms) jedoch nicht mehr.