Probleme bei Encoderauswertung ARDUINO MICRO

Hallo Community,

auch ich bin neu hier ich brache, wie nicht anders zu erwarten eure Hilfe. Ich arbeite erst seit ein paar Wochen mit dem Arduino Micro, bin also ein absuluter Neuling in der Welt der Microcontroller.

Ich habe folgendes Problem:
Ich möchte ein Encodersignal eines Drehgebers auswerten. Hierzu habe ich auch relativ schnell eine fertige libary gefunden und das ganze mal ausprobiert.

Encoder library
http://www.pjrc.com/teensy/td_libs_Encoder.html
Programm 'Basic'

das ganze funktioniert alerdings nur bei eher geringen Drehzahlen. Bei höheren Drehzahlen verzählt sich das System.
Also habe ich mir gedacht, kann ja nicht so schwer sein, da selbst schnell einen Programmablauf zu schreiben... doch siehe da :smiley: auch hier verzählt sich das Board. Hier einmal mein Code:

void setup() {

  Serial.begin(9600);
  
}
int sig_1_old = digitalRead(4);
int sig_2_old = digitalRead(6);
long pos = 0;

void loop() {

 int sig_1 = digitalRead(4);
 int sig_2 = digitalRead(6);

 if (sig_1 == sig_1_old && sig_2 == sig_2_old)
 {}
 else
 {
  if (sig_1 == 0 && sig_2 == 0 && sig_1_old == 0 && sig_2_old == 1)
  {
    pos = pos + 1;
    Serial.println({pos});
  }
  if (sig_1 == 0 && sig_2 == 0 && sig_1_old == 1 && sig_2_old == 0)
  {
    pos = pos - 1;
    Serial.println({pos});
  }
  }
 sig_1_old = sig_1;
 sig_2_old = sig_2; 
 }

Mein Drehgeber ist ein 05.2400.1222.0100 von der Firma Kübler
Das Ding hat 100 Rastpunkte, somit 400 Schaltwechsel auf den beiden Kanälen.
Ich möchte den mit maximal 10 Umdrehungen pro Sekunde betreiben.

Nur leider verpassen die bisher ausprobierten Varianten zwischenzeitlich den HIGH - LOW wechsel, was zu Fehlern bei der Position führt, die Kanäle also nicht schnell genug abgetastet werden. Habt ihr eine Idee wodran das liegen könnte?
Ich hab schon recht viel gelesen, und das was sich so rauskristallisiert hat ist, dass man Encodersignale mit Timer Interrupts und fester Frequenz abtasten sollte. Hier habe ich schon einige sachen ausprobiert, bin jedoch zu keiner Verbesserung gekommen. Auch hab ich gelesen, dass der Befehl "digitalread()" (oder viel mehr alle Befehle aus den Arduino libraries) recht langsam arbeitet und man hier auf anderem Wege evtl etwas mehr Zeit raushohlen kann.

Der Hintergrund des Ganzen:
Der Sensor soll genutzt werden, um den Lenkradwinkel in einem Auto zu ermitteln. Hierfür wird am Lenkrad ein Ring montiert, über den der Sensor laufen kann. Eine Lenkradumdrehung führt also zu 14 Sensorumdrehungen.
Desswegen ist es hier recht wichtig, richtig vorwärts und rückwärts zu zählen und dies auch bei schnellen Lenkimpulsen, um die Lenkradposition genau zu ermitteln.

Bin jetzt nicht so der Encoder Held...
Und KA, was ein "kübler 35.2400.1222.0100" sein soll...
Aber was solls...
Prellen und Nutzsignale sind nicht so leicht zu unterscheiden, wie man vielleicht glaubt.
Gerade bei hohen Drehzahlen.

Wenn es WIRKLICH auf die Position ankommt, dann wäre vielleicht ein etwas teureres optisches System angesagt.
Diese prellen nicht.

Prellen wird vor allem umso problematischer werden je schneller du versuchst auszulesen

Das Lesen von Pins 4 und 6 auf dem Micro geht so schneller:

byte sig_1_old = bitRead(PIND, 4);   //Pin D4 = Port D, Bit 4
byte sig_2_old = bitRead(PIND, 7);   //Pin D6 = Port D, Bit 7

PINx ist das Pin-Eingangs-Register

Siehe hier für die Pin-Belegung:
http://pighixxx.com/micropdf.pdf

EDIT: War ist bei den Bezeichnungen verrutscht. Die Pin-Namen stehen oben :frowning:

Für die Auswertung per Timer gibt es hier guten Code:
https://www.mikrocontroller.net/articles/Drehgeber#Dekoder_f.C3.BCr_Drehgeber_mit_wackeligen_Rastpunkten
Ist nicht schwer das für den Arduino anzupassen

hansi_rulz:
Also habe ich mir gedacht, kann ja nicht so schwer sein, da selbst schnell einen Programmablauf zu schreiben... doch siehe da :smiley: auch hier verzählt sich das Board.

Bei hohen Zählraten ballerst Du den seriellen Ausgangspuffer bis Anschlag mit Zeichen voll und bremst dann den gesamten Sketch aus:

Serial.println({pos}); // bei 9600 Baud maximal 960 Zeichen/s

Wenn Du dann z.B. einen dreistelligen Zählerstand plus zwei Zeilenendzeichen (CR/LF) senden möchtest, und der serielle Ausgangspuffer ist bereits voll, dann dauert das Senden tatsächlich ca. 5 Millisekunden, bis die Loop-Funktion wieder läuft, das ist viel zu lange.

Versuche mal, das Senden zu verzögern,

  • wenn schnelle Drehungen kurz nacheinander erfolgen und
  • die letzte Position erst wenige Mikrosekunden vorher gesendet wurde

Also Pseudologik zum Senden über Serial ungefähr:

  if (sig_1 == 0 && sig_2 == 0 && sig_1_old == 0 && sig_2_old == 1) pos = pos + 1;
  if (sig_1 == 0 && sig_2 == 0 && sig_1_old == 1 && sig_2_old == 0) pos = pos - 1;
    
  if (pos!=lastSentPos && micros()-lastSentTime>=10000) // nur alle 10 Millisekunden senden
 {
    Serial.println(pos);
    lastSentPos=pos;
    lastSentTime=micros();
  }

Wenn "Serial" der Flaschenhals bei Deiner Anwendung ist, dann ist es ein Irrglaube, dass Du irgendwas damit besonders schnell machen kannst, indem Du möglichst viel in den Flaschenhals hineinstopfst.

Großartig!
Danke für eure Vorschläge, ich werde das mal am Montag einbauen und gucken wie es dann ausschaut.

combie:
Und KA, was ein "kübler 35.2400.1222.0100" sein soll...

mein Fehler, muss natürlich kübler 05.2400.1222.0100 heißen :slight_smile:

also scheint es ja wirklich der Fall zu sein, dass das Arduino hier "zeitlich an seine Grenzen stößt?

hansi_rulz:
also scheint es ja wirklich der Fall zu sein, dass das Arduino hier "zeitlich an seine Grenzen stößt?

Beachte dass was jurs sagt. Daran hätte ich auch denken sollen

Wenn du den seriellen Ausgangspuffer nicht blockierst bekommst du mit der Zeit keine Probleme. Dann sollte auch das normale digitalRead() reichen. Das dauert etwa 4µs, aber es reicht wahrscheinlich auch wenn du alle 1-2ms abfragst. Das ist also wesentlich kürzer.

So schnell wie du die Daten ausgibst kannst du sowieso nicht lesen. Außerdem kann man zusätzlich auch die Baudrate erhöhen.

hansi_rulz:
Großartig!
Danke für eure Vorschläge, ich werde das mal am Montag einbauen und gucken wie es dann ausschaut.

mein Fehler, muss natürlich kübler 05.2400.1222.0100 heißen :slight_smile:

also scheint es ja wirklich der Fall zu sein, dass das Arduino hier "zeitlich an seine Grenzen stößt?

Der Geber....!
Der ist perfekt.
Kein prellen.
Keine Probleme.
Keine Schrittverluste.
(wenn man es richtig macht)

Höre auf die anderen!
Gib jede Sekunde die Position aus, das reicht (zum testen)....

hansi_rulz:
also scheint es ja wirklich der Fall zu sein, dass das Arduino hier "zeitlich an seine Grenzen stößt?

Nein. Du stößt mit "Serial.begin(9600);" an die Grenze.

Bei 9600 Baud kannst Du nur 960 Zeichen pro Sekunde über die serielle Schnittstelle senden.

Bei vorgegebener Baudrate passen nur eine bestimmte Anzahl Zeichen pro Zeit durch die Schnittstelle. Wenn Du versuchst, mehr Zeichen zu senden, dann blockierst Du diese Schnittstelle.

Sendest Du weniger, bleibt die Schnittstelle "verstopfungsfrei" und blockiert nicht.

Wenn die Ausgabe auf dem seriellen Monitor der Arduino-IDE erfolgen soll, kannst Du die Baudrate bis 115200 Baud hochsetzen. Wenn Du auf dem PC ein richtiges serielles Terminalprogramm verwendest, könntest Du auch 250000 oder 500000 Baud verwenden. Dann passen mehr Zeichen durch die Schnittstelle, ohne dass es zum Sendestau kommt.

Das Langsame an Deinem oben gezeigten Programm sind nicht die 16 Millionen Takte des Atmega-Controllers, sondern das sind die 9600 Bits/s (=960 Bytes/s) pro Sekunde auf der seriellen Schnittstelle, mit der Du Dein Programm ausbremst.

Hallo Leute,

hat bei mir leider etwas länger gedauert, aber ich wollte doch zumindest noch einmal von mir hören lassen.

Konnte das Projekt mit eurer Hilfe jetzt ganz gut abschließen. Das ausgewertete Zählersignal wird nun mit Hilfe eines DAC's (Adafruit MCP4725) als analoger Spannungswert ausgegeben, um dann von einem anderen Messsystem weiterverarbeitet zu werden (hier war die Auswertung des Encodersignales nicht möglich, daher der Umweg über das Arduino)

Ausgelesen wird jetzt über Timer Interrupts. Hierbei hat mir diese Webseite hier:

ganz gut weitergeholfen.

Auch werden jetzt alle VIER Kanäle des Sensors verarbeitet (Ich denke das heißt dann Differenzschaltung?)

Und falls das noch jmd anders in Zukunft gebrauchen kann, hier noch einmal der Programmcode:

// Auswertung Inkrementalgeber durch Timer Interrupts
// und analoge Ausgabe über Adafruit MCP4725
// Kommunikation durch I2C
// Sensor mit Differenzschaltung 
// fuer ARDUINO MICRO

#include <Wire.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;

void setup() 
{
  dac.begin(0x62);  //Kommunikationsbeginn I2C
  
  cli(); // disable interrupts
  
  // reset
  TCCR1A = 0; // set TCCR1A register to 0
  TCCR1B = 0; // set TCCR1B register to 0
  TCNT1  = 0; // reset counter value
  
  OCR1A = 19; // compare match register

  // set prescaler
  TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);   
  
  TCCR1B |= (1 << WGM12); // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  
  sei(); // allow interrupts
  
  
  Serial.begin(9600); // start serial connection
}
// Sensorstellung vor Beginn auslesen
byte sig_1_old = bitRead(PINB, 4);    // Pin  8
byte sig_2_old = bitRead(PINB, 5);    // Pin  9
byte sig_3_old = bitRead(PINB, 6);    // Pin 10
byte sig_4_old = bitRead(PINB, 7);    // Pin 11

// Anfangssignale definieren (wg Differenzschlatung)
int sig_a_old = sig_1_old - sig_2_old;
int sig_b_old = sig_3_old - sig_4_old;

// weitere Werte
long pos = 0;
long lastSentPos = 0;
long lastSentTime = 0;
const int scale = 4095;
int factor = 1.5;

ISR(TIMER1_COMPA_vect)  // Interrupt Funktion Sensor Auswertung Timer1
{
  byte sig_1 = bitRead(PINB, 4);    // Read digital Pin  8
  byte sig_2 = bitRead(PINB, 5);    // Read digital Pin  9
  byte sig_3 = bitRead(PINB, 6);    // Read digital Pin 10
  byte sig_4 = bitRead(PINB, 7);    // Read digital Pin 11

  int sig_a = sig_1 - sig_2;
  int sig_b = sig_3 - sig_4;

  if (sig_a == sig_a_old && sig_b == sig_b_old)
    {}
    else
    {
      if (sig_a == -1 && sig_b == 1 && sig_a_old == -1 && sig_b_old == -1)
      {
        pos = pos + 1;
      }

      if (sig_a == -1 && sig_b == 1 && sig_a_old == 1 && sig_b_old == 1)
      {
        pos = pos - 1;
       }
    }
    
  sig_a_old = sig_a;
  sig_b_old = sig_b;
}

void loop() 
{
  
  // Spannungswert ausgeben
  if (pos/factor < -scale/2) 
  {
    dac.setVoltage(0, false);
   }
   
  if (pos/factor > scale/2)
  {
    dac.setVoltage(scale, false);
  }
    
    else 
    {    
      dac.setVoltage(scale/2 + pos/factor, false);
    }
    
  // Kontrollausgabe Serieller Monitor   
  if (pos!=lastSentPos && millis()-lastSentTime>=1000) // nur jede Sekunde  senden
  {
    Serial.println(pos);
    lastSentPos = pos;
    lastSentTime += 1000;
  }
}

Danke für eure Hilfe!

Grüße
Hansi