STM32外部中斷
《用CubeMX學習STM32》
注釋 點擊上面藍字進入完整專欄,這個系列所有文章都會整合到這個專欄
STM32外部中斷
前言: 關於中斷簡單介紹
中斷的流程:
中斷流程圖:
中斷方式傳送數據具有可以有效提高單片機工作效率, 適合於實時控制系統等優點, 相對於查詢方式更為常用。
當CPU處理某件事情的時候, 外部發生的某一事件(如電平的改變、脈沖邊沿跳變、定時器/計數器溢出等)請求CPU迅速處理, 於是CPU暫時中斷當前的工作, 轉去處理發生的事件。 處理完該事件后, 再回到原來中斷處, 繼續工作。 這樣的過程稱為中斷 上圖為中斷流程圖
這個中斷的概念是不是有點晦澀難懂? 主要是這個是書上的內容, 所以不是很形象. 我再解釋一遍:想象一個場景,
1、當有一天你正在和川建國同志吃飯(你和他吃飯這個事情就是主程序);
2、突然有個電話打給你, 說有個十五億的小單子需要你去處理一下(這件事就是中斷源),
3、 你停止吃飯, 川建國同志在這等你, 你去處理你的十五億小合同(就是響應中斷請求, 簽合同過程就是中斷服務程序),
4、處理完合同之后你又回到餐桌繼續和他就餐(這就是返回主程序, 然后繼續執行主程序).
這樣解釋是不是清晰一點.
關於STM32F407的中斷介紹可以看一下原子的或者火哥的pdf, 如果實在看得下去, 看官方手冊也行。 這里就不贅述, 通過Cube配置以及編程過程理解這個外部中斷會好很多, 通過現象再回去看本質
同樣的, 還使用前面兩篇博客所用到的工程即可, 也可以自己新建一個, 當做對自己的測試
2-1. 使用核心板自帶按鍵
操作簡介 : 通過板子上的兩個按鈕控制LED燈的亮滅 WK_UP按鍵按下則進入中斷, 並翻轉LED0的狀態, KEY0按下時翻轉LED1的狀態. 兩者雖然功能一樣, 但卻有質的區別
這里要做的和按鍵那一篇一樣, 只是把其中一個按鍵改為中斷, 而不是作為GPIO_input 所以可以看完上一篇直接看這一篇繼續。 點擊下方藍字可以看上一篇的博客
第一節補充: 按鍵操作(CubeMX加HAL庫學STM32系列)
Step1 <CubeMX配置>
RCC&SYS配置這些都不用動, 時鍾樹的配置也不用動
(1) RCC&SYS以及時鍾樹配置不用改變
(2)更改一下PA0引腳配置:
把WKUP按鍵對應的PA0引腳模式由GPIO_input改為GPIO_EXIT0, 再把GPIO的配置更改一下即可 具體操作見下圖
注 : 如果是用的原來的工程, 只改這個即可, 其他的LED引腳和按鍵引腳不用動, 如果是自己又新建了一個工程, 那其他引腳按照前面兩篇的介紹配置, 然后這個PA0按照這一篇配置就好了, 問題不大
對應GPIO配置改為下圖
(3)中斷NVIC配置
我們設置了中斷, 在NVIC里面要記得使能PA0引腳的中斷
NVIC ( Nested Vectored Interrupt Controller ) : 中斷向量控制器
在中斷向量表里面使能EXIT line0中斷
關於搶占優先級和子優先級: 當你使用多個中斷的時候會用到這個。 就是為了防止多個中斷沖突, 所以需要給他們每個中斷排個號, 就不會亂了。 搶占優先級高的先執行, 若搶占優先級相同, 再看子優先級
(4)以上配置完之后就可以Generate CODE
Step2 <程序編寫>
(1) 中斷服務函數
stm32f4xx_it.c 這個文件里面看到我們要用的中斷服務函數
我們要在中斷里面做什么事情, 就要寫在中斷服務函數里面, 然后中斷到來之后, 單片機就回去處理中斷服務函數里面的工作
這個函數里面調用了 HAL_GPIO_EXTI_IRQHandler() 這個函數, 這個函數是處理GPIO外部中斷的函數 可以看到里面的參數是GPIO_PIN_0, 因為我們用的是PA0即GPIOA的0引腳
Go to definition 一下, 可以看這個函數的定義
/**
* @brief This function handles EXTI interrupt request. // 這個功能是處理外部中斷請求
* @param GPIO_Pin Specifies the pins connected EXTI line // GPIO_Pin指定連接EXTI線的引腳
* @retval None // 無返回值
*/
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); // 回調外部中斷
}
}
綜上: 中斷服務函數最終會執行中斷回調函數 HAL_GPIO_EXTI_Callback()
(2) 中斷回調函數
中斷回調函數如下圖 這個函數是空的, 所以我們可以自己重構這個函數, 在它內部實現我們要做的功能
我們需要重構中斷回調函數
在main.c里面寫入我們的代碼 :
提示 : 不要忘了把代碼寫在 /* USER CODE BEGIN / / USER CODE END */ 之間
/* USER CODE BEGIN 0 */
// 重構中斷回調函數
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 判斷是否為WKUP引腳(即GPIO_PIN_0)進入中斷
if (WKUP_Pin == GPIO_Pin)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin); // 翻轉LED0的的電平狀態
/* 下面這一句話與上面一句是等價的, 因為LED0是我們給這個引腳起的別名, 在main.h文件里面有對應的宏定義 */
//HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
}
}
/* USER CODE END 0 */
(3) 主函數
在主函數里面用KEY0做一個一樣的功能, 作為對比
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 在while(1)里面循環掃描, 判斷讀取的按鍵引腳狀態
// 下面掃描KEY0按鍵的引腳信號
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(10); // 延時10ms, 做一個軟件的消抖, 防止因抖動而檢測到按鍵按下
if (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
{
// 做一個松手檢測, 若KEY0一直是RESET(低電平),則一直在死循環
// 當KEY0位SET才會跳出,進而繼續執行下面的對 LED1 的操作
while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET);
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
}
}
/* USER CODE END 3 */
(4) 編譯下載到單片機, 看看單片機什么反應
左邊的紅色按鍵是WKUP, 左邊的藍色LED是LED0
仔細看一下動圖中的效果可以發現, 中斷的WKUP按鍵的功能並不是很完美, 這是因為沒有消抖導致的, 在中斷里面加個軟件消抖的程序就可以了。
此外, 雖然兩種方式實現的功能是一樣的, 但是他們的區別就在於, KEY0翻轉LED狀態實在while(1)循環里面做的, 這就相當於主函數里面一直循環掃描這個按鍵的狀態, 比較耗費資源.
而WKUP按鍵按下翻轉LED是在中斷里面做的,不影響主函數里面做其他事情. 如果以后做的東西要求寫很多代碼, 最好多多利用中斷,這樣會更高效。只有當事情來了CPU再去處理,其他時間主函數里面正常做其他事情。 這樣既能提高MCU效率, 也不會讓自己的代碼全部寫成一坨在主函數里面
破曉之日, 你的所有努力都會助你看到黎明的第一縷陽光。
請堅信: 你是一道光, 也是一把劍 ! 你終將刺破寒冰和黑夜, 重新定義你的人生 !
Author : 李光輝
date : Sun Dec 29 20:54:17 CST 2019
blog ID: Kevin_8_Lee
blog site : https://blog.csdn.net/Kevin_8_Lee/