Encoder - Relativzähler ändern ist fehlerhaft

Hallo,

Vorweg, ich nutzte den Encoder Code von Peter D.
https://www.mikrocontroller.net/articles/Drehgeber
Soweit so gut, funktioniert auch. Mittlerweile habe ich einen ALPS EM20B Handdrehencoder der 4 Steps pro Rastung macht.
https://www.alps.com/prod/info/E/HTML/Encoder/Incremental/EM20B/EM20B_list.html
Der prellt auch nicht dank Hallsensoren. Auch mit Datalogger überprüft, alles bestens. Ich kann vorwärts und rückwärts drehen, der "Absolutzähler" macht pro Rastung im Sketch genau +1 oder -1.

Dann habe ich einen Relativzähler hinzugefügt den ich bei Bedarf nullen kann oder auf einen anderen Wert je nach Anforderung. Dieser Relativzähler soll aktuell nur zwischen 0 und 30 zählen. Von 31 auf 0 springen und von -1 auf 30 springen. Je nach Drehrichtung.

Jetzt zu meinem Problem. Die Korrektur von 31 auf 0 klappt immer. Jedoch von -1 auf 30 klappt nicht. Er geht immer auf 29 statt 30. Ich weiß zum Henker nicht warum. Nur äußerst selten klappt es von -1 auf 30. Phasen vertauschen bringt nichts, Problem bleibt genauso bestehen nur das ich anders herum drehen muss. Diverse Debugausgaben sind alle okay. Die Phasensumme (0-3) ist auch okay. Der Absolutzähler funktioniert auch einwandfrei.

Ist meine Korrektor setRelCounter falsch?
Übersehe ich etwas in der Encoder Klasse?
Hat jemand irgendeine Idee?

Uno Pinout http://www.pighixxx.net/portfolio-items/uno/?portfolioID=314
Mega2560 Pinout http://www.pighixxx.net/portfolio-items/mega/?portfolioID=314

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    long enc_delta;
    int last;
    volatile long rel_counter;  // Zähler
    volatile long abs_counter;  // Zähler
    int direction;              // Richtungsanzeige  +1 / -1
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase


  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;          
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;           
      if ( Phase_B ) n ^= 1;          // convert gray to binary
      
      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (long) (diff & 2) - 1;      // bit 1 = direction (+/-), Zähler
        abs_counter += enc_delta;
        rel_counter += enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }
    }

    int getDirection()  {
      return direction;  // Richtungsabfrage
    }
};


// Klasse "ALPS_EM20B" erbt von "GrayEncoder"
class ALPS_EM20B : public GrayEncoder
{
  public:               // von außen zugängliche Elemente ...
                        //            Phasen vertauscht
    ALPS_EM20B(volatile byte *p_Port, byte _B, byte _A) : GrayEncoder (p_Port, _A, _B)
    { }
    void setRelCounter( long value ) {  // relativen Zählwert ändern
      rel_counter = value << 2;         // 4 Steps pro Rastung
    }

    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter >> 2;          // 
    }

    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter >> 2;          // 4 Steps pro Rastung
    }
};


ALPS_EM20B Encoder1 (&PINC, 1, 0);      // Phase A/B, Pin 36/37 > Port.C Bit.1 / Bit.0
int Encoder1_Direction;
int Encoder1_RelCounter;
int Encoder1_AbsCounter;


void setup() {
  Serial.begin(250000);

  pinMode(36, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  Encoder1.init();
  Encoder1.setRelCounter(5);

}

void loop() {

  update_Encoder();

  serieller_Monitor();

}


// ****** Funktionen ******

void update_Encoder ()
{
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_RelCounter = Encoder1.getRelCounter();
  Encoder1_AbsCounter = Encoder1.getAbsCounter();

  if (Encoder1_RelCounter < 0) {
    Encoder1.setRelCounter(30);                     // Variable geht auf 29 statt 30
  }

  else if (Encoder1_RelCounter > 30) {
    Encoder1.setRelCounter(0);
  }

}


void serieller_Monitor ()
{
  static const unsigned int INTERVAL = 150;
  static unsigned long last_ms = 0;

  if (millis() - last_ms < INTERVAL) return; // Zeit noch nicht erreicht, Funktion abbrechen

  last_ms += INTERVAL;
  Ueberschriftszeile();
  Serial.print(Encoder1_Direction);       Serial.print('\t');
  Serial.print(Encoder1_RelCounter);      Serial.print('\t');
  Serial.print(Encoder1_AbsCounter);
  Serial.println();
}


void Ueberschriftszeile ()
{
  static int counter = 33;

  counter++;

  if (counter < 30) return; // Zeilen noch nicht erreicht, Funktion abbrechen

  counter = 0;
  Serial.print(F("DIR")); Serial.print('\t');
  Serial.print(F("Relat")); Serial.print('\t');
  Serial.print(F("Absol"));
  Serial.println();
}
if (Encoder1_RelCounter < 0) {
 Encoder1_RelCounter+=32;  // modulo 32 :  0 ... 31
 Encoder1.setRelCounter(Encoder1_RelCounter);   
}

Hallo Michael,

klappt leider nicht, dann ist der Wert 31 und wird beim nächsten Aufruf auf 0 gesetzt ohne das ich drehe. Damit bleibt 0 bei 0 stehen wenn ich weiterdrehe. Ändere ich das Modulo auf 31

Encoder1_RelCounter+=31;  // modulo 31 :  0 ... 30

ist der Effekt wie vorher, er springt nach kleiner 0 auf 29 statt auf 30. Er soll nur zwischen 0 ... 30 zählen. Nach 30 + soll wieder 0 und nach 0 - wieder 30. Seltsamerweise gehts von 30 auf 0 immer und von 0 auf 30 ist etwas faul.

Edit:
Mir kommt das so vor als wenn die encode Methode aus einem absolut unbekannten Grund nochmal aufgerufen wird und nochmal eins abzieht. Seltsamerweise nur in der einen Zählrichtung. Drehrichtung durch Phasentausch ist egal.

Edit 2:
ich hatte schon vorher versucht nur mit Modulo zu arbeiten.

void update_Encoder ()
{
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_RelCounter = Encoder1.getRelCounter();
  Encoder1_AbsCounter = Encoder1.getAbsCounter();

  Encoder1_RelCounter = Encoder1_AbsCounter % 31;        // 0 ... 30
}

Das funktioniert nur im positiven Bereich.
Multipliziere ich negative Wert mit -1, dreht sich die Zählweise um.
Alles das was nicht gewollt ist passiert.
Deswegen die zwei if Abfragen die für mich syntaktisch fehlerfrei erscheinen.

Ich habe nur das Problem gesehen, dass bei deiner if ( < 0) Abfrage die -1 evtl. verloren gehen könnte.

In Wirklichkeit sehe ich den Fehler aber auch nicht.

Warum hast du überhaupt 2 Encoder Positionen, wenn rel_counter nur die Modulo-Variante von abs_counter ist?
Und warum als Property der class GrayEncoder?

Spontane 1/10 Idee: Irgendwas braucht zu lange. Beispielsweise modulo braucht Zeit. Gib mal Zeitimpulse aus und schaue sie mit dem Scope an.

Hallo,

es ist der zum Fehler suchen eingekürzte Teil meiner Lib. Ich habe drei Encoder, deswegen die Basisklasse und die Vererbungen. Der gezeigte Sketch ist der mit dem jeder umgehen kann ohne Ballast drin. Gut, ich könnte den nun noch ohne Vererbungen zusammenstreichen, sah darin nur keinen Sinn drin. Zudem wäre das eine Veränderung vom Sketch gewesen. Kann ich dennoch zusätzlich machen.

Der relative Zähler soll auch nicht Modulo vom Absolutzähler sein. Das war ja nur ein Versuch wert gewesen der auch nicht funktioniert. Der relative Zähler soll unabhängig sein. Deswegen die beiden if Vergleiche.

Wenn Modulo oder die if Vergleiche zuviel Rechenzeit benötigen würden, dann müsste auch zwischendrin rumgezickt werden und nicht nur bei genau der einen Korrektur von 0 (-1) auf 30. Hier muss eine Fehlersystematik drin stecken die ich noch nicht sehe.

Ich weiß nicht so richtig was du mit Zeitimpulsen meinst, aber vielleicht irgendwie doch. Ich baue in die encode Methode ein Triggersignal ein damit ich sehe wann sich darin etwas ändert und Auswirkung nach außen hat. Irgendwie sowas wirst meinen.

Ich bau mal um ...

Hallo,

Klasse umgebaut zum besseren lesen und Impulsausgabe hinzugefügt. Wie erwartet sehe ich pro Rastung immer exakt 4 Impulse. Keiner zu viel keiner zu wenig. Kann es sein das mit der Wert-Vervierfachung etwas nicht stimmt? Dieses links schieben um 2 Bits? Zeile 73. Wenn ich den Zählbereich auf 0...10 ändere bleibt der Effekt. Er springt von 0 auf 9 statt von 0 auf 10.

#include <CombiePin.h>
using namespace Combie::Pin;
RelaisINV<11> impuls;

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    long enc_delta;
    int last;
    volatile long rel_counter;  // Zähler
    volatile long abs_counter;  // Zähler
    int direction;              // Richtungsanzeige  +1 / -1
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase


  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;          
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;           
      if ( Phase_B ) n ^= 1;          // convert gray to binary
      
      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        impuls.on();
        last = n;                               // store new as next last
        enc_delta = (long) (diff & 2) - 1;      // bit 1 = direction (+/-), Zähler
        abs_counter += enc_delta;
        rel_counter += enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
        impuls.off();
      }
    }

    int getDirection()  {
      return direction;  // Richtungsabfrage
    }

    void setRelCounter( long value ) {  // relativen Zählwert ändern
      rel_counter = value << 2;         // 4 Steps pro Rastung
    }

    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter >> 2;          // 
    }

    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter >> 2;          // 4 Steps pro Rastung
    }
};

// ---------------------------------------------------------------------------------------

GrayEncoder Encoder1 (&PINC, 0, 1);      // Phase B/A, Pin 36/37 > Port.C Bit.1 / Bit.0
int Encoder1_Direction;
int Encoder1_RelCounter;
int Encoder1_AbsCounter;


void setup() {
  Serial.begin(250000);

  impuls.init();    // ohne kurze Low Phase
  
  pinMode(36, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  Encoder1.init();
  Encoder1.setRelCounter(5);

}

void loop() {

  update_Encoder();

  serieller_Monitor();

}


// ****** Funktionen ******

void update_Encoder ()
{
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_RelCounter = Encoder1.getRelCounter();
  Encoder1_AbsCounter = Encoder1.getAbsCounter();
  
  if (Encoder1_RelCounter < 0) {
    Encoder1.setRelCounter(10);                   // Variable geht auf 9 statt 10
  }

  else if (Encoder1_RelCounter > 10) {
    Encoder1.setRelCounter(0);
  }
  
}


void serieller_Monitor ()
{
  static const unsigned int INTERVAL = 150;
  static unsigned long last_ms = 0;

  if (millis() - last_ms < INTERVAL) return; // Zeit noch nicht erreicht, Funktion abbrechen

  last_ms += INTERVAL;
  Ueberschriftszeile();
  Serial.print(Encoder1_Direction);       Serial.print('\t');
  Serial.print(Encoder1_RelCounter);      Serial.print('\t');
  Serial.print(Encoder1_AbsCounter);
  Serial.println();
}


void Ueberschriftszeile ()
{
  static int counter = 33;

  counter++;

  if (counter < 30) return; // Zeilen noch nicht erreicht, Funktion abbrechen

  counter = 0;
  Serial.print(F("DIR")); Serial.print('\t');
  Serial.print(F("Relat")); Serial.print('\t');
  Serial.print(F("Absol"));
  Serial.println();
}

Edit: << 4 zu << 2 korrigiert

    void setRelCounter( long value ) {  // relativen Zählwert ändern
      rel_counter = value << 4;         // 4 Steps pro Rastung
    }

    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter >> 2;          //
    }

Merkwürdig.

Hallo,

okay, dass ist nun wirklich nur ein Schreibfehler eines letzten Tests vor im Posting einfügen.
Ich hatte noch schnell mal 4 probiert statt << 2.
Sorry.
Habs korrigiert.

Wenn Du alle 4 Flanken (L-H und H-L der beiden Signale) des Encoders nimmst dann ist nicht gesagt daß beim Drehrichtungsänderung wirklich einen Vollschritt gemacht hast oft machst Du einen Teilschritt.
Das kann beim Zählen der Impulse mit Division durch 4 Ungenauigkeiten führen.

Grüße Uwe

Hallo,

ich kann aktuell nur sagen was ich sehe. Ich sehe immer 4 Impulse, bedeutet für mich da gehts nichts verloren. Wenn ich im gewünschten Bereich 0...30 bleibe ohne gewollten Überlauf, dann kann ich wie wild hin- und her drehen, es verschluckt sich nichts. Der eine falsche Sprung tritt immer auf, auch wenn ich die Drehrichtung beibehalte macht er an der Stelle Mist. Nur äußerst selten, man könnte Lottospielen, springt auf gewollte 30.

Wegen "deiner Division durch 4 Ungenauigkeit" werde ich diese Korrektur beim auslesen und ändern rausnehmen. Mal sehen was dann passiert.

Hallo,

vielleicht nochmal von weiter vorn bevor wir uns in Details verzetteln.
Den Absolutzähler frage ich genauso ab geteilt durch 4. Der verzählt sich aber nie. Niemals. Der geht immer korrekt.

Habe die Teilung und Vervierfachung rausgenommen.

void setRelCounter( long value ) {      // relativen Zählwert ändern
      rel_counter = value; 
    }

    long getRelCounter()   {            // relativen Zählwert abfragen
      return rel_counter;
    }

    long getAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter;
    }

Hier sehe ich ebenso merkwürdige Effekte.
rel und abs Zähler starten bei 0. Ich drehe CCW.
relativer springt auf 27 und absoluter auf -4.
Verloren geht jedoch nichts, ich kann drehen sodass beide auseinander driften und ich kann zurückdrehen und beide zählen wieder syncron.

Ich bekomme jedoch langsam eine Vorstellung was du Uwe mit verloren gehen oder Rundungsfehler meinst. Ich kann die Gedanken nur noch nicht klar ordnen oder gar formulieren. Irgendwie müsste ich den Rundungsfehler mit in die Wertübergabe einbauen wenn ich den Zählerstand korrigiere.

Oder ich muss mir einen reinen "Skechzähler" bauen. Also einen der nur im Sketch zählt. Nicht in der Encoder Lib.

Warum hast du überhaupt 2 Encoder Positionen [...] als Property der class GrayEncoder?

? hatte ich das nicht schon beantwortet ?

Edit:
falls du auf den "Sketchzähler" anspielst, es muss eigentlich auch in der Lib funktionieren, egal wo der nun zählt und geändert wird

Hallo,

der Sketchzähler funktioniert laut ersten Tests.

Code nochmal aufgeräumt.

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    long enc_delta;
    int last;
    volatile long counter;  // Zähler
    int direction;              // Richtungsanzeige  +1 / -1
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase


  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;          
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;           
      if ( Phase_B ) n ^= 1;          // convert gray to binary
      
      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (long) (diff & 2) - 1;      // bit 1 = direction (+/-), Zähler
        counter += enc_delta;                   // Zähler public
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }
    }

    int getDirection()  {
      return direction;  // Richtungsabfrage
    }
    
    long getCounter()   {            // absoluten Zählwert abfragen
      return counter >> 2;          // 4 Steps pro Rastung
    }
};

// ---------------------------------------------------------------------------------------

GrayEncoder Encoder1 (&PINC, 0, 1);      // Phase B/A, Pin 36/37 > Port.C Bit.1 / Bit.0
int  Encoder1_Direction;
long Encoder1_AbsCounter;

int RelCounter;


void setup() {
  Serial.begin(250000);  
  pinMode(36, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  Encoder1.init();
}

void loop() {

  update_Encoder();
  update_relCounter();
  serieller_Monitor();

}


// ****** Funktionen ******

void update_relCounter ()
{
  static long abs_old = 0;
  
  if (Encoder1_AbsCounter != abs_old) {
    if (Encoder1_AbsCounter > abs_old) {
      RelCounter++;
    }
    else {
      RelCounter--;
    }  
    abs_old = Encoder1_AbsCounter;
  }
  
  if (RelCounter < 0) {
    RelCounter = 30;                   
  }
  else if (RelCounter > 30) {
    RelCounter = 0;
  }
  
}

void update_Encoder ()
{
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_AbsCounter = Encoder1.getCounter();  
}


void serieller_Monitor ()
{
  static const unsigned int INTERVAL = 150;
  static unsigned long last_ms = 0;

  if (millis() - last_ms < INTERVAL) return; // Zeit noch nicht erreicht, Funktion abbrechen

  last_ms += INTERVAL;
  Ueberschriftszeile();
  Serial.print(Encoder1_Direction);       Serial.print('\t');
  Serial.print(RelCounter);      Serial.print('\t');
  Serial.print(Encoder1_AbsCounter);
  Serial.println();
}


void Ueberschriftszeile ()
{
  static int counter = 33;

  counter++;

  if (counter < 30) return; // Zeilen noch nicht erreicht, Funktion abbrechen

  counter = 0;
  Serial.print(F("DIR")); Serial.print('\t');
  Serial.print(F("Relat")); Serial.print('\t');
  Serial.print(F("Absol"));
  Serial.println();
}

Vorerst merke ich mir man darf den Zählerstand aus der Lib nur auslesen, nicht ändern, damit der Rundungsfehler innerhalb der Lib bleibt und damit immer automatisch intern korrigiert wird. Ausgenommen wäre ein Encoder der pro Rastung nur ein Impuls liefern würde. Ist das so ungefähr richtig zusammengefasst?

Hallo,

jetzt habe ich das ursprüngliche Problem analysiert und verstanden. Man muss sich den initialen Phasenzustand merken. Und erst bei diesem Zustand darf man den relativen Zählwert überschreiben. Sonst kommt es zu einem Zählversatz der das Problem verursacht und letzlich für den Ganzzahldivisionfehler verantwortlich ist. Dabei kann es immer noch bei ungünstigen langsamen drehen und schneller Abfrage kurzzeitig beim Wechsel von 0 auf 10 temporär zu einer Ausgabe -1 kommen bevor es dann endgültig auf 10 wechselt. Das passiert alles während der Bewegung von einer Rastung zur nächsten Rastung wenn die 4 Pahsenzustände durchlaufen werden. Um diese -1 zu unterdrücken erfolgt noch eine Sicherheitsabfrage bevor der neue Wert zugegeben wird.

Soviel zu den Forschungsergebnissen.
Das relative Zählbereich ist im Debugtestsketch hart kodiert.

Danke Uwe mit den Schubs in die richtige Richtung. :slight_smile:

Forschungscode:

class GrayEncoder       // Klassenname "GrayEncoder"
{
  protected:                    // private wird zu protected, nur intern zugängliche Elemente ...
    volatile byte const *PORT;  // verwendeter Port
    byte const PinA;            // Pin Port.Bit erste Phase
    byte const PinB;            // Pin Port.Bit zweite Phase
    long enc_delta;
    int last;
    int init_Phasen;
    volatile long rel_counter;  // Zähler
    volatile long abs_counter;  // Zähler
    int direction;              // Richtungsanzeige  +1 / -1
    byte Phase_A;               // erste Phase
    byte Phase_B;               // zweite Phase


  public:              // von außen zugängliche Elemente ...
    GrayEncoder (volatile byte *p_Port, byte _A, byte _B):
      // Initialisierungsliste
      PORT(p_Port),
      PinA(_A),
      PinB(_B),
      rel_counter(0),   // Zähler
      abs_counter(0),   // Zähler
      direction(0)      // Richtungsanzeige  +1 / -1
    { }

    void init()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;          
      if ( Phase_B ) n ^= 1;

      last = n;                       // power on state
      init_Phasen = n;                // Init Phasenzustand merken
      enc_delta = 0;
    }

    void encode()
    {
      int n = 0;                      // new

      Phase_A = (*PORT >> PinA & 1);  // einzelnes Bit heraus fischen
      Phase_B = (*PORT >> PinB & 1);

      if ( Phase_A ) n = 3;           
      if ( Phase_B ) n ^= 1;          // convert gray to binary
      
      int diff = last - n;

      if ( diff & 1 )  {                        // bit 0 = value (1)
        last = n;                               // store new as next last
        enc_delta = (long) (diff & 2) - 1;      // bit 1 = direction (+/-), Zähler
        abs_counter += enc_delta;
        rel_counter += enc_delta;
        direction = enc_delta;                  // Richtungsanzeige (+1 / -1)
      }

      update_RelativCounter();
    }

    void update_RelativCounter() {      
       if ( ( (rel_counter>>2) > 10) && (last == init_Phasen) ) {
        rel_counter = 0;
      }
      if ( ( (rel_counter>>2) < 0) && (last == init_Phasen) ) {
        rel_counter = 10;
        rel_counter <<= 2;
      }
      
    }
    
    int getDirection()  {
      return direction;  // Richtungsabfrage
    }

    /*
    void setRelCounter( long value ) {  // relativen Zählwert ändern
      rel_counter = value << 2;         // 4 Steps pro Rastung
    }
    */
    
    long getRelCounter()   {              // relativen Viertel Zählwert abfragen
      static long old_rel = 0;
      long temp = rel_counter >> 2;
      if ( (temp < 0) || (temp > 10) ) {  // Sicherheitsabfrage wegen wackligen Rastpunkt
        return old_rel;
      }
      else {
        old_rel = temp; 
        return temp;
      }
    }

    long getOrgRelCounter()   {            // original relativen Zählwert abfragen
      return rel_counter;          
    }

    long getRelRest()   {                  // Rest vom Ganzzahlrechenfehler
      long temp = rel_counter >> 2;
      return (rel_counter - (temp << 2));          
    }

    long getAbsCounter()   {              // absoluten Zählwert abfragen
      return abs_counter >> 2;            // 4 Steps pro Rastung
    }

    long getOrgAbsCounter()   {            // absoluten Zählwert abfragen
      return abs_counter;          
    }

    int getlastPhasen()   {                // Phasenstellung abfragen
      return last;          
    }

    
    
};

// ---------------------------------------------------------------------------------------

GrayEncoder Encoder1 (&PINC, 0, 1);      // Phase B/A, Pin 36/37 > Port.C Bit.1 / Bit.0
int Encoder1_Direction;
int Encoder1_RelCounter;
int Encoder1_RelRest;
int Encoder1_OrgRelCounter;
int Encoder1_AbsCounter;
int Encoder1_OrgAbsCounter;
int Encoder1_lastPhasen;


void setup() {
  Serial.begin(250000);
  
  pinMode(36, INPUT_PULLUP);
  pinMode(37, INPUT_PULLUP);
  Encoder1.init();
  //Encoder1.setRelCounter(0);

}

void loop() {

  update_Encoder();

  serieller_Monitor();

}


// ****** Funktionen ******

void update_Encoder ()
{  
  Encoder1.encode();
  Encoder1_Direction  = Encoder1.getDirection();
  Encoder1_lastPhasen = Encoder1.getlastPhasen();
  Encoder1_RelCounter = Encoder1.getRelCounter();
  Encoder1_RelRest    = Encoder1.getRelRest();
  Encoder1_OrgRelCounter = Encoder1.getOrgRelCounter();
  Encoder1_AbsCounter = Encoder1.getAbsCounter();
  Encoder1_OrgAbsCounter = Encoder1.getOrgAbsCounter();
}


void serieller_Monitor ()
{
  static const unsigned int INTERVAL = 150;
  static unsigned long last_ms = 0;

  if (millis() - last_ms < INTERVAL) return; // Zeit noch nicht erreicht, Funktion abbrechen

  last_ms += INTERVAL;
  Ueberschriftszeile();
  Serial.print(Encoder1_Direction);       Serial.print('\t');
  Serial.print(Encoder1_lastPhasen);      Serial.print('\t');
  Serial.print(Encoder1_RelCounter);      Serial.print('\t');
  Serial.print(Encoder1_RelRest);         Serial.print('\t');
  Serial.print(Encoder1_OrgRelCounter);   Serial.print('\t');
  Serial.print(Encoder1_AbsCounter);      Serial.print('\t');
  Serial.print(Encoder1_OrgAbsCounter);
  Serial.println();
}


void Ueberschriftszeile ()
{
  static int counter = 33;

  counter++;

  if (counter < 30) return; // Zeilen noch nicht erreicht, Funktion abbrechen

  counter = 0;
  Serial.print(F("DIR"));     Serial.print('\t');
  Serial.print(F("Phasen"));  Serial.print('\t');
  Serial.print(F("Relat"));   Serial.print('\t');
  Serial.print(F("rRest"));   Serial.print('\t');
  Serial.print(F("OrgRel"));  Serial.print('\t');
  Serial.print(F("Absol"));   Serial.print('\t');
  Serial.print(F("OrgAbs"));
  Serial.println();
}