The ARM Cortex-M0 is an ultra low power 32-bit ARM processor core designed for microcontrollers and deeply embedded applications. It features a reduced instruction set computing (RISC) architecture and a flexible interrupt controller for rapid response to real-time events. The PGA970 is a specific implementation of the Cortex-M0 core by NXP Semiconductors.
A common need when working with embedded systems like the Cortex-M0 is to temporarily disable interrupts to allow a critical section of code to run without being preempted. On the Cortex-M0, this can be achieved by setting the PRIMASK register to 1, which prevents exceptions from invoking their handlers while it is set. This prevents interrupt service routines (ISRs) from running until PRIMASK is cleared again.
PRIMASK Register
The PRIMASK register is a 32-bit read/write register present in all Cortex-M processors. Setting bit 0 of this register to 1 raises the priority mask to the maximum level, preventing all exceptions with configurable priority from invoking their exception handlers. This prevents interrupt preemption during critical sections of code.
The PRIMASK register can be accessed directly using the MRS and MSR instructions, for example: MRS R0, PRIMASK ; Read PRIMASK into R0 MSR PRIMASK, R0 ; Set PRIMASK to value in R0
Setting PRIMASK to 1 prevents all interrupts and exceptions with configurable priority levels from preempting the processor. Exceptions that cannot be masked, like NMI, HardFault, and escalated FaultMask interrupts, can still be triggered when PRIMASK is set.
Disabling Interrupts with PRIMASK
To disable interrupts temporarily on the Cortex-M0, PRIMASK can be set to 1 before a critical section of code, and cleared to 0 after it completes. For example: MRS R0, PRIMASK ; Read current PRIMASK state CPSIE I ; Globally enable interrupts MOV R1, #1 ; Set mask value MSR PRIMASK, R1 ; Disable interrupts ;; Critical section MSR PRIMASK, R0 ; Restore PRIMASK state
This sequence saves the current PRIMASK state, globally enables interrupts via CPSIE, sets PRIMASK to 1 to disable interrupts, executes a critical section of code, then restores PRIMASK to its original state.
Alternatively, the CPSID I instruction can be used to disable interrupts instead of MSR PRIMASK: MRS R0, PRIMASK ; Read current PRIMASK state CPSIE I ; Globally enable interrupts CPSID I ; Disable interrupts ;; Critical section MSR PRIMASK, R0 ; Restore PRIMASK state
CPSID I sets PRIMASK to 1 internally, preventing exceptions from preempting execution until a matching CPSIE I instruction clears PRIMASK.
Using PRIMASK in a C Program
In C code, PRIMASK can be directly accessed using the special register intrinsics provided by ARM: #include //Device header int main(void) { // Local variable for PRIMASK state uint32_t primask_state; // Read current PRIMASK state primask_state = __get_PRIMASK(); // Enable interrupts __enable_irq(); // Set PRIMASK to disable interrupts __disable_irq(); // Critical section // Restore PRIMASK state __set_PRIMASK(primask_state); }
This saves the current PRIMASK state, enables interrupts globally, sets PRIMASK to disable interrupts for a critical section, then restores the original PRIMASK state.
Using CMSIS Functions
The Cortex Microcontroller Software Interface Standard (CMSIS) provides common APIs across Cortex-M devices. CMSIS functions can also be used to manipulate PRIMASK: #include “stm32xxxx.h” // Device header #include “core_cm0.h” // CMSIS core header int main(void) { // Local variable for PRIMASK state uint32_t primask_state; // Read current PRIMASK state primask_state = __get_PRIMASK(); // Enable interrupts __enable_irq(); // Set PRIMASK to disable interrupts __disable_irq(); // Critical section // Restore PRIMASK state __set_PRIMASK(primask_state); }
This achieves the same result as the previous example, using CMSIS functions.
Disabling Interrupts in C++
For C++ code, PRIMASK manipulation can be wrapped in a class for encapsulation: class InterruptDisable { public: InterruptDisable() { // Save current state state_ = __get_PRIMASK(); // Disable interrupts __disable_irq(); } ~InterruptDisable() { // Restore original state __set_PRIMASK(state_); } private: uint32_t state_; }; int main() { // Disable interrupts for this scope { InterruptDisable disable; // Critical section } // Interrupts restored when disable goes out of scope }
Here a class constructor and destructor are used to disable/restore interrupts for a particular scope. This encapsulates PRIMASK manipulation safely.
Using Inline Assembly
For complete control, PRIMASK can be directly accessed using inline assembly in C/C++ code: // Variable for PRIMASK register uint32_t primask; asm volatile(“mrs %0, primask” : “=r” (primask)); // Read PRIMASK // Enable interrupts __enable_irq(); asm volatile(“cpsid i”); // Set PRIMASK to disable interrupts // Critical section asm volatile(“msr primask, %0” : : “r” (primask)); // Restore PRIMASK
This allows the PRIMASK state to be saved, interrupts enabled, PRIMASK set to disable interrupts, a critical section executed, then the original PRIMASK state restored all in inline assembly for precise control.
Disabling Interrupts with BASEPRI
An alternative to using PRIMASK is to raise the BASEPRI priority mask register. Setting BASEPRI to a non-zero value prevents all exceptions with a priority less than or equal to BASEPRI from invoking their exception handlers.
For example: // Local variable for BASEPRI state uint32_t basepri_state; // Read current BASEPRI asm volatile(“mrs %0, basepri” : “=r” (basepri_state)); // Set BASEPRI to disable interrupts asm volatile(“mov r0, #128”); asm volatile(“msr basepri, r0”); // Critical section // Restore original BASEPRI asm volatile(“msr basepri, %0” : : “r” (basepri_state));
This saves the current BASEPRI value, sets BASEPRI to a priority level that masks all interrupts, executes a critical section, then restores the original BASEPRI state. BASEPRI allows selectively disabling a subset of lower priority interrupts unlike PRIMASK.
Choosing Between PRIMASK and BASEPRI
PRIMASK and BASEPRI both allow interrupts to be temporarily disabled on Cortex-M0 devices like the PGA970. Some key differences:
- PRIMASK disables all interrupts with configurable priority levels.
- BASEPRI allows selectively disabling interrupts below a set priority level.
- PRIMASK disables all faults/exceptions except NMI, HardFault, and escalated FaultMask interrupts.
- BASEPRI only masks peripheral interrupts, not faults or system exceptions.
- PRIMASK is simpler and faster to manipulate directly.
- BASEPRI allows finer gradation of interrupt disabling.
In general, PRIMASK is good for short critical sections that require no interrupts at all. BASEPRI can be used if finer grained interrupt control is needed for medium term critical sections.
Additional Cortex-M0 Interrupt Controls
In addition to PRIMASK and BASEPRI, the Cortex-M0 provides several other mechanisms for interrupt control:
- FAULTMASK – Generates a HardFault when a configurable fault occurs.
- CONTROLR – Controls exception behaviors and stack limit checking.
- SHPR – Configures system handler priorities like SVCall, PendSV, SysTick, etc.
- AIRCR – Controls priority grouping, preemption, vectored interrupt handling.
- SCR – Configures SleepOnExit, SEVONPEND, interrupt handling on wake up.
- ICER/ISER/ISPR – Enable/disable interrupts in the NVIC.
These provide a robust set of tools for managing interrupt response and behavior in the Cortex-M0. PRIMASK and BASEPRI are commonly used for temporary critical sections, while the other registers handle broader interrupt configuration and control.
Conclusion
Temporary interrupt disabling on the Cortex-M0 is critical for protecting access to shared resources and providing atomic operations. PRIMASK and BASEPRI give developers powerful options for disabling interrupts during critical code sections without having to manage interrupt enables/disables individually.
By understanding these registers and the techniques for utilizing them effectively, developers can write Cortex-M0 firmware that robustly handles complex real-time events and constraints. Mastering PRIMASK and BASEPRI usage unlocks the real performance potential of the Cortex-M0 in embedded systems.