Wie funktioniert der Call Stack während eines Interrupts auf AVR?

8

(Speziell für das Arduino Uno ...)

Was passiert mit dem Stack, wenn auf einem AVR-Mikrocontroller ein Interrupt auftritt und ich eine Funktion aufrufe? Inline der Compiler den Code? Zwischenspeichert es den Stapel irgendwo und setzt dann den Stapelzeiger zurück? Hat es einen sekundären Stapel nur für Interrupts?

Soweit ich weiß, ist der Vektor für den Interrupt ein direkter GOTO-Befehl in der Assembly. Ich glaube auch nicht, dass der Mikrocontroller automatisch mit dem Stack herumspielen würde, also wird er wahrscheinlich in Ruhe gelassen. Dies erklärt jedoch immer noch nicht, wie Funktionen während eines ISR funktionieren.

Anonymer Pinguin
quelle
Die Antwort unten ist großartig, nur bitte beachten Sie, dass der Compiler keine Interrupt-Aufrufe einbinden kann, weil ... Es handelt sich um Interrupt-Aufrufe: D
Vladimir Cravero
1
@VladimirCravero Ich meinte Inline die Funktionen innerhalb des Interrupts aufgerufen (wenn ich foo () in der ISR aufrufe, inline es so, dass es nicht wirklich ein Funktionsaufruf ist?)
Anonymer Pinguin
Die folgende Antwort beantwortet Ihre Frage also nicht, oder? Caveman erklärt, was passiert, wenn ein Interrupt auftritt, aber nicht, was passiert, wenn eine Funktion in einem unterbrochenen Kontext aufgerufen wird. Die Antwort auf Letzteres wäre: Nichts Besonderes, die Funktion wird aufgerufen und das wars. Die Magie entsteht, wenn der Interrupt ausgelöst wird. Was danach (vor dem iret) passiert, ist nur normaler, normalerweise ununterbrochener Code.
Vladimir Cravero
@VladimirCravero ja, das tut es (indirekt). Ich habe darüber gesprochen, wie der Stack für einen ISR geändert wurde, und dachte, dass er geändert werden muss, um Funktionen überhaupt verwenden zu können. Ich gehe davon aus, dass Funktionen nach dem Einrichten des ISR genauso funktionieren.
Anonymer Pinguin
Nun, du hast es dann verstanden. Nach dem Sprung zum Interrupt-Vektor ist alles in Ordnung und Sie können herumgehen, wo immer Sie wollen.
Vladimir Cravero

Antworten:

16

Der AVR ist eine RISC-Architektur, daher verfügt er über eine ziemlich grundlegende Hardware-Behandlung von Interrupts. Die meisten Prozessoren spielen während Interrupts mit dem Stack, obwohl es einige gibt, insbesondere ARM und PowerPC, die unterschiedliche Methoden verwenden.

In jedem Fall macht der AVR Folgendes für Interrupts:

Wenn ein Interrupt auftritt, führt die Prozessorhardware die folgenden Schritte aus, die nicht nur ein einfaches GOTO sind:

  1. Beenden Sie die aktuelle Anweisung.
  2. Deaktivieren Sie das globale Interrupt-Flag.
  3. Schieben Sie die Adresse der nächsten Anweisung auf den Stapel.
  4. Kopieren Sie die Adresse im richtigen Interrupt-Vektor (entsprechend dem aufgetretenen Interrupt) in den Programmzähler.

Zu diesem Zeitpunkt hat die Hardware alles getan, was sie tun wird. Die Software muss korrekt geschrieben sein, damit nichts kaputt geht. In der Regel erfolgen die nächsten Schritte in diese Richtung.

  1. Schieben Sie das Statusregister auf den Stapel. (Dies muss zuerst erfolgen, bevor es geändert wird).

  2. Schieben Sie alle CPU-Register, die geändert werden (oder werden könnten), auf den Stapel. Welche Register auf diese Weise gespeichert werden müssen, wird vom Programmiermodell festgelegt. Das Programmiermodell wird vom Compiler definiert.

Jetzt kann der Arbeitsinterrupt-Code ausgeführt werden. Um den Fall in der Frage des Aufrufs einer Funktion zu beantworten, tut sie einfach das, was sie immer tut, drückt den Rückgabewert auf den Stapel und legt ihn dann zurück, wenn sie fertig ist. Dies hat keinen Einfluss auf diese vorherigen Werte, die wir bisher auf dem Stapel gespeichert haben.

  1. Führen Sie den ISR-Arbeitscode aus.

Jetzt sind wir fertig und wollen vom Interrupt zurückkehren. Zuerst müssen wir die Software bereinigen.

  1. Pop die CPU-Register, die wir in Schritt 6 verschoben haben.
  2. Fügen Sie den gespeicherten Statuswert wieder in das Statusregister ein. Danach müssen wir darauf achten, keine Anweisung auszuführen, die das Statusregister ändern könnte.
  3. Führen Sie den RTI-Befehl aus. Die Hardware führt die folgenden Schritte für diese Anweisung aus:

    ein. Aktivieren Sie das globale Interrupt-Flag. (Beachten Sie, dass mindestens ein Befehl ausgeführt werden muss, bevor der nächste Interrupt berücksichtigt wird. Dadurch wird verhindert, dass schwere Interrupts die Hintergrundarbeit vollständig blockieren.)

    b. Geben Sie die gespeicherte Absenderadresse in den PC ein.

Jetzt kehren wir zum normalen Code zurück.

Beachten Sie, dass es einige Punkte gibt, an denen wir sehr vorsichtig sein müssen, insbesondere in Bezug auf das Statusregister und die Speicherregister, die geändert werden könnten. Wenn Sie einen C-Compiler verwenden, wird dies alles zum Glück unter der Decke erledigt.

Außerdem müssen Sie auf Ihre Stapeltiefe achten. Zu jedem Zeitpunkt, an dem Interrupts aktiviert sind, kann ein ISR mehr vom Stapel verwenden, als anhand des lokalen Codes ersichtlich ist. Natürlich kommt dies nicht viel auf, es sei denn, Sie bringen Ihr Gedächtnis an seine Grenzen.

Hier ist ein Link, der diesen Prozess beschreibt, wenn Sie eine Referenz wünschen.

Höhlenmensch
quelle
Was ist der Zweck der Schritte 5/6? Es erscheint mir albern, die Register nicht direkt zu ändern, obwohl ich denke, dass das Durcheinander mit einigen Registern während Interrupts zu bösen Ergebnissen führen kann.
Anonymer Pinguin
1
Die Schritte 5 und 6 speichern den aktuellen Status (vor dem Interrupt) des Systems, sodass er nach Abschluss des Interrupts wiederhergestellt werden kann (Schritte 8 und 9), damit das Hauptprogramm fortgesetzt werden kann, als wäre es nicht unterbrochen worden.
Peter Bennett
1
Dies ist eine hervorragende Zusammenfassung.
Connor Wolf
3
Es ist auch erwähnenswert, dass Sie, wenn Sie wirklich, wirklich wissen, was Sie tun, das automatische Speichern des Status und anderer Register mit einem GCC-Flag ( ISR_NAKED) deaktivieren können . Auf diese Weise können Sie die Schritte 5, 6, 8, 9 in einem Kontext überspringen, in dem Sie diese Zyklen wirklich benötigen. Der Nachteil ist, dass Sie absolut sicher sein müssen, dass Sie entweder alle relevanten Register beibehalten oder sie ohne Schaden überschreiben können.
Connor Wolf
2
Das ist interessant zu wissen, aber ich würde Assembler-Programmierung durchführen, bevor ich dieses Flag verwende. So gefährlich ...
Höhlenmensch