Panel For Example Panel For Example Panel For Example
Disabling & Canceling MCU Interrupts

Disabling & Canceling MCU Interrupts

Author : Adrian September 03, 2025

What happens when external interrupts arrive so quickly that a new one occurs before the previous one has finished processing?

Interrupts are typically generated by hardware, such as peripherals or external pins. When a specific internal or external event occurs, the MCU's interrupt system forces the CPU to pause its current program and execute an interrupt service routine (ISR) to handle the event. After the ISR completes, the CPU returns to the point of interruption and resumes the original program. All Cortex-M core-based systems feature a Nested Vectored Interrupt Controller (NVIC), a component responsible for managing interrupts and other service events. The NVIC is tightly coupled with the Cortex-M0 processor core, providing interrupt control and support for system exceptions.

The processor's NVIC can handle multiple maskable interrupt channels with programmable priorities. Interrupt requests can be level-triggered or triggered by a pulse as short as a single clock cycle. Each external interrupt line can be independently enabled, disabled, or pended. The pending status can also be set and cleared manually.

When an interrupt request occurs while the main program is running, the main program is paused, and the corresponding ISR is executed. This is known as an interrupt response. After the ISR finishes, execution returns to the breakpoint in the main program. Multiple interrupts can be nested; a lower-priority interrupt being serviced can be preempted by a higher-priority interrupt. After the higher-priority ISR is complete, execution returns to the lower-priority ISR. This is supported by a "tail-chaining" mechanism.

Core interrupts, such as those for exception management and sleep modes, have their priorities managed by the System Control Block (SCB) registers. The priorities of IRQs are managed by the NVIC. The NVIC registers are memory-mapped, starting at address 0xE000E100, and must be accessed with 32-bit operations. The SCB registers start at 0xE000ED00, also requiring 32-bit access, and primarily control SysTick operations, exception management, and sleep modes.

The NVIC has the following features:

  • Flexible interrupt management: enable, disable, and priority configuration
  • Hardware support for nested interrupts
  • Vectored exception entry
  • Interrupt masking

1. Enabling and Disabling Interrupts

ARM separates the interrupt enable-set and enable-clear registers into two different addresses. This design offers several advantages. First, it simplifies the process of enabling an interrupt, requiring only a single access to the NVIC, which reduces code size and execution time. Second, it prevents the loss of control signals. When multiple application processes access the registers simultaneously or if an operation on one interrupt enable bit occurs during a read-modify-write cycle for another, data can be lost. Separating the set and clear registers effectively prevents this issue.

This allows for independent control over enabling and disabling each interrupt.

C Code Example

*(volatile unsigned long) (0xE000E100) = 0x4; // Enable interrupt #2 *(volatile unsigned long) (0xE000E180) = 0x4; // Disable interrupt #2

Assembly Code Example

__asm void Interrupt_Enable() { LDR R0, =0xE000E100 ; // Address of ISER (Interrupt Set-Enable Register) MOVS R1, #04 ; // Value to set for interrupt #2 STR R1, [R0] ; // Enable interrupt #2 } __asm void Interrupt_Disable() { LDR R0, =0xE000E180 ; // Address of ICER (Interrupt Clear-Enable Register) MOVS R1, #04 ; // Value to set for interrupt #2 STR R1, [R0] ; // Disable interrupt #2 }

CMSIS Driver Functions

// Enable interrupt #IRQn __STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL)); } } // Disable interrupt #IRQn __STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { NVIC->ICER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL)); __DSB(); __ISB(); } } // Read the enable status of interrupt #IRQn __STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { return((uint32_t)(((NVIC->ISER[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); } else { return(0U); } }

2. Setting and Clearing Pending Interrupts

If an interrupt occurs but cannot be processed immediately, its request will be pended. The pending state is stored in a register and will remain until the processor's current priority level is low enough to service the request or until the pending state is manually cleared. The interrupt pending state can be accessed or modified through two separate registers: Interrupt Set-Pending and Interrupt Clear-Pending. This design allows each bit to be modified independently, preventing data loss when two application processes compete for access.

C Code Example

*(volatile unsigned long)(0xE000E100) = 0x4; // Enable interrupt #2 *(volatile unsigned long)(0xE000E200) = 0x4; // Set interrupt #2 to pending *(volatile unsigned long)(0xE000E280) = 0x4; // Clear the pending state of interrupt #2

Assembly Code Example

__asm void Interrupt_Set_Pending() { LDR R0, =0xE000E100 ; // Address of Interrupt Set-Enable Register MOVS R1, #0x4 ; // Interrupt #2 STR R1, [R0] ; // Enable interrupt #2 LDR R0, =0xE000E200 ; // Address of Interrupt Set-Pending Register MOVS R1, #0x4 ; // Interrupt #2 STR R1, [R0] ; // Set interrupt #2 to pending } __asm void Interrupt_Clear_Pending() { LDR R0, =0xE000E100 ; // Address of Interrupt Set-Enable Register MOVS R1, #0x4 ; // Interrupt #2 STR R1, [R0] ; // Enable interrupt #2 LDR R0, =0xE000E280 ; // Address of Interrupt Clear-Pending Register MOVS R1, #0x4 ; // Interrupt #2 STR R1, [R0] ; // Clear the pending state of interrupt #2 }

CMSIS Driver Functions

__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { NVIC->ISPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL)); } } __STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { NVIC->ICPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL)); } } __STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { return((uint32_t)(((NVIC->ISPR[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); } else { return(0U); } }

The NVIC is part of the processor core. Therefore, while it may be mentioned briefly in the user manual for a specific MCU like the MM32, detailed information about its registers and functionality is found in the Cortex-M0 Technical Reference Manual.