Forum: Mikrocontroller und Digitale Elektronik Handbetriebenen Drehgeber ohne Timer auswerten


von Wolfgang (wolle_wolle)


Lesenswert?

Ich nutze einen handbetriebenen Drehgeber von ALPS an einem Atmel328p, 
kann aber für das Lesen keinen Timer einsetzen. Also musste ein anderer 
Weg für das entprellte Erkennen beider Drehrichtungen her.
Ich kam nach einigen Versuchen und dem Lesen des Datenblattes vom 
Drehgebers (macht das Leben so viel leichter) zu folgendem Verfahren.

Schauen wir uns exemplarisch die Schalterstellung als Bitfolge an (A 
schaltet hier vor B mit Prellen beider Schalter, Ausgangslage ist 00):
1
BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA
2
00 01 00 01 00 01 01 01 01 11 01 11 01 11 01 11 11 11

A schaltet ab Schritt 2 und ist ab Schritt 6 stabil. Laut Datenblatt 
meines Drehgebers fängt B erst an zu Schalten, wenn A umgeschaltet hat 
(wichtig, geprüft per Oszilloskop). Im Beispiel schaltet B ab Schritt 10 
und ist ab Schritt 16 stabil.

Ansatz: es reicht zu erkennen, dass A geschaltet hat und stabil ist, und 
darauf zu warten, das B anfängt zu prellen. Und natürlich anders herum 
bei geänderter Drehrichtung. Das reicht als Indikator.
Vorgehen: die Schalterstellung als 2-Bit-Folge bei Änderungen in eine 
8-Bit-Queue schieben und die Queue für die Drehrichtungserkennung 
nutzen.

Wenn ich das obige Beispiel nehme, enthält die Queue als Ergebnis die 
Bitfolge xx000111 (Shift von Rechts, die beiden hohen Bits sind nicht 
von Belang). Wenn wir mit 11 starten und in die gleiche Richtung weiter 
drehen, enthält die Queue xx111000. Dieses Pattern zeigt mir eine 
Drehrichtung an. Wenn in die andere Richtung gedreht wird, sehen die 
Pattern so aus: xx001011 bzw. xx110100. Also reicht es, die Queue 
fortlaufend gegen die Änderungs-Pattern zu vergleichen und den Match als 
Drehrichtungserkennung zu werten.

Vereinfacht wird das Ganze, da die Pattern einer Drehrichtung genau 
gegenteilig sind. man schaut einfach auf Bit 6 und invertiert das Muster 
wenn das Bit auf 1 steht. Dann bedeutet Pattern xx000111 eine 
Drehrichtung und xx001011 die andere Drehrichtung.

Das Ganze läuft ohne Timer bei mir in meinem ziemlich proppevollen Code 
problemfrei. Die Variable ticks (siehe Code Beispiel) beinhaltet die 
Anzahl der Drehschritte und muss möglichst schnell ausgewertet werden. 
In meinem Projekt geschieht das ungefähr alle 80-120ms, das ist für die 
händische Nutzung bei mir völlig ausreichend.

Beispiel-Code:
1
// Defines
2
#define CW  0b00001011       // identification pattern clockwise
3
#define CCW 0b00000111       // identification pattern counter clockwise
4
#define QUEUE  0b00111111    // relevant bits of the queue (0-5)
5
#define REVERT 0b00100000    // inverting detector pattern
6
7
// function returns encoder switches A and B as lowest two bits (000000BA)
8
// Hier: A an D12, B an D11 (Arduino Nomenklatur)
9
uint8_t getEncoderStatus() {
10
    return ( ((~PINB & (1 << 4)) >> 4)  // A shift to the most right
11
            |((~PINB & (1 << 3)) >> 2));// B shift to the right before A
12
}
13
14
15
int main(void) {
16
  int8_t ticks;           // resulting number of turn ticks (must be signed)
17
  uint8_t encoder_queue;  // encoder queue (relevant bits xx111111)
18
  uint8_t encoder_status; // actual encoder status (relevant bits xxxxxx11)
19
20
  encoder_queue = GetEncoderStatus();   // initialize encode queue
21
  ticks = 0;                            // initialize ticks
22
    
23
  while (1) {
24
    
25
    encoder_status = getEncoderStatus(); // get actual encoder status
26
    // if actual bits differ from lowest two bits...
27
    if ((encoder_queue ^ encoder_status) & 3) {
28
      // ... push them into queue from the right
29
      encoder_queue = encoder_queue << 2 | encoder_status;
30
31
      encoder_temp = encoder_queue;   // queue bits into a temporary variable
32
33
      if (encoder_temp & REVERT) {    // if reverse bit (bit 6) is true...                
34
        encoder_temp = ~encoder_temp; // ...reverse the temporary variable 
35
      }
36
 
37
      encoder_temp &= QUEUE;          // select relevant queued bits
38
                              
39
      if (encoder_temp == CCW) {      // if counter clockwise pattern fits ...
40
        ticks--;                      // ... decrease the ticks
41
      }
42
      if (encoder_temp == CW)  {      // if clockwise pattern fits ...
43
        ticks++;                      // ... increment the ticks
44
      }
45
    }
46
  }
47
}

von Harald K. (kirnbichler)


Lesenswert?

Wolfgang schrieb:
> Ich nutze einen handbetriebenen Drehgeber von ALPS an einem Atmel328p,
> kann aber für das Lesen keinen Timer einsetzen.

Viele Probleme lassen sich lösen, wenn man einen Schritt zurückgeht und 
die vermeintlich unabänderlichen Rahmenbedingungen ändert. Wieso sollte 
Dein Atmega keinen Timer haben? Wieso sollte nicht in einer der von Dir 
vermutlich für andere Dinge genutzten Timerinterruptroutinen auch 
nebenbei das Pollen der zwei/drei I/O-Pins des Drehgebers eingefügt 
werden können?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Wolfgang schrieb:
> Das Ganze läuft ohne Timer
Natürlich ist da implizit ein "Timer" mit drin: die Durchlaufzeit der 
mainloop. Und auf diese Art hast du dann auch Abhängigkeiten von der 
Plattform, auf der die SW läuft.

Das, was du da im Grunde machst, ist eine Flankenerkennung über ein 
Schieberegister. Oder eher über zwei ineinander verwobene 
Schieberegister.

- 
https://www.lothar-miller.de/s9y/archives/18-Flinke-Flankenerkennung.html

> uint8_t encoder_status; // actual encoder status
Ein Tipp: "actual" stimmt hier eigentlich nicht, denn da drin steht 
nicht der "tatsächliche" Enoderzustand, sondern der "kürzlich" bzw. 
"zuletzt" eingelesene Wert. Die Worte "most recent" oder "latest" wären 
richtig, denn der "tatsächliche" Pegel an den Pins und damit der 
"tatsächliche" Encoderzustand kann sich schon wieder geändert haben.

von Mi N. (msx)


Lesenswert?

Harald K. schrieb:
> Wieso sollte nicht in einer der von Dir
> vermutlich für andere Dinge genutzten Timerinterruptroutinen auch
> nebenbei das Pollen der zwei/drei I/O-Pins des Drehgebers eingefügt
> werden können?

So isses!
Wenn nicht benötigt oder störend, kann man auch UART oder SPI für 
periodische Interrupts verwenden. Alles andere ist unötiges Gewürge.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.