當前位置:首頁 > 公眾號精選 > TopSemic嵌入式

前段時間一工程師向我咨詢了一個問題,問我為什么他的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)生的話,還得從源頭上配置相關外設的寄存器關掉中斷才行。


本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉