Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Sitting on the high-speed train from Shanghai to Beijing, the long journey really makes one feel bored (I couldn’t get a direct train, so I can only go slowly, stopping at every station. It’s ridiculous that in the high-speed train era, it still is like this. It reminds me of the time in college when I took a green train home for over ten hours during the holidays, haha). So, I felt a bit inspired to write and couldn’t help but sigh – spring is really here. Why is spring in quotes? I believe everyone understands, haha. It’s good that I can leisurely write again, just taking my time…

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Actually, after working on ARM development for so long, I have wanted to share with everyone about the crucial issue of interrupt priority for a long time. Unfortunately, on one hand, to explain this issue properly would require a lot of effort and ink, and on the other hand, I have become busier, which has made me lazier about writing articles, haha. So while I’m bored on the train, I quickly roll up my sleeves to tackle this, otherwise, it would be quite a waste to keep such important content locked away in my mind, haha. Let’s stop the chit-chat, or else it will be awkward when the train arrives, cough cough…

The need for interrupt priority configuration mainly arises when there are multiple possible interrupt sources in the system. At this point, to ensure the reliable execution of the code, we have to consider two very realistic questions: one is that if two or more interrupts occur simultaneously, pending requests to the CPU, how does the CPU decide which interrupt service routine to respond to first? The second question is that if the CPU is currently responding to a certain interrupt service, and another interrupt request arrives, whether to allow it to interrupt the current interrupt service to respond to the new interrupt request, which is what we commonly refer to as the interrupt nesting issue. Don’t tell me you haven’t considered this, haha (if that’s really the case, I can only say that handling microcontrollers like this is irresponsible and could potentially introduce risk bugs into the project), otherwise, my writing would be in vain, haha. Now, the main topic comes, how do the currently popular ARM Cortex-M4 and M0+ cores solve these two problems? Because these two cores have some differences in interrupt priority management (although both manage via NVIC and SCB registers), I will discuss them separately. Also, since interrupt priority management is a core issue, you won’t find this information in the official manuals of major semiconductor manufacturers; we can only trace back to the ARM website to find the core manuals for M4 and M0+. I suggest everyone interested download the core manuals from the ARM website and take a quick look; you might gain unexpected insights, haha:

ARM Cortex-M0+ Interrupt Priority and Nesting

Whether it’s the M0+ or M4 core, their interrupt priorities are actually managed by the NVIC and SCB registers. Here, I assume everyone knows that ARM’s interrupt sources are divided into core interrupts and IRQ interrupts (I’m afraid if I explain further, it will never end, haha). The management of IRQ interrupts is dominated by NVIC, while the core interrupt management is dominated by SCB. Let’s first talk about the priority issue of IRQ interrupts (the enabling and disabling of interrupts is not within the scope of this article, haha). Open the M0+ core manual to find the NVIC_IPR register (Interrupt Priority Registers) as shown in the figure below:

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Pay attention to the highlighted sections. From these sections, we can abstract several core issues: first, there are a total of 8 NVICIPR registers, and each register manages 4 IRQ interrupts. By calculating, we understand why the M0+ supports a maximum of only 32 IRQ interrupt sources. Adding 16 core interrupts, we can calculate that the M0+ supports a maximum of 48 interrupt sources. Therefore, M0+ is relatively easy to handle; later when we talk about M4, it will be more troublesome, haha. The second issue is that the configuration value in the priority register indicates that the corresponding interrupt priority is higher when the value is lower. The third issue is that only the highest two bits [7:6] of each PRIxx’s 8 bits are valid, meaning that the actual priority of M0+ is only four levels: 0, 1, 2, and 3, where 0 has the highest priority. The fourth issue, which I mentioned last because it is relatively easy to overlook, is that they are word-accessible, meaning that these registers can only be operated by word. Be careful not to use byte pointers to configure the priority of a single interrupt just to show off our programming skills. As for the nesting issue, for M0+, it is relatively simple; as long as the priority of the corresponding interrupt is higher, it can preempt the currently executing lower priority interrupt service at any time. For core interrupts, the interrupt priority is managed by the SCB module’s SCB_SHPR register as shown in the figure below. Actually, the system tick interrupt we commonly use has the same priority configuration as NVIC, so I won’t elaborate further. Additionally, I want to mention that if we do not configure the priority, then the lower the vector number of the corresponding interrupt source, the higher its priority. However, as I mentioned earlier, I strongly recommend configuring the priority of each interrupt used according to actual needs in a multi-interrupt situation to avoid potential risks.

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

ARM Cortex-M4 Interrupt Priority and Nesting

I mentioned earlier that although the M4 instruction set is downward compatible with M0+, there are differences in interrupt priority management. Due to the larger number of interrupt sources in M4 (up to 256), its management of interrupt priorities is slightly more complex. However, it is essential to look closely because we commonly use it, but I can guarantee that not everyone uses it correctly. Why? I will continue to break it down below (don’t worry, it’s not over yet, haha). Let’s continue with the picture:

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Continuing to find highlights in the above image, one is that M4 supports a maximum of 16+4*60=256 interrupt sources. The second is that these 60 registers can be operated by byte (we can continue to show off our skills using pointers, haha). The third is the same as M0+: the smaller the value, the higher the priority. Finally, the most significant difference is that each of your 8-bit data can define its interrupt priority. Speaking of this, a question arises: does this mean that M4 can have up to 256 priority levels? I can only say that you are thinking too much, haha. Let’s continue to look at the picture; it can be seen that PRIGROUP defines how the 8-bit data is allocated between group priority and subpriority. Here, two new terms appear: Group Priority and Subpriority. As for what these two priorities mean, everyone can continue to refer to Figure 2 for a detailed understanding. Let me take a sip of water first… Ok, back to the topic. From the following two figures, we can see that in the M4 core, the management of interrupt priority is divided into two parts: one is group priority, which manages preemption priority (whether it can be nested), meaning that a high group priority interrupt (lower value) can preempt a low group priority interrupt (higher value). If the group priorities are the same, even if the subpriority is higher than the currently executing interrupt’s subpriority, it cannot preempt it. Then some comrades may ask, what’s the use of subpriority? Haha, as shown in Figure 2, when group priorities are the same, if multiple interrupt requests occur simultaneously, the one with the higher subpriority can execute first, while the one with the lower subpriority must temporarily wait. Haha. Returning to the role of PRIGROUP, it is used to configure how the 8-bit data field of NVIC is allocated to preemption priority and subpriority. Generally, it is best to allocate 4 bits each to these two priorities, meaning that each can have a maximum of 16 levels. Where does PRIGROUP come from? Haha, actually, it is one of the three bits of the SCB_AIRCR register, as shown in Figure 3.

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Phew… Finally finished. At last, I have written down this issue, and I can free up some space in my head for other questions, haha. Additionally, while I wrote so much theoretical stuff above, ARM has already provided the function APIs for this part in the CMSIS library header files (core_cm4.h and core_cm0plus.h). The reason I went through the trouble to write this out is still due to my philosophy of wanting to understand the reasons behind things. However, in the end, I will provide two configurations for interrupt priority settings for M0+ and M4 for reference, and we won’t discuss more:

M0+

NVIC_SetPriority(PORTA_IRQn, 1);

NVIC_SetPriority(PORTB_IRQn, 2);

M4+

NVIC_SetPriorityGrouping(0x03);

NVIC_SetPriority(PORTA_IRQn, NVIC_EncodePriority(0x03,1, 2));

NVIC_SetPriority(PORTB_IRQn, NVIC_EncodePriority(0x03, 2, 2));

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Understanding ARM Cortex-M4 and Cortex-M0+ Interrupt Priority and Nested Preemption Issues

Leave a Comment

Your email address will not be published. Required fields are marked *