Forum: Projekte & Code Drehgeber mit ECC


von Rolf (Gast)


Lesenswert?

Weil der meiste Code zu Drehgebern mindestens die Hälfte der Information
ignoriert, die der Drehgeber liefert und damit unnötiges Prellen
verursacht, habe ich dazu eine Beschreibung und ein Beispiel gemacht:

Drehgeber werden auch als
Incremental Encoder
Inkrementalgeber
Rotary Encoders
shaft type encoder
bezeichnet.
Funktionsweise: Ausgegeben wird die gray-kodierte Position. Einfache
Ausführungen verwenden nur 2 Adern u. liefern während des Übergangs
ein Codewort und damit zwei Flanken beim monotonen Wechsel von einer
Rasterposition zur nächsten, so dass in den stationären Positionen
(Rasterpositionen) abwechselnd 00 und 11 ausgegeben wird
(2-er-Drehgeber).
Etwas kompliziertere Ausführungen liefern stattdessen entweder nur 11
oder 00
und geben während des Übergangs 3 Codeworte und damit 4 Flanken aus
(4-er-Drehgeber).
Im Folgenden wird nur die einfache Variante betrachtet.
Die korrekte Auswertung erfolgt durch richtungsabhängiges Zählen der
Interrupts:
- Auswerten aller Flanken: Falls kein double edge triggered IRQ:
Interrupt
  edge select passend zum aktuellen Zustand setzen, d. h. rising für
Ader
  auf 0, falling für Ader auf 1, sowohl beim Initialisieren als auch in
der ISR.
- Zählen der IRQs einzeln u. richtungsabhängig (+, -) und
"Weiterschalten" falls der
  richtungsabhängige Zähler (c_gc_c) den Wert +-2 bzw. erreicht.
- Zurücksetzen des Zählers (c_gc_c) bei startup/wakeup.

Kurzgesagt handelt es sich um einen ECC (Error Correcting Code), der
Ein- bis Zwei-Bit-Fehler korrigiert, denn ein Drehgeber ist ein
Gray-Encoder.
Praktisch bewirkt es auch bei starkem Prellen, dass die meisten Fehler
korrigiert werden.
Verglichen mit der primitivst-Lösung (nur ein IRQ wird ausgewertet;
"The winner takes all"-Algorithmus) ist es deutlich besser und
funktioniert
auch bei fast kaputten Drehgebern noch gut.

Ein Beispiel mit Port2, Pin 6 und Pin 7 eines MSP430, zweier-Drehgeber,
einfacher
Fehler-Zähler (d. h. ohne Berücksichtigung der Richtung):

Beispiel mit Port2, Pin 6 und Pin 7 eines MSP430, zweier-Drehgeber,
einfacher
Fehler-Zähler (d. h. ohne Berücksichtigung der Richtung):

// Macro for setting the IES flags for the gray encoder consitent.
// E. g. BIT6 set -> set the associated IES (for IRQ on falling edge).
// Has to be called e. g. at the start of main.
#define mc_CONSISTENT_J \
{ \
if (P2IN & BIT6) \
 P2IES |= BIT6; \
else \
P2IES &= ~BIT6; \
if (P2IN & BIT7) \
  P2IES |= BIT7; \
else \
  P2IES &= ~BIT7; \
}

signed char c_gc_c = 0;         // grey code (interrupt) counter with
sign for direction: + means right
unsigned char uc_l = 0;         // counter for the irqs from left
output (port2, Bit6) of grey encoder
unsigned char uc_r = 0;         //                           right
 (port2, Bit7)
uint32_t uli_errors = 0;

// macro for resetting the grey encoder variables (above)
// Has to be called e. g. at the start of main.
#define mc_RESET_J \
{ \
P2IFG &= ~(BIT6 | BIT7); \
c_gc_c = 0; \
uc_l = 0; \
uc_r = 0; \
}

#define mc_c_gc_cP {if (c_gc_c < 100) ++c_gc_c;}   // one step with
direction, increment with saturation
#define mc_c_gc_cM {if (c_gc_c > -100) --c_gc_c;}  // decrement

#define mc_uc_lP {if (c_gc_c < 100) ++uc_l;} // one step left;
increment with saturation
#define mc_uc_rP {if (c_gc_c < 100) ++uc_r;} // one step right


In der ISR:
    switch (P2_IRQs & (BIT6 | BIT7))     // Mask for BIT6 and BIT7.
    {
      case 0:                  // no irq; should never happen
        uli_errors++;
        break;

      case (BIT6 | BIT7):      // 2 irqs; should never happen
        mc_uc_lP;
        mc_uc_rP;
        uli_errors++;          // error
        break;

      case BIT6:
        mc_uc_lP;
        switch (P2IES & (BIT6 | BIT7))
        {
          case 0:              // 00 -> 01 (P2IN, bit6 = 0  and bit7 =
1), right
            mc_c_gc_cP;
            break;

          case (BIT6):         // 01 -> 00, left
            mc_c_gc_cM;
            break;

          case (BIT7):         // 10 -> 11
            mc_c_gc_cM;
            break;

          case (BIT6 | BIT7):  // 11 -> 10
            mc_c_gc_cP;
            break;

          default: // can never happen
            uli_errors++;
            break;
        }
        break;

      case BIT7:
        mc_uc_rP;
        switch (P2IES & (BIT6 | BIT7))
        {
          case 0:              // 00 -> 10
            mc_c_gc_cM;
            break;

          case (BIT6):         // 01 -> 11
            mc_c_gc_cP;
            break;

          case (BIT7):         // 10 -> 00
            mc_c_gc_cP;
            break;

          case (BIT6 | BIT7):  // 11 -> 01
            mc_c_gc_cM;
            break;

          default: // can never happen
            uli_errors++;
            break;
        }
        break;

      default: // can never happen because only PIN6 and PIN7 are
interrupt enaled
        uli_errors++;
        break;
    }
    mc_CONSISTENT_J;

    if (((P2IN & BIT6) >> 6 == (P2IN & BIT7) >> 7)      // equal
inputs
        && (uc_l)               // left irq
        && (uc_r)               // right irq
      )                         // stationary position
    {
      if (c_gc_c <= -2)
      {
        if (c_gc_c < -2)
          uli_errors++;
        <step_right_action>; // one step right
        mc_RESET_J;
      }
      else
      {
        if (c_gc_c >= 2)
        {
          if (c_gc_c > 2)
            uli_errors++;
          <step_left_action>; // one step right
          mc_RESET_J;
        }
      }
    }

Bei einem Prozessor, bei dem man die Eingänge zweifach
flankengetriggert einstellen kann, ist der Code kleiner.

Hierbei hat left u. right irq nichts mit der Drehrichtung zu tun; die
beiden sind für die beiden Adern des Drehgebers; die Information der
Drehrichtung ist zeitlich codiert (durch Phasenverschiebung).

Bei einem Prozessor, bei dem man die Eingänge zweifach
flankengetriggert einstellen kann, ist der Code kleiner.

Bei einem 4-er-Drehgeber ist es etwas komplizierter, aber dafür können
auch noch
3-Flanken-Fehler korrigiert werden.

Durch den Fehler-Zähler kann man angeben wie zuverlässig der Drehgeber
ist, indem man den Fehler-Zähler durch die Anzahl der Steps (Rechts- u.
Links-Schritte) zählt.

von Peter D. (peda)


Lesenswert?

@Rolf,

Du liest Dir nicht gerne andere Beiträge durch, stimmts ?

Die Prellprobleme hatte ich auch gehabt, aber nachdem ich das hier
entwickelt hatte, waren sie Schnee von gestern.

Besonders die Verwendung des Timerinterrupts hat geradezu einen
Quantensprung in der Störsicherheit gebracht.

Die 2. Variante ist noch einen Tick besser und kürzer, aber die
Unterschiede sind in der Praxis kaum auszumachen.

Die Routine ist so gut, daß ich sie sogar für optische
Positions-Encoder einsetze, obwohl die garnicht prellen. Nichtrastende
Encoder können nämlich hochfrequent oszillieren, wenn sie zufällig
genau auf einem Phasenwechsel zu stehen kommen. Eine
Flanken-Interruptlösung wird dann totgeblasen (zu viele Interrupts) und
spielt verrückt. Die Timerlösung läßt das völlig kalt und zählt korrekt
weiter.

Hier der Beitrag:

http://www.mikrocontroller.net/forum/read-4-37992.html#new


Peter

von Jens (Gast)


Lesenswert?

Tja Peter, auch andere machen sich Gedanken über ein Problem. Warum soll
den jeder deinen Code benutzen ?!?

von nobody0 (Gast)


Lesenswert?

Also ich habe den weitverbreiten The-winner-takes-all-Algorithmus mit
dem obigen Gray-Decoder-Algorithmus auch experimentell getestet auf
MSP430 und ARM9 und damit viel bessere Ergebnisse; das ist viel
zuverlässiger.
Dass zwischen Schalter und MC ein Tiefpass (notfalls auch ein
Schmitt-Trigger) gehört, ist selbstverständlich; da gibt es nichts
hochfrequentes.

Das entscheidende beim Gray-Decoder ist doch, dass man beispielsweise
durch Alterung langsam defekt werdende Drehgeber mit Software erkennen
kann, bevor der Benuzter etwas davon merkt; man kann z. B.
prognostizieren, dass der Drehgeber in 21 Tagen ausfallen wird
(basierend auf den Daten eines durchschnittlichen Drehgebers u.
durchschnittlicher Nutzung).
Außerdem kann man per Software einen defekten Drehgeber erkennen und
melden/ignorieren; dadurch kann man z. B. verhindern, dass etwas
anderes eingestellt wird, als der Benuter gedreht hat.

von Peter D. (peda)


Lesenswert?

@Jens,

"Warum soll den jeder deinen Code benutzen ?!?"

ich wollte nur klarmachen, daß der flankengetriggerte Interrupt für
alle Arten kontaktbehafteter Eingaben eine tiefe Sackgasse ist, die nur
durch einen erheblichen Aufwand an Code moderat zuverlässig gemacht
werden kann, wie man ja im direkten Vergleich sehr eindrucksvoll
sieht.


@Rolf,

"...weitverbreiten The-winner-takes-all-Algorithmus"

das scheint Dir nur so, weil er die meisten Probleme macht und deshalb
oft Fragen dazu in Foren auftreten.


"Dass zwischen Schalter und MC ein Tiefpass (notfalls auch ein
Schmitt-Trigger) gehört, ist selbstverständlich; da gibt es nichts
hochfrequentes."

daran ist überhaupt nichts selbstverständliches !

Wenn ich durch entsprechenden Code externe Hardware überflüssig machen
kann, dann tue ich das auch. Selbst auf externe pull-Ups verzichte ich,
wozu gibts ja interne.


"Das entscheidende beim Gray-Decoder ist doch, dass man
beispielsweise
durch Alterung langsam defekt werdende Drehgeber mit Software erkennen
kann, bevor der Benuzter etwas davon merkt; man kann z. B.
prognostizieren, dass der Drehgeber in 21 Tagen ausfallen wird"

Sowas habe ich ja noch nirgends gesehen.
Rein theoretisch wäre es denkbar, aber in er Praxis geht die Software
raus, sobald die Grundfunktion läuft, für irgendwelche Gimmicks hat da
keiner Zeit.

Außerdem würden Kunden sowas ignorieren oder sogar als störend
empfinden und das Gerät trotzdem nutzen, solange es nur irgend geht.


Wenn die Zuverlässig eine Rolle spielt, nimmt man eben optische Encoder
und die haben oft auch einen Errorsignalausgang.


Peter

von nobody0 (Gast)


Lesenswert?

Also das Auswerten von korrigierbaren Fehlern hast Du garantiert schon
gesehen beispielsweise bei Brenner- und Rohling-Tests. Wenn Du mal mit
smartctl -a nachgesehen hast, ob eine Festplatt noch brauchbar ist,
hast Du da auch Angaben wie Hardware_ECC_Recovered gesehen. Es gibt ja
Programme/Skripte, die diese Ausgaben regelmäßig überprüfen und dem
Benuter eine Mail schicken, wenn Datenverlust absehbar ist; danach wird
die Platte sicherlich nicht benutzt, bis nichts mehr geht, denn eine
ganz kaputte Platte stört den Benutzer mehr als eine rechtzeitige
Warnung.

Seit mir mehrmals RAM-Module im PC gestorben sind, benutze ich das
Prinzip auch mit ECC-RAM; wenn es 1-Bit-Fehler gibt (bisher gab's noch
keine), besorge ich neues ECC-RAM bevor es nicht korrigierbare
Mehrbit-Fehler gibt.

Gut, in der MC-Programmierung macht man das meist nicht und
beispielsweise 16x-DVD-Brenner werden an die Kunden abgegeben, bevor
sie richtig funktionieren (siehe entsprechende Tests in c't); da ist
das Meiste Quick and Dirty und geht an die Kunden wenn es meistens und
auch nur ausreichend funktioniert, aber ich wollte mal zeigen wie man
es ohne großen Aufwand besser machen kann; so wie man es als Kunde
erwartet.

von nobody0 (Gast)


Lesenswert?

Nachtrag: Mein obiges Beispiel war für ein schon vorhandenes Programm,
das keinen Timer mehr frei hatte. Man kann es natürlich auch
umschreiben auf einen Timer-Interrupt, der die Eingänge ausliest.
Generell ist es schon besser Taster/Schalter mit einem Timer-Interrupt
auszulesen, so wie man es beispielsweise von PC-Tastaturen kennt.

von Peter D. (peda)


Lesenswert?

Da bringst Du aber was gehörig durcheinander.

CRC, ECC usw. beruhen darauf, daß man zusätzliche Informationen mit
überträgt und diese überprüft.

Ein Encoder hat aber keine zusätzlichen Informationen, Du hast genau 2
Bits für 4 Zustände.

Das Einzige, was Du also machen kannst, ist zu prüfen, ob es gültige
Zustandswechsel sind und ob der Zustand für eine bestimmte Entprellzeit
konstant ist, d.h. kein Prellen ist.

Aber was Du da ECC nennst, hat nämlich nicht das geringste mit ECC auf
Datenträgern zu tun.


Man kann meine Routine auch genau so gut im Flankeninterrupt aufrufen,
wenn man auf beide Flanken von beiden Signalen triggert, z.B. mit dem
Pin-Change Interrupt.
Der Interrupt detektiert dann nur eine beliebige Änderung, aber die
Routine macht die Auswertung. Somit ist es unerheblich, ob der
Interrupt durch ein Prellen oder eine Störung ausgelöst wurde, die
Auswerteroutine erkennt das und macht dann einfach nichts.


Peter

von nobody0 (Gast)


Lesenswert?

Das ECC stimmt; die zusätzlichen Informationen sind die mehreren (mind.
2) Zustandswechsel, die nötig sind um von einer Raster-Position in eine
direkt benachbarte zu kommen und das ist hochgradig redundant. Das ist
ja der Grund, weshalb der The-Winner-Takes-All-Algorithmus halbwegs
brauchbar funktioniert, obwohl der (mindestens) die Hälfte aller
Flanken ignoriert, also bestenfalls die Hälfte der Informationen nutzt.

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.