STM32_EXTI外部中斷學習筆記

參考資料:《STM32F4xx中文參考手冊》系統配置控制器以及中斷和事件章節。

EXTI( External interrupt /evet controller)

之前接觸過51單片機的都瞭解到51單片機有兩個外部中斷 ,分別爲外部中斷0、1。用來實時地處理外部事件的一種內部機制。當某種外部事件發生時,單片機的中斷系統將迫使CPU暫停正在執行的程序,轉而去進行中斷事件的處理;中斷處理完畢後.又返回被中斷的程序處,繼續執行下去。而STM32的則有與之功能相同的外部中斷事件控制器。外部中斷/事件控制器(EXTI)管理了控制器的 23箇中斷/事件線。每個中斷/事件線都對應有一個邊沿檢測器,可以實現輸入信號的上升沿檢測和下降沿的檢測。EXTI可以實現對每個中斷/事件線進行單獨配置,可以單獨配置爲中斷或者事件,以及觸發事件的屬性。

23箇中斷事件線包含了16個與I/O有關的外部中斷/事件線和其餘七根 EXTI 線

由上圖可以看出PA-PI口的各個位對應EXT0~EXTI15 共16個外部中斷/事件線。

可以通過配置SYSCFG_EXTICR1~CR4來選擇用於外部中斷事件的I/O口。

其餘7個如下圖所示

下表即爲所列的外部中斷事件線

 

下圖即爲 外部中斷/ 事件控制器框圖

圖中左下角的輸入線即I/O口的輸入狀態,通過配置上升沿觸發或下降沿觸發選擇寄存器來選擇輸入線的觸發狀態(可以是隻有上升沿觸發、只有下降沿觸發或者上升沿和下降沿都觸發),如果檢測到有邊沿跳變就輸出有效信號 1,否則就輸出無線信號0,出來的信號作爲一路或門電路的輸入,另外一輸入來自軟件中斷事件寄存器(EXTI_SWIER)。EXTI_SWIER 允許我們通過程序控制就可以啓動中斷/事件線,當或門電路輸入中有一路信號爲1,那麼就會輸出信號1,或門電路輸出的信號出來分兩路,下面路作爲與門的輸入,通過配置事件屏蔽寄存器,兩個都爲1則輸出爲1,脈衝發生器則輸出脈衝,這個脈衝信號,就是產生事件的線路最終的產物,可以給其他外設電路使用,比如定時器 TIM、模擬數字轉換器 ADC等等。或門電路出來的上面一路通過配置中斷屏蔽寄存器,然後同樣經過一個與門電路後輸出,作爲掛起請求寄存寄存器的輸入,如果中斷屏蔽寄存器和掛起請求寄存器同時爲1,那麼就可以輸出作爲NVIC中斷控制器的輸入了,即可以實現系統中斷事件控制。產生中斷線路目的是把輸入信號輸入到NVIC,進一步會運行中斷服務函數,實現功能,這樣是軟件級的。而產生事件線路目的就是傳輸一個脈衝信號給其他外設使用,並且是電路級別的信號傳輸,屬於硬件級的。中斷在嵌入式應用中佔有非常重要的地位,幾乎每個控制器都有中斷功能。中斷對保證緊急事件得到第一時間處理是非常重要的,接下來通過一個小實驗要操作一下外部中斷

硬件電路圖如下所示

我要實現的操作是當我每按下一次開關,LED燈改變一次狀態(可以參考固件庫點亮LED燈輸入按鍵檢測以及NVIC概述)。

要利用外部中斷使LED燈改變狀態,首先就是需要檢測I/O口的狀態,當I/O口滿足觸發條件,就進入中斷,執行中斷服務程序。

在進行編程之前我們先理一理編程的思路

1、首先要初始化按鍵和LED相關的GPIO

2、接下來要初始化EXTI相關寄存器用來產生中斷

3、初始化NVIC,用於處理中斷。

4、編寫中斷服務函數

5、主函數

1、所以我們第一步就應該配置KEY1,KEY2的端口,那麼應該配置KEY1、KEY2的GPIO爲哪種狀態才能與中斷產生聯繫呢,

通過查閱手冊瞭解到上圖所表達的意思就是要使用外部中斷線的話,端口必須被配置成輸入模式,所以我們就要開始對GPIO進行初始化初始化包括按鍵GPIO端口的初始化與LED燈GPIO的初始化。首先我先把與硬件相關的宏定義放在定義好的bsp_exti.h與bsp_led.h文件中,方便程序的移植與可讀性,代碼如下

//引腳定義
/*******************************************************/
#define KEY1_INT_GPIO_PORT                GPIOA			        //按鍵1端口宏定義
#define KEY1_INT_GPIO_CLK                 RCC_AHB1Periph_GPIOA		//按鍵1端口總線時鐘宏定義
#define KEY1_INT_GPIO_PIN                 GPIO_Pin_0		        //按鍵1端口引腳宏定義
#define KEY1_INT_EXTI_PORTSOURCE          EXTI_PortSourceGPIOA		//按鍵1中斷端口宏定義
#define KEY1_INT_EXTI_PINSOURCE           EXTI_PinSource0	        //按鍵1中斷端口引腳宏定義
#define KEY1_INT_EXTI_LINE                EXTI_Line0	                //按鍵1外部中斷線宏定義
#define KEY1_INT_EXTI_IRQ                 EXTI0_IRQn			//按鍵1的中斷請求類型宏定義

#define KEY1_IRQHandler                   EXTI0_IRQHandler		//按鍵1的中斷服務函數名宏定義

#define KEY2_INT_GPIO_PORT                GPIOC
#define KEY2_INT_GPIO_CLK                 RCC_AHB1Periph_GPIOC
#define KEY2_INT_GPIO_PIN                 GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE          EXTI_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE           EXTI_PinSource13
#define KEY2_INT_EXTI_LINE                EXTI_Line13
#define KEY2_INT_EXTI_IRQ                 EXTI15_10_IRQn

#define KEY2_IRQHandler                   EXTI15_10_IRQHandler
#include "stm32f4xx_gpio.h"

#define GPIO_R_Pin  GPIO_Pin_10
#define GPIO_R_Port GPIOH

#define GPIO_G_Pin  GPIO_Pin_11
#define GPIO_G_Port GPIOH

#define GPIO_B_Pin  GPIO_Pin_12
#define GPIO_B_Port GPIOH

/************************控制LED燈亮滅的宏***************/

/*直接操作寄存器的方法控制IO*/

#define  LED_PORT_OUT_HI(p,i)				{ p->BSRRL = i ;}			//輸出爲高電平
#define  LED_PORT_OUT_LO(p,i)				{ p->BSRRH = i ;}			//輸出爲低電平
#define  LED_PORT_OUT_Toggle(p,i)		        { p->BSRRL ^= i ;}		       //輸出爲反狀態

/*定義控制IO的宏*/

#define LED_R_Toggle		        LED_PORT_OUT_Toggle(GPIO_R_Port,GPIO_R_Pin)
#define LED_R_ON			LED_PORT_OUT_LO(GPIO_R_Port,GPIO_R_Pin)
#define LED_R_OFF			LED_PORT_OUT_HI(GPIO_R_Port,GPIO_R_Pin)

#define LED_G_Toggle		        LED_PORT_OUT_Toggle(GPIO_G_Port,GPIO_G_Pin)
#define LED_G_ON			LED_PORT_OUT_LO(GPIO_G_Port,GPIO_G_Pin)
#define LED_G_OFF			LED_PORT_OUT_HI(GPIO_G_Port,GPIO_G_Pin)

#define LED_B_Toggle		        LED_PORT_OUT_Toggle(GPIO_B_Port,GPIO_B_Pin)
#define LED_B_ON			LED_PORT_OUT_LO(GPIO_B_Port,GPIO_B_Pin)
#define LED_B_OFF			LED_PORT_OUT_HI(GPIO_B_Port,GPIO_B_Pin)

#define LED_RGBOFF   LED_R_OFF;\
										LED_G_OFF;\
										LED_B_OFF

接下來開始初始化按鍵的GPIO與LED的端口引腳,在初始化按鍵的GPIO引腳時因爲要與外部中斷產生聯繫,所以通過查閱數據手冊,所以要將按鍵初始化爲輸入模式,代碼分別如下。

void bsp_exti_key_gpio_config()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*開啓按鍵GPIO口的時鐘*/
	RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK ,ENABLE);
	/* 選擇按鍵1的引腳 */ 
        GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
        /* 設置引腳爲輸入模式 */ 
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;	    		
        /* 設置引腳不上拉也不下拉 */
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        /* 使用上面的結構體初始化按鍵 */
        GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
	
	/* 選擇按鍵2的引腳 */ 
        GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;  
        /* 其他配置與上面相同 */
        GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); 

}
void LED_Config(void)
{
	GPIO_InitTypeDef GPIO_LED_Struct;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH , ENABLE);		 
	
	
	GPIO_LED_Struct.GPIO_Mode  =  GPIO_Mode_OUT;
	GPIO_LED_Struct.GPIO_Speed =  GPIO_Fast_Speed;
	GPIO_LED_Struct.GPIO_OType =  GPIO_OType_PP;
	GPIO_LED_Struct.GPIO_PuPd  =  GPIO_PuPd_UP;              
	
	GPIO_LED_Struct.GPIO_Pin   =  GPIO_R_Pin;
	GPIO_Init(GPIO_R_Port, &GPIO_LED_Struct); 	       

	GPIO_LED_Struct.GPIO_Pin   =  GPIO_B_Pin;
	GPIO_Init(GPIO_B_Port, &GPIO_LED_Struct);
	
	GPIO_LED_Struct.GPIO_Pin   =  GPIO_G_Pin;
	GPIO_Init(GPIO_G_Port, &GPIO_LED_Struct);
	
	LED_RGBOFF;			                       
}

 

關於按鍵及LED的GPIO配置可以參考STM32按鍵輸入檢測STM32固件庫點亮LED燈

2、當我們配置好按鍵的GPIO後,接下來我們要進行EXTI的有關配置,由於23個外部中斷事件線相關的外設都是掛載在APB2總線上的,所以們要線打開APB2總線上的時鐘,因爲STM32的外設設計的都是很棒的,當外設不工作時,都處於休眠狀態,要想配置使用相關的外設,首先要打開對應的時鐘(關於時鐘配置這塊可以參考STM32時鐘樹介紹

  /* 使能 SYSCFG 時鐘 ,使用GPIO外部中斷時必須使能SYSCFG時鐘*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

打開時鐘後,首先要做的是什麼?,肯定是要先先配置EXTI的輸入線,我們要把按鍵的GPIO端口與EXTI聯繫起來,這裏我們使用了STM32自帶的庫函數SYSCFG_EXTILineConfig函數,這個函數用來選擇GPIO的引腳作爲EXTI的中斷事件線,函數有兩個參數,一個是GPIO的端口(A~I)另一個是端口的哪個引腳(0~15),無返回值。函數說明大致意思就是這樣。

  /* 連接 EXTI 中斷源 到key1引腳 */
  SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);
  /* 連接 EXTI 中斷源 到key2 引腳 */
  SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE);

配置完輸入後,接下來就要開始對EXTI進行配置,在官方的固件庫exti.h文件中可以找到EXTI_InitTypeDef,進行配置,STM32的固件庫非常的好用,齊全,每一種外設在對應的.h文件中都可以找到xx_InitTypeDef這個初始化結構體和xx_Init初始化配置函數,非常棒。

  EXTI_InitTypeDef EXTI_InitStructure; 
 
  /* 選擇 EXTI 中斷源 */
  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
  /* 中斷模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  /* 下降沿觸發 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
  /* 使能中斷/事件線 */
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* 選擇 EXTI 中斷源 */
  EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  /* 上升沿觸發 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

好,上面的就已經把EXTI前面的準備(紅色方框裏的)工作都已經做好了。

3、當中斷髮生後,EXTI會產生一箇中斷請求,接來下就要配置NVIC的中斷優先級等來處理中斷信號,在講配置NVIC時說,首先要使能中斷源(就上是我上面配置EXTI的操作)已經完成,接下來要進行初始化 NVIC_InitTypeDef結構體。

 void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 配置NVIC爲優先級組1 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中斷源:按鍵1 */
  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
  /* 配置搶佔優先級:1 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 配置子優先級:1 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中斷通道 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  /* 配置中斷源:按鍵2,其他使用上面相關配置 */  
  NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
  NVIC_Init(&NVIC_InitStructure);
}

4、配置完NVIC後我們接下來就要進行第四步編寫中斷服務函數,中斷服務函數的函數名在啓動文件中都已經定義好了,注意引用的時候中斷服務函數的名字不要打錯,否則編譯器不會報錯,然後進入一個死循環當中,有關中斷服務函數我建議以後所有的中斷服務函數都放在一個文件中,避免出錯。在編寫中斷服務函數時,我們首先要檢查中斷是否真的發生,然後執行中斷程序,執行完程序後記得消除中斷標誌位爲了下次進入中斷。程序如下。

void KEY1_IRQHandler(void)
{
          //確保是否產生了EXTI Line中斷
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 
	{
		// LED紅色取反		
		LED_R_Toggle;
                //清除中斷標誌位
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     
	}  
}

void KEY2_IRQHandler(void)
{
          //確保是否產生了EXTI Line中斷
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
	{
		// LED藍色取反		
		LED_B_Toggle;
                //清除中斷標誌位
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     
	}  
}

5、接下來我們就編寫主函數來實現實驗最終現象

int main(void)
{
	/* LED 端口初始化 */
	LED_GPIO_Config();
  	
	/* 初始化EXTI中斷,按下按鍵會觸發中斷,
        *  觸發中斷會進入stm32f4xx_it.c文件中的函數
	*  KEY1_IRQHandler和KEY2_IRQHandler,處理中斷,反轉LED燈。
	*/
         bsp_exti_key_gpio_config();	
         EXTI_Key_Config();
         NVIC_Configuration() ;
	
	/* 等待中斷,由於使用中斷方式,CPU不用輪詢按鍵 */
	while(1)                            
	{
	}
}

   好了,以上就是我在學習過程中的感悟與總結,寫的不好敬請見諒。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章