1. 程式人生 > >第十三章:STM32-外部中斷學習

第十三章:STM32-外部中斷學習

中斷分類 


STM32的EXTI控制器支援19 個外部中斷/ 事件請求。每個中斷設有狀態位,每個中斷/ 事件都有獨立的觸發和遮蔽設定。


STM32的19個外部中斷對應著19路中斷線,分別是EXTI_Line0-EXTI_Line18

線0~15:對應外部 IO口的輸入中斷。
線16:連線到 PVD 輸出。
線17:連線到 RTC 鬧鐘事件。
線18:連線到 USB 喚醒事件。

 觸發方式:STM32 的外部中斷是有上升沿觸發,下降沿觸發,雙邊沿觸發。 

外部中斷分組:STM32 的每一個GPIO都能配置成一個外部中斷觸發源,STM32 通過根據引腳的序號不同將眾多中斷觸發源分成不同的組,比如:PA0,PB0,PC0,PD0,PE0,PF0,PG0為第一組,那麼依此類推,我們能得出一共有16 組,STM32 規定,每一組中同時只能有一箇中斷觸發源工作,那麼,最多工作的也就是16個外部中斷。

       

        

暫存器組

EXTICR暫存器組,總共有4 個,因為編譯器的暫存器組都是從0 開始編號的,所以EXTICR[0]~ EXTICR[3],對應《STM32參考手冊》裡的 EXTICR1~ EXTICR 4(查了好久才搞明白這個陣列的含義!!。每個 EXTICR只用了其低16 位。

EXTICR[0] ~EXTICR[3]的分配如下:




EXTI暫存器的結構體:

typedef struct 
{ 
  vu32 IMR; 
  vu32 EMR; 
  vu32 RTSR; 
  vu32 FTSR; 
  vu32 SWIER; 
  vu32 PR; 
} EXTI_TypeDef;
       IMR:中斷遮蔽暫存器

這是一個 32 暫存器。但是隻有前 19 位有效。當位 x 設定為1 時,則開啟這個線上的中斷,否則關閉該線上的中斷。

EMR:事件遮蔽暫存器

同IMR ,只是該暫存器是針對事件的遮蔽和開啟。

RTSR:上升沿觸發選擇暫存器

該暫存器同IMR ,也是一個32為的暫存器,只有前 19位有效。位 x 對應線x 上的上升沿觸發,如果設定為 1 ,則是允許上升沿觸發中斷/ 事件。否則,不允許。

FTSR:下降沿觸發選擇暫存器

同 PTSR,不過這個暫存器是設定下降沿的。下降沿和上升沿可以被同時設定,這樣就變成了任意電平觸發了。

SWIER:軟體中斷事件暫存器

通過向該暫存器的位x 寫入 1 ,在未設定 IMR 和EMR的時候,將設定PR中相應位掛起。如果設定了IMR 和EMR時將產生一次中斷。被設定的SWIER位,將會在PR中的對應位清除後清除。

PR:掛起暫存器

0 ,表示對應線上沒有發生觸發請求。

1,表示外部中斷線上發生了選擇的邊沿事件。通過向該暫存器的對應位寫入 1 可以清除該位。

在中斷服務函式裡面經常會要向該暫存器的對應位寫1 來清除中斷請求。

               Ex_NVIC_Config基本是按照這個結構來編寫的

中斷配置步驟

STM32的每個IO口都可以作為中斷輸入,這點很好用。要把IO口作為外部中斷輸入,有以下幾個步驟:

1)初始化IO口為輸入。

這一步設定你要作為外部中斷輸入的IO口的狀態,可以設定為上拉/下拉輸入,也可以設定為浮空輸入,但浮空的時候外部一定要帶上拉,或者下拉電阻。否則可能導致中斷不停的觸發。在干擾較大的地方,就算使用了上拉/下拉,也建議使用外部上拉/下拉電阻,這樣可以一定程度防止外部干擾帶來的影響。

2)開啟IO口複用時鐘,設定IO口與中斷線的對映關係。

STM32的IO口與中斷線的對應關係需要配置外部中斷配置暫存器EXTICR,這樣我們要先開啟複用時鐘,然後配置IO口與中斷線的對應關係。才能把外部中斷與中斷線連線起來。

3)開啟與該IO口相對的線上中斷/事件,設定觸發條件。
這一步,我們要配置中斷產生的條件,STM32可以配置成上升沿觸發,下降沿觸發,或者任意電平變化觸發,但是不能配置成高電平觸發和低電平觸發。這裡根據自己的實際情況來配置。同時要開啟中斷線上的中斷,這裡需要注意的是:如果使用外部中斷,並設定該中斷的EMR位的話,會引起軟體模擬不能跳到中斷,而硬體上是可以的。而不設定EMR,軟體模擬就可以進入中斷服務函式,並且硬體上也是可以的。建議不要配置EMR位。

例如:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能複用功能時鐘

    //GPIOE.2 中斷線以及中斷初始化配置   下降沿觸發
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
  EXTI_InitStructure.EXTI_Line=EXTI_Line2;//KEY2
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure); //根據EXTI_InitStruct中指定的引數初始化外設EXTI暫存器


   //GPIOE.3  中斷線以及中斷初始化配置 下降沿觸發 //KEY1
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
  EXTI_InitStructure.EXTI_Line=EXTI_Line3;
  EXTI_Init(&EXTI_InitStructure);  //根據EXTI_InitStruct中指定的引數初始化外設EXTI暫存器


   //GPIOE.4  中斷線以及中斷初始化配置  下降沿觸發//KEY0
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
  EXTI_InitStructure.EXTI_Line=EXTI_Line4;
  EXTI_Init(&EXTI_InitStructure);  //根據EXTI_InitStruct中指定的引數初始化外設EXTI暫存器

   //GPIOA.0  中斷線以及中斷初始化配置 上升沿觸發 PA0  WK_UP
 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); 
  EXTI_InitStructure.EXTI_Line=EXTI_Line0;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_Init(&EXTI_InitStructure);//根據EXTI_InitStruct中指定的引數初始化外設EXTI暫存器


4)配置中斷分組(NVIC),並使能中斷。
這一步,我們就是配置中斷的分組,以及使能,對STM32的中斷來說,只有配置了NVIC的設定,並開啟才能被執行,否則是不會執行到中斷服務函式裡面去的。關於NVIC的詳細介紹,請參考前面章節。

例如:

NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//使能按鍵WK_UP所在的外部中斷通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//搶佔優先順序2, 
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;//子優先順序3
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure); 

    NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//使能按鍵KEY2所在的外部中斷通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//搶佔優先順序2, 
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子優先順序2
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure);

  NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//使能按鍵KEY1所在的外部中斷通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//搶佔優先順序2 
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;//子優先順序1 
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure);    //根據NVIC_InitStruct中指定的引數初始化外設NVIC暫存器


NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//使能按鍵KEY0所在的外部中斷通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//搶佔優先順序2 
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;//子優先順序0 
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure);   //根據NVIC_InitStruct中指定的引數初始化外設NVIC暫存器
 

5)編寫中斷服務函式。

這是中斷設定的最後一步,中斷服務函式,是必不可少的,如果在程式碼裡面開啟了中斷,但是沒編寫中斷服務函式,就可能引起硬體錯誤,從而導致程式崩潰!所以在開啟了某個中斷後,一定要記得為該中斷編寫服務函式。在中斷服務函式裡面編寫你要執行的中斷後的操作。

例如:

//外部中斷0服務程式 
void EXTI0_IRQHandler(void)
{
delay_ms(10);//消抖
if(WK_UP==1)//WK_UP按鍵

BEEP=!BEEP;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中斷標誌位  
}
 
//外部中斷2服務程式
void EXTI2_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY2==0) //按鍵KEY2
{
LED0=!LED0;

EXTI_ClearITPendingBit(EXTI_Line2);  //清除LINE2上的中斷標誌位  
}
//外部中斷3服務程式
void EXTI3_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY1==0) //按鍵KEY1

LED1=!LED1;

EXTI_ClearITPendingBit(EXTI_Line3);  //清除LINE3上的中斷標誌位  
}


void EXTI4_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY0==0) //按鍵KEY0
{
LED0=!LED0;
LED1=!LED1; 

EXTI_ClearITPendingBit(EXTI_Line4);  //清除LINE4上的中斷標誌位  
}

實驗4--外部中斷實驗exit.c函式如下:

  1. #include "exti.h"
  2. #include "led.h"
  3. #include "key.h"
  4. #include "delay.h"
  5. #include "usart.h"
  6. //外部中斷0服務程式
  7. void EXTI0_IRQHandler(void)  
  8. {  
  9.     delay_ms(10);//消抖
  10.     if(KEY2==1)  //按鍵2
  11.     {  
  12.         LED0=!LED0;  
  13.         LED1=!LED1;   
  14.     }          
  15.     EXTI->PR=1<<0;  //清除LINE0上的中斷標誌位  
  16. }  
  17. //外部中斷15~10服務程式
  18. void EXTI15_10_IRQHandler(void)  
  19. {             
  20.     delay_ms(10);    //消抖            
  21.     if(KEY0==0)      //按鍵0
  22.     {  
  23.         LED0=!LED0;  
  24.     }elseif(KEY1==0)//按鍵1
  25.     {  
  26.         LED1=!LED1;  
  27.     }  
  28.     EXTI->PR=1<<13;     //清除LINE13上的中斷標誌位  
  29.     EXTI->PR=1<<15;     //清除LINE15上的中斷標誌位  
  30. }  
  31. //外部中斷初始化程式
  32. //初始化PA0,PA13,PA15為中斷輸入.
  33. void EXTIX_Init(void)  
  34. {  
  35.     RCC->APB2ENR|=1<<2;     //使能PORTA時鐘
  36.     JTAG_Set(JTAG_SWD_DISABLE);//關閉JTAG和SWD   
  37.     GPIOA->CRL&=0XFFFFFFF0;//PA0設定成輸入      
  38.     GPIOA->CRL|=0X00000008;     
  39.     GPIOA->CRH&=0X0F0FFFFF;//PA13,15設定成輸入      
  40.     GPIOA->CRH|=0X80800000;                   
  41.     GPIOA->ODR|=1<<13;    //PA13上拉,PA0預設下拉
  42.     GPIOA->ODR|=1<<15;    //PA15上拉
  43.     Ex_NVIC_Config(GPIO_A,0,RTIR); //上升沿觸發
  44.     Ex_NVIC_Config(GPIO_A,13,FTIR);//下降沿觸發
  45.     Ex_NVIC_Config(GPIO_A,15,FTIR);//下降沿觸發
  46.     MY_NVIC_Init(2,2,EXTI0_IRQChannel,2);    //搶佔2,子優先順序2,組2
  47.     MY_NVIC_Init(2,1,EXTI15_10_IRQChannel,2);//搶佔2,子優先順序1,組2    
  48. }  

其中的兩個函式:Ex_NVIC_Config(GPIO_A,0,RTIR);和MY_NVIC_Init(2,2,EXTI0_IRQChannel,2);這兩個函式都是在sys.c裡定義,分別完成了步驟2、3、4.函式原型如下:

  1. //外部中斷配置函式
  2. //只針對GPIOA~G;不包括PVD,RTC和USB喚醒這三個
  3. //引數:GPIOx:0~6,代表GPIOA~G;BITx:需要使能的位;TRIM:觸發模式,1,下升沿;2,上降沿;3,任意電平觸發
  4. //該函式一次只能配置1個IO口,多個IO口,需多次呼叫
  5. //該函式會自動開啟對應中斷,以及遮蔽線   
  6. //待測試...
  7. void Ex_NVIC_Config(u8 GPIOx,u8 BITx,u8 TRIM)   
  8. {  
  9.     u8 EXTADDR;  
  10.     u8 EXTOFFSET;  
  11.     EXTADDR=BITx/4;//得到中斷暫存器組的編號
  12.     EXTOFFSET=(BITx%4)*4;  
  13.     RCC->APB2ENR|=0x01;//使能io複用時鐘
  14.     AFIO->EXTICR[EXTADDR]&=~(0x000F<<EXTOFFSET);//清除原來設定!!!
  15.     AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx對映到GPIOx.BITx
  16.     //自動設定
  17.     EXTI->IMR|=1<<BITx;//  開啟line BITx上的中斷
  18.     //EXTI->EMR|=1<<BITx;//不遮蔽line BITx上的事件 (如果不遮蔽這句,在硬體上是可以的,但是在軟體模擬的時候無法進入中斷!)
  19.     if(TRIM&0x01)EXTI->FTSR|=1<<BITx;//line BITx上事件下降沿觸發
  20.     if(TRIM&0x02)EXTI->RTSR|=1<<BITx;//line BITx上事件上升降沿觸發
  21. }  

               這個函式完成了兩個步驟:

               2、開啟IO口複用時鐘,設定IO口與中斷線的對映關係

               3、開啟與該IO口相對的線上的中斷/時間,設定觸發條件

  1. //設定NVIC 
  2. //NVIC_PreemptionPriority:搶佔優先順序
  3. //NVIC_SubPriority       :響應優先順序
  4. //NVIC_Channel           :中斷編號
  5. //NVIC_Group             :中斷分組 0~4
  6. //注意優先順序不能超過設定的組的範圍!否則會有意想不到的錯誤
  7. //組劃分:
  8. //組0:0位搶佔優先順序,4位響應優先順序
  9. //組1:1位搶佔優先順序,3位響應優先順序
  10. //組2:2位搶佔優先順序,2位響應優先順序
  11. //組3:3位搶佔優先順序,1位響應優先順序
  12. //組4:4位搶佔優先順序,0位響應優先順序
  13. //NVIC_SubPriority和NVIC_PreemptionPriority的原則是,數值越小,越優先
  14. //CHECK OK
  15. //100329
  16. void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel,u8 NVIC_Group)    
  17. {   
  18.     u32 temp;     
  19.     u8 IPRADDR=NVIC_Channel/4;  //每組只能存4個,得到組地址 
  20.     u8 IPROFFSET=NVIC_Channel%4;//在組內的偏移
  21.     IPROFFSET=IPROFFSET*8+4;    //得到偏移的確切位置
  22.     MY_NVIC_PriorityGroupConfig(NVIC_Group);//設定分組
  23.     temp=NVIC_PreemptionPriority<<(4-NVIC_Group);     
  24.     temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);  
  25.     temp&=0xf;//取低四位
  26.     if(NVIC_Channel<32)NVIC->ISER[0]|=1<<NVIC_Channel;//使能中斷位(要清除的話,相反操作就OK)
  27.     else NVIC->ISER[1]|=1<<(NVIC_Channel-32);      
  28.     NVIC->IPR[IPRADDR]|=temp<<IPROFFSET;//設定響應優先順序和搶斷優先順序                               

這個函式完成了:

4、配置中斷分組(NVIC),並使能中斷

補充                                                                                       

在實驗18--觸控式螢幕實驗中,中斷初始化沒有呼叫這個函式,它是這樣配置的:

  1.        MY_NVIC_Init(2,0,EXTI1_IRQChannel,2);       
  2. RCC->APB2ENR|=0x01;    //使能io複用時鐘        <