Hallo,
laut dem Artikel zu Interrupts hier wird der loop Code bei Interrupt
unterbrochen, der ISR Code ausgeführt und dann fortgesetzt.
Wie verhält es sich, wenn der loop Code gerade mit delay pausiert ist
und ein Interrupt auftritt? Wird der Code dann mit dem Befehl nach delay
fortgesetzt oder wird die restliche Zeit nach Unterbrechung pausiert?
Danke für alle Antworten
Matthias
Matthias schrieb:> Wird der Code dann mit dem Befehl nach delay> fortgesetzt oder wird die restliche Zeit nach Unterbrechung pausiert?
Hallo,
Gemeint ist Maschinencode. Maschinencode "delay" gibt es nicht. Programm
wird ab nächster Maschinenoperation fortgesetzt. Mit welchen
Maschinencoden wird "delay" bei dir realisiert, solltest du in
Disassembler kucken.
http://www.avr-asm-tutorial.net/avr_de/beginner/commands.html
Matthias schrieb:> laut dem Artikel zu Interrupts hier wird der loop Code bei Interrupt> unterbrochen, der ISR Code ausgeführt und dann fortgesetzt.> Wie verhält es sich, wenn der loop Code gerade mit delay pausiert ist> und ein Interrupt auftritt? Wird der Code dann mit dem Befehl nach delay> fortgesetzt oder wird die restliche Zeit nach Unterbrechung pausiert?
delay ist kein befehl.
delay kann auf verschiedene Weise implementiert sein, die dazu führen
können das während des ISR der delay-"timer" weiterläuft ... oder auch
nicht.
https://youtu.be/5ZLtcTZP2js?t=114
Wenn das Delay aus der bei avrlibc-üblichen "Verbrauch n-Clock-Cycles"
Warteschleife besteht, dann wird es um die Interrupt-Laufzeit länger.
Für exakte Verzögerungen könnte man z.B. einen Timer benutzen und in
einer Schleife auf eines von dessen "Interrupt-Flags" warten. Ohne
allerdings diese Interrupts freizuschalten. Läuft der μC mit 1MHz und
Timer0 läuft mit Teiler 1, dann setzt er nach 256μs das
Timer0-Overflow-Flag.
Matthias schrieb:> void loop() {> digitalWrite(PIN, HIGH);> delay(10000);> digitalWrite(PIN, LOW);> }
Hallo,
das ist C-Code. Sie wird vom Compiler in Maschinencode umgesetzt. Die
Maschinencode ist, womit Mikrocontroller arbeitet. Er hat keine Ahnung
von loop und von delay - das ist nur für deine Bequemlichkeit so
geschrieben. Nach ISR wird nächste Maschinencode gemacht.
Willst du wirklich etwas außer "loop" machen, mit ISR usw, dann solltest
du bessere Vorstellung von Mikrocontroller haben. Kenntnisse nur von
Arduino-IDE reichen nicht aus, du solltest wissen, was C ist und was
Assembler ist. Arduino ist für einfache Sachen konzipiert, für Menschen,
die nichts außer "loop" machen wollen.
Bei Arduino wird der delay nach dem Abarbeiten des Interrupts
fortgesetzt. Das heißt, deine gewünschten 10.000ms sind nur eine
"mindestens" Angabe, das Delay kann beliebig länger dauern.
Außerdem wird der Pin auch ohne Interrupts nicht exakt im 10s Raster
umgeschaltet, weil dir digitalWrite() Aufrufe und der Rücksprung zum
Anfang der loop auch Zeit kosten.
In deinem Code-Beispiel fehlt vermutlich ein zweites Delay ganz unten.
Wenn du es genau haben willst, musst du einen Timer verwenden. Zum
Beispiel:
1
unsignedlongzeit;
2
3
voidsetup()
4
{
5
zeit=millis();
6
}
7
8
voidloop()
9
{
10
digitalWrite(PIN,HIGH);
11
12
while(millis()-zeit<10000){};
13
zeit+=10000;
14
15
digitalWrite(PIN,LOW);
16
17
while(millis()-zeit<10000){};
18
zeit+=10000;
19
}
Selbst wenn die while Schleife tatsächlich länger als 10s dauert,
beginnt das nächste Intervall dennoch exakt 10s später weil die Variable
Zeit immer exakt auf 10s weiter gesetzt wird.
Matthias schrieb:> Wie verhält es sich, wenn der loop Code gerade mit delay pausiert ist> und ein Interrupt auftritt?
Guck es dir einfach an.
Setzte vor dem Aufruf von delay() einen IO-Pin auf High und danach
wieder auf Low. Das Gleiche machst du für einen anderen IO-Pin am Anfang
und Ende der Interrupt-Routine. Den Signalverlauf an den beiden IO-Pins
zeichnest du mit einem einfachen Logikanalysator(*) auf. Dann kannst du
nach herzenzlust rummessen.
(*) z.B. https://www.ebay.de/itm/273246600838
Kommt drauf an, wie das Delay implementiert ist. Wenn mit einem Timer,
dann verlängert sich das Delay nicht. Wenn mit einer Zählschleife, dann
addiert sich die Zeit des Interrupts. Da typisch Interrupts <1% der
CPU-Zeit benötigen, ist das in der Regel kein Problem. Eine genaue Uhr
kann man mit Delay eh nicht machen.
> Sind die ersten Gehversuche in dieser Materie ;-)
Das sieht man. Dieser Code wird vermutlich nicht das tun, was du
erwartest. Der Pin wird zwar am Ende der Schleife auf LOW gesetzt, aber
unmittelbar darauf am Anfang der Schleife wieder auf HIGH. Weder mit dem
Auge noch mit den meisten Meßmitteln [1] wirst du überhaupt sehen, daß
der Pin alle 10 Sekunden mal für ein paar Mikrosekunden auf LOW geht.
[1] keine Change mit jeglichem Multimeter. Auch ein Oszilloskop hilft
nur bedingt. Am besten ist noch ein Logikprüfstift mit
Impulsverlängerung.
Wolfgang schrieb:> Axel S. schrieb:>> Auch ein Oszilloskop hilft nur bedingt.>> Quatsch, umgehen können muss man mit dem Oszilloskop allerdings schon.
Axel hat vielleicht an einen µC mit GHz-Takt gedacht. Dann sind die
Ansprüche an einen Oszi schon ziemlich groß und das "bedingt" passt ;-)
Matthias schrieb:> Wird der Code dann mit dem Befehl nach delay> fortgesetzt
Das würde bedeuten, dass der Interrupt erst nach Abarbeitung des Delays
auftreten kann und das wäre ein schwerer Softwarefehler. Der Interrupt
könnte so ja sekundenlang verzögert werden. Nein, der Interrupt muss den
Delay mittendrin unterbrechen (deswegen heisst er Interrupt, siehe auch
Coitus Interruptus).
Ich würde aber nicht die Hand dafür irgendwohin legen dass das immer so
ist, besonders wenn es sich um eine selbstgestrickte Delay-Routine
handelt.
Nach dem Interrupt wird der Rest des Delay abgearbeitet. Damit der
möglichst wenig verfälscht wird darf die Interrupt-Bearbeitung nur sehr
kurz laufen.
Georg
Wolfgang schrieb:> Guck es dir einfach an.
Genau, einfach mal in den SourceCode von delay() schauen. Wenn man nicht
die Arduino IDE sondern Visual Studio Code mit PlatformIO verwendet geht
das sehr einfach mit einem Mausclick, ansonsten muss man eben mit grep
suchen und wird dann hier fündig:
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c#L106:
void delay(unsigned long ms)
{
uint32_t start = micros();
while (ms > 0) {
yield();
while ( ms > 0 && (micros() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
Georg schrieb:> Das würde bedeuten, dass der Interrupt erst nach Abarbeitung des Delays> auftreten kann
Wenn das so sein muß - es gibt nichts einfacher.
Vor delay cli() schreiben und danach sei().
Die Funktionen '_delay_ms() und '_delay_us()' der AVRLib sind, sofern
sie nicht in der ISR selber stehen, immer unterbrechbar. ISR beim AVR
sind ja ohne besondere Massnahmen 'blockierend', d.h., sie können selber
nicht mehr unterbrochen werden. Ein '_delay_ms()' in einer ISR ist
deswegen tabu.
AVRGCC und die AVRLib erlauben allerdings unterbrechbare Sonderformen
der ISR.
Maxim B. schrieb:> Wenn das so sein muß - es gibt nichts einfacher.> Vor delay cli() schreiben und danach sei().
Das ist keine gute Idee:
Du bringst die Zeitverwaltung durcheinander, da die von millis() und
micros() zurückgelieferte Zeit nicht mehr durch die Timer IRQs erhöht
wird und quasi stehen bleibt. Möglicherweise funktioniert delay() dann
überhaupt nicht mehr, da es selbst micros() verwendet.
IRQs dürfen immer nur so kurz wie möglich gesperrt werden.
Michael
Michael D. schrieb:> Das ist keine gute Idee:> Du bringst die Zeitverwaltung durcheinander, da die von millis() und> micros()
Ich bin der Meinung, daß IDE Arduino in sich keine gute Idee ist, da
keine Debug-Mittel integriert. Weder JTAG-Emulator noch Simulator sind
möglich. Wenn man so IDE benutzt, kann man kaum noch etwas schlechter
machen.
Hi
Nun, einige Antworten sind ja schon in die richtige Richtung, bei
anderen bezweifle ich, das die Schreiber wissen, was ein Interrupt
ist.(gut, es gibt verschiedene Arten, aber bei einem µC ist es so
ziemlich immer gleich)
Ein Programm wird nach Maschinencode abgearbeitet. Maschinencode ist
nicht Assembler oder gar eine andere Sprache, sondern ganz schlicht
elektrische Zustände, gespeichert in einem (elektrisch) adressierten
Speicher ausgedrückt für die Programmierer in "1" und "0". Diese sind
dann 8, 16 oder auch mal 32 Bit breit. Diese Bitmuster werden im
Controller in der Befehlsmatrix zu einem Befehl umgesetzt.
(Oberflächlich erklärt) Nun zum Interrupt. Einige Hardwarekomponenten
sind in der Lage, den Programmablauf zu stören, also einzugreifen und
das Adressregister für die Befehlsspeicher einfach auf einen anderen
Speicherbereich zu setzen. Dort steht dann entweder der Programmcode für
die Interruptbearbeitung, oder einfach ein Rücksprung. Ablauf ist etwa
so:
Interrupt löst aus:
Adresse vom Adressregister für den Programmbereich wird
zwischengespeichert.
Adresse wird auf einen festen Wert passend zum Interrupt im
Programmspeicher gesetzt. (Stichwort Interruptvektortabelle)
Dort steht entweder ein Sprungbefehl zur Bearbeitung
(Interruptserviceroutine oder ISR) mit abschließendem "Return from
Interrupt" oder nur ein "Return from Interrupt" (RETI) der die Adresse
des letzten Befehles wieder in das Adressregister für das Programm
schreibt. (Rücksprung)
Nun ist ein Delay ein Befehl, der entweder in einer Schleife eine Anzahl
unnützer Befehle (NOP) ausführt, um Zeit totzuschlagen oder auch eine
etwas aufwändigere Programmierung über Zeitregister (Timer). Ersteres
ist eine berechnete Anzahl von Wiederholungen, da der Controller für
jede Bearbeitung Zeit benötigt. Etwa so:
Befehl lesen und Adressrregister erhöhen (nächste Befehlsadresse)
Adresse liefert Befehl Befehl: tu nix (NOP)
Dieser Schritt kann auch ausgelassen werden, entsprechend mehr
Schleifendurchläufe braucht es dann.
Weiter:
Befehl lesen und Adressrregister erhöhen (nächste Befehlsadresse)
Adresse liefert Befehl: Erhöhe einen Zähler (INC was auch immer)
Befehl lesen und Adressrregister erhöhen (nächste Befehlsadresse)
Adresse liefert Befehl: Vergleiche Zähler mit Vorgabe (CP was auch
immer)
Befehl lesen und Adressrregister erhöhen (nächste Befehlsadresse)
Adresse liefert Befehl: Sprung wenn Flag (Flag ist ein Bit und wird beim
Vergleich je nach Ergebnis gesetzt)
Entweder wird also wieder mit dieser Befehlsfolge erneut gearbeitet und
Zeit vertrödelt, wenn die vorgegebene Anzahl der Schleifendurchläufe
noch nicht erreicht ist oder die "normale" Programmbearbeitung
fortgesetzt. Ein Interrupt schiebt sich, egal wo, einfach dazwischen und
ob in einer Schleife oder den laufenden Programmcode, (Auch letztlich
eine Schleife) der Controller landet immer wieder beim nächsten Befehl
vor der Interruptauslösung. (Wenn man nicht irgendeinen Blödsinn in der
ISR programmiert....) Allerdings verlängert sich in diesem Fall die Zeit
um die Befehlsverarbeitungszeit in der ISR. Es ist möglich, das ein
Interrupt mehrfach in einer Schleife erzeugt wird und somit auch
entsprechend große Abweichungen der Delay-Zeit entsteht. Das Delay über
Timer erzeugt selbst einen Interrupt mit einer eigenen ISR und bremst
den Programmzyklus nicht aus. In C-Programmen wird die Bearbeitung so
nicht deutlich. Da ich mit C nix am Hut hab, kann ich noch nicht mal
sagen, ob der Programmierer weiß, das ein Delay eine
programmzyklusverlängernde Wirkung hat oder einen Interrupt auslöst. Das
könnt ihr besser.
Gruß oldmaX