ARM Cortex-M, Interrupts, and FreeRTOS (Part 2)

DZone 's Guide to

ARM Cortex-M, Interrupts, and FreeRTOS (Part 2)

Erich Styger clarifies a few points about the interrupting and prioritizing power of ARM Cortex-M. See examples of how the technology works.

· IoT Zone ·
Free Resource

In “ARM Cortex-M, Interrupts, and FreeRTOS: Part 1,” I started with the ARM Cortex-M interrupt system. Because the ARM implementation can be very confusing, I confused myself and had to fix and extend the description in part one.

Thank for all the feedback and comments!

Originally, I wanted to cover FreeRTOS in part 2. But based on the questions and discussions in part 1, I thought it might be a good idea to provide visual examples.

NXP KV58F ARM Cortex-M7

NXP KV58F ARM Cortex-M7


I’m using the NXP FRDM-K64F board, which uses an ARM Cortex-M4F (the F stands for Floating Point Unit). It implements four interrupt priority bits with optional sub-priority bits.

Note that the ARM Cortex-M0/M0+ does not implement the sub-priority bits.

Toggle LED


To illustrate the interrupts, I’m using the Segger SystemView with Segger RTT: It can record the interrupts without any external hardware or trace pins.

Nested Interrupts in Segger SystemView

Nested Interrupts in Segger SystemView

Another way to monitor interrupts is to toggle a GPIO pin at interrupt entry/exit and to record it with a logic analyzer. This requires free pins and an external logic analyzer. If you have SWO or other trace pins, these can be used too.

Interrupt Levels on K64F

The K64F on the FRDM-K64F board implements four priority bits:

Four NVIC Interrupt Bits Implemented

Four NVIC Interrupt Bits Implemented

So, we have 2^4 (16) priority levels with the following shifted values:

  • Hexadecimal: 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90 0xA0, 0xB0 0xC0, 0xD0, 0xE0, 0xF0.
  • Decimal: 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240.

Remember: the lower the numerical value, the higher the urgency. Zero is the highest urgency level.

Be aware that some CMSIS APIs are using the non-shifted (0-16) priority values, not the shifted ones (0x00-0xF0).

Nested Interrupts

The ARM NVIC is a Nested Vectored Interrupt Controller: IRQs with a higher urgency can nest (interrupt) IRQs with lower urgency.

To illustrate this, I have two timer interrupts configured on the board:

  • ISR #58 (exception #58, IRQ #42) is for a timer interrupt with a frequency of 100 Hz (every 10 ms).
  • ISR #59 (exception #59, IRQ #43) is for a timer interrupt with a frequency of 33.33 Hz (every 3 ms).

Remember the relationship between exception number and IRQ number (see image below). This adds yet another confusing part in CMSIS: Carefully check if exception numbers or IRQ numbers are expected in the API.

Cortex-M Vector Table

Cortex-M Vector Table (Source of images: ARM)

I have configured ISR #58 and ISR #59 with the following priorities:

  • ISR #58: interrupt priority value of 160 (0xA0).
  • ISR #59: interrupt priority value of 112 (0x70).
NVIC_SetPriority(58-16, 0xA); /* IRQ Number 42, Interrupt Priority 0xA0 */
NVIC_SetPriority(59-16, 0x7); /* IRQ Number 43, Interrupt Priority 0x70 */

To visualize the interrupts better, I have added some delays in each of them:

void ISR59_OnInterrupt(void)
  /* ISR #59, every 3 ms */
  SYS1_RecordEnterISR(); /* record interrupt entry in Segger SystemViewer */
  WAIT1_Waitms(1);       /* burn time for 1 ms */
  SYS1_RecordExitISR();  /* record interrupt exit in Segger SystemViewer */

void ISR58_OnInterrupt(void)
  /* ISR #58, every 10 ms */
  SYS1_RecordEnterISR(); /* record interrupt entry in Segger SystemViewer */
  WAIT1_Waitms(2);       /* burn time for 2 ms */
  SYS1_RecordExitISR();  /* record interrupt exit in Segger SystemViewer */

  • ISR #59 (orange) happens every 3 ms and takes 1 ms.
  • ISR #58 (red) happens every 10 ms and takes 2 ms.
Nested Interrupts

Nested Interrupts (click to enlarge)

As shown above, because ISR #59 has higher urgency (interrupt level value 0x70), it interrupts ISR #58 which has a lower urgency (interrupt level value 0xA0):

  1. A higher urgency IRQ is interrupting a lower urgency one (nesting).
  2. If two or more IRQs with the same urgency are occurring at the same time or are pending, then the one with the lower IRQ number will be executed first.

The image below shows the nesting in the Events view:

Interrupt Nesting

Interrupt Nesting

For the example below, I have swapped the priorities: ISR #58 has a more urgent priority (0x70) than ISR #59 (0xA0). As ISR #58 needs more time (2 ms in ISR #58 vs. 1 ms in ISR #59), it can extend the time needed for ISR #59:

Missed Deadlines

Missed Deadlines (click to enlarge)

Interrupt Latency

In the earlier example, because ISR #59 has a higher urgency, it can interrupt #SR #57. Additionally, if ISR #59 is active, ISR #58 cannot start and therefore might be delayed until the higher urgency interrupt has completed. This is causing interrupt latency: The time from where the interrupt is triggered until when it is serviced. The interrupt will be delayed for some time and won’t be able to start at the trigger time.

In the example below, ISR #58 should happen at +10 ms, but because ISR #59 is running at that time, it gets blocked and delayed by the higher urgency ISR #59.

Delayed Interrupt

Delayed Interrupt

The total execution time of ISR #58 is extended extra because ISR #59 is adding time because of nesting.


On the ARM Cortex-M4F of the NXP FRDM-K64F, there are four priority bits implemented. I’m now configuring it so it has one sub-priority:

Note that the ARM Cortex-M0/M0+ does *not* have sub-priorities

Three preemption bits and one subpriority bit

Three preemption bits and one sub-priority bit

CMSIS offers the following two functions for the grouping:

  \brief   Set Priority Grouping
  \details Sets the priority grouping field using the required unlock sequence.
           The parameter PriorityGroup is assigned to the field SCB->AIRCR [10:8] PRIGROUP field.
           Only values from 0..7 are used.
           In case of a conflict between priority grouping and available
           priority bits (__NVIC_PRIO_BITS), the smallest possible priority group is set.
  \param [in]      PriorityGroup  Priority grouping field.
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

  \brief   Get Priority Grouping
  \details Reads the priority grouping field from the NVIC Interrupt Controller.
  \return                Priority grouping field (SCB->AIRCR [10:8] PRIGROUP field).
uint32_t NVIC_GetPriorityGrouping(void);

I’m configuring it below for using one sub-priority bit, which remains 3 bits for preemption level. ISR #59 is configured with priority 4.0 (preemption priority 4, sub-priority 1) and ISR #58 is configured with priority 4.1.

For the ‘preemption priority’ usually the term ‘main priority’ is used too.

NVIC_SetPriorityGrouping(4); /* Having 4 interrupt priority bits: sub-priority at bit 4 ==> 3 preemption priority, 1 sub-priority bit */
NVIC_SetPriority(59-16, 0x8); /* IRQ Number 43, 0b100.0 (preempt prio 4, subprio 0), Interrupt Priority value 0x80 */
NVIC_SetPriority(58-16, 0x9); /* IRQ Number 42, 0b100.1 (preempt prio 4, subprio 1), Interrupt Priority value 0x90 */

The sub-priorities use the following rules:

  1. A higher preemption urgency IRQ (lower numerical preemption value) will interrupt any lower urgency ISR (normal nesting: 2.1 interrupts 3.0).
  2. A pending IRQ with the same preemption urgency as the running ISR does *not* interrupt it (higher sub-urgency does not interrupt an already running main-urgency service routine): A pending 2.0 does not interrupt a running 2.1.
  3. If two or more IRQ with the same preemption urgency are pending, then the one with the highest sub-urgency (lowest sub-priority value) is executed first: If 2.1 and 2.0 are pending, then 2.0 is executed first.
  4. If multiple IRQ with the same preemption and sub-priority value are pending, then the one with the lowest IRQ number is executed first: If IRQ #59 and IRQ #58 is pending, then IRQ #58 is executed first.

Rule two is important, as this rule really makes the difference between a system with sub-priorities and one without sub-priorities. This rule says that an already running ISR cannot be interrupted by an ISR of the same main priority or grouping. This is useful for example having two I²C bus interrupts, and in my driver, I don’t want to have both busses active with interrupts the same time: Then I could put them into one group and that way they will not nest. Or in other words, the grouping disables interrupt nesting inside the group, with the sub-priority giving a way to assign an urgency level in case multiple interrupts of the group are pending.

The Segger SystemView shows below the ISR #59 with priority 4.0 and ISR #58 with priority 4.1: They do not nest anymore, and 4.0 has precedence if both 4.0 and 4.1 are pending:

System with Subpriorites

System with Sub-Priorities


The ARM NVIC is a very flexible and powerful piece of hardware. Nesting allows it to interrupt a less urgent, already running interrupt service routine. The good thing with the NVIC is that the interrupt priorities are up to the user or application. Multiple IRQs can share the same urgency or interrupt level. So, the number of available bits in the NVIC is not limiting. Very few systems probably need more than 16 priority levels. Most, especially small systems, only need a few levels.

The feature of the NVIC to define priority groups is another way to fine-tune the system aside from normal interrupt priorities. It ensures that IRQs with the same grouping will not nest. While this is a great feature, most systems do not really need it. It can over-complicate the system, and not using it right will cause of bugs. I have used sub-priorities in a few systems only for very specific use cases. Keep in mind that the feature of sub-priorities adds to the silicon costs: That was probably the reason why ARM has removed that feature from the Cortex-M0/M0+ core. And my guess is that ~98% of the ARM Cortex-M3/M4/M7 systems are not using sup-priorities anyway. But that does not mean that it should be ignored — it is good to have it if I need it.

In part three, I’m going to cover how the NVIC is used by the FreeRTOS realtime operating system. Unless this post triggers comments and questions for yet another extension.

The example used in this article is available on GitHub.

Happy interrupting.

arm cortex-m ,field ,interrupt ,urgency ,value

Published at DZone with permission of Erich Styger , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}