Fundamentals 7 min read

Understanding __disable_irq() and __enable_irq() Intrinsic Functions in ARM Cortex-M

The article explains that __disable_irq() and __enable_irq() are compiler‑provided intrinsics that expand to CPSID i and CPSIE i instructions, return the prior PRIMASK state, globally mask CPU interrupt handling without stopping hardware events, and contrasts them with CMSIS __NVIC_DisableIRQ/EnableIRQ which control individual interrupt lines.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Understanding __disable_irq() and __enable_irq() Intrinsic Functions in ARM Cortex-M

Recently an engineer asked why the functions __disable_irq() and __enable_irq() could not be found in the KEIL project source files. The definitions are not present in the project because they are intrinsic functions provided by the compiler.

These intrinsics are recognized and replaced by the compiler with the corresponding assembly instructions. Their actual implementation resides in the arm_compat.h file located in the KEIL installation directory.

The core of the implementation uses the cpsid i and cpsie i instructions to disable and enable interrupts respectively.

static __inline__ unsigned int __attribute__((__always_inline__, __nodebug__))
__disable_irq(void) {
  unsigned int cpsr;
#if __ARM_ARCH >= 6
#if defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'M'
  __asm__ __volatile__("mrs %[cpsr], primask\n"
                       "cpsid i\n"
                       : [cpsr] "=r"(cpsr));
  return cpsr & 0x1;
#else
  __asm__ __volatile__("mrs %[cpsr], cpsr\n"
                       "cpsid i\n"
                       : [cpsr] "=r"(cpsr));
  return cpsr & 0x80;
#endif
#else
  unsigned int tmp;
  __asm__ __volatile__("mrs %[cpsr], CPSR\n"
                       "bic %[tmp], %[cpsr], #0x80\n"
                       "msr CPSR_c, %[tmp]\n"
                       : [tmp]"=r"(tmp), [cpsr]"=r"(cpsr));
  return cpsr & 0x80;
#endif
}
#if (defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'M' &&            \
    __ARM_ARCH == 6) || __ARM_ARCH_8M_BASE__
static __inline__ void __attribute__((unavailable(
    "intrinsic not supported for this architecture"))) __enable_fiq(void);
#else
static __inline__ void __attribute__((__always_inline__, __nodebug__))
__enable_fiq(void) {
#if __ARM_ARCH >= 6
  __asm__ __volatile__("cpsie f");
#else
  unsigned int tmp;
  __asm__ __volatile__("mrs %[tmp], CPSR\n"
                       "bic %[tmp], %[tmp], #0x40\n"
                       "msr CPSR_c, %[tmp]\n"
                       : [tmp]"=r"(tmp));
#endif
}
#endif

The __enable_irq() function expands to the cpsie i instruction, while __disable_irq() expands to cpsid i and also returns the previous PRIMASK value. A return value of 0 means interrupts were enabled before the call; 1 means they were disabled.

Note that disabling interrupts globally does not prevent the hardware from generating interrupt events; it only stops the CPU from servicing them. Pending interrupt flags remain set, and once __enable_irq() is called, the CPU will immediately handle any pending interrupts.

Example usage:

int main(void) {
    system_clock_config();
    std_delay_init();
    led_init();
    extx_init();
    __disable_irq();
    std_delayms(3000);
    __enable_irq();
    while (1) {
    }
}

void EXTI4_15_IRQHandler(void) {
    if (std_exti_get_pending_status(EXTI_LINE_GPIO_PIN13)) {
        std_exti_clear_pending(EXTI_LINE_GPIO_PIN13);
        LED1_TOGGLE();
    }
}

In addition to the global interrupt control functions, the CMSIS functions __NVIC_DisableIRQ(IRQn_Type IRQn) and __NVIC_EnableIRQ(IRQn_Type IRQn) control individual interrupt lines. They differ from the global functions in that they affect only the specified IRQ.

__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn) {
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ICER[0U] = (uint32_t)(1UL << ((uint32_t)IRQn & 0x1FUL));
        __DSB();
        __ISB();
    }
}
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn) {
    if ((int32_t)(IRQn) >= 0) {
        NVIC->ISER[0U] = (uint32_t)(1UL << ((uint32_t)IRQn & 0x1FUL));
    }
}

These functions disable or enable the handling of a specific peripheral interrupt. If an interrupt occurs while it is disabled, the pending flag remains set and will be serviced once the interrupt is re‑enabled, unless the flag is cleared manually.

In summary, __disable_irq() and __enable_irq() only affect the CPU's response to interrupts; they do not prevent the hardware from generating them. To truly stop interrupt generation, the corresponding peripheral registers must be configured.

C++InterruptARMembeddedintrinsicMCU
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.