The Cortex-M3 and Cortex-M4 microcontrollers allow for dynamic changing of interrupt priorities at runtime. This provides flexibility in adjusting the priority levels of interrupts in response to application needs. With static interrupt priorities, the priority levels are fixed at compile time and cannot be altered while the application is running. Dynamic priorities enable higher priority interrupts to be serviced more quickly when required.
Introduction to Interrupts and Priority Levels
An interrupt is a signal to the processor that an event requires immediate attention. When an interrupt occurs, the processor stops its current task and executes an Interrupt Service Routine (ISR) to handle the event. Without interrupts, the processor would have to continuously check for events, which is inefficient.
Each interrupt on Cortex-M3/M4 has a programmable priority level from 0 (highest) to 240 (lowest). The NVIC (Nested Vectored Interrupt Controller) uses these priority levels to determine which ISR to execute when multiple interrupt requests occur simultaneously. Higher priority interrupts will preempt lower priority ones.
Setting Interrupt Priorities
Interrupt priorities can be set statically at compile time by specifying the priority in the interrupt vector table. For example:
void TIM2_IRQHandler(void) {
// ISR Code
}
void ADC1_2_IRQHandler(void) {
// ISR Code
}
// Interrupt Vector Table
TIM2_IRQHandler, 0x01 // Priority 1
ADC1_2_IRQHandler, 0x02 // Priority 2
This establishes TIM2 at priority 1 and ADC1_2 at priority 2. TIM2 will always preempt ADC1_2.
To enable dynamic priority changes, priorities must be set at runtime. This is done using the NVIC’s SetPriority() and GetPriority() functions:
// Read current priority
uint32_t pri = NVIC_GetPriority(IRQn_Type IRQn);
// Set new priority level
NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
The IRQn parameter specifies the interrupt number. Priority is any value from 0 to 240.
Changing Priorities from ISR Code
A key use case for dynamic priorities is adjusting priorities from within ISR code itself. For example:
void TIM2_IRQHandler(void) {
// TIM2 ISR Code
// Increase ADC priority to preempt current ISR
NVIC_SetPriority(ADC1_2_IRQn, 1);
}
This allows TIM2 ISR to increase the ADC1_2 priority above its own, forcing the ADC ISR to preempt the currently running TIM2 routine. This is useful when the TIM2 routine needs to urgently trigger the ADC.
Changing Priorities from Thread Mode
Priorities can also be modified from thread mode (outside of ISRs):
// Main thread code
NVIC_SetPriority(ADC1_2_IRQn, 1);
The thread can raise ADC priority above TIM2, again forcing ADC to preempt TIM2 when triggered.
Atomic Priority Changes
Changing priorities across multiple interrupts requires atomic operations to avoid race conditions. For example:
uint32_t priMask;
// Disable interrupts
priMask = __get_PRIMASK();
__disable_irq();
// Change priorities
NVIC_SetPriority(ADC1_2_IRQn, 1);
NVIC_SetPriority(TIM2_IRQn, 2);
// Re-enable interrupts
__set_PRIMASK(priMask);
This prevents a priority inversion where TIM2 gets set to priority 2 before ADC is raised to priority 1.
Priority Grouping
The Cortex-M3/M4 allow interruprs to be grouped into priority buckets. For example, with 4-bit priority grouping, the 240 priority levels are divided into 16 groups with 16 priority levels per group.
The Application Interrupt and Reset Control Register (AIRCR) configures the number of priority bits. This determines the number of preemption levels supported.
// 4-bit priority grouping (16 priority levels)
AIRCR |= 0x05FA0000;
The priority grouping affects how priorities are interpreted by the processor. When changing priorities dynamically, the grouping must be taken into account.
Usage Guidelines
Here are some guidelines when using dynamic priority changes:
- Avoid excessive priority tweaking as this can make code complex and hard to follow.
- Use static priorities where possible and limit dynamic changes to targeted scenarios.
- Ensure atomic access when modifying multiple priorities.
- Keep in mind priority grouping configuration.
- Prioritize time-critical interrupts at higher levels.
- Watch for unintentional priority inversions.
- Test extensively for race conditions.
Use Cases
Some example use cases where dynamic priority changes are useful:
- Allowing a time-critical interrupt to preempt lower priority events
- Giving priority to motor control ISRs in motor drive systems
- Letting wireless receive interrupts preempt transmit routines
- Prioritizing touch sensing interrupts to improve response time
- Boosting graphics rendering routine priority to maintain frame rate
Conclusion
Dynamic interrupt priority modifications give developers added flexibility in Cortex-M3/M4 based systems. By adapting priorities at runtime, time-critical tasks can be executed faster and latency reduced when it matters most. However, dynamic priorities should be used judiciously to keep code understandable and prevent unintended effects.
With smart usage, developers can build more responsive real-time systems using the priority change capabilities of Cortex-M3/M4 microcontrollers.