STM32的“中斷”機制很複雜,看了PM(Cortex-m4)和RM,對它只瞭解了一個大概。首先,與“中斷”相關的術語就有 exception, interrupt, event 三個。Cortex-m4核中包含一個NVIC控制器,用於處理 exception。而 interrupt 是屬於 exc ...
STM32的“中斷”機制很複雜,看了PM(Cortex-m4)和RM,對它只瞭解了一個大概。首先,與“中斷”相關的術語就有 exception, interrupt, event 三個。Cortex-m4核中包含一個NVIC控制器,用於處理 exception。而 interrupt 是屬於 exception 之一種,其它 exception 類型包括 SysTick等。interrupt 又叫作IRQ。
STM32之中、Cortex-m4核之外的“中斷”,即為 interrupt/IRQ。STM32通過 IRQ Channel 向 NVIC 請求處理 IRQ,而 NVIC 處理包括 IRQ 在內的各種 exception,例如:優先順序...等等。對於 IRQ,NVIC 將調用其“中斷處理程式” ISR。
有些 STM32 外圍介面直接通過 IRQ Channel 與 NVIC 介面,而 GPIO 外部中斷則要通過另一個控制器--EXTI--與NVIC介面。GPIO與 EXTI 之間的介面稱為 EXTI line;而 EXTI 與 NVIC之間則為 IRQ Channel。GPIO pin與EXTI line之間是n:1的關係,而EXTI line與 IRQ Channel之間也是n:1的關係。基本上,PXn 對應 EXTI line n,這裡X=A, B, ... H,n=0, 1, 2 ... 15。例如,PX2(PA2, PB2 ...)都對應於 EXTI line 2。
EXTI line與 IRQ Channel之間的對應關係則稍微複雜,16個 EXTI line 占用7個 IRQ:
- EXTI line 0 - 4 分別對應一個IRQ,因此,共有5個 IRQ
- EXTI line 5 - 9 共用一個IRQ
- EXTI line 10 - 15 共用一個IRQ
此外,EXTI line 上除了支持 interrupt 之外,還支持 event。event 被觸發之後,並不傳遞給 NVIC 去處理(像 IRQ 那樣),而是發送一個脈衝給電源管理模塊,似乎是用來實現喚醒功能的。
GPIO、EXTI 與 NVIC 之間的關係,用下圖簡單表示:
因此,對於編程而言,需要對GPIO、EXTI、NVIC 3個模塊分別進行配置和操作。所幸,Cube HAL 以及 CubeMX 工具大大地降低了開發的複雜度。
Nucleo 開發板上有一個用戶按鈕B1和一個用戶LED LD2,可以用它們來實現一個簡單的 GPIO 外部中斷 Demo。Nucleo 原理圖顯示,B1 進行了 RC de-bouncing,因此可以作為外部中斷源。未經 de-bouncing 的按鈕,是不應該觸發中斷的。B1接在 PC13 口,已經設計了上拉電阻:
使用CubeMX,將B1口模式設置為 GPIO_EXIT13。可見,PC13 使用了 EXTI line 13。另外,由於使用了上拉電阻,選擇中斷為下降沿觸發:
這樣,GPIO和 EXTI 就配置好了。別忘了還需要配置 NVIC。這裡只需要簡單地啟用它對應的 IRQ即可,其餘保持預設:
簡要分析一下 CubeMX 生成的代碼。首先,中斷向量表定義在啟動代碼 startup_stm32f303xe.s 中,在這個文件中可以看到所有 exception 處理程式(函數名),包括 EXTI ISR:
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
...
.word SysTick_Handler
...
.word EXTI0_IRQHandler
.word EXTI1_IRQHandler
.word EXTI2_TSC_IRQHandler
.word EXTI3_IRQHandler
.word EXTI4_IRQHandler
...
.word EXTI9_5_IRQHandler
....
.word EXTI15_10_IRQHandler
....
其中,EXTI15_10_IRQHandler 就是按鈕B1的中斷處理程式。這個函數的實現在 stm32f3xx_it.c 中,它實際上僅僅調用了 Cube 庫的 HAL_GPIO_EXTI_IRQHandler() 函數,將埠號作為參數傳遞進去:
/** * @brief This function handles EXTI line[15:10] interrupts. */ void EXTI15_10_IRQHandler(void) { /* USER CODE BEGIN EXTI15_10_IRQn 0 */ /* USER CODE END EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); /* USER CODE BEGIN EXTI15_10_IRQn 1 */ /* USER CODE END EXTI15_10_IRQn 1 */ }
檢查 HAL_GPIO_EXTI_IRQHandler() 函數的實現,發現它位於 GPIO HAL 模塊內,它又調用了一個回調函數 HAL_GPIO_EXTI_Callback(),而該回調函數的預設實現聲明為 __weak 屬性,我們可以覆蓋:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { /* EXTI line interrupt detected */ if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } } __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { ...
因此,我們在 stm32f3xx_it.c 增加 HAL_GPIO_EXTI_Callback() 的實現,每當B1按下,開/關LD2:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); }
另外,在gpio.c 中的 MX_GPIO_Init() 函數中,看到了 NVIC 的配置,但並沒有看到與 EXTI 有關的配置。其實,EXTI 配置已由 HAL_GPIO_Init() 函數處理,不勞我們費心。也就是說,對 GPIO 的外部中斷的處理,要使用 GPIO 和 NVIC 2個Cube 模塊:
... HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct); ... /* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);