How are interrupt handlers implemented in CMSIS of Cortex M0?

The following information is in addition to Igor's excellent answer.

From a C programming perspective, the interrupt handlers are defined in the cr_startup_xxx.c file (eg cr_startup_lpc13.c file for LPC1343). All possible interrupt handlers are defined there as a WEAK alias. If you do not define your own XXX_Handler() for an interrupt source, then the default interrupt handler function defined in this file will be used. The linker will sort out which function to include in the final binary along with the interrupt vector table from cr_startup_xxx.c

Example of GPIO interrupts from ports are shown in the demo files in gpio.c. There is one interrupt input to the NVIC per GPIO port. Each individual bit in the port can be enabled/disabled to generate an interrupt on that port. If you require interrupts on ports PIO1_4, and PIO1_5 for example, then you would enable the individual PIO1_4 and PIO1_5 interrupt bits in GPIO0IE. When your PIOINT0_Handler() interrupt handler function fires, it's up to you to determine which of PIO1_4 or PIO1_5 (or both) interrupts are pending by reading the GPIO0RIS register and handling the interrupt appropriately.


(Please note that points 1 and 2 are implementation details and not architectural limitations.)

  1. In bigger NXP chips (such as LPC17xx) there are a couple of dedicated interrupt pins (EINTn) which have their own interrupt handler. The rest of GPIOs have to use one common interrupt (EINT3). You can then poll the interrupt status register to see which pins have triggered the interrupt.
  2. I'm not very familiar with LPC11xx but it seems that it has one interrupt per GPIO port. You again will have to check the status register to figure out the specific pins. There are also up to 12 pins that can act as wakeup sources. I'm not sure if you can hijack them as general interrupts (i.e. they will probably only be triggered when in sleep state).
  3. The default handler table is placed at the address 0 (which is in flash). The first entry is the reset value for the SP register, the second is the reset vector, and the rest are other exceptions and interrupt vectors. A couple of the first ones (such as NMI and HardFault) are fixed by ARM, the rest are chip-specific. If you need to change the vectors at runtime, you can remap it to RAM (you first need to copy the table). In LPC11xx the remapping is fixed to the start of SRAM (0x10000000), other chips can be more flexible.
  4. The NVIC is optimized for efficient interrupt handling:
    • programmable priority level of 0-3 for each interrupt. A higher-priority interrupt preempts lower-priority ones (nesting). The execution of the lower priority one resumes when the higher-priority interrupt is finished.
    • automatic stacking of the processor state on interrupt entry; this allows writing interrupt handlers directly in C and removes the need for assembly wrappers.
    • tail-chaining: instead of popping and pushing the state again, the next pending interrupt is handled immediately
    • late-arriving: if a higher-priority interrupt arrives while stacking the processor state, it's executed immediately instead of the previously pending one.

Since you're familiar with PICs, have a look at this App Note: Migrating from PIC Microcontrollers to Cortex-M3

It's about M3, but most of the points apply to M0 too.


Austin and Igor answers are detailed enough. However, I want to answer it in another way, maybe you find it helpful.

The LPC11xx (Cortex-M0) has 4 levels for GPIO pins, all the pins from GPIO0.0 to GPIO0.n share the same interrupt number, and all the pins from GPIO3.0 to GPIO3.m share the same interrupt number.

There are six steps to initialize GPIO interrupt in LPC11xx

  1. Set up the pin function by modifying Pin Connection Block Registers.
  2. Set up the pin direction by modifying GPIO data direction register (default value is input).
  3. Setup the interrupt for each individual pin, you have to go to the GPIO interrupt mask register GPIOnIE and set the bit (that corresponds to the pin) logic 1.
  4. Set up the interrupt for rising edge or falling edge or both by modifying the GPIO interrupt sense registers GPIOnIBE and GPIOnIS.
  5. Enable the interrupt source either PIO_0/PIO_1/PIO_2/PIO_3 in Nested Vectored Interrupt Control using CMSIS functions.
  6. Set interrupt priority by using CMSIS functions.

Code implementations. You need two functions: one initialize 6 above steps, and the second is the interrupt handler, which is required to be the same name as the handler defined in the start-up codes, startup_LPC11xx.s file. The names are from PIOINT0_IRQHandler to PIOINT3_IRQHandler. If you use different name, you have to change the names in start-up file.

/*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
}

Tags:

Arm

Interrupts