Anfänger will Drehgeber auslesen

Hallo zusammen,
möchte einen Drehgeber der an einer Spinndel montiert wird auslesen, also ganz einfach :~

Ich möchte zuerst wissen ob so ein Arduino dieser Aufgabe gewachsen ist.

Die Spindel bewegt einen Zylinder nach vorne bzw. nach hinten.
Pro Umdrehung bewegt sich der Zylinder 500 µm.
Möglich sind Werte von 0,5 µm bis 500 µm.
Für eine Umdrehung benötigt die Spindel ca. 0,75 sec.
Ich brauche eine Auflösung von 0,5 µm.

Ich habe mir gedacht ich nehme einen inkrementalgeber mit 1000 Impulse / Umdrehung, dann komme ich auf 0,5 µm / Impuls.
Das sind dann ca. 1333 Impulse / Sekunde.

Die Spindel dreht sich mit einem Wert X nach links um dann ca. 200ms später mit einem anderen Wert nach rechts zu drehen usw.

Die Impulszahl für links und rechts soll schlussendlich auf einem Display angezeigt werden.

Glaubt ihr das ist zu machen ?

Gruss

hansheiri:
Die Impulszahl für links und rechts soll schlussendlich auf einem Display angezeigt werden.

Glaubt ihr das ist zu machen ?

1333 Impulse pro Sekunde zu zählen ist kein großes Thema für einen Arduino.

Die Frage ist aber, was für eine Art von Impulsen Dein Drehgeber abgibt:

a) Hat der Drehgeber zwei Ausgänge und liefert einen 4-bit Graycode am Ausgang, kann anhand der Abfolge der Impulse an beiden Ausgängen die Drehrichtung nur mit Hilfe des Drehgebers festgestellt und damit rechts/links gezählt werden.

b) Hat der Drehgeber nur einen einzigen Ausgangs-Pin und liefert nur ein Tachosignal, dann benötigst Du ein zusätzliches Signal für Rechts- oder Linksdrehung um Rechtsimpulse von Linksimpulsen unterscheiden zu können.

Wenn beim Drehrichtungswechsel eine Pause entsteht und es ausreicht, das Display nur in der Pause zu aktualisieren, dann kann man sogar per Polling in der loop() zählen.

Falls allerdings das Display auch aktualisiert werden soll, während Drehimpulse gezählt werden, müßte man die Zählimpulse über Hardware-Interrupts zählen, damit keine verlorengehen.

@jurs
Benutzen normale 2 Phasen Inkrementalgeber nicht einen 2Bit Gray Code ?

@hansheiri

Die Spindel bewegt einen Zylinder nach vorne bzw. nach hinten.
Pro Umdrehung bewegt sich der Zylinder 500 µm.
Möglich sind Werte von 0,5 µm bis 500 µm.

Bist Du sicher, daß die Mechanik kein Spiel hat?

Grüße Uwe

Hallo jurs
danke für deine Antwort.
Der Drehgeber habe ich noch nicht, denke aber es wird ein "Drehgeber zwei Ausgänge und liefert einen 4-bit Graycode am Ausgang".
Es reicht aus das Display in der Pause zu aktualisieren.

Reicht ein Arduino Mega ADK R3 ?
habe vor einiger Zeit mit Picaxe einige kleine Projekte realisiert und bin jetzt auf Arduino gekommen.
Kann löten und habe etwas Erfahrung mit Elektronik aber leider nicht viel Ahnung vom Programmieren.

Danke

Hallo Uwe,
Ja, Spindel hat kein Spiel.

Gruss

hansheiri:
Reicht ein Arduino Mega ADK R3 ?

Nach deiner jetzigen Beschreibung reicht auch ein kleienr Arduino Uno. Wie kommst du darauf, einen Mega ADK nutzen zu wollen? Wenn man mehr RAM und mehr freie Pins möchte, reicht ja auch der Arduino Mega 2560.

uwefed:
@jurs
Benutzen normale 2 Phasen Inkrementalgeber nicht einen 2Bit Gray Code ?

Ja, stimmt. 2-bit, 4 Zustände.

hansheiri:
Reicht ein Arduino Mega ADK R3 ?
habe vor einiger Zeit mit Picaxe einige kleine Projekte realisiert und bin jetzt auf Arduino gekommen.
Kann löten und habe etwas Erfahrung mit Elektronik aber leider nicht viel Ahnung vom Programmieren.

Für zwei Eingänge eines Drehgebers und den Anschluß eines Displays reicht sogar ein UNO. Oder ein beliebiges anderes Arduino-kompatibles Board.

Großartig Löten braucht man kaum, um ein LCD an einen Arduino zu bekommen:

Entweder

  • UNO oder MEGA (oder kompatibles) Board
  • ein "LCD Keypad Shield" zum direkten Aufstecken auf das Board

Oder

  • beliebiges Arduino-Board
  • ein "I2C LCD" zum Anschließen über den I2C-Bus (nur 2 Anschlüsse außer GND/5V)

Für beide LCD-Lösungen gibt es fertige Libraries. Wobei es bei "I2C LCDs" ggf. zu kleineren Kompatibilitätsproblemen kommen kann, je nach verwendetem I2C-Adapter.

"Nackte" 16x2 bis 20x4 LCDs kann man zwar auch relativ einfach anschließen, aber man hat dann schon ein paar Drähte mehr zu verkabeln.

Von der Programmierung her sind die Drehencoder mit 2-bit Gray-Code (vier Codes im Wechsel) nicht besonders anspruchsvoll. Im Prinzip gibt es ja bei zwei Eingängen nur vier verschiedene Zustände, die diese Eingänge annehmen können. Und dann muß man bei jeder Zustandsänderung nur nachsehen, ob es eine Zustandsänderung ist, die beim Rechtsdrehen oder beim Linksdrehen auftritt und entsprechend zählen.

Hallo zusammen,
Hardware wird nicht das Problem sein.
Im Moment habe ich nur kein Plan wie das Programm aussehen soll.
Habe heute bestellt, werde mich morgen wieder melden.

Danke

Hallo,

die grundsätzliche Auswertung ist hier sehr gut beschrieben: Grundsatz

Speziell auf den Arduino passend (wegen Timerinterrupts) siehe hier: Arduino

Hoffentlich hilft Dir das etwas weiter.

Gruß,
Ralf

jurs:
Von der Programmierung her sind die Drehencoder mit 2-bit Gray-Code (vier Codes im Wechsel) nicht besonders anspruchsvoll. Im Prinzip gibt es ja bei zwei Eingängen nur vier verschiedene Zustände, die diese Eingänge annehmen können.

Im Prinzip hast du damit recht. Aber aus eigener Erfahrung kann ich sagen, dass es ziemlich knifflig sein kann so einen Drehencoder gescheit zum laufen zu bekommen, auch wenn die Grundsituation simpel ist. Man sollte viel Geduld und Ruhe mitbringen wenn man einen Drehencoder in sein Projekt integrieren will :slight_smile:

hansheiri:
Hardware wird nicht das Problem sein.
Im Moment habe ich nur kein Plan wie das Programm aussehen soll.

Als erstes einfach mal klarmachen, was läuft.

Die zwei Ausgänge des Drehgebers, die an zwei Eingänge des Arduino angeschlossen werden, können vier Zustände annehmen. Beim Gray-Code ändert sich von einem zum nächsten Zustand immer nur ein einziges Bit. Es gibt zwei Bitfolgen, die möglich sind:

Bitfolge 1:
00
01
11
10
00

Bitfolge 2:
00
10
11
01
00

Diese beiden Bitfolgen stehen für "links" bzw. "rechts" Impulse, wenn sie in dieser Abfolge erfolgen.

D.h. Du brauchst immer "alter Zustand" und "neuer Zustand", um zu sehen, was für ein Impuls vorliegt.

Ich nehme nun mal an, Bitfolge1 stehe für links und Bitfolge2 für rechts (da braucht man ggf. nur die beiden Eingänge vertauschen), dann entsprechen folgende Änderungen:
00-01 ==> links
01-11 ==> links
11-10 ==> links
10-00 ==> links

00-10 ==> rechts
10-11 ==> rechts
11-01 ==> rechts
01-00 ==> rechts

Falls keine Änderung stattfindet, hat sich nichts gedreht, falls andere Bitübergänge stattfinden, liegt ein Fehler vor, z.B. weil Änderungen an den Eingängen "verpaßt" wurden.

Diese Logik zur Encoder-Abfrage in eine Funktion gegossen sähe bei mir beispielsweise so aus:

// Pins für die Eingänge am Arduino festlegen
#define ENCODER_A 2
#define ENCODER_B 3

int encoderImpuls()
{
  static byte state=0;
  state= state<<2;    // Bits um zwei Stellen nach links schieben
  if (digitalRead(ENCODER_A)) bitSet(state,1);  // Eingang A abfragen und Bit1 setzen
  if (digitalRead(ENCODER_B)) bitSet(state,0);  // Eingang B abfragen und Bit0 setzen
  state= state & 0xF;   // Nur die unteren 4 Bits behalten
  switch(state)
  {
    case 0b0000:
    case 0b0101:
    case 0b1010:
    case 0b1111: return 0; // keine Änderung an den Eingängen
    case 0b0001:
    case 0b0111:
    case 0b1110:
    case 0b1000: return -1; // links Impuls an den Eingängen
    case 0b0010:
    case 0b1011:
    case 0b1101:
    case 0b0100: return 1; // rechts Impuls an den Eingängen
    default: return 9999;  // Error (z.B. wegen verpaßter Impulse)
  }  
}

Diese Funktion einfach in einer engen Schleife ständig aufrufen und Impulse zusammenzählen. Über den Stand des millis() Zählers den Stillstand der Spindel erkennen und jeweils beim Stillstand das Display aktualisieren.

Sag Bescheid, wenn Du nicht nur eine Funktion sondern ein ganzes Beispielprogramm brauchst.

@jurs: Hallo,

irgendwann fängt die Funktion ja mal an zu laufen, z.B. wenn der Arduino eingeschaltet wird. Dann ist die erste Auswertung nicht korrekt, deshalb würde ich den Anfang der Funktion so ändern:

int encoderImpuls()
{
  static byte state=0;
  
/**/  if(!(state & 0xF)) {
/**/    if (digitalRead(ENCODER_A)) bitSet(state,1);  // Eingang A abfragen und Bit1 setzen
/**/    if (digitalRead(ENCODER_B)) bitSet(state,0);  // Eingang B abfragen und Bit0 setzen
/**/    return(9999);
/**/  }
  
  state= state<<2;    // Bits um zwei Stellen nach links schieben

Gruß,
Ralf

Schachmann:
irgendwann fängt die Funktion ja mal an zu laufen, z.B. wenn der Arduino eingeschaltet wird. Dann ist die erste Auswertung nicht korrekt, deshalb würde ich den Anfang der Funktion so ändern:

Deine Änderung verstehe ich nicht.

Ja, der Arduino-Sketch fängt irgendwann an zu laufen, und zu dem Zeitpunkt besteht eine nicht geringe Wahrscheinlichkeit, dass der letzte Status in der Funktion beim Programmstart falsch gesetzt ist.

Die Ausgänge von "rastenden" Drehgebern können zu Anfang entweder auf 00 oder 11 gesetzt sein. Das hängt davon ab, ob die Drehgeber-Ausgänge mit PullDown- oder PullUp-Widerständen beschaltet sind.

Ausgänge an nicht-rastendenden Drehgebern können bei Programmstart jeden beliebigen Wert aufweisen, egal ob mit PullUp- oder PullDown-Widerständen beschaltet, also 00, 01, 10 oder 11.

Damit es beim Programmstart mit der von mir geposteten Funktion nicht zu einem Fehlimpuls kommt, wenn die Funktion das erste mal ausgewertet wird und der Drehgeber sich gar nicht dreht, dachte ich es mir einfach so, dass im Setup die Funktion zunächst ein einziges mal aufgerufen wird, ohne den Rückgabewert weiter auszuwerten:

void setup()
{
  encoderImpuls(); // Initialisiert den Anfangszustand des Drehgebers
}

Dann ist für den weiteren Gebrauch mit meiner Funktion im Programm alles korrekt initialisiert:

  • Egal ob der Drehgeber mit PullUp- oder PullDown Widerständen betrieben wird
  • Egal ob er Raststellungen auf einem bestimmten Wert hat
  • oder ob es ein nicht-rastender Drehgeber ist
  • und mit beliebigen Anfangswerten der Eingänge beim Programmstart

Ich sehe da mit der von mir geposteten Funktion kein Problem.
Man muß eben nur den allerersten Funktionsaufruf machen, ohne versuchen zu wollen, schon diesen allerersten Funktionsaufruf im gerade gestarteten Programm einer Drehbewegung zuzuordnen. Die Zuordnung zu einer Drehbewegung ist erst ab dem zweiten (und nachfolgenden) Funktionsaufrufen korrekt.

Hallo Jurs,

klar Aufruf ohne das Ergebnis auszuwerten ist auch eine Möglichkeit.

Wenn Du es aber so machen würdest wie ich vorschlage, meldet Deine Funktion solange Fehler, bis erstmalig Bits geshiftet worden sind. Da Fehler nicht weiter ausgewertet werden sollen, würde sich die Funktion so quasi selbst einlaufen.

Gruß,
Ralf

Schachmann:
Wenn Du es aber so machen würdest wie ich vorschlage, meldet Deine Funktion solange Fehler, bis erstmalig Bits geshiftet worden sind. Da Fehler nicht weiter ausgewertet werden sollen, würde sich die Funktion so quasi selbst einlaufen.

Wenn ich es so mache, dann geht jede vierte Statusänderung verloren.
Pro vier Statusänderungen an den Eingängen werden dann immer nur drei Impulse gezählt.

Nein, so wie ich das sehe, läuft es sich ein. State kann nur solange 0 sein, bis einmal eine Zustandsänderung stattgefunden hat, dann wird entweder durch die Zustandsänderung mindestens ein Bit reingeshiftet oder aber es ist von der vorherigen Zustandsänderung mindestens ein Bit noch gesetzt. Probier mal folgenden Sketch:

// Pins für die Eingänge am Arduino festlegen
#define ENCODER_A 2
#define ENCODER_B 3

int encoderImpuls(unsigned char encoderA, unsigned char encoderB)
{
  static byte state=0;
  
  if(!(state & 0xF)) {
    if (encoderA) bitSet(state,1);  // Eingang A abfragen und Bit1 setzen
    if (encoderB) bitSet(state,0);  // Eingang B abfragen und Bit0 setzen
    return(9999);
  }
  
  state= state<<2;    // Bits um zwei Stellen nach links schieben
  if (encoderA) bitSet(state,1);  // Eingang A abfragen und Bit1 setzen
  if (encoderB) bitSet(state,0);  // Eingang B abfragen und Bit0 setzen
  state= state & 0xF;   // Nur die unteren 4 Bits behalten
  switch(state)
  {
    case 0b0000:
    case 0b0101:
    case 0b1010:
    case 0b1111: return 0; // keine Änderung an den Eingängen
    case 0b0001:
    case 0b0111:
    case 0b1110:
    case 0b1000: return -1; // links Impuls an den Eingängen
    case 0b0010:
    case 0b1011:
    case 0b1101:
    case 0b0100: return 1; // rechts Impuls an den Eingängen
    default: return 9999;  // Error (z.B. wegen verpaßter Impulse)
  }  
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  int state;
  
  state = encoderImpuls(0, 1);
  Serial.println(state);
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);
  state = encoderImpuls(0, 1);
  Serial.println(state);
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);
  
  
  delay(5000);
  Serial.println();  
}

Da ich keinen passenden Encoder hier habe, simuliere ich die Abfragewerte des Encoders - Drehung in eine Richtung. Wie Du siehst, läuft es sich ein.

Gruß,
Ralf

Schachmann:
Wie Du siehst, läuft es sich ein.

Dein Code läuft sich nur ein, wenn Du vorher schon weißt, dass sich die Eingänge zwischen zwei Aufrufen geändert haben und die Funktion nur dann aufrufst, wenn es auch eine Änderung gab.

Teste mal Deinen Code, wenn sich der Eingang nicht jedesmal, sondern nur jedes zweite mal ändert. Normalerweise sollte dann einmal 0 und einmal 1 bzw. -1 im Wechsel zurückgeliefert werden. Dein Code liefert zwischendurch aber auch immer wieder einen Errorcode zurück.

void loop() {
  int state;
    
  state = encoderImpuls(0, 1);
  Serial.println(state);
  state = encoderImpuls(0, 1);
  Serial.println(state);
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);
  
  delay(5000);
  Serial.println();  
}

Du hast zu einem Viertel Recht :wink:

Mit folgender Sequenz, Drehgeber wird gedreht, Drehen wird pausiert, Drehgeber wird weitergedreht:

  state = encoderImpuls(0, 1);
  Serial.println(state);
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);
  state = encoderImpuls(0, 1);
  Serial.println(state);
  
  state = encoderImpuls(0, 1);
  Serial.println(state);  
  state = encoderImpuls(0, 1);
  Serial.println(state);
  
  state = encoderImpuls(1, 1);
  Serial.println(state);
  state = encoderImpuls(1, 0);
  Serial.println(state);
  state = encoderImpuls(0, 0);
  Serial.println(state);

funktioniert es richtig. Wenn aber der Drehgeber zufällig auf 0/0 stehen bleibt und dann wiederholt abgefragt wird, wird 9999 zurückgemeldet.

Aber, siehst Du darin ein Problem? Ich denke im Programm bleibt es sich gleich, ob 0 gemeldet wird und das Programm nicht reagiert, weil ja keine gültige Eingabe gemacht wurde, oder ob 9999 gemeldet wird und das Programm nicht reagiert, weil ja keine gültige Eingabe gemacht wurde.

Ich stimme Dir aber zu, wenn man einen ersten Aufruf macht und das Ergebnis ignoriert und die von mir vorgeschlagenen Zeilen weglässt, erhält man auch an der oben beschriebenen Stelle 0 als Rückgabewert.

Gruß,
Ralf

Schachmann:
Aber, siehst Du darin ein Problem?

Die von Dir vorgenommene Modifizierung meines Codes funktioniert nur korrekt, wenn die Funktion bei jedem Aufruf mit einer anderen Stellung der Eingänge aufgerufen wird. Beispielsweise, wenn man beide Eingänge mit Interrupt-Routinen verbindet, so dass nur bei einer Änderung der Eingänge der Interrupt und damit Deine Routine aufgerufen wird.

Mein Code ist universell verwendbar, auch per Polling in der loop(), wenn man beispielsweise eine loop-Funktion macht, die 10000 mal pro Sekunde durchläuft und der Drehgeber der Spindel gibt nur 0 bis 1333 Impulse pro Sekunde, so dass jede Stellung des Drehgebers mindestens sechsmal abgelesen wird, bevor sie sich ändert.

Besser als mit dem Beispielcode aus meinem letzten Posting mit der Doppelabfrage jeder Stellung kann ich es auch nicht erklären.

Hallo zusammen,
habe heute die Hardware bekommen, 1x Arduino Uno, ein Drehgeber und zwei Bücher, Arduino für Anfänger.
@jurs
Ich komme gerne auf dein Angebot mit dem Beispielprogramm zurück, denn im Moment habe ich keinen Plan.
Das einzige was ich aus deinem Programm lesen kann, ist das der Drehgeber auf Pin 2,3 gehört.
Werde mich zuerst einlesen müssen.

Danke