Cortex-M microcontrollers allow developers to assign different priority levels to interrupts. This allows high priority interrupts to preempt lower priority ones. Changing the priority of interrupts dynamically at runtime can help optimize interrupt response and overall system performance.
Interrupt Priority Levels
On Cortex-M processors, interrupts can have 256 priority levels from 0 (highest priority) to 255 (lowest priority). The NVIC (Nested Vectored Interrupt Controller) handles prioritization and dispatching of interrupts based on their priority level.
Higher priority interrupts can interrupt lower priority ones. This allows time critical functions to take precedence over less important background tasks. For example, a timer interrupt used to implement an RTOS would have very high priority to ensure task scheduling is always responsive.
Setting Interrupt Priorities
Interrupt priority is configured statically at compile time by setting the Priority field in the Interrupt Number Definition section of the startup file. For example: /* Interrupt Priorities */ #define WWDG_IRQ_PRIO 0 #define PVD_IRQ_PRIO 1 #define RTC_IRQ_PRIO 2
This sets the priority of the RTC interrupt to 2, PVD to 1, and WWDG to 0 (highest priority). The application can still change priorities dynamically at runtime if needed.
Reading Interrupt Priorities
To read the current priority of an interrupt, use the NVIC_GetPriority() function. It takes the interrupt number as a parameter and returns the 8-bit priority value (0-255). uint32_t priority; priority = NVIC_GetPriority(RTC_IRQn);
This reads back the current priority value assigned to the RTC interrupt.
Changing Interrupt Priorities Dynamically
The NVIC_SetPriority() function can be used to change the priority of an interrupt at runtime. This takes the interrupt number and 8-bit priority value as parameters. NVIC_SetPriority(RTC_IRQn, 10);
This sets the priority of the RTC interrupt to 10 dynamically at runtime. Higher priority interrupts can still preempt this interrupt.
Use Cases
Some common use cases for dynamically changing interrupt priorities include:
- Increasing the priority of a time critical interrupt temporarily during a critical code section to prevent disruption.
- Lowering the priority of a bandwidth consuming interrupt temporarily to allow higher priority transfers to complete.
- Boosting the priority of an interrupt from a communication peripheral to minimize latency.
- Implementing a dynamic interrupt priority scheme in an RTOS or hybrid RTOS system.
Important Considerations
There are some important factors to keep in mind when changing interrupt priorities dynamically:
- Do not lower the priority of an interrupt you are currently servicing below the current executing priority level. This could cause deadlock.
- Avoid boosting multiple interrupt priorities above the SysTick priority. This could impact RTOS timing.
- Only modify interrupt priorities from privileged thread mode, not handler mode.
- Ensure interrupt priority changes are atomic and thread safe.
- For Cortex-M parts without priority grouping (M0+/M1), avoid priority inversion which could cause deadlocks.
Maintaining Forward Progress
When changing priorities dynamically, the developer must ensure that all higher priority interrupts can eventually complete so lower priority ones can resume normal operation. Techniques to ensure system forward progress include:
- Limiting long running interrupts – Long interrupts at any priority level can block lower priority ones indefinitely. Keep ISRs short with most work done at thread level.
- Avoiding nested interrupts – Supporting nested interrupts requires carefully managing priority levels and stack usage.
- Allowing lower priority interrupts periodically – Occasionally drop the priority of long running interrupts to allow others to run.
- Checking for pending interrupts – Check NVIC_GetPendingIRQ() periodically and handle pending lower priority IRQs.
NVIC Priority Grouping
Cortex-M3 and M4 processors support priority grouping, which divides the 8-bit priority field into a group priority and subpriority. The NVIC_SetPriorityGrouping() API configures how the bits are split between group and subpriority: void NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
When priority grouping is used, the group priority determines the overall preemption level while the subpriority differentiates between interrupts with the same group priority level. This can help prevent priority inversion issues.
Example Priority Grouping Configurations
- No grouping – All 8 bits used for subpriority. (0xFF)
- Binary point between bit 7 and 6 – Bit 7 is group priority, bits 6-0 are subpriority. (0x80)
- Binary point between bit 5 and 4 – Bits 7-5 are group priority, bits 4-0 are subpriority. (0xA0)
The developer should carefully choose a priority grouping scheme that makes optimal use of the priority levels for the application’s use case.
Coding Best Practices
Here are some recommended coding best practices when working with interrupt priorities on Cortex-M:
- Initialize all interrupt priorities properly at startup even if they are changed later.
- Utilize priority grouping features to prevent priority inversion.
- Minimize the use of higher priority and nested interrupts when possible.
- Keep ISRs short and move processing to threads.
- Ensure interrupt priority changes are thread safe.
- Do not call NVIC priority APIs from interrupt handlers.
- Analyze worst case interrupt latency from simulation.
- Ensure system tick has a priority lower than application tasks.
Conclusion
Dynamically changing interrupt priorities can optimize responsiveness and throughput in Cortex-M systems. However, priority levels interactions can be complex so caution must be taken. Usage of priority grouping, avoidance of nested interrupts, deterministic context switch timings, and proper application design is needed to build robust systems that leverage dynamic priority levels effectively.