上一章節我們已經詳細介紹了NVIC,對STM32F7xx中斷管理系統有個全局的了解,我們這章的內容是NVIC的實例應用,也是STM32F7xx控制器非常重要的一個資源。學習本章時,配合《STM32F76xxx參考手冊》系統配置控制器以及中斷和事件章節一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。
特別說明,本書內容是以STM32F767xx系列控制器資源講解。
17.1 EXTI簡介
外部中斷/事件控制器(EXTI)管理了控制器的25個中斷/事件線。每個中斷/事件線都對應有一個邊沿檢測器,可以實現輸入信號的上升沿檢測和下降沿的檢測。EXTI可以實現對每個中斷/事件線進行單獨配置,可以單獨配置為中斷或者事件,以及觸發事件的屬性。
17.2 EXTI功能框圖
EXTI的功能框圖包含了EXTI最核心內容,掌握了功能框圖,對EXTI就有一個整體的把握,在編程時就思路就非常清晰。EXTI功能框圖見圖 17-1。

EXTI可分為兩大部分功能,一個是產生中斷,另一個是產生事件,這兩個功能從硬件上就有所不同。
首先我們來看圖 171中紅色虛線指示的電路流程。它是一個產生中斷的線路,最終信號流入到NVIC控制器內。
編號1是輸入線,EXTI控制器有25個中斷/事件輸入線,這些輸入線可以通過寄存器設置為任意一個GPIO,也可以是一些外設的事件,這部分內容我們將在后面專門講解。輸入線一般是存在電平變化的信號。
編號2是一個邊沿檢測電路,它會根據上升沿觸發選擇寄存器(EXTI_RTSR)和下降沿觸發選擇寄存器(EXTI_FTSR)對應位的設置來控制信號觸發。邊沿檢測電路以輸入線作為信號輸入端,如果檢測到有邊沿跳變就輸出有效信號1給編號3電路,否則輸出無效信號0。而EXTI_RTSR和EXTI_FTSR兩個寄存器可以控制器需要檢測哪些類型的電平跳變過程,可以是只有上升沿觸發、只有下降沿觸發或者上升沿和下降沿都觸發。
編號3電路實際就是一個或門電路,它一個輸入來自編號2電路,另外一輸入來自軟件中斷事件寄存器(EXTI_SWIER)。EXTI_SWIER允許我們通過程序控制就可以啟動中斷/事件線,這在某些地方非常有用。我們知道或門的作用就是有1就為1,所以這兩個輸入隨便一個有有效信號1就可以輸出1給編號4和編號6電路。
編號4電路是一個與門電路,它一個輸入編號3電路,另外一個輸入來自中斷屏蔽寄存器(EXTI_IMR)。與門電路要求輸入都為1才輸出1,導致的結果如果EXTI_IMR設置為0時,那不管編號3電路的輸出信號是1還是0,最終編號4電路輸出的信號都為0;如果EXTI_IMR設置為1時,最終編號4電路輸出的信號才由編號3電路的輸出信號決定,這樣我們可以簡單的控制EXTI_IMR來實現是否產生中斷的目的。編號4電路輸出的信號會被保存到掛起寄存器(EXTI_PR)內,如果確定編號4電路輸出為1就會把EXTI_PR對應位置1。
編號5是將EXTI_PR寄存器內容輸出到NVIC內,從而實現系統中斷事件控制。
接下來我們來看看綠色虛線指示的電路流程。它是一個產生事件的線路,最終輸出一個脈沖信號。
產生事件線路是在編號3電路之后與中斷線路有所不同,之前電路都是共用的。編號6電路是一個與門,它一個輸入編號3電路,另外一個輸入來自事件屏蔽寄存器(EXTI_EMR)。如果EXTI_EMR設置為0時,那不管編號3電路的輸出信號是1還是0,最終編號6電路輸出的信號都為0;如果EXTI_EMR設置為1時,最終編號6電路輸出的信號才由編號3電路的輸出信號決定,這樣我們可以簡單的控制EXTI_EMR來實現是否產生事件的目的。
編號7是一個脈沖發生器電路,當它的輸入端,即編號6電路的輸出端,是一個有效信號1時就會產生一個脈沖;如果輸入端是無效信號就不會輸出脈沖。
編號8是一個脈沖信號,就是產生事件的線路最終的產物,這個脈沖信號可以給其他外設電路使用,比如定時器TIM、模擬數字轉換器ADC等等。
產生中斷線路目的是把輸入信號輸入到NVIC,進一步會運行中斷服務函數,實現功能,這樣是軟件級的。而產生事件線路目的就是傳輸一個脈沖信號給其他外設使用,並且是電路級別的信號傳輸,屬於硬件級的。
另外,EXTI是在APB2總線上的,在編程時候需要注意到這點。
17.3 中斷/事件線
EXTI有25個中斷/事件線,每個GPIO都可以被設置為輸入線,占用EXTI0至EXTI15,還有另外七根用於特定的外設事件,見表 17-1。
七根特定外設中斷/事件線由外設觸發,具體用法參考《STM32F4xx中文參考手冊》中對外設的具體說明。
| 中斷/事件線 |
輸入源 |
| EXTI0 |
PX0(X可為A,B,C,D,E,F,G,H,I) |
| EXTI1 |
PX1(X可為A,B,C,D,E,F,G,H,I) |
| EXTI2 |
PX2(X可為A,B,C,D,E,F,G,H,I) |
| EXTI3 |
PX3(X可為A,B,C,D,E,F,G,H,I) |
| EXTI4 |
PX4(X可為A,B,C,D,E,F,G,H,I) |
| EXTI5 |
PX5(X可為A,B,C,D,E,F,G,H,I) |
| EXTI6 |
PX6(X可為A,B,C,D,E,F,G,H,I) |
| EXTI7 |
PX7(X可為A,B,C,D,E,F,G,H,I) |
| EXTI8 |
PX8(X可為A,B,C,D,E,F,G,H,I) |
| EXTI9 |
PX9(X可為A,B,C,D,E,F,G,H,I) |
| EXTI10 |
PX10(X可為A,B,C,D,E,F,G,H,I) |
| EXTI11 |
PX11(X可為A,B,C,D,E,F,G,H,I) |
| EXTI12 |
PX12(X可為A,B,C,D,E,F,G,H,I) |
| EXTI13 |
PX13(X可為A,B,C,D,E,F,G,H,I) |
| EXTI14 |
PX14(X可為A,B,C,D,E,F,G,H,I) |
| EXTI15 |
PX15(X可為A,B,C,D,E,F,G,H) |
| EXTI16 |
可編程電壓檢測器(PVD)輸出 |
| EXTI17 |
RTC鬧鍾事件 |
| EXTI18 |
USB OTG FS喚醒事件 |
| EXTI19 |
以太網喚醒事件 |
| EXTI20 |
USB OTG HS(在FS中配置)喚醒事件 |
| EXTI21 |
RTC入侵和時間戳事件 |
| EXTI22 |
RTC喚醒事件 |
| EXTI23 |
LPTIM1 異步事件 |
| EXTI24 |
MDIO Slave 異步事件 |
EXTI0至EXTI15用於GPIO,通過編程控制可以實現任意一個GPIO作為EXTI的輸入源。由表 171可知,EXTI0可以通過SYSCFG外部中斷配置寄存器1(SYSCFG_EXTICR1)的EXTI0[3:0]位選擇配置為PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0或者PI0,見圖 172。其他EXTI線(EXTI中斷/事件線)使用配置都是類似的。

17.4 EXTI初始化詳解
HAL庫函數的EXIT初始化非常簡單,只需配置好IO口的模式,然后配置中斷源、中斷優先級、使能中斷。
1) HAL_NVIC_SetPriority:該函數負責EXTI中斷/事件線選擇,可選EXTI0至EXTI25,可參考表 171選擇,配置優先級。
2) HAL_NVIC_EnableIRQ:該函數負責控制使能中斷。
17.5 外部中斷控制實驗
中斷在嵌入式應用中占有非常重要的地位,幾乎每個控制器都有中斷功能。中斷對保證緊急事件得到第一時間處理是非常重要的
我們設計使用外接的按鍵來作為觸發源,使得控制器產生中斷,並在中斷服務函數中實現控制RGB彩燈的任務。17.5.1 硬件設計輕觸按鍵在按下時會使得引腳接通,通過電路設計可以使得按下時產生電平變化,見17-3

圖 17-3 按鍵電路設計
17.5.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_exti.c和bsp_exti.h文件用來存放EXTI驅動程序及相關宏定義,中斷服務函數放在stm32f7xx_it.c文件中。
1. 編程要點
1) 初始化系統時鍾;
2) 初始化RGB彩燈的GPIO;
3) 開啟按鍵GPIO時鍾;
4) 配置NVIC;
5) 配置按鍵GPIO為輸入模式;
6) 將按鍵GPIO連接到EXTI源輸入;
7) 配置按鍵EXTI中斷/事件線;
8) 編寫EXTI中斷服務函數。
2. 軟件分析
按鍵和EXTI宏定義
代碼清單 171 按鍵和EXTI 宏定義
1 #define KEY1_INT_GPIO_PORT GPIOA 2 #define KEY1_INT_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE(); 3 #define KEY1_INT_GPIO_PIN GPIO_PIN_0 4 #define KEY1_INT_EXTI_IRQ EXTI0_IRQn 5 #define KEY1_IRQHandler EXTI0_IRQHandler 6 7 #define KEY2_INT_GPIO_PORT GPIOC 8 #define KEY2_INT_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE(); 9 #define KEY2_INT_GPIO_PIN GPIO_PIN_13 10 #define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn 11 #define KEY2_IRQHandler EXTI15_10_IRQHandler
使用宏定義方法指定與電路設計相關配置,這對於程序移植或升級非常有用的。
EXTI中斷配置
代碼清單 172 EXTI中斷配置
1 void EXTI_Key_Config(void) 2 { 3 GPIO_InitTypeDef GPIO_InitStructure; 4 5 /*開啟按鍵GPIO口的時鍾*/ 6 KEY1_INT_GPIO_CLK_ENABLE(); 7 KEY2_INT_GPIO_CLK_ENABLE(); 8 9 /* 選擇按鍵1的引腳 */ 10 GPIO_InitStructure.Pin = KEY1_INT_GPIO_PIN; 11 /* 設置引腳為輸入模式 */ 12 GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING; 13 /* 設置引腳不上拉也不下拉 */ 14 GPIO_InitStructure.Pull = GPIO_NOPULL; 15 /* 使用上面的結構體初始化按鍵 */ 16 HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure); 17 /* 配置 EXTI 中斷源 到key1 引腳、配置中斷優先級*/ 18 HAL_NVIC_SetPriority(KEY1_INT_EXTI_IRQ, 0, 0); 19 /* 使能中斷 */ 20 HAL_NVIC_EnableIRQ(KEY1_INT_EXTI_IRQ); 21 22 /* 選擇按鍵2的引腳 */ 23 GPIO_InitStructure.Pin = KEY2_INT_GPIO_PIN; 24 /* 其他配置與上面相同 */ 25 HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); 26 /* 配置 EXTI 中斷源 到key1 引腳、配置中斷優先級*/ 27 HAL_NVIC_SetPriority(KEY2_INT_EXTI_IRQ, 0, 0); 28 /* 使能中斷 */ 29 HAL_NVIC_EnableIRQ(KEY2_INT_EXTI_IRQ); 30 }
首先,使用GPIO_InitTypeDef結構體定義用於GPIO初始化配置的變量,關於這個結構體前面都已經做了詳細的講解。
使用GPIO之前必須開啟GPIO端口的時鍾;
調用HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ函數完成對按鍵1、按鍵2優先級配置並使能中斷通道。
作為中斷/時間輸入線把GPIO配置為中斷上升沿觸發模式,這里不使用上拉或下拉,有外部電路完全決定引腳的狀態。
我們的目的是產生中斷,執行中斷服務函數,EXTI選擇中斷模式,按鍵1使用下降沿觸發方式,並使能EXTI線。
按鍵2基本上采用與按鍵1相關參數配置,只是改為上升沿觸發方式。
EXTI中斷服務函數
代碼清單 17-3 EXTI中斷服務函數
1 void KEY1_IRQHandler(void) 2 { 3 //確保是否產生了EXTI Line中斷 4 if (__HAL_GPIO_EXTI_GET_IT(KEY1_INT_GPIO_PIN) != RESET) { 5 // LED1 取反 6 LED1_TOGGLE; 7 //清除中斷標志位 8 __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN); 9 } 10 } 11 12 void KEY2_IRQHandler(void) 13 { 14 //確保是否產生了EXTI Line中斷 15 if (__HAL_GPIO_EXTI_GET_IT(KEY2_INT_GPIO_PIN) != RESET) { 16 // LED2 取反 17 LED2_TOGGLE; 18 //清除中斷標志位 19 __HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN); 20 } 21 }
當中斷發生時,對應的中斷服務函數就會被執行,我們可以在中斷服務函數實現一些控制。
一般為確保中斷確實發生,我們會在中斷服務函數調用中斷標志位狀態讀取函數讀取外設中斷標志位並判斷標志位狀態。
__HAL_GPIO_EXTI_GET_IT函數用來獲取EXTI的中斷標志位狀態,如果EXTI線有中斷發生函數返回“SET”否則返回“RESET”。實際上,__HAL_GPIO_EXTI_GET_IT函數是通過讀取EXTI_PR寄存器值來判斷EXTI線狀態的。
按鍵1的中斷服務函數我們讓LED1翻轉其狀態,按鍵2的中斷服務函數我們讓LED2翻轉其狀態。執行任務后需要調用__HAL_GPIO_EXTI_CLEAR_IT函數清除EXTI線的中斷標志位。
主函數
代碼清單 17-4 主函數
1 int main(void) 2 { 3 /* 系統時鍾初始化成216 MHz */ 4 SystemClock_Config(); 5 /* LED 端口初始化 */ 6 LED_GPIO_Config(); 7 8 /* 初始化EXTI中斷,按下按鍵會觸發中斷, 9 * 觸發中斷會進入stm32f7xx_it.c文件中的函數 10 * 11 KEY1_IRQHandler和KEY2_IRQHandler,處理中斷,反轉LED燈。 12 */ 13 EXTI_Key_Config(); 14 15 /* 等待中斷,由於使用中斷方式,CPU不用輪詢按鍵 16 */ 17 while (1) { 18 } 19 }
主函數非常簡單,只有三個任務函數。SystemClock_Config 初始化系統時鍾,LED_GPIO_Config函數定義在bsp_led.c文件內,完成RGB彩燈的GPIO初始化配置。EXTI_Key_Config函數完成兩個按鍵的GPIO和EXTI配置。
17.5.3 下載驗證
保證開發板相關硬件連接正確,把編譯好的程序下載到開發板。此時RGB彩色燈是暗的,如果我們按下開發板上的按鍵1,RGB彩燈變亮,再按下按鍵1,RGB彩燈又變暗;如果我們按下開發板上的按鍵2並彈開,RGB彩燈變亮,再按下開發板上的KEY2並彈開,RGB彩燈又變暗。
