Rückrufe für Interrupts definieren

7

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 (NVIC genannt), 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 für jede Art von Interrupt möglich ist, eine Rückruffunktion anzuhängen, und ich vermute, dass die an jeden Interrupt angehängte Adresse mit der Adresse des Rückrufs zusammenhängt.

Was genau ist die Adresse, die einem bestimmten Interrupt zugeordnet ist? Wie kann ich (beispielsweise in C) die Rückruffunktion für einen bestimmten Interrupt definieren?

Zufälliges Blau
quelle
Ich weiß aus erster Hand nichts über den Arm, aber viele Prozessoren haben eine spezielle Anweisung zum Zurückkehren aus dem Interrupt, die die zusätzlichen Informationen vom Stapel entfernt, die normalerweise mit einem Interrupt einhergehen. Nur ein echtes Problem, wenn Sie Assembler schreiben; Bei den meisten C-Compilern können Sie Ihre Funktion mit dem Wort 'Interrupt' dekorieren, und der Compiler kümmert sich um die Auswahl des richtigen Exit-Codes.
JustJeff
@ JustJeff: Könnten Sie auf die Dokumentation zu dieser 'Interrupt'-Dekoration verweisen?
Randomblue
Es ist normalerweise trivial - setzen Sie einfach das Wort 'Interrupt' vor den Funktionsrückgabetyp. Anstelle von void foo () {...} hätten Sie void foo () {...} unterbrochen
JustJeff
@JustJeff Nur um zu beachten, 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
@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 wird als für reguläre Aufrufe.
JustJeff

Antworten:

4

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 an einem vordefinierten Ort im Programmspeicher gespeichert. Wenn ein Interrupt auftritt, weiß der Prozessor, wo sich der Eintrag des Interrupts in der Tabelle befindet, greift danach und verzweigt zu der dort gespeicherten Adresse.

Wie füllen Sie diese Tabelle? Normalerweise sind alle diese Informationen in einer Startdatei enthalten. Normalerweise definieren Sie nur ein konstantes Array von Zeigern und füllen es mit den Adressen der Rückrufe, die Sie verwenden möchten. Auf dieser Website erfahren Sie, wie Sie eine Startdatei für einen bestimmten ARM-Mikrocontroller erstellen. Das Knifflige dabei ist, dass fast alle Einzelheiten der Erstellung der Datei stark von dem Linker, Compiler und Chip abhängen, den Sie verwenden. Sie müssen also entweder ein Beispiel finden oder Ihre eigenen durcheinander bringen.

AngryEE
quelle
4

Der Cortex-M3 ist sehr Interrupt-freundlich. Sie benötigen kein Trampolin in asm oder müssen eine nicht standardmäßige Compilersyntax hinzufügen, damit der Compiler dies für Sie erledigt. Die Hardware entspricht einem Abi, indem eine bestimmte Anzahl von Registern für Sie beibehalten und die Modi geändert werden. Das Verbindungsregister 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. Also all die Dinge, die Sie an einem Arm und vielen anderen Prozessoren tun mussten, müssen Sie nicht tun.

Ebenso haben der Cortex-m (und andere neuere Arme) bis zu einem gewissen Schmerzniveau zig Vektoren in der Vektortabelle, Dutzende bis Hunderte von Interrupts usw., wie in einem Kommentar oben erwähnt, siehe http://github.com/ dwelch67 / stm32f4d Das Beispiel blinker05 verwendet Interrupts mit einem Timer. Sie können in vectors.s sehen, dass Sie nur 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 das Gerät wahrscheinlich im Handler anweisen, den Interrupt zu löschen, da Sie sonst möglicherweise stecken bleiben und ständig in den Handler zurückkehren.

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

Ich empfehle dringend, Ihre Binärdatei zu zerlegen, insbesondere wenn Sie versuchen, einen Handler in die Vektortabelle einzufügen. Bei so vielen ist es leicht, falsch zu zählen und nicht an der richtigen Adresse zu haben. Sie müssen die Adresse aus den Armdokumenten kennen und dann die Demontage überprüfen. Das Beispiel blinker05 bei Demontage:

 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 Zieleintrag. Beachten Sie, dass die Adressen in der Tabelle ungerade sein sollten. 0x178 ist die tatsächliche Adresse. Arm möchte, dass der lsbit-Satz anzeigt, dass es sich um eine Daumenanweisungssatzadresse handelt (der cortex-m3 kennt nur thumb und die thumb2-Erweiterungen, die er nicht ausführen kann).

Oldtimer
quelle