__disable_irq() 和 __enable_irq()定義在哪?
掃描二維碼
隨時隨地手機看文章
前段時間一工程師向我咨詢了一個問題,問我為什么他的MCU KEIL工程代碼里沒有找到__disable_irq() 和 __enable_irq()的具體定義,是不是有問題。
直接在工程里搜索,確實只能在cmsis_armcc.h文件里看到下面的兩處注釋說明,并沒有這倆函數(shù)的具體定義。
可是如果直接去調(diào)用這倆函數(shù)的話,編譯又不會報錯,那么這倆函數(shù)的定義到底在哪呢?
__disable_irq() 和 __enable_irq() 是所謂的intrinsic函數(shù),編譯器自動識別并替換為相關的指令,它們其實是編譯器的一部分,實際的定義位于arm_compat.h 文件中(位于KEIL的安裝目錄里),
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 /* !defined(__ARM_ARCH_PROFILE) || __ARM_ARCH_PROFILE != 'M' */ __asm__ __volatile__("mrs %[cpsr], cpsr\n" "cpsid i\n" : [cpsr] "=r"(cpsr)); return cpsr & 0x80; #endif #else /* __ARM_ARCH < 6 */ 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 // (!defined(__ARM_ARCH_PROFILE) || __ARM_ARCH_PROFILE != 'M' || // __ARM_ARCH != 6) && !__ARM_ARCH_8M_BASE__ static __inline__ void __attribute__((__always_inline__, __nodebug__)) __enable_fiq(void) { #if __ARM_ARCH >= 6 __asm__ __volatile__("cpsie f"); #else /* __ARM_ARCH < 6 */ unsigned int tmp; __asm__ __volatile__( "mrs %[tmp], CPSR\n" "bic %[tmp], %[tmp], #0x40\n" "msr CPSR_c, %[tmp]\n" : [tmp]"=r"(tmp)); #endif } #endif
核心是 cpsie i 和 cpsid i 這兩個指令。
cps全稱change processor state,即改變PRIMASK這個寄存器值
ie: interrupt enable. 中斷使能,即PRIMASK.PM設置為0
id: interrupt disable. 中斷關閉,即PRIMASK.PM設置為1
__enable_irq()函數(shù)調(diào)用cpsie i指令。
__disable_irq()函數(shù)除調(diào)用cpsid i 指令,同時返回了PRIMASK的值,即如果返回值為 0,則表示中斷在調(diào)用該函數(shù)之前是使能的;如果返回值為1,則表示中斷在調(diào)用函數(shù)之前是禁用的。
需要注意的是:如果之前開啟了相關外設的中斷功能,在調(diào)用__disable_irq()函數(shù)關中斷后,這時如果有中斷觸發(fā),那么不會去進行中斷響應。但是在調(diào)用__enable_irq()開啟中斷后,MCU會立即處理之前觸發(fā)的中斷。這說明__disable_irq()只是禁止CPU去響應中斷,沒有真正的去屏蔽中斷的觸發(fā),當中斷發(fā)生后,相應的寄存器會將中斷標志置位,在__enable_irq()開啟中斷后,由于相應的中斷標志沒有清空,因而還會觸發(fā)中斷。
以下述代碼為例,程序中使用了一個GPIO中斷,當按鍵按下時翻轉(zhuǎn)一次LED。實際測試如果在調(diào)用__disable_irq()后、__enable_irq()之前的這3s時間內(nèi)按下按鍵,并不會進入中斷翻轉(zhuǎn)LED,雖然這時中斷標志位已經(jīng)產(chǎn)生了。
但是調(diào)用__enable_irq()之后就會立刻進入到中斷服務函數(shù)中。
int main(void) { /* 配置系統(tǒng)時鐘 */ system_clock_config(); /* Systick初始化 */ std_delay_init(); /* LED初始化 */ led_init(); /* EXTI初始化 */ exti_init(); __disable_irq(); std_delayms(3000); __enable_irq(); while (1) { } } /** * @brief EXTI4_15中斷服務函數(shù) * @retval 無 */ void EXTI4_15_IRQHandler(void) { /* 讀取EXTI通道中斷掛起狀態(tài) */ if (std_exti_get_pending_status(EXTI_LINE_GPIO_PIN13)) { /* 清除EXTI通道中斷掛起狀態(tài) */ std_exti_clear_pending(EXTI_LINE_GPIO_PIN13); LED1_TOGGLE(); } }
說到這里你可能還注意到還有__NVIC_DisableIRQ(IRQn_Type IRQn)、__NVIC_EnableIRQ(IRQn_Type IRQn) 這倆函數(shù)
/** \brief Disable Interrupt \details Disables a device specific interrupt in the NVIC interrupt controller. \param [in] IRQn Device specific interrupt number. \note IRQn must not be negative. */ __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(); } }
/** \brief Enable Interrupt \details Enables a device specific interrupt in the NVIC interrupt controller. \param [in] IRQn Device specific interrupt number. \note IRQn must not be negative. */ __STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) >= 0) { NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL)); } }
這倆函數(shù)和上述函數(shù)的區(qū)別是,上面的兩個函數(shù)是開關全局的中斷,這倆函數(shù)是針對某特定的中斷。
但是有一點相同的是,如果在調(diào)用__NVIC_DisableIRQ之后發(fā)生了中斷事件,當調(diào)用__NVIC_EnableIRQ(IRQn_Type IRQn)之后還是會進入到中斷處理。
綜上disable函數(shù)只是不響應中斷,并不會影響中斷的產(chǎn)生,在disable狀態(tài)下如果發(fā)生中斷則會掛起,等到enable后滿足條件還是會被執(zhí)行。如果不希望此現(xiàn)象發(fā)生,那么需要再enable前清除掉相關外設模塊中斷掛起請求標志。
如果想真正禁止中斷的產(chǎn)生的話,還得從源頭上配置相關外設的寄存器關掉中斷才行。