Drehgeber Signale zählen und Ausgang setzen

Hallo Leute,

ich habe folgende Aufgabenstellung:

ein externes Startsignal soll Drehgeberimpulse zählen (hoch oder runter - Wert soll einstellbar sein) und wenn der eingestellte Wert erreicht wird, dann soll das zählen beendet werden und ein Ausgang soll geschaltet werden. (Diagramm siehe Anhang)

Im habe es mit diesem Sketch versucht:

volatile int counter_value = 2000;
bool trigger = false;


/* ################################################################################################################  */

void setup() {
  pinMode(13,OUTPUT);
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(2), ISR_Aufgabe_Start, RISING);
  Serial.println("Programm gebootet - Warten auf StartTrigger");
  digitalWrite(13,LOW);
}

void loop() {

  if (counter_value == 0 && trigger == false)
  {
    Trigger_Out();
    trigger = true;
    detachInterrupt(digitalPinToInterrupt(3));   
  }

}
/* ################################################################################################################  */

void ISR_Aufgabe_Start()
{
  digitalWrite(13,LOW);
  counter_value = 2000;
  attachInterrupt(digitalPinToInterrupt(3), ISR_Aufgabe_runterzaehlen, RISING);
  trigger = false;
  Serialprint();
}

/* ################################################################################################################  */

void ISR_Aufgabe_runterzaehlen()
{
  counter_value --;
  Serialprint();
}

/* ################################################################################################################  */

void Serialprint()
{
  Serial.print("Counter: ");
  Serial.println(counter_value);
}

/* ################################################################################################################  */

void Trigger_Out()
{
    Serial.println("Counter = 0, Trigger OUT");
    digitalWrite(13,HIGH);
}

Leider habe ich festgestellt wenn die Interrupts zu schnell kommen dann wird der Ausgang nicht gesetzt.

Vermutlich wird der Code im Loop aufgrund der Geschwindigkeit nicht ausgeführt.

Ich denke es muss auch anders gehen (Timer, … )

Nur weiß ich nicht wie ich ansetzen kann.

Hat jemand Erfahrungen mit so einer Aufgabe sammeln können und könnte mir auf den richtigen Weg helfen?!

Würde mich echt über Antworten freuen.

Vielen Dank im Voraus.

Grüße

Artur

1.) Serial hat in Interrupts nichts verloren! Das kann nur schief gehen
2.) Multi-Byte Variablen die in Interrupts geändert werden müssen außerhalb atomar ausgelesen werden. Das heißt bei abgeschalteten Interrupts

Drehgeber lassen sich auf grob auf 2 Arten einlesen:
1.) Mit Pin Interrupts. Aber nicht so wie du das tust
2.) Regelmäßiges Polling in einem Timer Interrupt. Das ist besonders geeignet für mehrere Drehgeber um die Interrupt-Last zu reduzieren

Lese erst mal etwas darüber wie Drehgeber generell funktionieren. Die zwei Pins sind gegeneinander phasenverschoben um die Drehrichtung zu erkennen.
Es gibt auch fertige Bibliotheken dafür

Hey Serenifly,

danke für deinen Beitrag - ich weiß schon wie Drehgeber generell funktionieren.

Für meine Aufgabe nutze ich nur eine Line (Signal A) des Drehgebers. Ich brauche nur die Anzahl der Impulse (x Impulse = x Strecke).

Damit will ich eine Wegverzögerung realisieren. Ein Startsignal (von z.B. einer Lichtschranke) gibt vor ab wann gezählt werden soll.. Das Objekt gewegt sich auf einem Band, der Drehgeber gibt Impulse aus und nach einer gewissen Strecke soll ein Trigger ausgelöst werden (z.B. an eine Kamera).

Das mit dem Serial in der ISR war mir bewusst - hab ja ne Funktion eingebaut und nicht Serial.println(). Nunja dann habe ich mich wohl geirrt. Ist ja quasi das gleiche.

Hab das jetzt mal ausgebaut und es scheint nun zu funktionieren.

Ah. Ich dachte weil du +/- zählen willst.

Ist aber trotzdem sehr seltsam gelöst mit dem wechselseitigen Aktivieren von Interrupts

MasT3r-A:
Das mit dem Serial in der ISR war mir bewusst - hab ja ne Funktion eingebaut und nicht Serial.println(). Nunja dann habe ich mich wohl geirrt. Ist ja quasi das gleiche.

Das macht doch keinen Unterschied. Es geht darum dass Serial selbst Interrupts braucht und auf AVR Prozessoren sich Interrupts nicht gegenseitig unterbrechen können. Die Interrupts werden aber erst beim Verlassen der ISR wieder aktiviert.

Und für atomares Auslesen gibt es auch ein paar nützliche Makros:
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
Das musst du noch einbauen, sonst ist es Zufall dass es geht. Praktisch kann man eine globale Variable atomar in eine temporäre lokale Variable kopieren. Dann werden die Interrupts gleich wieder aktiviert und man arbeitet mit der temporären Version weiter. Bei so langsamen Vorgängen ist das aber nicht so wichtig

Hey,

also ich habe auf dem UNO beide Interrupt Pins belegt (2 und 3).

2 ist für den Starttrigger (Lichtschranke) ISR_Aufgabe_Start()

3 für die Drehgeber Impulse ISR_Aufgabe_runterzaehlen()

wenn meine ISR_Aufgabe_Start() ausgeführt wird (Aktivierung durch die Lichtschranke) dann soll die ISR_Aufgabe_runterzaehlen() aktiviert werden (diese zählt den Wert runter vom eingestellten Wert).

In meiner Loop deaktiviere ich die ISR_Aufgabe_runterzaehlen() wenn der Wert 0 erreicht hat und Setze meinen Output auf HIGH.

Habe das jetzt mal mit 5KHz (Encoder) Input getestet und es scheint zu klappen.

Edit: Das mit dem atomaren Auslesen muss ich mir mal ansehen.. Lese das zum ersten Mal :slight_smile:

MasT3r-A:
Edit: Das mit dem atomaren Auslesen muss ich mir mal ansehen… Lese das zum ersten Mal :slight_smile:

Du musst nur den IRQ sperren, während Du auf die Variable zugreifst. Der AVR ist ja ein 8-Bit Prozessor. Deine Count Variable ist aber ein 16-Bit Wert. Der Prozessor braucht also letztendlich 2 Befehle, um diese Variable auszulesen. Wenn nun dein Interrupt genau zwischen diesen beiden Befehlen zuschlägt und die count-Variable verändert, hast Du die eine Hälfte vor, und die andere Hälfte nach dem Interrupt gelesen. Und wenn es dann auch noch gerade einen Überlauf von einem Byte ins andere gegeben hat, hast Du einen vollkommen falschen Wert.

z.B.
count vor ISR: 0x0100 - highbyte gelesen: 0x01
count nach ISR 0x00FF - lowbyte gelesen 0xFF
tatsächlicher Wert von count: 0x00FF
gelesener Wert von count 0x01FF

solche Fehler treten dann sporadisch auf, und sind extrem schwer zu finden, wenn man da nicht von Anfang an dran denkt.

MicroBahner:
Du musst nur den IRQ sperren, wenn Du die Variable ausliest. Der AVR ist ja ein 8-Bit Prozessor. Deine Count Variable ist aber ein 16-Bit Wert. Der Prozessor braucht also letztendlich 2 Befehle, um diese Variable auszulesen. Wenn nun dein Interrupt genau zwischen diesen beiden Befehlen zuschlägt und die count-Variable verändert, hast Du die eine Hälfte vor, und die andere Hälfte nach dem Interrupt gelesen. Und wenn es dann auch noch gerade einen Überlauf von einem Byte ins andere gegeben hat, hast Du einen vollkommen falschen Wert.

z.B.
count vor ISR: 0x0100 - highbyte gelesen: 0x01
count nach ISR 0x00FF - lowbyte gelesen 0xFF
tatsächlicher Wert von count: 0x00FF
gelesener Wert von count 0x01FF

Hey,

und wie mache ich das genau in meinem Beispiel?

z.B.

void loop() {
  int tempCnt;
  noInterrupts();
  tempCnt = counter_value;
  interrupts();
  if (tempCnt == 0 && trigger == false)
  {

Ich danke dir.. sieht ja gar nicht so kompliziert aus.. ich werde das mal ausprobieren und mal oszillographieren

Danke!!!

Bitte schön :slight_smile:

MasT3r-A:
sieht ja gar nicht so kompliziert aus..

Ist es ja auch nicht. Man muss halt nur dran denken. Das Problem dabei ist, dass es ja meistens auch funktioniert, wenn man nicht dran denkt - nur eben nicht immer ...

MicroBahner:
Bitte schön :slight_smile: Ist es ja auch nicht. Man muss eben nur dran denken. Das Problem dabei ist, dass es ja meistens auch funktioniert, wenn man nicht dran denkt - nur eben nicht immer ...

Danke für die Info.. gut zu wissen.. habe bisher nicht viel mit ISR gearbeitet..

Signale auf dem Oszilloskop sehen gut aus.. ab Start bis Trigger immer dieselbe Zeit...

Ich denke ich habe was ich brauche..

Danke für die Hilfe =)