The ARM Cortex-M0 is an extremely popular 32-bit embedded processor used in a wide range of microcontroller units (MCUs). As an ultra low-power processor, the Cortex-M0 is designed to maximize energy efficiency in IoT and industrial applications. When optimizing for low-power usage, it may be necessary to disable peripheral interrupts to avoid waking the processor from sleep modes.
Disabling all interrupts on the Cortex-M0 can be easily achieved by setting the PRIMASK register to 1. The PRIMASK register acts as the priority mask and prevents activation of all exceptions and interrupts when set to 1. This provides a simple method to disable all interrupts without needing to manually disable individual interrupt sources.
Purpose of Disabling Interrupts
There are several reasons why a developer may want to disable all interrupts on a Cortex-M0 processor:
- Protect critical sections of code from being interrupted
- Prevent interrupts during flash memory writes
- Avoid interrupt overhead during time-sensitive operations
- Maximize low-power sleep mode by blocking wake-up events
- Simplify debugging by eliminating asynchronous interrupts
Disabling interrupts is key to maximizing sleep mode efficiency. Peripherals such as timers, ADCs, and serial ports can generate interrupts that will wake the processor. By globally disabling interrupts, the Cortex-M0 can stay in deep sleep modes for longer periods of time.
PRIMASK Register
The PRIMASK register is part of the system control block in Cortex-M0 processors. It indicates the minimal priority level for allowed exceptions and interrupts. The PRIMASK register can be set through special register access instructions such as MRS and MSR.
Setting PRIMASK to 1 will mask all interrupts and exceptions. The exception and interrupt priority levels go from 0 (highest priority) to 256 (lowest priority). So by setting PRIMASK to 1, no interrupts or exceptions below priority level 256 will be able to activate.
The PRIMASK register uses bit 0 as the enable/disable bit. When bit 0 is cleared to 0, interrupts and exceptions will function normally based on their priority levels. Setting bit 0 to 1 will disable all interrupts regardless of priority.
PRIMASK Register Format
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 __________________________________________________________________________________________________ | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | P | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| | |
- P – Interrupt disable bit
- x – Unused bits (read as zero, ignore on write)
Cortex-M0 Interrupt Handling
The Cortex-M0 processor implements interrupt handling through the Nested Vectored Interrupt Controller (NVIC). This allows flexible prioritization and vectoring of interrupt sources.
When an interrupt occurs, the processor will suspend execution of the current code and instead execute the Interrupt Service Routine (ISR) associated with the interrupt source. Upon completing the ISR, the processor will return to the original code execution.
The NVIC manages up to 32 external interrupt sources with configurable priority levels. Higher priority interrupts will interupt lower priority code execution. The PRIMASK register controls the threshold above which interrupts are masked.
Cortex-M0 Interrupt Priority Levels
- Level 0 = highest priority
- Level 1-3 = reserved for system exceptions
- Level 4-7 = external interrupts
- Level 8-14 = more external interrupts
- Level 256 = lowest priority
Setting PRIMASK to 1 masks all interrupt priority levels. This prevents any interrupt or exception from triggering, since nothing is above level 256.
Disabling Interrupts with PRIMASK
There are a few simple steps needed to disable all Cortex-M0 interrupts using the PRIMASK register:
- Include the CMSIS core_cm0.h header file
- Add MSR and MRS instructions to set/read PRIMASK register
- Set PRIMASK bit 0 to 1 before critical code section
- Clear PRIMASK bit 0 to 0 after critical code section
Here is an example code snippet to disable interrupts: #include “core_cm0.h” void critical_function() { // Disable interrupts __asm volatile (“MRS r0, PRIMASK”); __asm volatile (“ORR r0, r0, #1”); __asm volatile (“MSR PRIMASK, r0”); // Critical section of code // Re-enable interrupts __asm volatile (“MRS r0, PRIMASK”); __asm volatile (“BIC r0, r0, #1”); __asm volatile (“MSR PRIMASK, r0”); }
The in-line assembly instructions use the MSR and MRS instructions to set and read the PRIMASK register directly. ORR sets bit 0 to 1 to mask interrupts. BIC clears bit 0 to 0 to re-enable interrupts.
Alternatives to PRIMASK
While the PRIMASK register provides a simple method to disable all Cortex-M0 interrupts, there are some alternative approaches that provide finer control over interrupt masking:
- BASEPRI – Masks interrupts below a set priority level
- Interrupt Priority Registers – Change priority of individual interrupts
- NVIC_DisableIRQ() – Disable specific interrupt vectors
BASEPRI allows you to set an interrupt priority threshold, rather than disabling all interrupts. This allows higher priority interrupts to still occur while masking lower priority sources.
The NVIC interrupt priority registers let you configure the priority of specific interrupt sources. Setting a source to low priority can prevent it from interrupting time-critical code sections.
NVIC_DisableIRQ() can disable individual interrupt vector sources. This provides very fine control but requires more configuration compared to PRIMASK.
Saving and Restoring PRIMASK State
When disabling interrupts, it is important to restore the previous PRIMASK state after finishing the critical code section. This ensures that interrupt behavior returns to normal. uint32_t primask_state; // Save current state __asm volatile (“MRS %0, PRIMASK” : “=r” (primask_state) ); // Disable interrupts __asm volatile (“MOV r0, #1”); __asm volatile (“MSR PRIMASK, r0”); // Critical section // Restore previous state __asm volatile (“MSR PRIMASK, %0” :: “r” (primask_state) );
This code sample shows how to save PRIMASK to a variable, disable interrupts, restore PRIMASK, and ensures proper behavior after the critical section.
Using CMSIS Intrinsic Functions
The CMSIS headers for Cortex-M0 provide intrinsic functions that can be used instead of direct MSR and MRS instructions.
For example: // Disable interrupts __disable_irq(); // Critical section // Enable interrupts __enable_irq();
The __disable_irq() intrinsic sets PRIMASK to 1 to disable interrupts. __enable_irq() clears PRIMASK to 0 to re-enable interrupts.
CMSIS functions like __get_PRIMASK() and __set_PRIMASK() can also be used to save/restore the PRIMASK state if necessary.
Ensuring Compiler Awareness
When using instructions like MSR and MRS to directly access system registers like PRIMASK, it is necessary to ensure compiler awareness. This prevents the compiler from optimizing away the intended operations.
The volatile assembly instructions in the prior examples will prevent unintended compiler optimizations. Intrinsic CMSIS functions also inhibit optimizations.
You can also surround inline assembly with compiler optimization barriers: __asm volatile (“.syntax unified”); // Compiler specific // Inline asm to disable interrupts __asm volatile (“.syntax unified”);
The .syntax unified directives notify the compiler about the use of ARM assembly so code is not optimized incorrectly.
Interrupt Latency
When interrupts are disabled via PRIMASK or other methods, keep in mind that this increases interrupt latency. Any pending interrupts will have to wait until interrupts are re-enabled to be handled.
Leaving interrupts disabled for too long can cause problems:
- Input data loss if buffers overflow
- Control system instability
- Missed timing deadlines
Where possible, use other techniques like BASEPRI masking and priority levels to minimize the duration interrupts are disabled. This helps keep interrupt latency short and system responsiveness high.
Conclusion
The PRIMASK register provides an efficient way to disable all interrupts on an ARM Cortex-M0 processor. Setting bit 0 of PRIMASK masks all interrupt priority levels until it is cleared again.
This allows time-critical code sections to run without being interrupted. However, PRIMASK should be used carefully and interrupts enabled again promptly to minimize latency issues.
Other techniques like BASEPRI priority masking, NVIC register configuration, and CMSIS intrinsic functions can also be used for more advanced interrupt control.
Understanding how to properly disable and re-enable Cortex-M0 interrupts using PRIMASK is key to maximizing efficiency in embedded applications requiring low-power sleep modes or interrupt-free code execution.