Drehgeber mit Arduino Uno auslesen

Guten Tag!

Ich soll ein Projekt machen mit dem Uno (Ethernet) zum Auslesen eines Drehgebers.
Die Bezeichnung lautet:

Baumer BHE 03.24G360-6-1
360lmp/U
3,6378lmp/mm
d=31,5mm
1m/min=60,63Hz

Nun zu meinem Problem. Wenn ich den Drehgeber sagen wir mal mit unter 8m/min bewege (egal in welche Richtung) dann habe ich ein relativ starkes "Prellen". Wenn ich ihn jedoch ziemlich schnell drehe (ab 25m/min) dann habe ich zb bei einer Rechtsdrehung nur noch jeden 20 Wert falsch (also anstatt rechts links).
Teilweise sind bei schnellen Richtungswechseln auch Werte von 3100m/min dabei was überhaupt nicht stimmt.
Von was kann das kommen?

Kann man das irgendwie per Software lösen?

Hier das Programm:

float Azeit=0, Bzeit=0, Zeit=0, meter=0, alt, Astatus, Bstatus;

void setup() 
{
  Serial.begin(57600);
  attachInterrupt(0, A, CHANGE);
  attachInterrupt(1, B, CHANGE);
}

void loop()  
{
  if(Astatus == 1 && Bstatus == 1)
  {
      if(Azeit < Bzeit)
      {
          Zeit=Bzeit-Azeit;
          meter=(1000000*60*0.3025)/(Zeit*4*360);
          alt=Azeit;          
          
          Serial.print("Links  ");
          Serial.print(meter);
          Serial.println(" m/min ");
      }
      
      else if(Bzeit < Azeit)
      {
          Zeit=Azeit-Bzeit;
          meter=(1000000*60*0.3025)/(Zeit*4*360);
          alt=Azeit;  
      
          Serial.print("Rechts  ");
          Serial.print(meter);
          Serial.println(" m/min ");

      }
  
      if (alt == Azeit)
      {
          Azeit=0;
          Bzeit=0;  
    }
  }
}

void A(){
  Azeit = micros();
  Astatus = digitalRead(2);
}

void B(){
  Bzeit = micros(); 
  Bstatus = digitalRead(3);
}

Um welchen Drehgeber handelt es sich? Google findet zu dem Typ nichts. Für sowas sollte man optische Encoder (nicht mechanische) nutzen. Keine Ahnung, was deiner ist.

Des weiteren verbraucht dein Sketch doch recht viel Rechenzeit mit den floats und der seriellen Ausgabe.

https://www.mikrocontroller.net/articles/Drehgeber

pirflo:
Kann man das irgendwie per Software lösen?

Erstmal ins Datenblatt schauen:
Ist es ein mechanischer Drehgeber mit prellenden mechanischen Schleifkontakten?
Oder ist es ein opto-elektronischer Drehgeber mit eingebauter Auswerteelektronik ohne Prellen?

Ich habe einen opto-elektronischen Drehgeber.
Anscheinend ist der schon so alt das man nichts mehr davon findet :smiley:
Dieser hier ist auf jeden fall der Nachfolger:
http://pfinder.baumer.com/pfinder_motion/downloads/Produkte/PDF/Datenblatt/Drehgeber/Baumer_BHK_DS_DE.pdf

Von den Daten her (Strom,Spannung und Belegung der Drähte) ident.

@sschultewolter die Seite hab ich mir auch schon angeschaut jedoch werde ich aus dem Sketch nicht recht schlau (vielleicht deswegen weil ich mit programmieren null Erfahrung hab).

pirflo:
Ich habe einen opto-elektronischen Drehgeber.
Anscheinend ist der schon so alt das man nichts mehr davon findet :smiley:

So, dann hast Du also einen opto-elektronischen Drehgeber mit nicht prellendem Signal und 360 Impulsen pro Umdrehung.

Und wieviele Umdrehungen pro Sekunde macht das Ding nun maximal, damit Du mal überschlagen kannst, auf welche Rate "Impulse pro Sekunde" Du maximal kommst, die Deine Software auswerten können muss?

Hallo, ich hätte kleine Änderungsvorschläge:

float meter = 0;
unsigned long Azeit = 0, Bzeit = 0, Zeit = 0, alt;
bool Astatus, Bstatus;

...

meter = 45375 / (Zeit * 3.6);

Was passiert eigentlich beim Überlauf von micros()?

digitalRead() verplempert unglaublich viele Takte.
Das will man in einer ISR nicht.

bool Astatus, Bstatus;

Richtig!

Aber auch nur die halbe Miete.

Die Variablen, welche von der ISR und im Loop verwendet werden, müssen als volatile deklariert werden.
Und: Das Auslesen dieser Variablen im Loop muss atomic werden.
Bis dahin ist das ein Zufallsgenerator.

Die micros() messen die Zeit die ich später zur Berechnung von "Zeit=Azeit-Bzeit" oder umgekehrt nehme.

Wie meinst du das mit "atomic"?

Ich habe bis jetzt herausgefunden das ich am besten eine "Entprellschaltung" auf die Eingänge vom Arduino lege, da ich den Drehgeber mit 12V versorge der Uno jedoch sowieso nur 5V aushält kann ich ja gleich aus dem Spannungsteiler ein R-C Glied mit (oder ohne?!?) Schmitttrigger machen.

Er wird für einen Drahtvorschub verwendet der maximal 30m/min macht.
Das wären ja dann ca. 5 Umdrehungen/s (wenn ich mich nicht verrechnet hab).

Bitte korrigiert mich wenn ich jetzt falsch liege.

Mfg

google: "arduino volatile atomic"

"Entprellschaltung"

Erst um volatile und atomic kümmern.
Denn:

Bis dahin ist das ein Zufallsgenerator.

Da kannst du tausende von Kondensatoren einsetzen.

Ein optischer Sensor, welcher "prellt"?
Da ist Dreck drin!

Du musst beim Zugriff auf 16 und 32 Bit Variablen die Interrupts sperren. Sonst kann dir während dem Auslesen ein Interrupt dazwischenfunken und die Variable ändern die du gerade ausliest.

Das geht entweder per Hand mit cli()/noInterrupts() und sei()/interrupts() oder so:
http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

Auf digitalRead() verzichten macht das auch ca. 20mal so schnell. Am einfachsten hiermit:
https://code.google.com/p/digitalwritefast/downloads/list

Ok Danke schonmal bis jetzt werde mir das Morgen alles mal genauer anschauen.

Und wie gesagt volatile nicht vergessen bei Variablen die innerhalb und außerhalb von ISRs verwendet werden:
https://www.arduino.cc/en/Reference/Volatile

Serenifly:
Du musst beim Zugriff auf 16 und 32 Bit Variablen die Interrupts sperren. Sonst kann dir während dem Auslesen ein Interrupt dazwischenfunken und die Variable ändern die du gerade ausliest.

Das betrifft nicht nur einzelne Variablen, sondern auch logisch zusammenhängende.
Hier Astatus, Azeit, Bstatus, und Bzeit,
Gerade eine ausgelesen, zupp, hat sich die andere heimlich geändert.

Ich verstehe das nicht so ganz mit dem "atomic". NoInterrupts bewirkt das selbe oder? Wären die nicht leichter?

Und Volatile muss ich eigentlich nur oben setzen sehe ich das richtig:
volatile unsigned long Azeit = 0, Bzeit = 0, Zeit = 0, alt;
volatile bool Astatus, Bstatus;

NoInterrupts bewirkt das selbe oder?

Nein!

NoInterrupts schaltet die Interrupts ab, ohne Rücksicht drauf, ob sie aktiviert waren oder nicht.
Ebenso schaltet Interrupts sie Rücksichtslos wieder an.
Nach mir die Sintflut... auf ins nächste Problem... Abenteuer pur...

Wären die nicht leichter?

Leichter als was?

Leichter als dieses?

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{ 
  // hier tuwas mit deinen auf volatile gesetzten Variablen

  // ! Halte diesen Anweisungsblock schlank !
}

NoInterrupts() und der ATOMIC_BLOCK Makro sind im wesentlichen dasselbe.
{ Mit der Klammer Syntax finde ich ATOMIC_BLOCK hübscher }

Care should be taken that ATOMIC_BLOCK(ATOMIC_FORCEON) is only used when it is known that interrupts are enabled before the block's execution

Wenn du es in loop machst und sonst keine Fehler machst, kannst du davon ausgehen, dass die Interrupts vorher enabled waren, und nach Interrupts() wieder enabled sind, wie es sein soll.
Das "Abenteuer" hält sich also in Grenzen.
ATOMIC_FORCEON und wohl auch noInterrupts() braucht ein Byte weniger RAM.

Das wahre Abenteuer liegt darin, sich gar nicht um volatile und noInterrupts zu kümmern und nie ein Problem zu bemerken 8)

pirflo:
Das wären ja dann ca. 5 Umdrehungen/s (wenn ich mich nicht verrechnet hab).

Also 5 Umdrehungen pro Sekunde bei 360 Full-Steps pro Umdrehung = 1800 Full-Steps pro Sekunde.

Jedes Signal A und B wechselt pro Schritt einmal HIGH und einmal LOW, also bekommst Du bis zu
1800 Full-Steps * 4 = 7200 Quarter-Steps bzw. 7200 Interrupts pro Sekunde

Das ist nicht wenig.

Bei einer Baudrate von 57600 kannst Du pro Sekunde über Serial 5760 Bytes pro Sekunde senden.

Wenn Du nun im Code nach jedem vierten Interrupt

         Serial.print("Rechts  ");
          Serial.print(meter);
          Serial.println(" m/min ");

an die 15 Zeichen auf Serial ausgibst, dann dauert das jedesmal: 15/5760= ca. 2,6 Millisekunden

Du kannst nicht 1800 mal pro Sekunde eine Ausgabe an Serial senden, die 2,6 Millisekunden dauert, dann müßte eine Sekunde bei Dir 1800*2,6= 4680 Millisekunden haben. Das passt nicht, denn eine Sekunde hat nur 1000 Millisekunden, und außerdem soll Dein Programm ja noch was anderes machen als Ausgaben auf Serial.

Mit Deiner Programmlogik stimmt also irgendwas nicht.

Was möchtest Du denn genau haben?

Irgendwie sieht das so aus, als wenn Dich nur die Geschwindigkeit interessiert?
Ist das so?
Oder interessiert auch die Summe des Drehwinkels insgesamt?

Läuft der Drehgeber immer nur in eine Drehrichtung?
Oder interessieren Dich auch Drehrichtungswechsel, die genau erfaßt werden sollen?

Auf jeden Fall müßtest Du die Anzahl der an Serial gesendeten Zeichen reduzieren. Du darfst bei 57600 Baud nicht versuchen, mehr als 5760 Bytes pro Sekunde zu senden. Also 576 Zeichen pro Zehntelsekunde, 57 Zeichen pro Hundertstelsekunde oder 5 Zeichen pro Tausendstelsekunde. Oder eine höhere Baudrate verwenden, wenn Du mehr Zeichen pro Sekunde auf Serial senden möchtest.

Auf dem Arduino kann man

michael_x:
Wenn du es in loop machst und sonst keine Fehler machst, kannst du davon ausgehen, dass die Interrupts vorher enabled waren, und nach Interrupts() wieder enabled sind, wie es sein soll.

Zustimmung. Auf dem Arduino kann man sich in den meisten Fällen darauf verlassen, dass die Interrupts außerhalb von ISRs aktiviert sind. Und kann dann auch ATOMIC_FORCEON oder noInterrupts()/interrupts() verwenden. ATOMIC_RESTORESTORE ist natürlich sauberer, aber nicht zwingend nötig.

Als Ausnahme fällt vielleicht FastLED ein, welches die Interrupts generell deaktiviert.

ATOMIC_RESTORESTORE ist natürlich sauberer, aber nicht zwingend nötig.

Sofern man nicht zu 100% weiß, was man tut, ist es der sicherste Weg.
Und die 100% sehe ich hier noch nicht.

Randbemerkung:
Diese Fehler, die dann auftreten "könnten", sind wohl die am schwersten zu findenden. Denn der Code sieht korrekt aus.

Aber, was soll ich hier groß argumentieren ...
Da muss halt jeder selber durch und eigene Erfahrungen machen, und dann daraus lernen.
(hoffentlich das richtige)