Nested Interrupts (eingebettete Interrupts)

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Nested Interrupts (eingebettete Interrupts)

      Ich möchte hier nochmal auf das Thema 'Nested Interrupts' eingehen.

      Das Thema kam bei einem anderen Thema auf.
      Hier der Link: PCINT Attiny Flankenerkennung

      Was sind nested Interrupts. Das sind eingebettete Interrupts, oder auch verschachtelte Interrupts genannt.
      Das bedeutet, wenn ein Interrupt ausgelöst wird und die entsprechende ISR-Routine in Abarbeitung ist, dass diese unterbrechbar ist durch einen anderen Interrupt. Ist diese ISR abgearbeitet, wird zur vorherigen ISR zurückgekehrt und diese weiterbearbeitet.

      Ich habe dieses Thema bereits im Lexikon schon einmal beschrieben (Interrupts Unterbrechbar)

      Es wurde teilweise behauptet, dass AVR's aus aus Gründen seiner Architektur verschachtelte Interrupts (nested Interrupts) nicht ausführen können. Das ist jedoch aus meiner Sicht schlichtweg falsch.

      Der AVR kann nested Interrupts ausführen, egal ob man in C, C++, Assembler oder in Bascom programmiert. Das hat nichts mit der Programmiersprache zu tun. Die werden genauso unterstützt oder nicht unterstützt wie normale Interrupts. Der Assembler-Programmierer weiß, das bei normalen Interrupts (nicht nested Interrupts) die Registersicherung selber gemacht werden muss. Nichts anderes ist es bei Nested Interrupts.

      Bascom übernimmt die Sicherung und Restaurieruung der Register in den ISR_Routinen. Außer man gibt bei der Konfiguration des Interrupt-Vectors explizit NoSave an (On Interrupt Routine [NoSave]).

      Ob ein Interrupt Unterbrechbar ist oder nicht, hängt nur davon ab, ob man in der ISR_Routine, die unterbrechbar sein soll, explizit angibt, ob Interrupts zugelassen werden oder nicht.

      Und jetzt werde ich auch den Beweis antreten (also keine Behauptung aufstellen), dass der AVR Ebenso in Basic Nested Interrupts unterstützt.

      Hier zunächst das Programm das explizit getestet wurde und funktioniert:

      BASCOM Source Code

      1. ' Nested Interrupt mit AVR
      2. ' Nested Interrupts (eingebettete Interrupts) nennt man das Verhalten,
      3. ' wenn eine aktive Interrupt-Service-Routine (ISR) durch einen weiteren Interrupt
      4. ' unterbrochen werden kann.
      5. ' Einige sind der Meinung, dass AVR-Controller aus Gründen der Architektur
      6. ' nested Interrupts nicht ausführen können. Dies ist jedoch ein Trugschluss.
      7. ' Anschlüsse:
      8. ' Taster an Pin INT0 gegen Masse
      9. ' Status-Ausgabe über UART an PC / Terminal (19200 Baud)
      10. ' Funktionsweise
      11. ' Mit Timer2 wird ein regelmäßiger Interrupt (OC2-Interrupt) mit 1kHz ausgelöst.
      12. ' In der ISR_1ms wird ein Counter inkrementiert, der die ms Zählt.
      13. ' Der Counter kann mit Funktion Millis() ausgelesen werden.
      14. ' Taster löst bei neg. Flanke einen INT-Interrupt aus.
      15. ' Die ISR liest zu Beginn und am Ende mittels Function Millis()
      16. ' den ms-Counter aus. Dazwischen wird 1000ms Mittels Wait gewartet.
      17. ' Die Ausführungsdauer von INT0 beträgt also 1000ms.
      18. ' In der ISR wird noch ein Flag gesetzt, damit in der Hauptschleife
      19. ' Notitz von der ausgeführten ISR_INT0 Routine genommen wird.
      20. ' In der ISR_INT0 wird explizit mit 'Enable Interrupts' die Unterbrechung
      21. ' der ISR zugelassen, wodurch der MS-Counter (Timer-Interrupt) weiterzählen kann.
      22. ' Im Hauptprogramm wird die ausgeführte ISR_INT0 furch das gesetzte Flag erkannt.
      23. ' Dies führt zur Berechnung und Ausgabe per UART der Ausführungsdauer in ms.
      24. ' Wenn AVR-Controller also Nested Interrupts nicht zulassen, dürfte während
      25. ' der Ausführung von ISR_INT0 der msCounter nicht weiter laufen.
      26. ' Die Ausgabe würde dann 'Ausführungsdauer: 0ms' ergeben.
      27. ' Das ISR_INT0 durch 'Enable Interrupts' unterbrechbar gemacht wurde läuft der
      28. ' msCounter weiter und die Ausgabe dürfte einen Wert von ca. 1000ms ergeben.
      29. ' Gegenprobe:
      30. ' Zeile 'Enable Interrupts' in ISR_INT0 auskommentieren.
      31. ' Dann werden keine Interrupts mehr ausgelöst, während ISR_INT0 ausgeführt wird.
      32. $Regfile = "m8def.dat"
      33. $HWStack = 80
      34. $SWStack = 20
      35. $Framesize = 30
      36. $Crystal = 8000000 ' Quarz
      37. $Baud = 19200
      38. Waitms 1000
      39. ' --------------------------------------------------------
      40. ' Variablen
      41. ' --------------------------------------------------------
      42. Dim Flags as Byte
      43. Const FLAG_INT0 = 1 ' Ext. Interrupt wurde ausgelöst
      44. Dim msStart as Long ' Zeitstempel Start-Zeit [in ms]
      45. Dim msEnd as Long ' Zeitstempel End-Zeit [ms]
      46. Dim msDauer as Long ' Berechnete Dauer [ms]
      47. ' interne Variablen
      48. Dim _msCounter as Long ' intern, msCounter
      49. ' --------------------------------------------------------
      50. ' Deklarationen
      51. ' --------------------------------------------------------
      52. Declare Function Millis() as Long ' liest msCounter aus
      53. ' --------------------------------------------------------
      54. ' Initialisierung
      55. ' --------------------------------------------------------
      56. Set PortD.2 ' INT0 PullUp einschalten
      57. ' Timer als RTC-Timer
      58. Config Timer2 = Timer , Prescale = 32 , Clear Timer = 1
      59. Compare2 = 250 - 1 ' 1000Hz einstellen
      60. ' 1ms-ISR aktivieren
      61. On OC2 ISR_1ms
      62. Enable OC2
      63. ' Taster-Interrupt
      64. Config INT0 = Falling
      65. On INT0 ISR_INT0
      66. Enable INT0
      67. Enable Interrupts ' Global Interrupts zulassen (I-Flag setzen)
      68. Print
      69. Print "Testprogramm Nested-Interrupts"
      70. ' --------------------------------------------------------------------------------------
      71. ' Hauptschleife
      72. ' --------------------------------------------------------------------------------------
      73. Do
      74. If Flags.FLAG_INT0 = 1 then ' INT 0 wurde ausgelöst
      75. msDauer = msEnd - msStart ' Bechechnung der Dauer
      76. Print
      77. Print "INT0 wurde ausgelöst"
      78. Print "Start-Zeit: " ; msStart ; "ms"
      79. Print "End-Zeit: " ; msEnd ; "ms"
      80. Print "Ausführungsdauer: " ; msDauer ; "ms"
      81. Print
      82. Reset Flags.FLAG_INT0 '
      83. End If
      84. Loop
      85. ' --------------------------------------------------------
      86. ' Dieser Interrupt wird durch Timer2 (OC2-Interrupt) 1000x pro Sekunde aufgerufen
      87. ' Hier wird die Variable _msCounter alle ms um 1 erhöht.
      88. ' So sind Zeitmessungen im ms-Bereich möglich
      89. ' --------------------------------------------------------
      90. ISR_1ms:
      91. Incr _msCounter
      92. Return
      93. ' --------------------------------------------------------
      94. ' Dieser Interrupt wird mit Taster (Low-Flanke) aktiviert
      95. ' --------------------------------------------------------
      96. ISR_INT0:
      97. msStart = Millis() ' Zeitstempel holen
      98. Enable Interrupts ' INT0 soll unterbrechbar sein
      99. Wait 1
      100. Set Flags.FLAG_INT0 ' Dem Hauptprogramm einen absolvierten INT0 Interrupt mitteilen
      101. msEnd = Millis() ' Zeitstelpel holen
      102. Return
      103. ' --------------------------------------------------------
      104. ' Gibt aktuellen Wert von ms-Counter zurück
      105. ' --------------------------------------------------------
      106. Function Millis() as Long
      107. !PUSH r24 ' Statusregister retten
      108. !IN r24,SREG
      109. !PUSH r24
      110. !CLI ' Interrupts sperren (I-Flag=
      111. Millis = _msCounter ' Statusregister restaurieren (auch I-Flag)
      112. !POP r24
      113. !OUT SREG,r24
      114. !POP r24
      115. End Function
      Display All

      Die Funktionsweise ist im Code erklärt.

      Aber Kurz zusammengefasst:
      Ein Timer löst alle 1ms einen Interrupt aus, der die ISR_1ms (Interrupt-Service-Routine) aufruft.
      Darin wird ein ms-Zähler hochgezählt.

      Ein Taster (Low-Aktiv mit aktiviertem PullUp) am INT0 Eingang löst den INT0-Interrupt aus und ruft die ISR_INT0 auf.
      Darin ist ein Waitms 1000. Bedeutet die ISR_INT0 benötigt mindestens 1000ms zur Abarbeitung.

      Wenn Nestet Interrupts nicht funktionieren, kann während die ISR_INT0 in Bearbeitung ist, der ms-Zähler nicht weiter laufen.
      Dann würde in der Hauptschleife als Dauer 0ms heraus kommen.

      Der entscheidende Unterschied, ob nested oder nicht ist die Zeile 130. Wird "Enable Interrupts" auskommentiert, hat man "normale" Interrupts.

      In der ISR_INT0 wird bei Eintritt der ms-Counter ausgelesen und vor dem beenden auch.
      In der Hauptschleife werden dann per UART die ms-Couter-Werte und die Dauer ausgegeben.

      Die Ausgabe im Terminal siht dann etwa so aus:

      Source Code

      1. Testprogramm Nested-Interrupts
      2. INT0 wurde ausgelöst
      3. Start-Zeit: 4716ms
      4. End-Zeit: 5735ms
      5. Ausführungsdauer: 1019ms
      6. INT0 wurde ausgelöst
      7. Start-Zeit: 6744ms
      8. End-Zeit: 7763ms
      9. Ausführungsdauer: 1019ms
      10. INT0 wurde ausgelöst
      11. Start-Zeit: 8663ms
      12. End-Zeit: 9682ms
      13. Ausführungsdauer: 1019ms
      14. INT0 wurde ausgelöst
      15. Start-Zeit: 10154ms
      16. End-Zeit: 11173ms
      17. Ausführungsdauer: 1019ms
      Display All

      Ich hoffe nun, dass das Thema, ob Nested Interrupts beim AVR funktionieren oder nicht, nun gegessen sein dürfte.
    • Hallo Mitch,
      nur weil es geht, macht es meiner Meinung nach nicht unbedingt Sinn. Wäre mir viel zu komplex und risikoreich.
      Auch scheint mir dein Beispiel ziemlich konstruiert. Du hast das jetzt wahrscheinlich nur mal kurz hingeschrieben, sodass da einige Dinge sind, die Probleme machen würden.
      Ich habe schon mehrere größere Programme geschrieben, auch solche wo fast jeder Interrupt drin vorkommt, den die AVRs bieten. Aber die Notwendigkeit, nested Interrupts nutzen zu müssen, war noch nie gegeben.
      Wenn ich wirklich mal in einer ISR wissen möchte, ob ein anderer Interrupt aufgetreten ist und dann dessen Aktion vorziehen möchte, dann würde ich ganz sicher nicht die Interrupts enablen, sondern das Interrupt Flag abfragen und dann die Aktion des anderen Interrupts ausführen. Dazu allerdings nicht einfach die ISR anspringen, sondern alle Anweisungen der anderen ISR in eine Sub packen und diese ausführen.
      Aber das ist nur meine Meinung.
    • Ob das überhaupt funktioniert, innerhalb einer ISR eine andere ISR auszuführen (RCALL oder CALL) per direktem Aufruf ist fraglich. Denn die schleiß ab mit RETI. Dadurch ist das I-Flag wieder gesetzt und deine ISR die noch immer in Abarbeitung ist, ist plötzlich unterbrechbar.

      Ich gebe dir recht, es mag ein konstruiertes Beispiel sein, mir ist aber nichts besseres eingefallen. Es ging mir ja darum, einen funktionsfähigen Code zu zeigen, wie man das macht. Ob risikohaft oder nicht, das entscheidet schlussendlich der Programmierer. Er muss Sorgfalt walten lassen, dass es nicht zu unerwünschten Seiteneffekten kommt.

      Ebenso bestimmt dieser, ob für eine Anwendung nested Interrupts notwendig sind oder nicht.
      Und da gebe ich dir auch recht. In der Regel ist es nicht notwendig.

      Aber will man tatsächlich mal die Laufzeit einer ISR messen, wäre das eine Möglichkeit.
      In dem Fall würde ich die ISR_1ms aber dann in Assembler schreiben.

      War nur als Demo gedacht.
    • Du hast nicht verstanden, wie ich das machen würde.

      ISR1:
      Gosub ISR1_Befehle
      Return

      ISR2:
      ...
      If "Flag_von_ISR1"=1 then Gosub ISR1_Befehle
      ...
      Return

      ISR1_Befehle:
      ...
      Return

      Das ist meiner Meinung nach sauberer.
      Um die Laqufzeit einer ISR zu messen gibt es sicherlich eine Menge anderer Möglichkeiten, angefangen vom Simulator bis zur Abfrage des Timerwertes tatsächlich während der Laufzeit. Dazu braucht es keine nested Interrupts.