Forum: Mikrocontroller und Digitale Elektronik Arduino Interrupt Verständnisfrage


von S. D. (der_nachtfuchs)


Lesenswert?

Hi Leute, ich hoffe ihr könnt mir helfen.

Es geht mir um das Verständnis von Interrupts bei Atmel-Prozessoren bzw. 
auf dem Arduino.

1
void interrupt() {
2
  //some stuff here
3
}
4
5
6
void loop() {
7
  delay(300);
8
  state != state;
9
  digitalWrite(LED, state);
10
}


Im Loop steuere ich eine LED an, die mit 300 ms Verzögerung blinkt.
Wenn jetzt über einen Pin 2 (rising) die ISR interrupt() aufgerufen 
wird, wird der normale Loop ja unterbrochen, meinetwegen bei 157 ms, und 
die ISR wird ausgeführt. Dazu habe ich zwei Fragen:

[1] Wenn der Interrupt fertig ist, startet loop() dann von vorne oder 
macht er bei 157 ms weiter?

[2] Wenn der Interrupt am Pin 2 so schnell getriggert wird, dass die ISR 
noch nicht fertig ist, wird sie dann unterbrochen und fängt von vorne 
an, oder ignoriert der µC dann das Signal am Pin, bis die ISR fertig 
ist?

MfG :)

von Oliver S. (oliverso)


Lesenswert?

1) oder
2) oder, ... aber das ISr- Flag wird gesetzt, und nach Beendigung der 
laufenden ISR wird die dann direkt wieder ausgeführt.

Generelle Infos, wie ein AVR so funktioniert, finden sich im Datenblatt 
oder im Tutorial hier oben links. Allerdings ohne Arduino...

Oliver

von Jürgen S. (jurs)


Lesenswert?

Sunny J. D. schrieb:
> [1] Wenn der Interrupt fertig ist, startet loop() dann von vorne oder
> macht er bei 157 ms weiter?

Wenn der Interrupt (die "Unterbrechung" des laufenden Programms) fertig 
ist, macht sie genau dort weiter, wo unterbrochen wurde. Wenn also die 
Unterbrechung mitten in einem "delay(300)" nach 157ms stattfindet, 
folgen danach noch 143 ms weiteres delay und danach die nächste 
Programmzeile.

> [2] Wenn der Interrupt am Pin 2 so schnell getriggert wird, dass die ISR
> noch nicht fertig ist, wird sie dann unterbrochen und fängt von vorne
> an, oder ignoriert der µC dann das Signal am Pin, bis die ISR fertig
> ist?

Wenn ein Interrupt getriggert wird, während eine 
Interruptbehandlungsroutine (für diesen oder einen anderen Interrupt) 
bereits läuft, wird der betreffende Interrupt zunächst auf "waiting" 
gestellt. D.h. dieser Interrupt wird dann ausgeführt, nachdem die 
laufende Interruptbehandlung abgearbeitet ist. Dadurch geht der 
Interrupt also nicht verloren.

Verloren geht ein Interrupt dann, wenn der Interrupt bereits auf 
"waiting" gesetzt ist und derselbe Interrupt dann nochmals auf "waiting" 
gesetzt werden soll. "Waiting" geht nur einmal, d.h. in dem Fall, dass 
ein Interrupt während einer (zu) lange laufenden Interruptbehandlung 
mehrmals auf waiting gesetzt wird, wird er danach nur einmal ausgeführt 
und die überzähligen Interruptanforderungen gehen verloren.

von S. D. (der_nachtfuchs)


Lesenswert?

Danke ihr beiden :)

von S. D. (der_nachtfuchs)


Lesenswert?

Wie kann ich, nachdem die ISR beendet ist, den Loop von vorne beginnen 
lassen?

Hab grad keinen Arduino da, sonst würde ich mal ausprobieren, was 
passiert, wenn man am Ende der ISR einfach loop() aufruft...

von npn (Gast)


Lesenswert?

Sunny J. D. schrieb:
> Wie kann ich, nachdem die ISR beendet ist, den Loop von vorne beginnen
> lassen?

Aber daß die loop() von ganz alleine wieder von vorn beginnt, weißt du?
Du willst also, daß bei Beendigung der ISR die loop() SOFORT wieder von 
vorn beginnt?
Du könntest dir in der ISR ein Flag setzen, was du im Hauptprogramm 
auswertest.

von S. D. (der_nachtfuchs)


Lesenswert?

Ja, das weiß ich, das Ding heißt ja nicht umsonst "loop" ;)

Mir geht es im Groben darum, ein Schaltmuster bei jedem Signaleingang 
(Takt, Clk) zu ändern, dazu dient die ISR. Wenn das Taktsignal aber 
ausbleibt, soll das Schaltmuster komplett auf null gesetzt werden, aber 
erst nach einer bestimmten Zeit.

Ich müsste also in der ISR eine Variable setzen und diese dann im Loop 
abfragen, mit einer if-Bedingung, wie npn es vorgeschlagen hat. Das geht 
aber nur dann, wenn das Flag vorher gesetzt wurde, dann im Loop 
entsprechend die Verzögerung erfolgte und das Flag wieder zurückgesetzt 
wurde.

Würde ich nämlich nur einen einzigen Takt auf den Interrupt geben, würde 
mein Delay vom vorherigen Durchlauf weitergeführt und anschließend 
nochmal ausgeführt werden.

Wahrscheinlich muss ich mit pulseIn oder ähnlichem die Länge des 
Eingangssignals prüfen und vergleichen...

von Karl H. (kbuchegg)


Lesenswert?

Sunny J. D. schrieb:
> Ja, das weiß ich, das Ding heißt ja nicht umsonst "loop" ;)
>
> Mir geht es im Groben darum, ein Schaltmuster bei jedem Signaleingang
> (Takt, Clk) zu ändern, dazu dient die ISR. Wenn das Taktsignal aber
> ausbleibt, soll das Schaltmuster komplett auf null gesetzt werden, aber
> erst nach einer bestimmten Zeit.

Und an dieser Stelle lautet die Antwort:
Hör auf derartige Konstrukte zu versuchen sondern akzeptier lieber, dass 
du mit delay nicht weiter kommst.

Ganz klare Aussage: Dein Problem ist nicht die Interrupt STeuerung. Dein 
Problem ist das delay.
Delay benutzt man am Anfang, wenn man noch nichts anderes kennt. Aber 
für reale Programme sind derartige Warte-Konstrukte unbrauchbar.

Die Lösung läuft über die Funktion millis(). Such mal auf den Arduinio 
Webseiten nach einem Beispiel für eine blinkende LED, die mit millis() 
realisiert ist. Das ist die Technik, wie du deinen Timeout realisieren 
kannst.

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Sunny J. D. schrieb:
> Wie kann ich, nachdem die ISR beendet ist, den Loop von vorne beginnen
> lassen?
>
> Hab grad keinen Arduino da, sonst würde ich mal ausprobieren, was
> passiert, wenn man am Ende der ISR einfach loop() aufruft...

Erkläre lieber mal, was Du genau machen möchtest!

Normalerweise ist die Verwendung der "delay"-Funktion (Blockierung der 
Programmausführung für eine bestimmte Zeit) bei FAST JEDEM halbwegs 
sinnvollen Programm völlig kontraproduktiv und sollte daher vermieden 
werden.

An einem Programm, das Du mit "delay(300)" also bereits vollkommen 
verhunzt und unbrauchbar für die meisten Zwecke gemacht hast, durch eine 
extrem schnelle Interruptbehandlung irgendwas dranstricken zu wollen, 
ist möglicherweise der völlig falsche Ansatz für Dein 
Programmiervorhaben.

Also erkläre lieber, was es werden soll, bevor Du außer "delay(300)" 
noch mehr Scheiß programmierst.

von S. D. (der_nachtfuchs)


Lesenswert?

Ich möchte, dass durch ein Rechtecksignal ein bestimmtes Schaltmuster 
geändert wird. Dies geschieht in der ISR, in der die Schaltzustände 
gespeichert sind - und funktioniert bereits.

Mit der Frequenz des "Taktsignals" bestimme ich, wie schnell die 
Schaltmuster geändert werden.

Bleibt das Taktsignal aus, soll der letzte Schaltzustand für 300 ms 
gehalten werden, danach sollen alle Schaltzustände auf null gehen.

Ich könnte vielleicht einen Zeitstempel mit millis() erstellen, und den 
dann abfragen. Aber das ist auch keine saubere Lösung.

Was wäre dein Vorschlag?

von Karl H. (kbuchegg)


Lesenswert?

Sunny J. D. schrieb:

> Ich könnte vielleicht einen Zeitstempel mit millis() erstellen, und den
> dann abfragen. Aber das ist auch keine saubere Lösung.

Was ist daran unsauber?
1
unsigned long lastChange;
2
3
void setup()
4
{
5
  lastChange = millis();
6
}
7
8
void loop()
9
{
10
  unsigned long now  = millis();
11
12
  if( now - lastChange > 300 ) {
13
    setze alles auf 0
14
    lastChange = now;
15
  }
16
17
  if( Flanke an Takteingang ) {
18
    Muster verändern
19
    lastChange = now;
20
  }
21
}

du brauchst noch nicht mal einen Interrupt, sofern deine Taktpulse sich 
nicht gerade im kleinen Mykrosekundenbereich abspielen, was recht 
unwahrscheinlich ist.

: Bearbeitet durch User
von S. D. (der_nachtfuchs)


Lesenswert?

Schon so im 200 µs Bereich.

Die Änderung der Schaltzustände müssen aber zeitlich exakt erfolgen, 
daher hatte ich den Interrupt angedacht.

Was passiert in deinem Beispiel, wenn die Flanke eine Anstiegszeit von 
25 µs hat, der loop aber in der Zeit dreimal durchläuft? Dann wäre bei 
jedem Durchgang "if ( Flanke an Taktsignal)" wahr und das Muster würde 
geändert.

Ich kann mir da vielleicht helfen, wenn ich den vorherigen Zustand des 
Input-Pins speichere und mit dem aktuellen vergleiche - und dann 
natürlich diese Variable wieder speichere.

Und was mache ich, wenn millis() einen Overflow macht und wieder bei 
null anfängt? Dann könnte die Abfrage "now-lastChange" negative Werte 
auswerfen und die Nullsetzung würde bei diesem Zyklus nicht mehr 
funktionieren, bis ich ein neues Taktsignal anlege.

von Karl H. (kbuchegg)


Lesenswert?

Sunny J. D. schrieb:
> Schon so im 200 µs Bereich.

Das ist selbst für einen digitalRead noch langsam genug :-)

> Die Änderung der Schaltzustände müssen aber zeitlich exakt erfolgen,
> daher hatte ich den Interrupt angedacht.

ok. Dann machs über Interrupt. volatile nicht vergessen!
Die Technik bleibt ja dieselbe. Wann immer sich der Zustand ändert, 
setzt du lastChange auf die aktuelle 'Zeit' und loop() schaltet 300ms 
später die Ausgänge ab.

> Was passiert in deinem Beispiel, wenn die Flanke eine Anstiegszeit von
> 25 µs hat, der loop aber in der Zeit dreimal durchläuft? Dann wäre bei
> jedem Durchgang "if ( Flanke an Taktsignal)" wahr und das Muster würde
> geändert.

:-)

Nochmal drüber nachdenken. Wir sind hier in der Digitaltechnik. EIn 
Eingang kann entweder nur 0 oder 1 sein. Entweder du hast EINE Flanke, 
oder du hast sie nicht.

> Ich kann mir da vielleicht helfen, wenn ich den vorherigen Zustand des
> Input-Pins speichere und mit dem aktuellen vergleiche - und dann
> natürlich diese Variable wieder speichere.
>

Gratuliere. Genau so stellt man einen Flankenwechsel fest

> Und was mache ich, wenn millis() einen Overflow macht und wieder bei
> null anfängt? Dann könnte die Abfrage "now-lastChange" negative Werte
> auswerfen

Nein. kann es nicht. Die Variablen sind unsigned long. Da gibt es kein 
Vorzeichen und auch bei einem Überlauf kommt die richtige Differenz 
raus.

von Jürgen S. (jurs)


Lesenswert?

Sunny J. D. schrieb:
> Ich möchte, dass durch ein Rechtecksignal ein bestimmtes Schaltmuster
> geändert wird. Dies geschieht in der ISR, in der die Schaltzustände
> gespeichert sind - und funktioniert bereits.

Ich verstehe immer noch nicht:
Ist jetzt nur die steigende Flanke interessant, also wie oft und wann 
das Taktsignal kommt? Oder sind steigende und fallende Flanke relevant, 
und auch die Dauer des HIGH-Signals soll auf den weiteren 
Programmverlauf irgeneinen Einfluss haben?

> Mit der Frequenz des "Taktsignals" bestimme ich, wie schnell die
> Schaltmuster geändert werden.

OK, mal angenommen, nur die Frequenz "steigende Flanke" ist relevant.

> Bleibt das Taktsignal aus, soll der letzte Schaltzustand für 300 ms
> gehalten werden,

Wie ist dann bei Dir die Definition von "Taktsignal bleibt aus"?
Da muss es doch eine Timeout-Zeit geben?
Sollen das die 300ms sein?

> Ich könnte vielleicht einen Zeitstempel mit millis() erstellen, und den
> dann abfragen. Aber das ist auch keine saubere Lösung.

Vorsicht: "millis()" unter Arduino tickt nicht sauber im 
Millisekundentakt! Für exakte Timings nimmst Du besser die "micros()" 
Funktion, die tickt auf den 8-Bit AVRs unter Arduino in einem 4µs-Takt 
sauber durch.

> Was wäre dein Vorschlag?

Wenn ich den genauen zeitlichen Ablauf der Signale verstehen würde, 
könnte ich auch einen Codevorschlag machen. Aber so ganz klar ist es mir 
immer noch nicht.

Ich versuche nochmal in Worte zu fassen, was ich meine verstanden zu 
haben:

Ein Ausgangssignal soll:
- immer dann toggeln, wenn ein Taktsignal mit steigender Flanke kommt
- und ansonsten 300ms nach dem letzten Taktsignal abgeschaltet werden

So? Oder anders?

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> ok. Dann machs über Interrupt. volatile nicht vergessen!

und auch nicht die atomare Verriegelung beim Zugriff auf die unsigned 
long bzw. die Race Condition zwischen der Interrupt Funktion und dem 
abschalten in loop()

von S. D. (der_nachtfuchs)


Lesenswert?

Jürgen S. schrieb:
> Ein Ausgangssignal soll:
> - immer dann toggeln, wenn ein Taktsignal mit steigender Flanke kommt
> - und ansonsten 300ms nach dem letzten Taktsignal abgeschaltet werden
>
> So? Oder anders?

Jürgen S. schrieb:
> Wie ist dann bei Dir die Definition von "Taktsignal bleibt aus"?
> Da muss es doch eine Timeout-Zeit geben?
> Sollen das die 300ms sein?

Genau. Bei steigender Flanke sollen die Schaltzustände geändert werden, 
es dürfen maximal 300 ms vergehen, bis wieder eine steigende Flanke 
kommen muss. Ansonsten werden alle Ausgänge auf low gezogen.


Ich hatte das anfangs so gedacht, dass im loop nach delay(300) die 
Ausgänge mit digitalWrite() auf Low geschrieben werden. Da aber der Loop 
einfach da weiter macht, wo er unterbrochen wurde, werde ich ganz 
einfach die Lösung mit dem now=micros() angehen.


Danke für die vielen Anregungen :)

von Karl H. (kbuchegg)


Lesenswert?

Sunny J. D. schrieb:

> Ich hatte das anfangs so gedacht, dass im loop nach delay(300)

mag sein.
Aber in dem Moment, in dem du 'delay' sagst, hast du schon verloren.

Delay ist ein Hilfsmittel für Anfänger. Irgendwo müssen die ja 
schliesslich anfangen und auch mal ein einfaches Blink-Beispiel 
hinkriegen.
Aber für konkrete Anwendungen ist das nicht zu gebrauchen. Am Besten 
vergisst du gleich wieder, dass du je von dieser Funktion gehört hast.

von S. D. (der_nachtfuchs)


Lesenswert?

Okay :)

von Jürgen S. (jurs)


Lesenswert?

Sunny J. D. schrieb:
> Genau. Bei steigender Flanke sollen die Schaltzustände geändert werden,
> es dürfen maximal 300 ms vergehen, bis wieder eine steigende Flanke
> kommen muss. Ansonsten werden alle Ausgänge auf low gezogen.

Dafür ist ja noch nicht einmal ein Interrupt notwendig.

Du schaust einfach in der loop immer nur nach, ob ein Flankenwechsel am 
Pin stattgefunden hat.

Wenn es einen Flankenwechsel auf HIGH gab, toggelst Du den Ausgang und 
setzt den Zeitpunkt des Flankenwechsels.
Und wenn für eine bestimmte Zeit kein Flankenwechsel stattgefunden hat, 
setzt Du den Ausgang auf LOW.

Diese loop-Funktion dürfte so weit über 10000 mal pro Sekunde 
durchlaufen, so dass die Latenzzeit weit unter 100µs liegt.

Falls es Dir zu langsam ist, kannst Du Zeit rausholen, indem Du die 
recht langsamen Arduino-Komfortbefehle digitalRead und digitalWrite (die 
je ca. 4-5µs dauern) durch schnellere direkte Portzugriffe ersetzt.

Oder Du zusätzlich doch noch eine Interruptverarbeitung einbaust und den 
Ausgang innerhalb der ISR-Verarbeitung toggelst.
1
#define EINGANGSPIN 2  
2
#define AUSGANGSPIN 13 // die Pin-13 LED
3
4
void setup() {
5
  pinMode(EINGANGSPIN,INPUT);
6
  pinMode(AUSGANGSPIN,OUTPUT);
7
}
8
9
boolean lastState;
10
unsigned long lastToggle;
11
void loop() 
12
{
13
  boolean state=digitalRead(EINGANGSPIN);
14
  if (state!=lastState) // Flankenwechsel
15
  {
16
    lastState=state;
17
    if (state==HIGH) // Flanke hat auf HIGH gewechselt
18
    {
19
      digitalWrite(AUSGANGSPIN,!digitalRead(AUSGANGSPIN));
20
      lastToggle=micros();
21
    }
22
  }
23
  else if (micros()-lastToggle>300000L)
24
  {
25
    digitalWrite(AUSGANGSPIN,LOW);
26
  }
27
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.