Go Down

Topic: Anfänger will Drehgeber auslesen (Read 3 times) previous topic - next topic

jurs


nein, von Hand gedreht müssen 1000 Interrupts pro Sekunde reichen.


Ja, bei meinem Drehgeber auf jeden Fall: Ich brauche (grob geschätzt) mindestens 1/10 Sekunde, um die Drehachse halb zu drehen, entsprechend 40 Zählimpulsen. Macht eine Datenrate von max. 400/s, da reicht also eine Timerauflösung von 1000/s locker aus.

Im Gegensatz zum Anwendungsfall des OPs, der möchte ja mit einer Rate von mehr als 5000/s zählen.


Ich habe einige andere Dinge geändert. Wenn Du Muße hast, kannst Du den Sketch ja mal mit einem Deiner Geber probieren.


Ich habe Deinen Sketch mal geladen und ausprobiert.

Im Quellcode ist mir aufgefallen: Variablen, die sowohl in einer ISR als auch im normalen Programm verwendet werden, müssen "volatile" deklariert sein. Das trifft in Deinem Sketch auf die "schritt" Variable zu.

Außerdem habe ich den Code mal umgeschrieben auf die Ausgabe nach jedem Einzelschritt, das ist bei einer Serial-Baudrate von 115200 leicht möglich. Und ich persönlich finde die Anzeige zum Testen aussagekräftiger.

Mit den Änderungen
- fehlende "volatile" Deklaration nachgeholt (und umbenannt in "schrittISR")
- Baudrate 115200
- Ausgabe jeder Änderung nach jedem Einzelschritt auf seriellem Monitor
habe ich diesen Code getestet:
Code: [Select]

//
// STEC12E07 - Überarbeitete Version
// ---------------------------------
//
// Auswertung des ALPS STEC12E07 Drehimpulsgebers
// mit Arduino Diecimila oder Duemilanove mit
// ATMega168 oder ATMega328
//
// www.Elektronik-Bastelkeller.de / 09.2013
// Der Code darf unter Hinweis auf die Quelle
// weitergegeben und/oder verwendet werden.
//
// -----------------------------------------------------

// Globale Variablen
const int geberPin1 = 2; // An Pins DP2 und DP3 ist der
const int geberPin2 = 3; // Encoder angeschlossen
int signalA, signalB;
int geber, geberAlt;
int diff;
volatile int schrittISR;

//
// Initialisierungen
// -----------------
//
// Hier hauptsächlich der Timer-Interrupt
//
void setup(){
  noInterrupts();

  TIMSK1 &= ~(1<<TOIE0); // Für ATMega8 TIMSK verwenden

  TCCR1A = (0<<COM1A1) | (0<<COM1A0) | (0<<WGM11) | (0<<WGM10);
  TCCR1B = (0<<CS12) | (1<<CS11) | (1<<CS10);

  TIMSK1 |= (1<<TOIE1); // Für ATMega8 TIMSK verwenden

  TCNT1H = 0xFF; // Werte für 1000 Interrupts/s
  TCNT1L = 0x06;

  // Diese beiden Zeilen verwenden, wenn keine externen Pullup-Widerstände
  // vorhanden sind
  pinMode(geberPin1, INPUT_PULLUP);
  pinMode(geberPin2, INPUT_PULLUP);

  // ansonsten, diese beiden Zeilen:
  // pinMode(geberPin1, INPUT);
  // pinMode(geberPin2, INPUT);

  interrupts();

  Serial.begin(9600);
}


//
// Interrupt Service Routine wird 1000 mal
// pro Sekunde aufgerufen und aktualisiert
// die Variable schritt
//
ISR(TIMER1_OVF_vect) {
  TCNT1H = 0xFF;
  TCNT1L = 0x06;

  signalA = digitalRead(geberPin1);
  signalB = digitalRead(geberPin2);

  geber = 0;
  if(signalA == 0) geber = 3;
  if(signalB == 0) geber ^= 1;

  diff = geber - geberAlt;

  if(diff == -3 || diff == 1) schrittISR -= 1;
  if(diff == 3 || diff == -1) schrittISR += 1;

  if(diff & 1 == 1) {
    geberAlt = geber;
  }
}


//
// Hauptprogramm
// -------------
//
// Gibt den Wert von pos auf der seriellen
// Schnittstelle aus, falls der Encoder
// gedreht wurde
//
void loop(){
  static long gesamt=0;
  if(schrittISR != 0) {
    noInterrupts();          // Während der seriellen Übertragung ist der
    gesamt+=schrittISR;
    schrittISR = 0;             // werden Impulse des Drehgebers übersehen.
    interrupts();            // Interrupts wieder an.
    Serial.println(gesamt); // Interrupt ausgeschaltet. In dieser Zeit
  }
//  delay(200); // Entfernen, wenn Ihr Programm tatsächlich etwas macht
}


Funktioniert bei normalen Drehgeschwindigkeiten wunderbar.


Ich habe allerdings mittlerweile einen finsteren Verdacht. Und zwar folgendes: Wenn Dir wirklich der Puffer überläuft, halte ich es durchaus für möglich, dass Deine, wie Du schreibst "Billig-Ware aus China", dermaßen prellt, dass da eine ziemliche Menge unnötiger Impulse erzeugt werden. Ich habe da im Internet einige wirklich unschöne Oszilloskop-Aufnahmen gesehen.


Nein, in meinem Code polle ich ja, und wo es prellt, das kann ich ganz leicht erkennen, weil bei Einzelschrittdarstellung dann vor-rück-vor-rück-vor-rück etc. gezählt wird.
Es liegt schon an der Baudrate: 9600 Baud sind auch beim Drehen von Hand zu wenig, um alle Einzelschritte beim schnellen Drehen auf dem seriellen Monitor auszugeben. Mit 115200 Baud klappt es.

Prellen tritt bei meinem Drehgeber gelegentlich am Ende eines Drehvorgangs auf, wenn die Raststellung nicht genau eingerastet ist und ich die Drehachse leicht neben der Raststellung genau am Umschaltpunkt festhalte, so dass der Wert springt.

Meine China Drehgeber funktionieren schon ganz gut. Und die habe ich mir auch nur geholt, um mal für keine Gadgets einfache "Einknopf-LCD-Menüs" zu programmieren. Die Drehachse ist gleichzeitig noch ein Tastschalter. So dass man alleine mit diesem Drehgeber ein ganzes Einstellmenü basteln könnte:
- Achse eindrücken ==> Menü aktivieren
- Achse drehen ==> Einstellwert auswählen
- Achse nochmal eindrücken ==>  Einstellwert höher/tiefer einstellen
- Achse nochmal eindrücken ==> Einstellwert übernehmen
  (oder einige Sekunden warten ohne weitere Tastenbetätigung ==> Änderungen verwerfen)

Schachmann


Im Quellcode ist mir aufgefallen: Variablen, die sowohl in einer ISR als auch im normalen Programm verwendet werden, müssen "volatile" deklariert sein. Das trifft in Deinem Sketch auf die "schritt" Variable zu.


Stimmt, das habe ich bei diesem simplen Test-Sketch einfach vergessen. Allerdings muss ich jetzt der Vollständigkeit halber auch dazu schreiben, dass bei Integer-Variablen, die ja keinen atomaren Zugriff garantieren, es nicht ausreicht, sie nur volatile zu deklarieren, sondern sie müssen auch vor konkurrierenden Zugriffen durch die ISR geschützt werden. Ich werde mein Progrämmchen morgen nochmal überarbeiten. Danke für den Hinweis. Dann dürfte es ziemlich wasserdicht sein. Mal sehen, vielleicht portiere ich es dann incl. I²C-Interface auf einen ATTiny13, der dann als Slave einfach so einen Encoder überwachen kann und auf Anforderung vom Master die Position zurückliefert.

Die Taster-Funktion haben die Panasonic-Encoder übrigens auch und genau Dein war auch mein Beweggrund sie mal zu bestellen. Ich denke damit und mit einem LCD-Display könnte man ein durchaus ansprechendes und brauchbares Menüsystem auf die Beine stellen.

Gruß,
Ralf

PS: Je länger ich über die Idee mit dem Tiny nachdenke, desto besser finde ich sie. Es gibt zwar auch fertige Chips mit dieser Funktionalität auf dem Markt, aber entweder nur in Riesenstückzahlen oder zu unerschwinglichen Preisen. Bei der neuesten Generation brauchst Du nur an einer Achse einen Magneten über dem Chip zu rotieren und der erkennt mit Hall-Sensoren die Drehung und liefert die Daten fix und fertig aufbereitet zurück (die sind übrigens garnicht so teuer...).
Es gibt 10 Arten von Menschen, die einen verstehen Binär, die anderen nicht...

hansheiri

Hallo zusammen
habe mich jetzt an die Anzeige gemacht, ein LCD 16/2 Paralleler Anschluss.

Ich glaube dass das LCD zu langsam reagiert, schreibgeschwindigkeit zu langsam ist oder ich die falschen Befehle verwende.
Da manchmal es so scheint, dass Impulse verloren gehen.

Gibt es schnellere Warianten, Text auszugeben als mit oben erwähnter Methode?
Ist denn die Serielle Übertragung zum PC mit 9600baud (das funktioniert Super) schneller als Parallel?

Praktisch wäre es um einiges besser ein LCD oder der gleichen zu verwenden, da es ein mobiles Gerät werden soll.

Danke

Serenifly

LCDs sind generell langsam. Aber du musst es ja auch nicht jede Millisekunde updaten. Mach das einfach alle 500ms oder so.


Schachmann

Hallo,

es dürfte eigentlich egal sein, wie oft oder selten der TO das Display updatet (in vernünftigen Grenzen), wenn in dieser Zeit Impulse vom Drehgeber kommen, können Sie beim Polling verloren gehen. Beim Abfragen im Interrupt muss dieser aber auch oft genug kommen, um keine Impulse zu verlieren. Bei der von mir vorgeschlagenen Routine ist noch reichlich Platz nach oben für die Frequenz der Interrupt-Abfragen.

Da scheint sich jetzt das Problem zu manifestieren, welches ich schon angesprochen hatte: Wenn das Programm irgend etwas anderes tun muss, außer den Drehgeber pollen, wird es kritisch.

Gruß,
Ralf
Es gibt 10 Arten von Menschen, die einen verstehen Binär, die anderen nicht...

jurs


Ich glaube dass das LCD zu langsam reagiert, schreibgeschwindigkeit zu langsam ist oder ich die falschen Befehle verwende.
Da manchmal es so scheint, dass Impulse verloren gehen.


Ja, natürlich gehen beim Pollen des Drehgebers Impulse verloren, wenn Du versuchst, auf das LCD zu schreiben, während sich Dein Drehgeber dreht und vor allem mit der Geschwindigkeit, bei der die Zählimpulse bei Deinem Anwendungsfall kommen. LCDs sind lahm.

LCD.clear() ==> kostet so roundabout 2 ms
Eine LCD-Zeile mit 16 Zeichen vollschreiben ==> Auch so ca. 2 ms

Wenn Du also gemütlich eine LCD-Zeile in zwei Millisekunden vollschreibst und in den zwei Millisekunden kommt ein Impuls, dann geht der Impuls beim Pollen verloren.

Wenn Du nominell bei 4000 Zählimpulsen auf einer Umdrehung nun eine Umdrehung in 0,75 s machst, hast Du nominell 5333 Zählimpulse pro Sekunde, also 5,33 Impulse pro Millisekunde. In zwei Millisekunden gehen Dir 10 bis 11 Impulse verloren, während Du also z.B. eine LCD-Zeile vollschreibst.

Deshalb habe ich Dir doch extra einen Beispiel-Sketch mit Timeout-Erkennung gemacht:
Die Ausgabe erfolgt erst, wenn eine PAUSE/TIMEOUT erkannt wird, also dass sich eine bestimmte zeitlang nichts dreht.

Hast Du doch geschreiben: dreht sich - 200 ms Pause - dreht sich entgegengesetzt - 200 ms Pause.
In dem Beispiel-Sketch habe ich das Timeout auf 20 ms gesetzt. Erst wenn so lange kein Impuls kommt, erfolgt die Ausgabe des Zählwerts mit anschließender Rücksetzung auf Null.

Wenn Du gemütlich irgendwelche LCD-Ausgaben zu anderen Zeiten machen möchtest als in den 200ms-Drehpausen, irgendwo mitten im "dreht sich" irgendwelche Zählstände zwischendurch bei voller Drehgeschwindigkeit ausgeben, dann mußt Du so zählen wie Schachmann: Mit Zählung in einer Interrupt-Behandlungsroutine. Wobei die Interrupt-Behandlungsroutine eine deutlich höhere Interruptrate haben muss als die höchste auftretende Zählrate.

Schachmann hat Beispielcode gepostet für 1000 Interrupts pro Sekunde. Das ist für 5333 Zählimpulse pro Sekunde viel zu wenig. Wenn Du das wirklich so machen möchtest statt mit Timeout-Erkennung und Zählstandausgabe in den Drehpausen, müßtest Du die Interruptrate des Timers auf deutlich über 5333 pushen. Z.B. 8000 oder noch besser 16000 Interrupts pro Sekunde. Dann ginge es mit Timer und Timer-Interrupts.

Also entweder Drehgeber-Auswertung nach "jurs":
- Drehgebereingänge pollen, wenn keine Impulse kommen ("Timeout") ==> LCD aktualisieren

Oder Drehgeber-Auswertung nach "Schachmann":
- Drehgebereingänge durch Timer-Interrupts auswerten, LCD zu beliebigen Zeiten aktualisieren

Ich war der Meinung, Du brauchst immer die Werte, wenn der Drehgeber stillsteht.
Aber jetzt willst Du offenbar die Werte aufs LCD bringen, wenn der Drehgeber gerade am Drehen ist.

Schachmann


Oder Drehgeber-Auswertung nach "Schachmann":


Wow, jurs, das hört sich ja so schön an wie: "Bachblütentherapie nach Krämer" oder "Theorie der schwarzen Löcher nach Hawking"  XD

Aber ernsthaft, ich hatte gar nicht mehr auf dem Schirm, dass es auch Drehpausen gibt. Dann kann der TO ja auch eigentlich weiter pollen - vor allem, wenn Du das schon vorgesehen hattest.

Gruß,
Ralf
Es gibt 10 Arten von Menschen, die einen verstehen Binär, die anderen nicht...

jurs



Oder Drehgeber-Auswertung nach "Schachmann":


Wow, jurs, das hört sich ja so schön an wie: "Bachblütentherapie nach Krämer" oder "Theorie der schwarzen Löcher nach Hawking"  XD


Auch ein Stephen Hawking hat seine Prominenz ja nur dadurch, dass er von anderen zitiert wird.  ;)


Aber ernsthaft, ich hatte gar nicht mehr auf dem Schirm, dass es auch Drehpausen gibt. Dann kann der TO ja auch eigentlich weiter pollen - vor allem, wenn Du das schon vorgesehen hattest.


Wann gepollt werden kann und wann Interrupts verwendet werden müssen, hatte ich gleich in meiner ersten Antwort in diesem Thread erklärt.

hansheiri müßte jetzt nur mal damit rüberkommen, was denn in meinem Beispielsketch aus Beitrag http://forum.arduino.cc/index.php?topic=189578.msg1405271#msg1405271 tatsächlich nicht funktioniert, wenn er wissen möchte, wie er gegensteuern kann.

In Frage kommen folgende Problemfelder:

a) Die Spindel "zittert" und löst auch beim Stillstand ständig Zählimpulse aus, z.B. ständig links-rechts-links-rechts Zählungen, ohne dass überhaupt eine Pause von 20ms ohne einen einzigen Zählimpuls auftritt. Dann tritt bei dem geposteten Code überhaupt kein "Timeout" auf, und in der loop() Funktion wird niemals irgendein Wert angezeigt. In dem Fall müßte die Funktion impulsAuswertung() geändert werden. Z.B. so, dass der Timeout-Zähler nicht bei jedem Zählimpuls neu startet, sondern beispielsweise nur dann, wenn der nachfolgende Zählimpuls in dieselbe Richtung zählt wie der vorangegangene. Damit würde "Zittern im Stillstand" mit ständigen links-rechts Zählungen wirkungsvoll unterdrückt und es tritt in dem Fall trotzdem ein Timeout auf.

b) Oder das Timeout wird zwar ausgelöst, tritt aber in der Stillstandsphase mehrfach auf. D.h. die Zählung kommt zwar für 20ms zum Stillstand, ein Wert wird ausgegeben. Dann tickt der Drehgeber doch noch um eins nach links oder rechts, wieder tritt ein Timeout auf und es wird gleich nach dem "richtigen" Wert nochmal (ein oder mehrmals) eine "1" als Anzahl der gezählten Impulse ausgegeben. Das ließe sich einfach in der loop()-Funktion unterdrücken, indem man z.B. vorgibt, dass nur Zählungen von mehr als 2 Impulsen ausgegeben werden sollen:
Code: [Select]

void loop() {
  if (!impulsAuswertung()) return;
  if (impulseRechts>impulseLinks+2)
  {
     Serial.print("R: ");Serial.println(impulseRechts-impulseLinks);
     // LCD.print(....
  }
  else if (impulseLinks>impulseRechts+2)
  {
     Serial.print("L: ");Serial.println(impulseLinks-impulseRechts);
     // LCD.print(....
  }
  if (impulseError>0)
  {
     Serial.print("E: ");Serial.println(impulseError);
  }
  impulseRechts=0;
  impulseLinks=0;
  impulseError=0;
}


Falls aber Fall a) vorliegt und gar nichts ausgegeben wird (weil Timeouts beim Drehen nicht erkannt werden), ist die Änderung an der Funktion  impulsAuswertung() notwendig, in der die Timeout-Erkennung verbessert werden müßte.

hansheiri

Hallo zusammen
Konnte den Drehgeber noch nicht an seinen Bestimmungsort einbauen, da dies Vorrichtungen braucht die ich noch nicht habe.
Also habe ich bis anhin von Hand gedreht.

Habe heute die Maschiene nochmal genauer beobachten können, es scheint längere Pausen zu geben als ich Simuliert habe.
Daher könnte es mit dem LCD evtl. doch funktionieren.

Ausserdem habe ich vermutlich unnötig viele Komandos verwendet.

Ich bin mit dem Beispielcode sehr zufrieden.
Werde ihn soweit nötig und möglich anpassen und hier rein stellen.

Danke

Go Up