Callbacks für Interrupts definieren

Ich arbeite mit einem STM32 und bin etwas verwirrt über Interrupts, insbesondere die Nested Vectored Interrupts (NVI). Soweit ich weiß, gibt es einen NVI -Vektor (genannt NVIC), bei dem jeder Interrupt eine Priorität (manchmal einstellbar) und eine Adresse hat (siehe Seite 157 des ARM-Referenzhandbuchs hier ).

Jetzt gehe ich davon aus, dass es möglich ist, für jede Art von Interrupt eine Callback-Funktion anzuhängen, und ich vermute, dass die an jedem Interrupt angehängte Adresse mit der Adresse des Callbacks zusammenhängt.

Was genau ist die Adresse, die an einen bestimmten Interrupt angehängt ist? Wie kann ich (sagen wir in C) die Callback-Funktion für einen bestimmten Interrupt definieren?

Ich kenne den Arm nicht aus erster Hand, aber viele Prozessoren haben eine spezielle Return-from-Interrupt-Anweisung, die die zusätzlichen Informationen vom Stack abzieht, die normalerweise mit einem Interrupt einhergehen. Nur ein wirkliches Problem, wenn Sie Assembler schreiben; Die meisten C-Compiler erlauben es Ihnen, Ihre Funktion mit dem Wort „Interrupt“ zu schmücken, und der Compiler kümmert sich um die Auswahl des richtigen Exit-Codes.
@JustJeff: Könnten Sie auf eine Dokumentation zu dieser "Unterbrechungs" -Dekoration verweisen?
Es ist normalerweise trivial - stecken Sie einfach das Wort "Interrupt" vor den Rückgabetyp der Funktion. Anstelle von void foo() { ... } hätten Sie also interrupt void foo() { ... }
@JustJeff Nur um anzumerken, dass die Syntax zum Definieren eines Interrupts von der Compiler/Linker-Kombination und nicht vom Zielprozessor abhängt. Beispielsweise können der ARM-GCC-Compiler und der ARM-IAR-Compiler die Syntax für eine Interrupt-Funktion unterschiedlich definieren, obwohl sie Code für denselben Prozessor erstellen.
@AngryEE - Ich glaube nicht, dass ich tatsächlich gesagt habe, dass es von der Zielarchitektur abhängt. Aber ja, die Einzelheiten können je nach den Umständen variieren. Ich wollte nur darauf hinweisen, dass Sie normalerweise etwas tun müssen, da der Stack für Interrupts etwas anders eingerichtet ist als für normale Aufrufe.
github.com/dwelch67 schauen Sie sich mbed, stm32f4d, stm32vld und andere an. Ich weiß, dass stm32f4d/blinker05 einen Interrupt hat. Es ist ein Cortex-m4, aber für das, was Sie fragen, spielt es keine Rolle, die Unterschiede liegen nicht in diesem Bereich.

Antworten (2)

Die ARMs implementieren eine Interrupt-Tabelle, um die Adresse für jeden Interrupt-Handler (oder Rückruf, im Grunde dasselbe) zu speichern. Grundsätzlich werden alle Adressen der Interrupt-Handler im Programmspeicher an einer vordefinierten Stelle gespeichert. Wenn ein Interrupt auftritt, weiß der Prozessor, wo in der Tabelle der Eintrag dieses Interrupts ist, greift ihn und verzweigt zu der dort gespeicherten Adresse.

Wie füllt man diese Tabelle? Normalerweise sind alle diese Informationen in einer Startdatei enthalten. Normalerweise definieren Sie einfach ein konstantes Array von Zeigern und füllen es mit den Adressen der Callbacks, die Sie verwenden möchten. Hier ist eine Website, die beschreibt, wie man eine Startdatei für einen bestimmten ARM-Mikrocontroller erstellt. Das Knifflige daran ist, dass fast alle Einzelheiten zum Erstellen der Datei stark von dem verwendeten Linker, Compiler und Chip abhängen, sodass Sie entweder ein Beispiel finden oder Ihr eigenes erstellen müssen.

der cortex-m3 ist sehr unterbrechungsfreundlich. Sie brauchen kein Trampolin in asm oder müssen eine nicht standardmäßige Compiler-Syntax hinzufügen, damit der Compiler dies für Sie erledigt. Die Hardware passt sich einem abi an, indem sie eine bestimmte Anzahl von Registern für Sie beibehält und den Modus ändert. Das Verknüpfungsregister ist so codiert, dass die Hardware bei der Rückkehr von der Funktion weiß, dass es sich tatsächlich um eine Rückkehr vom Interrupt handelt. All die Dinge, die Sie an einem Arm und vielen anderen Prozessoren tun mussten, müssen Sie also nicht tun.

Ebenso haben der Cortex-m (und andere neuere Arme) bis zu einem gewissen Schmerzniveau eine Zillion Vektoren in der Vektortabelle, Dutzende bis Hunderte von Unterbrechungen usw., wie in einem Kommentar oben erwähnt, siehe http://github.com/ dwelch67/stm32f4d Das blinker05-Beispiel verwendet Interrupts mit einem Timer. Sie können in vectors.s sehen, dass Sie lediglich den Namen der Funktion eingeben:

.word hang
.word tim5_handler
.word hang

Und dann schreiben Sie den C-Code:

//-------------------------------------------------------------------
volatile unsigned int intcounter;
//-------------------------------------------------------------------
// CAREFUL, THIS IS AN INTERRUPT HANDLER
void tim5_handler ( void )
{
    intcounter++;
    PUT32(TIM5BASE+0x10,0x00000000);
}
// CAREFUL, THIS IS AN INTERRUPT HANDLER
//------------------------------------------------------------------

Wie bei jedem Interrupt von einem Peripheriegerät/Gerät müssen Sie jetzt wahrscheinlich im Handler dem Gerät sagen, dass es den Interrupt löschen soll, sonst könnten Sie beim ständigen erneuten Betreten des Handlers hängen bleiben.

Meine Beispiele verwenden keine IDE, sie verwenden Open-Source-Tools (gnu gcc und binutils und den Clang-Compiler von llvm). Entfernen Sie die Clang-Binärdateien aus der all:-Zeile im Makefile, wenn Sie llvm nicht verwenden/verwenden möchten. Die GNU-Tools sind leicht zu bekommen, sowohl aus Quellen zu bauen (ich habe Anweisungen irgendwo bei github, wahrscheinlich an einigen Stellen) oder einfach die Lite-Version von Codesourcery (jetzt Mentor-Grafiken) zu bekommen. Meine Beispiele wurden unter Linux entwickelt und getestet, aber das sollte Windows-Benutzer nicht davon abhalten, ein paar Dinge wie rm -f *.o in del *.o oder ähnliches zu ändern oder einfach eine Batch-Datei aus den Assembler-/Compiler-/Linker-Befehlen zu erstellen im Makefile.

Ich empfehle dringend, Ihre Binärdatei zu zerlegen, insbesondere wenn Sie versuchen, einen Handler in die Vektortabelle zu platzieren. Bei so vielen ist es leicht, sich zu verzählen und ihn nicht an der richtigen Adresse zu haben. Sie müssen die Adresse aus den Armdokumenten kennen und dann die Demontage überprüfen. das blinker05 Beispiel im zerlegten Zustand:

 8000100:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}
 8000104:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}
 8000108:       08000179        stmdaeq r0, {r0, r3, r4, r5, r6, r8}
 800010c:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}
 8000110:       0800014f        stmdaeq r0, {r0, r1, r2, r3, r6, r8}

Offset 0x108 ist der gezielte Eintrag. Beachten Sie, dass die Adressen in der Tabelle ungerade sein sollten. 0x178 ist die tatsächliche Adresse. Arm möchte, dass das lsbit gesetzt ist, um anzuzeigen, dass es sich um eine Thumb-Befehlssatzadresse handelt (der Cortex-m3 kennt nur Thumb und die Thumb2-Erweiterungen, er kann keine Arm-Anweisungen ausführen).