大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是嵌入式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首頁、知乎首頁、微信公衆号 平台上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。
最後歡迎關注痞子衡個人微信公衆号【痞子衡嵌入式】,一個專注嵌入式技術的公衆号,跟着痞子衡一起玩轉嵌入式。
衡傑(痞子衡),目前就職于恩智浦MCU系統部門,擔任嵌入式系統應用工程師。
專欄内所有文章的轉載請注明出處:http://www.cnblogs.com/henjay724/
與痞子衡進一步交流或咨詢業務合作請發郵件至 [email protected]
可以關注痞子衡的Github首頁 https://github.com/JayHeng,有很多好玩的嵌入式項目。
關于專欄文章有任何疑問請直接在部落格下面留言,痞子衡會及時回複免費(劃重點)答疑。
痞子衡郵箱已被私信擠爆,技術問題不推薦私信,堅持私信請先掃碼付款(5元起步)再發。