ich war bislang der Meinung, dass die Interrupts per per sei() freigegeben werden (ist klar),
SEI
ist eigentlich ein Assembler Mnemonic, und ist die Abkürzung für "Set Global Interrupt Flag". BASCOM (und sicher auch andere Hochsprachen) bedient sich nur des Assembler-Befehls.
Der tatsächliche Maschinencode von SEI wäre 1001 0100 0
111 1000.
Noch deutlicher wird's wenn man weiß (
), daß
1001 0100 0xxx 1000
der Maschinencode für "Bit Set in SREG" (BSET) ist, wobei "xxx" das zu setzende Bit festlegt.
SEI setzt also Bit7 im Statusregister (und das ist eben das Globale Interrupt Flag "I"), nichts weiter.
Das SREG wiederum wird (z.B.)im Datenblatt des Mega8 (S.11) erklärt, zu "I"-Flag steht da unter anderem:
If the Global Interrupt Enable Bit is cleared, none of the interrupts are enabled independent of the individual Interrupt enable Settings.
Ein nicht gesetztes "I" blockiert also generell (global) alle IRQs, auch wenn die für sich (lokal) enabled sein sollten.
und ein 2.ter Interrupt, der während der Ausführung der ISR des 1.ten Interrupts auftritt, gespeichert und nach Beendigung der Ausführung der ISR des 1.ten Interrupts ausgeführt wird (da gespeichert) und dann wieder die Interrupts global freigegeben werden.
Tritt während der Ausführung der ISR des 1.ten Interrupts zusätzlich ein weiterer Interrupt auf, wird dieser "verworfen" (nicht gespeichert).
Nein, da wird nicht wirklich was gespeichert (also nicht so, wie Du es möglicherweise denkst). Eigentlich ist es ganz einfach:
Der Mega8 hat zB (abgesehen vom Reset, was kein Interrupt ist) 18 mögliche Interruptquellen (siehe Datenblatt Seite 46).
Jede dieser Quellen besitzt irgendwo sowas wie ein eigenes (lokales) Interrupt Enable Bit.
Jede dieser Quellen besitzt außerdem auch irgendwo sowas wie ein Interrupt-Anforderungsflag. Das muß nicht immer ein Bit in einem I/O-Register sein.
Die Interrupt-Anforderungsflags werden meist durch die Quelle bei entsprechenden Ereignissen gesetzt, und bleiben dann gesetzt.
Ist gleichzeitig das lokale Enable-Bit und das "I" in SREG gesetzt, wird der Interrupt bei der nächsten Gelegenheit behandelt. Das heißt, das Programm wird "unterbrochen": "I" wird gelöscht, der Programmzähler (zeigt normalerweise auf die nächste Instruktion im Programmspeicher) wird automatisch auf den Stack kopiert, Der Programmzähler wird "auf den Interruptvektor" gesetzt (=mit der Adresse beladen). Dort steht dann üblicherweise (bei Hochsprachen quasi immer, bei ASM kann jeder machen, was er will) ein Sprunginstruktion in die ISR.
Die ISR wiederum wird (üblicherweise) mit einem speziellen "Return from Subroutine" beendet, bei der nebenbei auch das "I" wieder gesetzt wird (RET nimmt die Adresse vom Stack, und springt dahin - RETI macht dasselbe, und setzt nebenbei das "I".
Ganz klar ist mir das bei BASCOM noch nicht - das Return am Ende einer Subroutine wird eigentlich in RET übersetzt, zeigt ein angelegter Interruptvektor ("On Interrupt...") auf die Adresse, wird es stattdessen in RETI übersetzt.
Ok, es gibt also Interruptquellen die ein richtiges Interrupt-Anforderungsflag haben (z.B. die Überlaufs- und Compare-Ereignisse der Timer, diverse "hab-fertig-Ereignisse" bei seriellen Schnittstellen, dem ADC usw), da zieht das Ereignis salopp gesagt die Fahne hoch, und die bleibt oben bis irgendwer sie wieder runterzieht.
Viele dieser Fahnen werden automatisch beim "behandeln" des Interruptes (also während des "Sprunges" in die IVT) durch die "Interrupt-Maschine" wieder runtergezogen (die Timer-Interrupts z.B.) - aber eben nicht alle, der TWI-Interrupt zB nicht.
Die meisten dieser Fahnen können aber "zu Fuß" wieder runtergezogen werden (indem man das Flag selbst mit einer "1" beschreibt (auch hier gibt's Ausnahmen).
Nehmen wir also mal an, der Timer wäre übergelaufen. Dann würde automatisch das TOV-Flag gesetzt werden (egal, ob "I" oder das lokale Enable-Bit gesetzt ist).
Nehmen wir weiter an, beide Bits sind gesetzt, dann würde also die korrespondierende ISR angesprungen werden, "I" wird vorerst gelöscht (Interrupts global gesperrt), Außerdem wird das TOV-Fähnchen runtergezogen.
Was passiert also, wenn der Timer nochmal überläuft, bevor die ISR beendet wurde?
Da "I" gelöscht ist, erstmal nichts, TOV bleibt oben. Egal ob der Timer währenddessen einmal, dreimal oder hundertmal überläuft - TOV bleibt gesetzt.
Wenn aber die ISR beendet wird, und "I" wieder gesetzt wird, schlägt der TOV sofort wieder zu, klar? (abgesehen von anderen gesetzten Interrupt-Fähnchen, die 'ne höhere Priorität haben, die sind dann vorher dran, und Dein TOV bleibt weiterhin oben, bis es irgendwann dann doch mal behandelt wird).
Neben diesen Quellen mit richtigem Interrupt-Anforderungsflag (also einem tatsächlichen Bit in irgendeinem I/O-Register) gibt es aber auch Quellen, bei denen irgendein Zustand oder Pegel den Interrupt anfordert. Der Low-Level-Interrupt bei den Externen IRQs zB. Solange das Bein Low ist, wäre hier die Fahne oben, ist das Bein nicht low, ist die Fahne unten. Dieser Zustand wird also nicht in irgendeinem Bit "gespeichert", wenn des Bein wieder hochgeht, bevor der Interrupt behandelt werden konnte (Sprung in die IVT), weil zB "I" grad nicht gesetzt war, wird der Interrupt nicht erkannt/ausgelöst. Die Fahne ist wieder unten.
Ein anderes Beispiel ist das UART-Data-Register-Empty-Flag. UDRE ist immer gesetzt genau dann wenn der Transmit-Buffer des UART leer ist. Wenn also der korrespondierende Interrupt lokal ("UDRIE") und global("I") freigegeben ist, und man nichts sendet, würde UDRE ununterbrochen triggern...
Stichwort "nested interrupts"
Bisher sind wir davon ausgegangen, daß während der Behandlung eines Interruptes "I" gelöscht ist, und weitere Unterbrechungen somit global blockiert sind. Etwaige Ereignisse ziehen ihre Fähnchen im Hintergrund trotzdem hoch, "speichern" die Anforderung (mehr oder weniger dauerhaft) in Form ihrer Anforderungs Flags, bis "I" wieder frei ist.
Und jetzt setzt Du selbst mal innerhalb der ISR das "I". Was geschieht (wenn währenddessen ein weiteres Interrupt-Fähnchen oben ist)?
Genau, der Interrupt wird sofort durch den nächsten IRQ unterbrochen (welcher erstmal seinerseits "I" wieder löscht - aber auch da kannst Du "I" ja wieder setzen)
Wird dieser zweite Interrupt beendet, kehrt der Controller wieder zum Unterbrechungspunkt zurück, was ja der erste Interrupt war. Und wird dieser beendet, bist Du wieder zurück im Hauptprogramm.
Die Interrupts sind also verschachtelt (nested). Ein Interrupt kann sich so insbesondere auch selbst unterbrechen.
Wie oft geht das, gibt's da 'ne Grenze?
Bei jeder Unterbrechung wird wie gesagt die Rücksprungadresse auf den Stack gelegt, und bei der Rückkehr wieder runtergenommen. Der Stack liegt im SRAM (i.a. am Ende, und wächst nach vorn), jede Adresse belegt (beim Mega8) ein Word, also zwei Bytes. Der Mega8 besitzt 1KByte SRAM, also 512 Words. Mit jeder weiteren Verschachtelungstiefe wächst der Stack also um mindestens dieses Adressword, und um das, was die ISR so nimmt. Bei Bascom wird zB jedesmal SREG auf den Stack gelegt, und diverse Rechenregister. Von "vorne" legt Bascom seinen "SoftStack" und den "Farmesize" an, und dann alle Variablen. Wenn der (Hardware-)Stack in diesen Bereich hineinwächst, gibt's lustige (und schwer lokalisierbare) Fehlerbilder.
Aber auch, wenn dem Stak der ganze SRAM zur Verfügung steht, ist bei 512 Words Schluß. Der Stackpointer würde dann theoretisch in die I/O-Register wandern, ein Push auf den Stack verpufft im Nirvana, ein Pop holt 'ne Null vom "Stack".
Die Priorisierung der Interrupts ist ja festgelegt
Es wird einfach immer derjenige aktive Interrupt mit der niedrigsten Adresse in der IVT abgearbeitet. Insbesondere kann also ein dauertriggernder Interrupt alle anderen (und das Hauptprogramm) ausbremsen.
Nein.
Also zumindest bei den klassischen AVR nicht.
Bei den X-Core-AVR kannst Du einigen Interrupts salopp gesagt 'ne höhere Priorität zuweisen - da wird aber das "I" auch nicht mehr Automatisch gelöscht usw. Da müßte ich mich aber selbst erst einarbeiten, falls Dich das wirklich interessiert … Mikro23 hat da sicher mehr Ahnung von - meiner Meinung nach verunsichert Dich das aber im Moment eher...