大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是嵌入式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主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。