The Cortex-M0 is an ultra low power 32-bit microcontroller core designed by ARM. It is intended for use in embedded systems requiring high efficiency and low cost. The Cortex-M0 implements the ARMv6-M architecture, which includes support for managing interrupts through a Nested Vectored Interrupt Controller (NVIC).
When an interrupt occurs on the Cortex-M0, the processor will stop executing the current program and instead execute an Interrupt Service Routine (ISR) to handle the interrupt. The NVIC allows prioritizing and nesting of interrupts, so that more critical interrupts can preempt less critical ones. This helps ensure timely response to important external events.
Interrupt Priority Levels
The NVIC in the Cortex-M0 supports up to 32 external interrupt inputs. Each interrupt input can be assigned a priority level from 0 (highest priority) to 3 (lowest priority). The priority levels allow higher priority interrupts to preempt lower priority ones.
When multiple pending interrupts have the same priority level, they will execute in order of arrival. For example, if two level 2 interrupts are pending, the one that arrived first will execute first. If an interrupt arrives while another is executing, it will pend until the current one finishes.
Default Priority Levels
By default, all Cortex-M0 interrupt inputs are assigned priority level 0, the highest priority. This means that by default, all interrupts are treated equally. If multiple interrupts arrive at the same time, the order they will execute is basically unpredictable.
To prioritize interrupts appropriately for a given application, the firmware needs to explicitly assign priority levels other than 0 to at least some interrupts. This allows distinguishing critical interrupts from less important ones.
Setting Priority Levels
There are a few ways to set the priority level for a Cortex-M0 interrupt in firmware:
- Directly write to the Priority Registers in the NVIC. There is one 8-bit register for each interrupt input. The upper bits of the priority register contain the priority level.
- Use the NVIC_SetPriority() function provided by ARM CMSIS libraries. This provides a simpler interface to set the priority level for a specific interrupt.
- Set priority levels at compile time by providing directives in the vector table. This hard codes priority levels rather than setting them in runtime firmware.
The first two options allow changing priorities dynamically at runtime, while the vector table approach fixes the priorities at compile time.
Premption of Lower Priority Interrupts
A key feature of the Cortex-M0 NVIC is the ability of higher priority interrupts to preemptlower priority ones. When a pending interrupt’s priority is higher than the currently executing interrupt, it will trigger a context switch.
This context switch involves automatically saving processor state of the current interrupt, then loading the state of the higher priority interrupt and executing its ISR. When the higher priority ISR finishes, the lower priority context is reloaded and execution resumes.
This preemption capability is important for meeting real-time requirements in embedded systems. The highest priority interrupts can interrupt at any point to be serviced immediately. This allows rapid response to important external events detected by the interrupts.
Enabling/Disabling Preemption
The Cortex-M0 provides flexibility in managing preemption of interrupts. There are a few different ways preemption can be controlled:
- Set priority levels appropriately – higher priority interrupts will always preempt lower priority ones.
- Disable interrupts globally – no preemption will occur while interrupts are disabled.
- Disable preemption explicitly – prevents context switch but allows interrupt detection.
- Mark ISR as non-preemptible – the current ISR will run to completion regardless of pending interrupts.
For most applications, configuring priority levels appropriately avoids the need to explicitly manage preemption in the code. However in some cases, temporarily disabling preemption around critical sections of code may be necessary.
Nested Interrupts
The Cortex-M0 NVIC allows nested interrupts, meaning interrupts can preempt other interrupts. An interrupt of any priority level may arrive and preempt the currently executing interrupt routine.
This nested interrupt capability is enabled by default. It can improve real-time response by allowing very high priority interrupts to preempt even interrupts already in progress.
However, excessive levels of nesting can be detrimental to performance and increase stack usage. The NVIC supports nesting up to 16 levels deep, but nesting beyond 4-6 levels is generally not recommended for most applications.
Disabling Nesting
Nested interrupts can be disabled globally by setting the PRIGROUP field in the Application Interrupt and Reset Control Register. This will disable nesting and only allow priority levels to determine preemption.
Nesting of a particular ISR can also be disabled by marking it as non-preemptible. Even if nesting is enabled globally, a non-preemptible ISR will run to completion regardless of pending interrupts.
Interrupt Latency
An important performance characteristic of interrupts is the latency from when the interrupt signal is asserted to when the processor actually begins executing the associated ISR. Lower interrupt latency allows faster reaction to external events.
Factors impacting interrupt latency on the Cortex-M0 include:
- Interrupt detection and prioritization by the NVIC
- Completion of the current processor cycle before handling the interrupt
- Context saving of current stack frame
- Jumping to vector table and fetching the ISR address
Typical interrupt latency is around 12-20 clock cycles. But this can vary depending on interrupt priority, nesting, pipeline effects, and the point in the program where the interrupt occurs.
Ways to reduce interrupt latency include:
- Using higher interrupt priorities to avoid preemption delays
- Minimizing context saving requirements
- Optimizing ISR entry code for efficient pipelining
For the fastest response, critical interrupts should use the highest priority level. They should have minimal stack usage and simple, efficient ISR entry sequences.
Tail-Chaining Interrupts
Tail-chaining is a technique to reduce interrupt latency by avoiding context switches between certain interrupts. It works by having an ISR deliberately return to Thread Mode instead of returning to the preempted code.
This causes the NVIC to directly chain to the next highest priority pending interrupt rather than restoring the previous stack frame first. It saves context switching time when alternating between specific fast ISRs.
To implement tail-chaining in the Cortex-M0:
- Set up the ISRs to tail-chain at the end by returning to Thread Mode instead of the preempted code.
- Ensure the ISRs share the same or compatible stack requirements.
- Set the interrupt priorities appropriately so the ISRs tail-chain in the desired order.
Tail-chaining improves determinism and reduces latency between select ISRs. But it requires carefully managing stack usage and interrupt priorities to work correctly.
Conclusion
Managing interrupt handling on the Cortex-M0 requires configuring NVIC priority levels, preemption and nesting appropriately for the application. Higher priority levels allow more critical interrupts to preempt lower priority ones. Preemption and nesting provide flexibility in balancing latency, determinism and stack usage.
Understanding these priorities and preemption capabilities allows optimizing interrupt handling for responsive and robust embedded systems built on the Cortex-M0 and similar Cortex-M processors.