第17章 EXTI—外部中斷/事件控制器
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx中文參考手冊》系統配置控制器以及中斷和事件章節。
上一章節我們已經詳細介紹了NVIC,對STM32F4xx中斷管理系統有個全局的了解,我們這章的內容是NVIC的實例應用,也是STM32F4xx控制器非常重要的一個資源。學習本章時,配合《STM32F4xx中文參考手冊》系統配置控制器以及中斷和事件章節一起閱讀,效果會更佳,特別是涉及到寄存器說明的部分。
特別說明,本書內容是以STM32F42xxx系列控制器資源講解。
17.1 EXTI簡介
外部中斷/事件控制器(EXTI)管理了控制器的23個中斷/事件線。每個中斷/事件線都對應有一個邊沿檢測器,可以實現輸入信號的上升沿檢測和下降沿的檢測。EXTI可以實現對每個中斷/事件線進行單獨配置,可以單獨配置為中斷或者事件,以及觸發事件的屬性。
17.2 EXTI功能框圖
EXTI的功能框圖包含了EXTI最核心內容,掌握了功能框圖,對EXTI就有一個整體的把握,在編程時就思路就非常清晰。EXTI功能框圖見圖 171。
在圖 171可以看到很多在信號線上打一個斜杠並標注"23"字樣,這個表示在控制器內部類似的信號線路有23個,這與EXTI總共有23個中斷/事件線是吻合的。所以我們只要明白其中一個的原理,那其他22個線路原理也就知道了。
圖 171 EXTI功能框圖
EXTI可分為兩大部分功能,一個是產生中斷,另一個是產生事件,這兩個功能從硬件上就有所不同。
首先我們來看圖 171中紅色虛線指示的電路流程。它是一個產生中斷的線路,最終信號流入到NVIC控制器內。
編號1是輸入線,EXTI控制器有23個中斷/事件輸入線,這些輸入線可以通過寄存器設置為任意一個GPIO,也可以是一些外設的事件,這部分內容我們將在后面專門講解。輸入線一般是存在電平變化的信號。
編號2是一個邊沿檢測電路,它會根據上升沿觸發選擇寄存器(EXTI_RTSR)和下降沿觸發選擇寄存器(EXTI_FTSR)對應位的設置來控制信號觸發。邊沿檢測電路以輸入線作為信號輸入端,如果檢測到有邊沿跳變就輸出有效信號1給編號3電路,否則輸出無效信號0。而EXTI_RTSR和EXTI_FTSR兩個寄存器可以控制器需要檢測哪些類型的電平跳變過程,可以是只有上升沿觸發、只有下降沿觸發或者上升沿和下降沿都觸發。
編號3電路實際就是一個或門電路,它一個輸入來自編號2電路,另外一輸入來自軟件中斷事件寄存器(EXTI_SWIER)。EXTI_SWIER允許我們通過程序控制就可以啟動中斷/事件線,這在某些地方非常有用。我們知道或門的作用就是有"就為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有23個中斷/事件線,每個GPIO都可以被設置為輸入線,占用EXTI0至EXTI15,還有另外七根用於特定的外設事件,見表 171。
七根特定外設中斷/事件線由外設觸發,具體用法參考《STM32F4xx中文參考手冊》中對外設的具體說明。
表 171 EXTI中斷/事件線
中斷/事件線 |
輸入源 |
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喚醒事件 |
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中斷/事件線)使用配置都是類似的。
圖 172 EXTI0輸入源選擇
17.4 EXTI初始化結構體詳解
標准庫函數對每個外設都建立了一個初始化結構體,比如EXTI_InitTypeDef,結構體成員用於設置外設工作參數,並由外設初始化配置函數,比如EXTI_Init()調用,這些設定參數將會設置外設相應的寄存器,達到配置外設工作環境的目的。
初始化結構體和初始化庫函數配合使用是標准庫精髓所在,理解了初始化結構體每個成員意義基本上就可以對該外設運用自如了。初始化結構體定義在stm32f4xx_exti.h文件中,初始化庫函數定義在stm32f4xx_exti.c文件中,編程時我們可以結合這兩個文件內注釋使用。
代碼清單 171 EXTI初始化結構體
1 typedef struct {
2 uint32_t EXTI_Line; // 中斷/事件線
3 EXTIMode_TypeDef EXTI_Mode; // EXTI模式
4 EXTITrigger_TypeDef EXTI_Trigger; // 觸發事件
5 FunctionalState EXTI_LineCmd; // EXTI控制
6 } EXTI_InitTypeDef;
1) EXTI_Line:EXTI中斷/事件線選擇,可選EXTI0至EXTI22,可參考表 171選擇。
2) EXTI_Mode:EXTI模式選擇,可選為產生中斷(EXTI_Mode_Interrupt)或者產生事件(EXTI_Mode_Event)。
3) EXTI_Trigger:EXTI邊沿觸發事件,可選上升沿觸發(EXTI_Trigger_Rising)、下降沿觸發( EXTI_Trigger_Falling)或者上升沿和下降沿都觸發( EXTI_Trigger_Rising_Falling)。
4) EXTI_LineCmd:控制是否使能EXTI線,可選使能EXTI線(ENABLE)或禁用(DISABLE)。
17.5 外部中斷控制實驗
中斷在嵌入式應用中占有非常重要的地位,幾乎每個控制器都有中斷功能。中斷對保證緊急事件得到第一時間處理是非常重要的
我們設計使用外接的按鍵來作為觸發源,使得控制器產生中斷,並在中斷服務函數中實現控制RGB彩燈的任務。
17.5.1 硬件設計
輕觸按鍵在按下時會使得引腳接通,通過電路設計可以使得按下時產生電平變化,見圖 171。
圖 173 按鍵電路設計
17.5.2 軟件設計
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_exti.c和bsp_exti.h文件用來存放EXTI驅動程序及相關宏定義,中斷服務函數放在stm32f4xx_it.h文件中。
1. 編程要點
1) 初始化RGB彩燈的GPIO;
2) 開啟按鍵GPIO時鍾和SYSCFG時鍾;
3) 配置NVIC;
4) 配置按鍵GPIO為輸入模式;
5) 將按鍵GPIO連接到EXTI源輸入;
6) 配置按鍵EXTI中斷/事件線;
7) 編寫EXTI中斷服務函數。
2. 軟件分析
按鍵和EXTI宏定義
代碼清單 172 按鍵和EXTI 宏定義
1 //引腳定義
2 /*******************************************************/
3 #define KEY1_INT_GPIO_PORT GPIOA
4 #define KEY1_INT_GPIO_CLK RCC_AHB1Periph_GPIOA
5 #define KEY1_INT_GPIO_PIN GPIO_Pin_0
6 #define KEY1_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOA
7 #define KEY1_INT_EXTI_PINSOURCE EXTI_PinSource0
8 #define KEY1_INT_EXTI_LINE EXTI_Line0
9 #define KEY1_INT_EXTI_IRQ EXTI0_IRQn
10
11 #define KEY1_IRQHandler EXTI0_IRQHandler
12
13 #define KEY2_INT_GPIO_PORT GPIOC
14 #define KEY2_INT_GPIO_CLK RCC_AHB1Periph_GPIOC
15 #define KEY2_INT_GPIO_PIN GPIO_Pin_13
16 #define KEY2_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOC
17 #define KEY2_INT_EXTI_PINSOURCE EXTI_PinSource13
18 #define KEY2_INT_EXTI_LINE EXTI_Line13
19 #define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
20
21 #define KEY2_IRQHandler EXTI15_10_IRQHandler
使用宏定義方法指定與電路設計相關配置,這對於程序移植或升級非常有用的。
嵌套向量中斷控制器NVIC配置
代碼清單 173 NVIC配置
1 static void NVIC_Configuration(void)
2 {
3 NVIC_InitTypeDef NVIC_InitStructure;
4
5 /* 配置NVIC為優先級組1 */
6 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
7
8 /* 配置中斷源:按鍵1 */
9 NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
10 /* 配置搶占優先級:1 */
11 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
12 /* 配置子優先級:1 */
13 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
14 /* 使能中斷通道 */
15 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
16 NVIC_Init(&NVIC_InitStructure);
17
18 /* 配置中斷源:按鍵2,其他使用上面相關配置 */
19 NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
20 NVIC_Init(&NVIC_InitStructure);
21 }
有關NVIC配置問題可參考上一章節內容,這里不做過多解釋。
EXTI中斷配置
代碼清單 174 EXTI中斷配置
1 void EXTI_Key_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4 EXTI_InitTypeDef EXTI_InitStructure;
5
6 /*開啟按鍵GPIO口的時鍾*/
7 RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK ,ENABLE);
8
9 /* 使能 SYSCFG 時鍾,使用GPIO外部中斷時必須使能SYSCFG時鍾*/
10 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
11
12 /* 配置 NVIC */
13 NVIC_Configuration();
14
15 /* 選擇按鍵1的引腳 */
16 GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
17 /* 設置引腳為輸入模式 */
18 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
19 /* 設置引腳不上拉也不下拉 */
20 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
21 /* 使用上面的結構體初始化按鍵 */
22 GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
23
24 /* 連接 EXTI 中斷源到key1引腳 */
25 SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,
26 KEY1_INT_EXTI_PINSOURCE);
27
28 /* 選擇 EXTI 中斷源 */
29 EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
30 /* 中斷模式 */
31 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
32 /* 下降沿觸發 */
33 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
34 /* 使能中斷/事件線 */
35 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
36 EXTI_Init(&EXTI_InitStructure);
37
38 /* 選擇按鍵2的引腳 */
39 GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
40 /* 其他配置與上面相同 */
41 GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
42
43 /* 連接 EXTI 中斷源到key2 引腳 */
44 SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,
45 KEY2_INT_EXTI_PINSOURCE);
46
47 /* 選擇 EXTI 中斷源 */
48 EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
49 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
50 /* 上升沿觸發 */
51 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
52 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
53 EXTI_Init(&EXTI_InitStructure);
54 }
首先,使用GPIO_InitTypeDef和EXTI_InitTypeDef結構體定義兩個用於GPIO和EXTI初始化配置的變量,關於這兩個結構體前面都已經做了詳細的講解。
使用GPIO之前必須開啟GPIO端口的時鍾;用到EXTI必須開啟SYSCFG時鍾。
調用NVIC_Configuration函數完成對按鍵1、按鍵2優先級配置並使能中斷通道。
作為中斷/時間輸入線把GPIO配置為輸入模式,這里不使用上拉或下拉,有外部電路完全決定引腳的狀態。
SYSCFG_EXTILineConfig函數用來指定中斷/事件線的輸入源,它實際是設定SYSCFG外部中斷配置寄存器的值,該函數接收兩個參數,第一個參數指定GPIO端口源,第二個參數為選擇對應GPIO引腳源編號。
我們的目的是產生中斷,執行中斷服務函數,EXTI選擇中斷模式,按鍵1使用下降沿觸發方式,並使能EXTI線。
按鍵2基本上采用與按鍵1相關參數配置,只是改為上升沿觸發方式。
EXTI中斷服務函數
代碼清單 175 EXTI中斷服務函數
1 void KEY1_IRQHandler(void)
2 {
3 //確保是否產生了EXTI Line中斷
4 if (EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) {
5 // LED1 取反
6 LED1_TOGGLE;
7 //清除中斷標志位
8 EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
9 }
10 }
11
12 void KEY2_IRQHandler(void)
13 {
14 //確保是否產生了EXTI Line中斷
15 if (EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) {
16 // LED2 取反
17 LED2_TOGGLE;
18 //清除中斷標志位
19 EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
20 }
21 }
當中斷發生時,對應的中斷服務函數就會被執行,我們可以在中斷服務函數實現一些控制。
一般為確保中斷確實發生,我們會在中斷服務函數調用中斷標志位狀態讀取函數讀取外設中斷標志位並判斷標志位狀態。
EXTI_GetITStatus函數用來獲取EXTI的中斷標志位狀態,如果EXTI線有中斷發生函數返回"SET"否則返回"RESET"。實際上,EXTI_GetITStatus函數是通過讀取EXTI_PR寄存器值來判斷EXTI線狀態的。
按鍵1的中斷服務函數我們讓LED1翻轉其狀態,按鍵2的中斷服務函數我們讓LED2翻轉其狀態。執行任務后需要調用EXTI_ClearITPendingBit函數清除EXTI線的中斷標志位。
主函數
代碼清單 176 主函數
1 int main(void)
2 {
3 /* LED 端口初始化 */
4 LED_GPIO_Config();
5
6 /* 初始化EXTI中斷,按下按鍵會觸發中斷,
7 * 觸發中斷會進入stm32f4xx_it.c文件中的函數
8 * KEY1_IRQHandler和KEY2_IRQHandler,處理中斷,反轉LED燈。
9 */
10 EXTI_Key_Config();
11
12 /* 等待中斷,由於使用中斷方式,CPU不用輪詢按鍵 */
13 while (1) {
14 }
15 }
主函數非常簡單,只有兩個任務函數。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彩燈又變暗。
每課一問
1、 是否可以同時使用PA0和PB0中斷?如果不可以,有什么解決方法。
2、 從硬件角度結合程序分析,為什么按下按鍵1RGB彩燈就馬上變化,而按鍵2卻需要按下按鍵再彈開之后RGB彩燈才變化?