Cortex-M Interrupt Architecture

ARM Cortex-M has a flexible yet easy to program interrupt architecture, consisting of these pieces:

 

     The core architecture interrupts and vector layout.

     The core architecture interrupt priority and priority groups.

     The device specific I/O peripheral interrupt vectors.

     The Nested Vectored Interrupt Controller (NVIC).

 

It is beyond the scope of this document to describe these features in detail. Please refer to the ARM and silicon vendors’ documentations for further information.

Cortex-M Base Interrupts

Every interrupt type has a number associated with it ranging from 1 up to 240. Cortex-M defines 15 system exceptions, where numbers 16 and above are for device specific exceptions.

 

Most of the exceptions have a programmable priority, but a few have fixed priority.

The Interrupt Vector Table

Interrupt vectors normally are located at the start of the flash memory, which is usually 0x2000000 for Cortex-M MCU. However, it may be moved to a different location by setting the content of the vector table offset register in the NVIC.

 

Location 0 of the interrupt vector table contains the initial stack pointer (SP) value when the CPU comes out of a reset condition.

 

All other locations in the table contain the address of the function that should handle that interrupt. As an artifact of the ARM Thumb2 architecture definition, while all functions start at a 4 byte boundary, each interrupt entry is actually the function address + 1.

 

A common source of errors is when a user does not provide interrupt handlers for all interrupts, since some interrupts supposedly “cannot happen in this program”. However, Murphy’s Law frequently shows up in embedded systems, so a good practice is to provide handlers for ALL interrupts, and to be most effective, all interrupt handlers should be different from each other. See also: “GCC Weak Attributes” below.

 

Notice the system-defined exceptions have negative IRQ numbers.

 

Exception Number

IRQ Number

Address Offset

Vector

 

 

0x0000

Initial SP Value

1

 

0x0004

Reset

2

-14

0x0008

NMI

3

-13

0x000C

Hard fault

4

-12

0x0010

Memory management fault

5

-11

0x0014

Bus fault

6

-10

0x0018

Usage fault

7

-9

0x001C

Reserved

8

-8

0x0020

Reserved

9

-7

0x0024

Reserved

10

-6

0x0028

Reserved

11

-5

0x002C

SVCall

12

-4

0x0030

Reserved for Debug

13

-3

0x0034

Reserved

14

-2

0x0038

PendSV

15

-1

0x003C

Systick

16

0

0x0040

IRQ0 [5]

17

1

0x0044

IRQ1

18

2

0x0048

IRQ2

.

.

.

.

.

.

.

.

.

.

.

.

16+n

n

0x0040+4n

IRQn

 

 

Vendor Device-Specific Interrupts

Interrupts numbered 1 to 240 are device-specific interrupts. Not all devices define all 240 interrupts, and some devices may use a single interrupt for multiple sources. For example, interrupt 25 on a ST STM32F411 device can be triggered by two different events with TIMER1 and TIMER5. In these cases, your interrupt handler code must first determine the source of the interrupt, typically by examining some device specific I/O registers, before proceeding.

Priority Level

Cortex-M architecture defines up to 255 priority levels, with priority level zero being the highest (most urgent) priority. The actual number of priority levels (which must be a power of two) which an MCU supports is left to be decided by the silicon vendors.

 

Priority level is encoded in one of the NVIC registers. The actual register encoding is shifted by the number of unimplemented bits for some clever but dubious reasons by ARM. However, if you always use the function NVIC_SetPriority then you do not need to worry about such issues.

 

NOTE: as priority level zero has the highest priority, and priority level 255 has the lowest priority, be careful to remember that the word “priority” by itself has the opposite connotation from the term “priority level”.

NVIC

The Nested Vectored Interrupt Controller lets you control interrupt handling behaviors.

To set up the NVIC to handle an interrupt (assuming the variable IRQn contains the interrupt number):

 

     Disable the interrupt: NVIC_DisableIRQ(IRQn);

     Perform device specific setup for the interrupt

     Set the priority level: NVIC_SetPriority(IRQn, priority);

     Enable the interrupt: NVIC_EnableIRQ(IRQn);

 

When an interrupt comes in, after your interrupt handler performs whatever it is needed to do, it should clear the pending interrupt bits in the NVIC: NVIC_ClearPendingIRQ(IRQn).

Disabling and Enabling of Interrupts

There are two methods of disabling and re-enabling interrupts globally. The simplest is to set or clear the interrupt bit in the PRIMASK (Priority Mask) register. These two functions are the only methods to globally control interrupts on the Cortex-M0 and Cortex-M0+ based MCU.

 

Effect

CMSIS Function

Asm Instruction

Disable all interrupts

__disable_irq()

CPSID i

Enable all interrupts

__enable_irq()

CPSIE i

 

For Cortex-M architecture other than the M0/M0+ such as the Cortex-M3,-M4,-M7, you can alternatively disable and enable interrupts with lower priority than a specific priority level by modifying the BASEPRI (Base Priority) register..

 

Effect

CMSIS Function

Disable interrupts with lower priority than priority level N

__set_BASEPRI(N << (8 - __NVIC_PRIO_BITS))

Enable all interrupts

__set_BASEPRI(0)

 

As mentioned above, the NVIC_SetPriority function hides register encoding detail. Unfortunately, with the __set_BASEPRI function, the actual register encoding must be used, and the expression in the table above must also be used. __NVIC_PRIO_BITS is the MCU-specific number of priority bits defined in the device-specific header file.