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
voidinterrupt(){
2
//some stuff here
3
}
4
5
6
voidloop(){
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 :)
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
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.
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...
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.
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...
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.
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.
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?
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
unsignedlonglastChange;
2
3
voidsetup()
4
{
5
lastChange=millis();
6
}
7
8
voidloop()
9
{
10
unsignedlongnow=millis();
11
12
if(now-lastChange>300){
13
setzeallesauf0
14
lastChange=now;
15
}
16
17
if(FlankeanTakteingang){
18
Musterverä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.
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.
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.
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?
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()
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 :)
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.
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.