1. 程式人生 > >GPIO外部中斷

GPIO外部中斷

mil image leo 術語 中斷處理 cor 沒有 簡單 只需要

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);

  

GPIO外部中斷