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

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 werden. 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 steht, dass alle GPIOs als externe Interrupt-Quellen verwendet werden können. In der Startdatei werden jedoch nur 4 PIO-Interrupts erwähnt. Nun, sag' mir:

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

Antworten (3)

Die folgenden Informationen ergänzen Igors hervorragende Antwort.

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 sind dort als WEAK-Alias ​​definiert. Wenn Sie keinen eigenen XXX_Handler() für eine Interrupt-Quelle definieren, wird die in dieser Datei definierte Standard-Interrupt-Handler-Funktion verwendet. Der Linker sortiert aus, welche Funktion zusammen mit der Interrupt-Vektortabelle aus cr_startup_xxx.c in die endgültige Binärdatei aufgenommen werden soll

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

(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 ein paar dedizierte Interrupt-Pins (EINTn), die ihren eigenen Interrupt-Handler haben. Der Rest der GPIOs muss einen gemeinsamen Interrupt (EINT3) verwenden. Sie können dann das Interrupt-Statusregister abfragen, um zu sehen, 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 fungieren können. Ich bin mir nicht sicher, ob Sie sie als allgemeine Interrupts kapern können (dh sie werden wahrscheinlich nur im Ruhezustand ausgelöst).
  3. Die Standard-Handler-Tabelle wird an der Adresse 0 (die sich im Flash befindet) abgelegt. Der erste Eintrag ist der Reset-Wert für das SP-Register, der zweite ist der Reset-Vektor und der Rest sind andere Ausnahmen und Interrupt-Vektoren. Ein paar 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 das Remapping auf den Beginn des SRAM (0x10000000) festgelegt, andere Chips können flexibler sein.
  4. Das NVIC ist für eine effiziente Interrupt-Behandlung optimiert:
    • programmierbare Prioritätsstufe von 0-3 für jeden Interrupt. Ein Interrupt mit höherer Priorität verdrängt Interrupts mit niedrigerer Priorität (Verschachtelung). Die Ausführung des Interrupts mit niedrigerer Priorität wird fortgesetzt, wenn der Interrupt mit höherer Priorität beendet ist.
    • automatisches Stacking des Prozessorzustands bei Interrupt-Eintritt; Dies ermöglicht das Schreiben von Interrupt-Handlern direkt in C und macht Assembly-Wrapper überflüssig.
    • Tail-Chaining: Anstatt den Zustand erneut zu poppen und zu pushen, wird der nächste anstehende Interrupt sofort behandelt
    • Late-arriving: Wenn ein Interrupt mit höherer Priorität eintrifft, während der Prozessorstatus gestapelt wird, wird er sofort anstelle des zuvor anstehenden Interrupts ausgeführt.

Da Sie mit PICs vertraut sind, werfen Sie einen Blick auf diesen App-Hinweis: Migration von PIC-Mikrocontrollern zu Cortex-M3

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

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) hat 4 Ebenen für GPIO-Pins, alle Pins von GPIO0.0 bis GPIO0.n teilen sich dieselbe Interrupt-Nummer, und alle Pins von GPIO3.0 bis GPIO3.m teilen sich dieselbe Interrupt-Nummer.

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

  1. Richten Sie die Pin-Funktion ein, indem Sie die Pin-Verbindungsblockregister ändern.
  2. Stellen Sie die Pin-Richtung ein, indem Sie das GPIO-Datenrichtungsregister ändern (Standardwert ist Eingabe).
  3. Richten Sie den Interrupt für jeden einzelnen Pin ein, Sie müssen zum GPIO-Interrupt-Maskenregister GPIOnIE gehen und das Bit (das dem Pin entspricht) auf logisch 1 setzen.
  4. Richten Sie den Interrupt für steigende Flanke oder fallende Flanke oder beides ein, indem Sie die GPIO-Interrupt-Sense-Register GPIOnIBE und GPIOnIS ändern.
  5. Aktivieren Sie die Interrupt-Quelle entweder PIO_0/PIO_1/PIO_2/PIO_3 in Nested Vectored Interrupt Control unter Verwendung von CMSIS-Funktionen.
  6. Legen Sie die Interrupt-Priorität mithilfe von CMSIS-Funktionen fest.

Code-Implementierungen. startup_LPC11xx.sSie benötigen zwei Funktionen: eine initialisiert die 6 obigen Schritte, und die zweite ist der Interrupt-Handler, der den gleichen Namen haben muss wie der Handler, der in der Datei mit den Startcodes definiert ist . 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
}