Freitag, Januar 09, 2009

Encoder Auswertung

Ausgangssituation

An den Motoren sind Lichtschranken und Schlitzscheiben zur Erfassung der Drehung angebracht. Jeder Motor hat dabei zwei Gabellichtschranken (A und B) die ein um 90° versetztes Signal liefern. Ein Mikrocontroller soll nun diese Signale auswerten.

Schematischer Ablauf der Auswertung

Für die Auswertung eines Encoders soll ein Interrupt bei jeder steigenden und fallenden Flanke der Signale A und B am Mikrocontroller ausgelöst werden. Innerhalb einer Interrupt Service Routine (ISR) werden die Signale ausgewertet und die zurückgelegten Umdrehungen des Motors ermittelt. Durch die Auswertung beider Flanken von zwei Gabellichtschranken, ergibt sich eine vierfache größere Genauigkeit als Schlitze auf der Scheibe sind. Der Nachteil ist aber auch eine höhere rechen Belastung des Mikrocontrollers.

Für einen Motor mit max 5000 UPM und einer Schlitzscheibe mit 32 Strichen ergibt sich:

5000 upm∙36 Striche ∙4 Flanken = 12kHz

Da der µC die Auswertung für zwei Motoren machen muss, müssen 24.000 ISR pro Sekunde verarbeitet werden.

Bei jedem Interrupt werden die Zustände der beiden Signale erfasst und gespeichert (kleines a und b). Dabei durchläuft die Signalabfolge genau 4 Phasen (s. Bild oben) bis die Abfolge wieder von Forn beginnt. Bei jedem Interrupt weren die zuvor gespeicherten und neuen (großes A und B) Zustände verglichen. Die Signale a, b und A, B können zusammen 16 verschiedene Werte annehmen, je nachdem, ob das Sibnal 1 oder 0 ist.

Da sich von der einen auf die andere Phase nur ein Signal ändern darf, sind in den 16 möglichen Werten einige die ungültig. Das nebenstehende KV-Diagramm zeigt alle möglichen Zustände, wobei A und B die aktuellen und a und b die Signale der vorhergehenden Messung darstellen. Die Signale mit einem Ausrufezeichen stehen für LOW und die ohne für HIGH.

Zur Veranschaulichung des KV-Diagrams, hier einmal ein Beispiel: Im zweiten dunkel grauen Feld oben links (das mit der 1) ist das Signal B HIGH und hat sich nicht geändert (kleines b war auch HIGH), wobei A von LOW auf HIGH gewechselt hat. Dies ergibt dann eine Drehung des Motors um genau einen Flanke(Schritt). Bei negativen Schritten hat sich der Motor in die entgegengesetzte Richtung gedreht und bei 0 gar nicht.

Programmstruktur

Die alten und neuen Signale werden bei jedem Interrupt in einer Variablen (bPhase) gespeichert, welche genau dann genau die 16 möglichen Werte darstellt. Mit diesem Wert wird aus einer festen Tabelle (aSteps) der entsprechende Schritt ermittelt. Die Tabelle ergibt sich aus dem KV-Diagramm.










Quellcode

 
#DEFINE IS_SET_MOT1_SIG_A (PINC & (1<< PINC1)
#DEFINE IS_SET_MOT1_SIG_B (PINC & (1<< PINC2)
#DEFINE IS_SET_MOT2_SIG_A (PINC & (1<< PINC3)
#DEFINE IS_SET_MOT2_SIG_B (PINC & (1<< PINC4)
#DEFINE INV -128
typedef union {
 struct {
  unsigned Signal_A :1;   // neues Signal A
  unsigned Signal_B :1;   // neues Signal B
  unsigned AltSignal_A :1;  // altes Signal a
  unsigned AltSignal_B :1;  // altes Signal b
 } x;
 uint8_t bRow;
} tPhase;
Encoder
{
// Member Variablen
long lEncoder; // enthält die gezählten Flanken des Motors
long lError; // enthält die gezählten Fehler des Motors
tPhase bPhase; // enthält das vergangenen und neuen Signale

// Funktionen
void ISR(); // Interrupt Service Routine
long GetEncoder(); // gibt die Anzahl der Flanken zurück
long GetError(); // gibt die Anzahl der Fehler zurück
void Reset(); // setzt die Flanken und Fehler auf Null zurück


ISR(PCINT0_vect)
{
// lookup table
 static const int8_t aSteps[] = { 0, -1, 1, ENC_INV, 1, 0, ENC_INV, -1, -1, ENC_INV, 0, 1, ENC_INV, 1, -1, 0 };
 int8_t iStep = 0; // Schrittweite des Motors

 // alte Phase um zwei Bit nach links verschieben
 bPhase.bRow = (bPhase.bRow << 2);
 // alles ausser a und b löschen
 bPhase.bRow &= 0x0C;    // 0000 1100 = 0x0C
 // Signal A einlesen
 bPhase.x.Signal_A = IS_SET_MOT1_SIG_A ? true : false;
 // Signal B einlesen
 bPhase.x.Signal_B = IS_SET_MOT1_SIG_B ? true : false;

  // Schrittweite aus Tabelle auslesen
 iStep = aSteps[bPhase.bRow];
 if (iStep != ENC_INV)
 {
  lEncoder += iStep;
 }
 else
 {
  lError++;
 }
}
 

Reichweite

In den Variablen lEncoder_R und lEncoder_L werden die zurückgelegten Drehungen der Motoren gespeichert. Diese Werte sind auch außerhalb der ISR verfügbar und können so für eine Streckenmessung oder eine Drehzahlregelung verwendet werden. Nun ist es aber auch wichtig zu überprüfen wie weit man mit einer Streckenmessung kommt. Bei meinem Roboter speichere ich die zurückgelegten Drehungen, genauer gesagt die gezählten Flanken in einer 64bit Variablen. Diese hat einen Wertebereich von -2.174.483.648 bis +2.174.483.647. An den Motoren ist ein Getriebe mit einer Übersetzung von 1/30 direkt angebracht und zu den Reifen noch einmal zwei Zahnräder mit 45/60 Zähnen. Jeder Reifen hat einen Radumfang von ca. 31cm.

Encoder Hardware

Encoder Hardware

Viele von euch, die in der Freizeit gerne an Robottern basteln, standen schon sicher irgend wann einmal vor dem Problem die Drehzahl der Reifen erfassen zu wollen. Nicht immer hat man das Glück einen Motor mit angebauten Drehgeber zu besitzen oder den Platz mal eben schnell etwas zurecht zu friemeln.

Mein Bruder und ich standen vor einiger Zeit vor diesem Problem. Für unseren balancierenden Roboter brauchten wir eine sehr gute erfassung der Drehzahl, um die Motoren für das Balancieren auch richtig regeln zu können. Wir endschieden uns daher dafür die Drehzahl direkt an den Motoren mit Hilfe einer Gabellichtchranke zu erfassen. Dies sollte die größte Messgenauigkeit und schnellste Erfassung der Motoren ergeben, da das Lagerspiel nicht mit erfasst wird. Leider haben die Motoren (vom Typ RB-40) am hinteren Ende keine verlängerte Welle, sondern nur einen kleinen Nupsi der etwas über das Lager hinausragt. Eine Schlitzscheibe zu befestigen schien also so gut wie unmöglich.

Das zweite Problem war der geringe Platz den wir für die Lichtschranke hatten. Wir Ihr im Beitrag Alu und Glas sehen könnt, haben wir bereits ein fetiges und sehr schönes Chassi und so bleibt hinter den Motoren gerade einmal 14mm Platz um die Lichtschranke und eventuel auch etwas Elektronik zu befestigen.

Im März 2007 starteten wir bereits unseren ersten Versuch dieses Problem zu lösen. Damals scheiterten wir knapp an der zu schlechten Auflösung der Lichtschranken.

Die neuen Encoder

Für die neuen Encoder verwenden wir daher zwei Lichtschranken ROHM RPI 243 von Pollin, welche nur wenige Milimeter groß sind. Anstelle der Lochraster Platinen erstellten wir "echte" Platinen und verwendeten ausschliesslich SMD-Bausteine. Der Aufbau wurde wie im ersten Versuch beibehalten.

Die Encoder bestehen aus zwei Platinen die zusammengesteckt werden können. Auf der einen Platine befinden sich zwei Gabellichtschranken und einige Widerstände zur Ansteuerung. Rechts ist diese Platine einmal bgebildet. Gut zu erkennen ist die Aissparung in der Mitte für das Motorlagen. Die großen Kupferflöchen sind für die Spannungsversorgung der Motoren. Im Hinteren Teil befinden sich die beiden Lichtschranken. Mit Hilfe eines Spigels und der Handykamera konnten wir die Richtige Pohling der IR-LEDs testen.