背景
上一講 STM32 CubeMX 學習:GPIO的使用 介紹了如何配置以及操作GPIO引腳。
這一講我們通過中斷來控制按鍵。關於中斷的概念不做介紹。
HOST-OS : Windows-10
STM32 Cube :v5.6
MCU : STM32F429
LIB : stm32cube_fw_f4_v1250
知識
cortex-M4支持256個中斷,其中包含了16個內核中斷和240個外部中斷,並且具有256級的可編程中斷設置。每個中斷設有狀態位,每個中斷/事件都有獨立的觸發和屏蔽設置。
NVIC
是嵌套向量中斷控制器(Nested Vectored Interrupt Controller),控制着整個芯片中斷相關的功能,它跟內核緊密耦合,是內核里面的一個外設。但是各個芯片廠商在設計芯片的時候會對Cortex-M4
內核里面的NVIC
進行裁剪,把不需要的部分去掉,所以說STM32
的NVIC
是Cortex-M4
的NVIC
的一個子集。
NVIC
負責控制中斷響應,主要有三個參數:中斷使能(Interrupts Enabled),搶占優先級(Preemption Priority),次優先級(Sub Priority,也叫響應優先級)。(優先級數值越小,優先級別越高)
- 中斷使能:表面是否開啟中斷,如果開啟中斷,則滿足中斷觸發條件時程序會跳到中斷服務程序運行,否則不響應中斷主程序繼續運行。
- 搶占優先級:用來判斷一個中斷是否可以打斷另外一個中斷的中斷服務程序搶先運行。
例如A中斷觸發,正在運行A中斷的服務程序,此時B中斷也觸發,如果B中斷的搶占優先級比A的高,則程序會打斷A的中斷服務程序,去運行B的中斷服務程序,即中斷嵌套。等B的中斷服務程序運行完后繼續運行A的中斷服務程序。如果B的搶占優先級沒有高過A的搶占優先級,則程序不會打斷A的中斷服務程序,而是待定A的中斷服務程序運行完成后才運行B的中斷服務程序。
- 次優先級:用來判斷搶占優先級相同的幾個中斷那個中斷會優先響應。如果幾個搶占優先相同的中斷同時觸發,那么次優先級高的最先運行。
判斷中斷的優先級,先看搶占優先級,搶占優先級高的中斷優先級別高。搶占優先級相同的情況下,響應優先高的中斷優先級別高。搶占優先級和次優先級相同的情況下,根據中斷向量表確定。
其實把Sub Priority翻譯成次優先級就很好理解了:先看主優先級,再看次優先級,都相同則根據中斷向量表的順序。
優先級分組(Priority Group)
STM32以4個比特位表示中斷的搶占優先級和次優先級。中斷優先級分組是為了給搶占式優先級
和次優先級
在中斷優先級寄叢器的四個比特位分配各個優先級數字所占的位數。
例如3位用於搶占優先級(優先級有23=8種優先級),1位用於次優先級(優先級有21=2種優先級)。
STM32F4的每個IO都可以作為外部中斷的中斷輸入口,這點也是STM32F4的強大之處。
有關的定義(位於文件stm32f4xx_hal_gpio.h
中)
下面的代碼會引申出2個概念:中斷/事件、觸發方式。
#define GPIO_MODE_IT_RISING 0x10110000U /*!< External Interrupt Mode with Rising edge trigger detection */
#define GPIO_MODE_IT_FALLING 0x10210000U /*!< External Interrupt Mode with Falling edge trigger detection */
#define GPIO_MODE_IT_RISING_FALLING 0x10310000U /*!< External Interrupt Mode with Rising/Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING 0x10120000U /*!< External Event Mode with Rising edge trigger detection */
#define GPIO_MODE_EVT_FALLING 0x10220000U /*!< External Event Mode with Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING_FALLING 0x10320000U /*!< External Event Mode with Rising/Falling edge trigger detection */
我們會發現,GPIO_MODE_IT_xxx(中斷, interrupt)
與 GPIO_MODE_EVT_xxx(事件, event)
2種定義。那么它們有什么不同呢?
GPIO_MODE_IT_RISING
等能夠觸發中斷,用在中斷方式編程。GPIO_MODE_EVT_RISING
等只設置中斷標志位,不產生中斷,可以用在輪詢方式。(比較少用)
中斷/事件 的觸發方式(沒有邊沿觸發)
RISING : 上升沿觸發。
FALLING: 下降沿觸發。
RISING_FALLING:上升沿和下降沿都觸發。
CubeMx 對 中斷 的配置
只要我們使用到外部中斷,就必須打開SYSCFG時鍾。
1)在Pinout& Confiurgation
頁的Pinout view
中,點擊引腳,設置為GPIO_EXTI..
。
EXTI后面跟着的數字n代表第n個中斷線,這會代表在哪個中斷處理函數來響應它
2)點擊左欄的GPIO
,選擇配置的引腳,在界面下方中部靠左的位置可以看到 類似PF4 Configuration
這一欄,有以下選項:
- GPIO mode(模式)
External Interrupt Mode with Rising edge trigger detection(上升沿式外部中斷模式)
、External Interrupt Mode with Falling edge trigger detection(下降沿式外部中斷模式)
、External Interrupt Mode with Rising/Falling edge trigger detection(上升下降沿式外部中斷模式)
External Event Mode with Rising edge trigger detection(上升沿式外部置位模式)
、External Event Mode with Falling edge trigger detection(下降沿式外部置位模式)
、External Event Mode with Rising/Falling edge trigger detection(上升下降沿式外部置位模式)
GPIO_MODE_IT_RISING能夠觸發中斷,用在中斷方式編程。而GPIO_MODE_EVT_RISING只設置中斷標志位,不產生中斷,可以用在查詢方式
- GPIO Pull-up/Pull-down(上下拉):默認設置為
No pull-up and no pull-down
- User Label(用戶標簽): 可以標記這個引腳是做什么用的,提高可讀性。
3)點擊左側NVIC
- 根據需要設置
Priority Group
- 勾選對應的中斷使能(Enabled)
- 根據需要填寫搶占優先級(Preemption Priority),次優先級(Sub Priority)
4)點擊右上角的GENERATE CODE
生成工程
5)使用外部的工具鏈編譯工程,確保沒有問題。
代碼分析
通過配置以后,在GPIO的初始化函數中就有了 中斷的配置與使能
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin : PF7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/*Configure GPIO pin : PB14 */
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 2);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
// 補充,關閉中斷的函數為:
// void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
}
同時,CubeMx已經根據配置的情況在./Src/stm32f4xx_it.c
文件中。
雖然外部中斷可以來自不同的中斷線,但在HAL庫的機制中,外部中斷函數都會先在HAL_GPIO_EXTI_IRQHandler
清除中斷標志位,再調用同一個中斷回調函數:HAL_GPIO_EXTI_Callback
。
從
MX_GPIO_Init
不難看出,我已經配置了2個中斷,在下文中的EXTI9_5_IRQHandler
與EXTI15_10_IRQHandler
都會調用HAL_GPIO_EXTI_Callback
,並給出對應的中斷引腳作為參數;所以,我們只需要重寫HAL_GPIO_EXTI_Callback
時,判斷參數就可以正確地做出響應。
/**
* @brief This function handles EXTI line[9:5] interrupts.
*/
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
/**
* @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_14);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}
此時,根據startup_stm32f429xx.s中的描述,每個中斷服務函數代表一個(0~4)或者一類([9:5]為一類;[15:10]為另一類)
即:只要是中斷線在EXTI_Line9_5,或者 EXTI_Line在10_15 的,中斷服務函數就只有一個。
你可能會想,那這么多中斷,我同時啟動2個中斷,會不會造成沖突。
由於中斷線只有唯一性,可以在中斷服務函數里面判斷到底是哪個中斷線觸發,這也保證不會誤觸發或者是占線的一系列的問題。
; 中斷線與中斷處理函數 來自 startup_stm32f429xx.s
DCD EXTI0_IRQHandler ; EXTI Line 0 //只是管腳的中斷服務函數,還有串口等等中斷服務函數
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
...
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
...
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
添加代碼
通過上面的分析,我們對於HAL處理外部中斷有了一個系統的認識,那么我們目前唯一欠缺的就是重寫HAL_GPIO_EXTI_Callback
函數。注意,代碼應該插在 USER CODE BEGIN
、USER CODE END
之間。
/* USER CODE BEGIN 4 */
/**
* @brief EXTI line detection callbacks
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case GPIO_PIN_7:
// do_someting_A();
break;
case GPIO_PIN_14:
// do_someting_B();
break;
default : break;
}
return ;
}
/* USER CODE END 4 */