ARM Cortex-M, Interrupts and FreeRTOS: Part 2

In “ARM Cortex-M, Interrupts and FreeRTOS: Part 1”  I started with the ARM Cortex-M interrupt system. Because the ARM implementation cann be very confusing, I confused myself and had to fix and extend the description in Part 1 :-). Thank for all the feedback and comments!

Originally I wanted to cover FreeRTOS in Part 2. 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 4 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 having SWO or other trace pins, these can be used too.

Interrupt Levels on K64F

The K64F on the FRDM-K64F board implements 4 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 333.3 Hz (every 3 ms)

Remember the relationship between exception number and IRQ number (see image below). This adds yet another confusion 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 */

💡 Keep in mind that NVIC_SetPriority expects the IRQ number (-15….n) and the non-shifted Interrupt Priority value!

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 IRQ 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 an 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 are 4 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 2 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 any more, and 4.0 has precedence if both 4.0 and 4.1 are pending:

System with Subpriorites

System with Sub-Priorites


The ARM NVIC is very flexible and powerful piece of hardware. Nesting allows 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 IRQ can share the same urgency or interrupt level. So the number of available bits in the NVIC is not limiting. Very few system problably need more than 16 priority levels. Most and 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 beside of normal interrupt priorities. It ensures that IRQ 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 be a source of bugs. I have used sub-priorities in a few systems, for very specific use cases only. 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 3 I’m going to cover how the NVIC is used by 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 🙂



14 thoughts on “ARM Cortex-M, Interrupts and FreeRTOS: Part 2

  1. Hi Erich,

    Thanks for the excellent article, looking forward to part 3 :).

    Maybe worth considering a cook book using all the helpful articles on your site combined into a book / pdf i would certainly buy it as a quick reference guide and there is certainly enough content on the blog for it.

    Thanks again for taking the time to write all the helpful articles it is much appricated – your blog is quickly becoming the go to place for help with Cortext M processors, RTOS and Segger


    • Hi Andy,
      thanks for your suggestion about a book/pdf. I had that considered a while back. I might consider this for my university scriptum and lab material maybe. But the overhead for publishing in an ‘official’ is not small. But maybe there is a big interest for something like this?


      • Hi Erich,

        Do don’t think you need to go for an official publishing, i would just like a reference book manual on most of the topics covered in this blog, it does not have to be fancy.

        I did try sometime ago to convert each blog post into a pdf page and the sort them into a manual of some kind for offline viewing but the conversion from html to pdf was not good and the adverts did not help either :).


      • CreateSpace an independent subsidiary of Amazon is free print on demand service. It is how I publish my late wife Karen’s medical Journal. They make a nice quality book at the price of printing. For example Karen’s Journal at ~350 pages costs me ~$5 for each copy. There are no up front costs, only thing that costs is printing the physical book and shipping the book.

        The advantage of CreateSpace over other publishers is you can print as few as one or as many as you want and the book can be updated as often as you want. They have templates to get you started. One tip I’ve learned is upload a PDF, not docx, for a physical book.

        Books can also be placed on Kindle via this route. An other tip, from learning the hard way, is do not hit “Publish on Kindle” button after doing an update. That creates a new Kindle book, not update the existing one. To do an actual update of Kindle, have to log into the Kindle publishing site. Also best to use a Kindle aware editor that can take a docx as input. Docx doesn’t always place images where you want them in Kindle.

        Books done on CreateSpace may be placed on Amazon Books for sale with a couple of clicks, at the price of your choosing.

        Happy to help out anyone that wants to get started .


        • Hi Bob,
          thanks for that link and information about CreateSpace (I did not know about this one), I definitely need to look at that option (and if it is available that way worldwide).


      • If you only want an electronic book (not paper), then is a fairly simple way to publish. (I do my formatting myself with LaTeX, and just distribute the PDF through leanpub—too many figures and too much math to use leanpub’s tools for producing EPUB and MOBI formats.)


  2. Pingback: ARM Cortex-M Interrupts and FreeRTOS: Part 3 | MCU on Eclipse

  3. Pingback: ARM Cortex-M, Interrupts and FreeRTOS: Part 1 | MCU on Eclipse

  4. While moving to Cortex-M from other processor families, one thing that has surprised me is how interrupt routines are handled. I kept looking for something like ‘attribute (interrupt)’ on the interrupt handler functions, such as the ones in your example code. I couldn’t figure out how the compiler knew to use a return from interrupt instruction, not knowing that Cortex-M doesn’t have one. The Cortex-M exception handling is quite clever, but it’s certainly non-obvious if you keep looking for a non-existent ‘reti’.

    Liked by 1 person

    • This was something new to me to learn on ARM Cortex-M too: Interrupt Service Routines are for the compiler like normal functions, so it does not need to use such a special ‘return from interrupt’ instruction as for example the HCS08 or HCS12 has. Always something new to learn 🙂

      Liked by 1 person

  5. Hi Erich,

    super useful topics in this series… Thank you so much.
    Since I’m here I point you to a typo:

    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)

    where of course the frequency is 333.3 Hz



  6. Hi Erich,

    super useful topics in this series… Thank you so much.
    Since I’m here I point you to a typo:

    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)

    where of course the frequency is 333.3 Hz instead of 33.33 Hz


What do you think?

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.