痞子衡嵌入式:嵌入式MCU中標准的三重中斷控制設計



  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是嵌入式MCU中標准的三重中斷控制設計

  我們知道在 MCU 裸機中程序代碼之所以能完成多任務並行實時處理功能,其實主要是靠中斷來調度的,沒有中斷,CPU 就只能按順序"呆板"地執行代碼。很多人都說是中斷能力賦予了 MCU 真正的靈魂,能正確認識和熟練使用 MCU 中斷,基本上就算玩熟了這顆 MCU。

  痞子衡之前寫過一篇 《中斷處理函數(IRQHandler)的標准流程》,里面詳細講了中斷處理函數里的標准代碼流程與寫法,這篇文章可讓大家對 MCU 里的中斷用法有個初步認識。今天痞子衡以 ARM Cortex-M 內核 MCU 為例再來介紹下業界標准的三重中斷控制設計:

一、外設事件中斷控制

  MCU 中最底層的中斷控制針對的是外設里某個具體的事件,這個控制來自於外設模塊本身,以恩智浦 i.MXRT 系列 MCU 的 GPT 定時器模塊為例。如下是 GPT 模塊寄存器列表,你可以發現其中有經典的 IR 和 SR 寄存器,SR 是事件狀態寄存器,IR 是中斷事件控制寄存器:

  GPT 定時器一旦被使能,其運行狀態(一共支持 6 個事件:超時、輸入捕獲 x 2ch、比較輸出 x 3ch)都會實時記錄在 SR 寄存器中,如果不在 IR 寄存器中將事件中斷開啟(默認是關閉的),那么就需要用戶在代碼里手動去查詢 SR 寄存器置起的事件標志位以處理對應事件。

  • Note:SR 寄存器中置起的事件標志位需要在事件處理前手動清除掉。如果標志位不及時清除,可能會遺漏下一次事件的處理(比如先處理當前事件,后清除事件標志位,那么處理事件期間再次發生的事件就會被漏掉)。如果標志位忘了清除,同一次事件就會被處理兩次及以上。

  當然在實際應用中,為了節省 CPU 帶寬,我們都是要開啟外設事件中斷的,MCU 廠商 SDK 包里一般都會提供相應接口函數(取自 fsl_gpt.h):

typedef enum _gpt_interrupt_enable
{
    kGPT_OutputCompare1InterruptEnable = GPT_IR_OF1IE_MASK,
    kGPT_OutputCompare2InterruptEnable = GPT_IR_OF2IE_MASK,
    kGPT_OutputCompare3InterruptEnable = GPT_IR_OF3IE_MASK,
    kGPT_InputCapture1InterruptEnable  = GPT_IR_IF1IE_MASK,
    kGPT_InputCapture2InterruptEnable  = GPT_IR_IF2IE_MASK,
    kGPT_RollOverFlagInterruptEnable   = GPT_IR_ROVIE_MASK,
} gpt_interrupt_enable_t;

// 開啟 GPTx 的 xx 事件中斷
static inline void GPT_EnableInterrupts(GPT_Type *base, uint32_t mask)
{
    base->IR |= mask;
}

// 關閉 GPTx 的 xx 事件中斷
static inline void GPT_DisableInterrupts(GPT_Type *base, uint32_t mask)
{
    base->IR &= ~mask;
}

  使能 GPT1 的超時事件中斷代碼示例如下:

void periph_int_config(void)
{
    // 初始化 GPT1...
    GPT_Init(GPT1, &gptConfig);
    // ...

    // 開啟 GPT1 的超時事件中斷
    GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);
}

二、外設全局中斷控制

  MCU 中第二層的中斷控制針對的是整個外設,這個控制來自於 Cortex-M 內核的 NVIC 模塊。如下是 NVIC 模塊寄存器列表(取自 ARMv8-M 手冊,除了 IABRn 和 ITNSn 寄存器組外,其余寄存器適用全部的 Cortex-M 家族),其中跟中斷開關相關的是 ISER 和 ICER 寄存器:

  當 MCU 中某外設(比如上一節里的 GPT)被使能后,即使其內部事件中斷已被開啟,也不意味着系統中斷一定會被觸發,因為 NVIC 里對於這個外設的全局中斷開關(同一外設中所有事件共享一個系統中斷資源,即一個中斷號)還沒有開啟。ARM CMSIS 包里提供了外設全局中斷控制函數(取自 core_cm7.h 文件):

#define NVIC_EnableIRQ              __NVIC_EnableIRQ
#define NVIC_DisableIRQ             __NVIC_DisableIRQ

// 開啟 xx 外設的全局中斷
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    __COMPILER_BARRIER();
    NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __COMPILER_BARRIER();
  }
}

// 關閉 xx 外設的全局中斷
__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ICER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __DSB();
    __ISB();
  }
}

  增加了使能 GPT1 的全局中斷代碼示例如下,其中 GPT1_IRQn 和 GPT1_IRQHandler 是固定名字,在 MCU 廠商提供的頭文件(MIMXRT1176_cm7.h)和啟動文件(startup_MIMXRT1176_cm7.s)里有定義。

void periph_int_config(void)
{
    // 初始化 GPT1...
    GPT_Init(GPT1, &gptConfig);
    // ...

    // 開啟 GPT1 的超時事件中斷
    GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);

    // 開啟 GPT1 的全局中斷
    NVIC_EnableIRQ(GPT1_IRQn);
}

// GPT1 的中斷響應函數
void GPT1_IRQHandler(void)
{
    GPT_ClearStatusFlags(GPT1, kGPT_RollOverFlagInterruptEnable);

    // 中斷業務處理代碼
}

三、系統全局中斷控制

  MCU 中最頂層的中斷控制針對的是整個芯片系統,這個控制來自於 Cortex-M 內核的 CPS 指令。如下是 CPS 指令用法(取自 ARMv7-M 手冊):

  當你想對 MCU 整個芯片的所有中斷進行統一開關控制時,就必須借助 CPS 指令。一般情況下開啟芯片系統全局中斷動作在 MCU 啟動文件里已經做好了,所以在用戶代碼環境里常常不需要使能系統全局中斷的動作。如下是 IAR 環境下 i.MXRT1170 啟動文件中系統全局中斷操作,基於匯編指令實現:

  為了便於用戶在 C 代碼中操作系統全局中斷,各 IDE 下均按同樣的接口函數( __disable_irq / __enable_irq )做了封裝實現。IAR 環境見 \IAR Systems\Embedded Workbench 8.50.6\arm\inc\c\iccarm_builtin.h 文件,但是封裝進其 Lib 了,沒有暴露源碼:

#include "iccarm_builtin.h"

#define __disable_irq       __iar_builtin_disable_interrupt
#define __enable_irq        __iar_builtin_enable_interrupt

  Keil 環境見 \Keil_v5\ARM\ARMCLANG\include\arm_compat.h 文件,我們可以看到源碼:

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;
#endif
#endif
}

static __inline__ void __attribute__((__always_inline__, __nodebug__))
__enable_irq(void) {
#if __ARM_ARCH >= 6
  __asm__ __volatile__("cpsie i");
#endif
}

  最終 GPT 例程里完整的三重中斷使能代碼應如下:

void periph_int_config(void)
{
    // 初始化 GPT1...
    GPT_Init(GPT1, &gptConfig);
    // ...

    // 開啟 GPT1 的超時事件中斷
    GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);

    // 開啟 GPT1 的全局中斷
    NVIC_EnableIRQ(GPT1_IRQn);

    // 開啟芯片系統全局中斷
    __enable_irq();
}

  至此,嵌入式MCU中標准的三重中斷控制設計痞子衡便介紹完畢了,掌聲在哪里~~~

歡迎訂閱

文章會同時發布到我的 博客園主頁CSDN主頁知乎主頁微信公眾號 平台上。

微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM