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.
@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
Tja Peter, auch andere machen sich Gedanken über ein Problem. Warum soll den jeder deinen Code benutzen ?!?
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.
@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
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.
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.