Inkrementalgeber durch Interrupts funktioniert nicht

Moin zusammen,

ich plane die Umsetzung eines Tachos, bzw. eines Inkrementalgeber mit Hilfe von zwei Sensoren, damit ich zusätzlich die Drehrichtung ermitteln kann. Ich habe es soweit auch umgesetzt bekommen, allerdings würde ich meine Logik gerne auf Interrupts umstellen, bekomm's aber nicht gebacken. Hier mein aktueller Aufbau:

void setup()
{
  Serial.begin(9600);
  pinMode(interruptPin_first, INPUT);
  pinMode(interruptPin_second, INPUT);
  //attachInterrupt(digitalPinToInterrupt(interruptPin_first), isr_1, RISING);
  //attachInterrupt(digitalPinToInterrupt(interruptPin_second), isr_2, RISING);
}

void loop()
{
  state_first = digitalRead(interruptPin_first);
  state_second = digitalRead(interruptPin_second);

  if (!state_first && !state_second) {
    initial_state = true;
  }

  while (initial_state) {
    state_first = digitalRead(interruptPin_first);
    state_second = digitalRead(interruptPin_second);
    while (state_first && initial_state) {
      if (digitalRead(interruptPin_second)) {
        turning_amount++;
        initial_state = false;
      }
    }
    while (state_second && initial_state) {
      if (digitalRead(interruptPin_first)) {
        turning_amount--;
        initial_state = false;
      }
    }
  }
}

Es funktioniert soweit, allerdings schätze ich nicht bei etwas höheren Geschwindigkeiten, hab aber kein Signalgeber um das zu prüfen, realisiere ich vielleicht mit einem anderen Arduino, wenn's mir spanisch vorkommt.

Nun hab ich auf dem Breadboard die 5V vom Arduino in meine TCRT5000 Boards mit IR Sensor gesteckt, GND von den Boards zurück in den Arduino und den D0 Ausgang (TTL Switch Signal Output) in die Pins 2 und 3 des Arduino mit 10KOhm Widerstand vor.

Wenn ich nun versuchen möchte das mit Interrupts zu inkrementieren kommentiere ich das attachedInterrupts ein und schreibe bspw. die Funktion:

void isr_1 () { turning_amount++; }

inkrementiert er mir nicht nur einen, sondern mehrere. Liegt das daran, dass man Signal zu unsauber ist? Habe ich den Befehl falsch verstanden?! Sollte ich einen Kondensator vorschalten?!

Liebe Grüße,
BooWseR

Du machst Dir das Leben viel zu schwer. Reagiere auf eine Flanke des einen Signals und prüfe dann den Zustand des anderen Signals, der die Richtung angibt.

Gute Idee, auf die Flanke bin ich so gar nicht gekommen (trotz des Rising im Interrupt). Aktuell erlaubt der Aufbau es nicht, dass gleichzeitig beide Sensoren getriggerd werden, sprich es ist entweder:

Pin1 && !Pin2 >>> !Pin1 && Pin2 = links
!Pin1 && Pin2 >>> Pin1 && !Pin2 = rechts

Ich denke aber mit einer neuen Ausrichtung der Sensoren kann ich das Problem umgehen. Der Aufbau auf dem Breadboard ist ja eh nur exemplarisch.

Zur digitalen Umsetzung:
Heißt ich würde beide Pins als Interrupt festlegen und dann entsprechend counten:

void setup() {
  attachInterrupt(digitalPinToInterrupt(interruptPin_first), isr_1, RISING);
  attachInterrupt(digitalPinToInterrupt(interruptPin_second), isr_2, RISING);
}

void isr_1 () { if (digitalRead(interruptPin2)) { value++; } }
void isr_2 () { if (digitalRead(interruptPin1)) { value--; } }

Sind if-Bedingungen für einen Interrupt zulässig? Ich hab gelesen man soll in solchen Funktionen möglichst wenig machen.

Im Loop könnte ich mich dann um die Darstellung auf einem MAX7219 LED Array kümmern, das funktioniert immerhin. :smiley:

Bleibt noch die Frage weshalb er bei jeder Flanke 2-3 Mal isr_* ausführt?! Muss ich das Signal entprellen? Einfach einen kleinen Kondensator hinter dem Widerstand gegen GND?

D0 ---> 10K Res ---> || ---> GND
'---> PIN2

LG

Edit.: Hab die Signalleitung von den Sensoren mit einem 104 Kondensator gegen GND entprellt, funktioniert über den seriellen Monitor auch wunderbar. Schließe ich zusätzlich das LED Array an bekomme ich leider falsche Ergebnisse. Immer wenn ein Sensor getriggert wird, springt die Value um 1-30 Werte nach oben, hab anscheinend ordentlich rauschen auf der Leitung. Eine Ahnung wie ich das beheben kann?

Ich wiederhole das nur noch einmal: nimm ein Signal als Takt mit steigender oder fallender Flanke, das andere für hoch oder runterzählen.

void isr_1 () { if (digitalRead(Pin2))  value++;  else value--; }

Du brauchst keine 2 Interruptroutinen. Du brauchst nur eine. Die eine sagt Dir ob eine Änderung am Signal ist und der Zustand des zweiten Signals, normal über digitalRead() eingelesen, gibt die Drehrichtung.
Beide Signale Interrupt-überwacht brauchst Du nur, wenn Du die Schrittzahl des Encoder erhöhen willst und darum alle 4 Flanken der 2 Signale auswerten mußt.
Grüße Uwe

Hallo,

man sollte immer beide Signal zur gleichen Zeit einlesen. Ansonsten kann das Richtungssignal schon wieder anders zum Taktsignal stehen. Also falls man den Takt per Interrupt liest, sollte das Richtungssignal am gleichen Port hängen und man liest als erstes das Portregister ein.
Zum Verständnis der grundlegenden Arbeitsweise und wie man es vernünftig macht, sollte man das hier gelesen haben. Drehgeber – Mikrocontroller.net Kann man schon als Standardwerk betrachten. Bei Handdrehgebern ist auch pollen ausreichend, solange das Programm blockierfrei durchlaufen wird, was es sowieso sollte.

Moin zusammen, vielen Dank für Euer Input. Ich hab mir den Link von Doc_Arduino mal angeschaut und durchgelesen. Dann habe ich das "best practice" genommen und versucht vom Atmega16 auf den Atmega32 meines Unos zu adaptieren. Es sieht ganz aus, er zählt auf den ersten Blick korrekt, wobei ich ja aktuell zum Testen lediglich eine alte Festplatte mit Klebestreifen habe. Den Rest muss die Praxis zeigen.

Zum Code (Ich klammer mal die LED Matrix aus):

#define PHASE_A     (PIND & 1<<PD2) // If digital PIN 2 is high
#define PHASE_B     (PIND & 1<<PD3) // If digital PIN 3 is high
#define F_CPU 8000000UL // 8 MHz Frequenz für die CPU

volatile int8_t enc_delta;
static int8_t last;

void setup() {
  TCCR1B=(1<<WGM12);    //Set the CTC mode   
  OCR0A=0xF9; //Value for ORC0A for 1ms
  TIMSK1|=(1<<OCIE0A);   //Set the interrupt request
  sei(); //Enable interrupt
  TCCR1B|=(1<<CS11);    //Set the prescale 1/64 clock
  TCCR1B|=(1<<CS10);
}

ISR(TIMER1_COMPA_vect){    //This is the interrupt request
  int8_t _new = 0;
  int8_t diff;
  
  if ( PHASE_A ) { _new = 3; }
  if ( PHASE_B ) { _new ^= 1; }
  diff = last - _new;
  if( diff & 1 ) {
    last = _new;
    enc_delta += (diff & 2) - 1;
  }
}

void encode_init() {
  int8_t _new = 0;
  if ( PHASE_A ) { _new = 3; }
  if ( PHASE_B ) { _new ^= 1; }
  last = _new;
  enc_delta = 0;
}

int8_t encode_read4( void )         // read four step encoders
{
  int8_t val;

  cli();
  val = enc_delta;
  enc_delta = val & 3;
  sei();
  return val >> 2;
}

void loop() {
  int32_t val = 0;

  encode_init();
  sei();

  for(;;){
    val += encode_read4();
    displayNumber(val); //Für die LED Matrix
  }
}

Wenn jemand bei meiner Übersetzung irgendwelche Fehler sieht, gerne her damit, ansonsten berichte ich, wenn ich es unter realen Bedingungen getestet habe.

Abschließend noch eine Frage: Ich werde ja ein Decoder-Rad nutzen, ist es sinnvoller die beiden Sensoren so zu platzieren, dass sie eng beieinander sind und das selbe schwarze Feld ertasten, oder um bspw. 90° versetzt?!

Liebe Grüße,
BooWseR

Hallo,

#define F_CPU 8000000UL // 8 MHz Frequenz für die CPU

Ist das notwendig? Sollte aus der Boardkonfig heraus definiert sein.

Abschließend noch eine Frage: Ich werde ja ein Decoder-Rad nutzen, ist es sinnvoller die beiden Sensoren so zu platzieren, dass sie eng beieinander sind und das selbe schwarze Feld ertasten, oder um bspw. 90° versetzt?!

Was du wie versetzt bleibt dir überlassen. Am Ende muss ein Phasenverschobenes Signal rauskommen.
Kannst auch die Suchmaschine anwerfen um zu sehen was sich die Leute so einfallen lassen.

Thema Timer initialisierung.
Beim Arduino sind die Timerregister für analogWrite usw. vorbelegt. Das heißt du musst erstmal alle Timerregister löschen bevor du eine eigene Konfiguration erstellst. Sonst macht der Timer nicht das was du möchtest.

Bsp.

#include <util/atomic.h>
...
...
void preSetTimer1 (void)  // Voreinstellungen, läuft noch nicht los
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    TCCR1B = 0;             // Resets
    TCCR1A = 0;             //
    TIMSK1 = 0;             //
    TCNT1  = 0;             //
    OCR1A  = TOPmax;
    OCR1B  = TOPmax / 2;
    TCCR1A = _BV(COM1B1) | _BV(WGM11) | _BV(WGM10); // OC1B Pin nicht invertiert
    TCCR1B = _BV(WGM13);                            // Mode 11, Phase Correct
  }                 
}


void runTimer1 (const unsigned int prescaler)
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    switch (prescaler) {  // set Prescaler Clock Select Bits
      case    1 : TCCR1B |= _BV(CS10);              break;
      case    8 : TCCR1B |= _BV(CS11);              break;
      case   64 : TCCR1B |= _BV(CS11) | _BV(CS10);  break;
      case  256 : TCCR1B |= _BV(CS12);              break;
      case 1024 : TCCR1B |= _BV(CS12) | _BV(CS10);  break;
      default :   // falscher Prescaler
    }  
  }
}


void stopTimer1 (void)
{
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
  {
    TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
  }
}

boowser:

  OCR0A=0xF9; //Value for ORC0A for 1ms

TIMSK1|=(1<<OCIE0A);  //Set the interrupt request

Du musst schon konsequent sein und immer Timer1 ansprechen

Auch beachten dass Timer1 etwas anders aufgebaut ist als Timer0

Doc_Arduino:

#define F_CPU 8000000UL // 8 MHz Frequenz für die CPU

Ist das notwendig? Sollte aus der Boardkonfig heraus definiert sein.

Hm, eigentlich nicht. Wobei der ATMega328p mit 16 MHz läuft und das Code-Beispiel für den ATMega16 ebenfalls 8 MHz nutzt, dachte ich, ich übernehme den Takt mal und überschreibe die 16 MHz.

Thema Timer initialisierung.
Beim Arduino sind die Timerregister für analogWrite usw. vorbelegt. Das heißt du musst erstmal alle Timerregister löschen bevor du eine eigene Konfiguration erstellst. Sonst macht der Timer nicht das was du möchtest.

Gut zu wissen, ich hatte nur gelesen, dass Timer0 für die millis genutzt wird und hab dann extra den Timer1 verwendet, in der Hoffnung dieser sei frei. Aber Danke für den Code, ich werd's einbauen.

Serenifly:
Du musst schon konsequent sein und immer Timer1 ansprechen

Das ist mir tatsächlich durchgerutscht. Danke für deine Adleraugen.

Auch beachten dass Timer1 etwas anders aufgebaut ist als Timer0

Hab ich gesehen, ja. Die Bezeichnung der Register hab ich auch eigentlich aus einer Tabelle entnommen (https://www.mikrocontroller.net/articles/AVR-Tutorial:_Timer). Also müsste es wie folgt heißen:

TIMSK1|=(1<<OCIE1A);

Ob ich jetzt das A oder B Register verwende ist egal, oder?

LG

Hallo,

Hm, eigentlich nicht. Wobei der ATMega328p mit 16 MHz läuft und das Code-Beispiel für den ATMega16 ebenfalls 8 MHz nutzt, dachte ich, ich übernehme den Takt mal und überschreibe die 16 MHz.

Wenn dein µC >DEINER< mit 16Mhz taktet, warum versuchst du dann die Konfig zu überschreiben?
Alle Berechnungen die in dem dir bekannten Bsp. mit 8MHz gemacht wurden, musste mit deinen 16MHz erneut machen. Die Formeln stehen im Manual.

Ob ich jetzt das A oder B Register verwende ist egal, oder?

Nein das ist nicht egal, je nachdem wofür sie genutzt werden sollen.
Die Unterscheidung liegt zwischen Top Register und Compare Register.
B/C ist für TOP sowieso nicht vorgesehen.

Für das allgemeine Grundverständnis.
https://forum.arduino.cc/index.php?topic=519208.msg3538992#msg3538992
Den Rest musste dir für dich ableiten und mittels Manual.

Danke für deine Antwort

Doc_Arduino:
Wenn dein µC >DEINER< mit 16Mhz taktet, warum versuchst du dann die Konfig zu überschreiben?
Alle Berechnungen die in dem dir bekannten Bsp. mit 8MHz gemacht wurden, musste mit deinen 16MHz erneut machen.

Weil ich den Weg des geringsten Widerstandes gehen wollte und mir gedacht habe, wenn das Programm für 8 MHz gedacht ist, dann belasse ich es dabei. Ich brauch keine so schnelle Abtastung, die beschriebenen 1 ms reichten mir vollkommen. Aber okay.

Folge ich deinem Link und geh zu deiner Formel von TOP komme ich auf:
TOP = (CPU Takt / Prescaler / Frequenz) -1
TOP = 16*10^6 / 64 / 1000 - 1 = 249 = 0xF9

0xF9 ist aber genau der Wert, den ich schon zuvor aus dem Beispiel für 8 MHz entnommen hatte. Das blicke ich nicht so ganz.

Und nochmal zurück zur Initialisierung:

Beim Arduino sind die Timerregister für analogWrite usw. vorbelegt. Das heißt du musst erstmal alle Timerregister löschen bevor du eine eigene Konfiguration erstellst. Sonst macht der Timer nicht das was du möchtest.

Eigentlich hatte der Arduino gemacht was ich möchte, ich hab zwar keine Ahnung weshalb, aber ich war glücklich. Und eben echt verzweifelt, weil mir nicht aufgefallen ist, dass ich mich auf dem Board versteckt hatte und deshalb der zweite D_Out vom IR Sensor in der Luft hing und nichts mehr ging :smiley:

Ich zeig dir mal mein setup()

void setup() {
  preSetTimer1(); //Habe ich so von dir übernommen, anstelle von TOPMax dann 0xF9 eingetragen
  TCCR1B=(1<<WGM12); //Set the CTC mode
  runTimer1(64); //Für den 64 Bit Prescaler

  TIMSK1|=(1<<OCIE1A);   //Set the interrupt request
  sei(); //Enable interrupt
}

Ich habe preSetTimer1, runTimer1 und stopTimer1 so von dir übernommen, bei preSetTimer1 für TOPMax dann 0xF9 eingetragen und bei runTimer hinter "default : " nocht "TCCR1B |= _BV(CS11);" ergänzt. Ich denke da hast du dich verkopiert. Ist denn meine Einbettung so okay?

Außerdem, müssen jetzt WGM12 oder WGM13 gesetzt werden? In meinem ursprünglichen Beispiel war es der WGM12, bei dir der 13.

Und wann nutze ich die stopTimer1 Funktion?

LG