Wie werden Interrupt-Handler in CMSIS von Cortex M0 implementiert?

9

Ich habe ein LPC1114-Kit. In den letzten Tagen habe ich die CMSIS-Implementierung von Cortex M0 ausgegraben, um herauszufinden, wie die Dinge darin gemacht werden. Bisher habe ich verstanden, wie die einzelnen Register zugeordnet sind und wie ich darauf zugreifen kann. Aber ich weiß immer noch nicht, wie Interrupts darin implementiert sind. Alles, was ich über Interrupts in CMSIS weiß, ist, dass einige Interrupt-Handler-Namen in der Startdatei erwähnt sind. Und ich kann meine eigenen Handler schreiben, indem ich einfach eine C-Funktion mit denselben Namen schreibe, die in der Startdatei erwähnt werden. Was mich verwirrt, ist, dass im Benutzerhandbuch angegeben ist, dass alle GPIOs als externe Interruptquellen verwendet werden können. In der Startdatei werden jedoch nur 4 PIO-Interrupts erwähnt. Also sag mir:

  1. Wie kann ich externe Interrupt-Handler für andere GPIOs implementieren?
  2. Wo ist die Interrupt-Tabelle im CMSIS zugeordnet?
  3. Was sind die Hauptunterschiede zwischen NVIC und der Interrupt-Implementierung in AVRs / PICs? (außer NVIC kann überall im Flash abgebildet werden)
0xakhil
quelle

Antworten:

14

Die folgenden Informationen ergänzen die hervorragende Antwort von Igor.

Aus Sicht der C-Programmierung sind die Interrupt-Handler in der Datei cr_startup_xxx.c definiert (z. B. Datei cr_startup_lpc13.c für LPC1343). Alle möglichen Interrupt-Handler werden dort als WEAK-Alias ​​definiert. Wenn Sie keinen eigenen XXX_Handler () für eine Interruptquelle definieren, wird die in dieser Datei definierte Standard-Interrupt-Handler-Funktion verwendet. Der Linker sortiert zusammen mit der Interrupt-Vektortabelle aus cr_startup_xxx.c, welche Funktion in die endgültige Binärdatei aufgenommen werden soll

Beispiele für GPIO-Interrupts von Ports finden Sie in den Demo-Dateien in gpio.c. Pro GPIO-Port gibt es einen Interrupt-Eingang zum NVIC. Jedes einzelne Bit im Port kann aktiviert / deaktiviert werden, um einen Interrupt an diesem Port zu generieren. Wenn Sie beispielsweise Interrupts an den Ports PIO1_4 und PIO1_5 benötigen, aktivieren Sie die einzelnen Interrupt-Bits PIO1_4 und PIO1_5 in GPIO0IE. Wenn Ihre Interrupt-Handler-Funktion PIOINT0_Handler () ausgelöst wird, müssen Sie bestimmen, welche der Interrupts PIO1_4 oder PIO1_5 (oder beide) anstehen, indem Sie das GPIO0RIS-Register lesen und den Interrupt entsprechend behandeln.

Austin Phillips
quelle
10

(Bitte beachten Sie, dass die Punkte 1 und 2 Implementierungsdetails und keine architektonischen Einschränkungen sind.)

  1. In größeren NXP-Chips (wie LPC17xx) gibt es einige dedizierte Interrupt-Pins (EINTn), die über einen eigenen Interrupt-Handler verfügen. Die übrigen GPIOs müssen einen gemeinsamen Interrupt (EINT3) verwenden. Sie können dann das Interrupt-Statusregister abfragen, um festzustellen, welche Pins den Interrupt ausgelöst haben.
  2. Ich bin mit LPC11xx nicht sehr vertraut, aber es scheint, dass es einen Interrupt pro GPIO-Port gibt. Sie müssen erneut das Statusregister überprüfen, um die spezifischen Pins herauszufinden. Es gibt auch bis zu 12 Pins, die als Weckquellen dienen können. Ich bin nicht sicher, ob Sie sie als allgemeine Interrupts entführen können (dh sie werden wahrscheinlich nur im Schlafzustand ausgelöst).
  3. Die Standardhandlertabelle befindet sich an der Adresse 0 (in Flash). Der erste Eintrag ist der Rücksetzwert für das SP-Register, der zweite ist der Rücksetzvektor und der Rest sind andere Ausnahmen und Interruptvektoren. Einige der ersten (wie NMI und HardFault) werden von ARM behoben, der Rest ist chipspezifisch. Wenn Sie die Vektoren zur Laufzeit ändern müssen, können Sie sie dem RAM neu zuordnen (Sie müssen zuerst die Tabelle kopieren). In LPC11xx ist die Neuzuordnung auf den Start des SRAM (0x10000000) festgelegt, andere Chips können flexibler sein.
  4. Der NVIC ist für eine effiziente Interrupt-Behandlung optimiert:
    • programmierbare Prioritätsstufe 0-3 für jeden Interrupt. Ein Interrupt mit höherer Priorität verhindert solche mit niedrigerer Priorität (Verschachtelung). Die Ausführung der niedrigeren Priorität wird fortgesetzt, wenn der Interrupt mit der höheren Priorität beendet ist.
    • automatisches Stapeln des Prozessorzustands bei Interrupt-Eingabe; Dies ermöglicht das Schreiben von Interrupt-Handlern direkt in C und macht Assembly-Wrapper überflüssig.
    • Tail-Chaining: Anstatt den Status erneut zu aktivieren und zu verschieben, wird der nächste anstehende Interrupt sofort behandelt
    • Verspätetes Eintreffen: Wenn beim Stapeln des Prozessorstatus ein Interrupt mit höherer Priorität eintrifft, wird er sofort anstelle des zuvor anstehenden ausgeführt.

Sehen Sie sich diese App an, da Sie mit PICs vertraut sind. Hinweis: Migration von PIC-Mikrocontrollern zu Cortex-M3

Es geht um M3, aber die meisten Punkte gelten auch für M0.

Igor Skochinsky
quelle
8

Die Antworten von Austin und Igor sind detailliert genug. Ich möchte es jedoch anders beantworten, vielleicht finden Sie es hilfreich.

Der LPC11xx (Cortex-M0) verfügt über 4 Ebenen für GPIO-Pins, alle Pins von GPIO0.0 bis GPIO0.n haben dieselbe Interrupt-Nummer und alle Pins von GPIO3.0 bis GPIO3.m haben dieselbe Interrupt-Nummer.

Es gibt sechs Schritte zum Initialisieren des GPIO-Interrupts in LPC11xx

  1. Richten Sie die Pin-Funktion ein, indem Sie die Pin-Verbindungsblockregister ändern.
  2. Richten Sie die Pin-Richtung ein, indem Sie das GPIO-Datenrichtungsregister ändern (Standardwert ist Eingabe).
  3. Wenn Sie den Interrupt für jeden einzelnen Pin einrichten, müssen Sie zum GPIO-Interrupt-Maskenregister GPIOnIE gehen und die Bit-Logik (die dem Pin entspricht) 1 setzen.
  4. Richten Sie den Interrupt für steigende oder fallende Flanke oder beides ein, indem Sie die GPIO-Interrupt-Erfassungsregister GPIOnIBE und GPIOnIS ändern.
  5. Aktivieren Sie die Interrupt-Quelle entweder PIO_0 / PIO_1 / PIO_2 / PIO_3 in der verschachtelten Vektor-Interrupt-Steuerung mithilfe von CMSIS-Funktionen.
  6. Stellen Sie die Interrupt-Priorität mithilfe von CMSIS-Funktionen ein.

Code-Implementierungen. Sie benötigen zwei Funktionen: eine initialisiert 6 der obigen Schritte und die zweite ist der Interrupt-Handler, der denselben Namen haben muss wie der in der Startcode- startup_LPC11xx.sDatei definierte Handler . Die Namen sind von PIOINT0_IRQHandlerbis PIOINT3_IRQHandler. Wenn Sie einen anderen Namen verwenden, müssen Sie die Namen in der Startdatei ändern.

/*Init the GPIO pin for interrupt control */
void GPIO_Init(){
    LPC_IOCON-> =..              //Pin configuration register
    LPC_GPIO1->FIODIR = ...      //GPIO Data direction register
    LPC_GPIO1->FIOMASK = ..      //GPIO Data mask register - choose  the right pin
    LPC_GPIO1->GPIOnIE = ..      //Set up falling or rising edge 
    NVIC_EnableIRQ(PIO_1);       //Call API to enable interrupt in NVIC
    NVIC_SetPriority(PriorityN); //Set priority if needed
}


/*Must have the same name as listed in start-up file startup_LPC11xx.s */
void PIOINT1_IRQHandler(void){
   //Do something here
}
Phuong Pham
quelle