第十三章: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;
這是一個 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函式如下:
- #include "exti.h"
- #include "led.h"
- #include "key.h"
- #include "delay.h"
- #include "usart.h"
- //外部中斷0服務程式
- void EXTI0_IRQHandler(void)
- {
- delay_ms(10);//消抖
- if(KEY2==1) //按鍵2
- {
- LED0=!LED0;
- LED1=!LED1;
- }
- EXTI->PR=1<<0; //清除LINE0上的中斷標誌位
- }
- //外部中斷15~10服務程式
- void EXTI15_10_IRQHandler(void)
- {
- delay_ms(10); //消抖
- if(KEY0==0) //按鍵0
- {
- LED0=!LED0;
- }elseif(KEY1==0)//按鍵1
- {
- LED1=!LED1;
- }
- EXTI->PR=1<<13; //清除LINE13上的中斷標誌位
- EXTI->PR=1<<15; //清除LINE15上的中斷標誌位
- }
- //外部中斷初始化程式
- //初始化PA0,PA13,PA15為中斷輸入.
- void EXTIX_Init(void)
- {
- RCC->APB2ENR|=1<<2; //使能PORTA時鐘
- JTAG_Set(JTAG_SWD_DISABLE);//關閉JTAG和SWD
- GPIOA->CRL&=0XFFFFFFF0;//PA0設定成輸入
- GPIOA->CRL|=0X00000008;
- GPIOA->CRH&=0X0F0FFFFF;//PA13,15設定成輸入
- GPIOA->CRH|=0X80800000;
- GPIOA->ODR|=1<<13; //PA13上拉,PA0預設下拉
- GPIOA->ODR|=1<<15; //PA15上拉
- Ex_NVIC_Config(GPIO_A,0,RTIR); //上升沿觸發
- Ex_NVIC_Config(GPIO_A,13,FTIR);//下降沿觸發
- Ex_NVIC_Config(GPIO_A,15,FTIR);//下降沿觸發
- MY_NVIC_Init(2,2,EXTI0_IRQChannel,2); //搶佔2,子優先順序2,組2
- MY_NVIC_Init(2,1,EXTI15_10_IRQChannel,2);//搶佔2,子優先順序1,組2
- }
其中的兩個函式:Ex_NVIC_Config(GPIO_A,0,RTIR);和MY_NVIC_Init(2,2,EXTI0_IRQChannel,2);這兩個函式都是在sys.c裡定義,分別完成了步驟2、3、4.函式原型如下:
- //外部中斷配置函式
- //只針對GPIOA~G;不包括PVD,RTC和USB喚醒這三個
- //引數:GPIOx:0~6,代表GPIOA~G;BITx:需要使能的位;TRIM:觸發模式,1,下升沿;2,上降沿;3,任意電平觸發
- //該函式一次只能配置1個IO口,多個IO口,需多次呼叫
- //該函式會自動開啟對應中斷,以及遮蔽線
- //待測試...
- void Ex_NVIC_Config(u8 GPIOx,u8 BITx,u8 TRIM)
- {
- u8 EXTADDR;
- u8 EXTOFFSET;
- EXTADDR=BITx/4;//得到中斷暫存器組的編號
- EXTOFFSET=(BITx%4)*4;
- RCC->APB2ENR|=0x01;//使能io複用時鐘
- AFIO->EXTICR[EXTADDR]&=~(0x000F<<EXTOFFSET);//清除原來設定!!!
- AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx對映到GPIOx.BITx
- //自動設定
- EXTI->IMR|=1<<BITx;// 開啟line BITx上的中斷
- //EXTI->EMR|=1<<BITx;//不遮蔽line BITx上的事件 (如果不遮蔽這句,在硬體上是可以的,但是在軟體模擬的時候無法進入中斷!)
- if(TRIM&0x01)EXTI->FTSR|=1<<BITx;//line BITx上事件下降沿觸發
- if(TRIM&0x02)EXTI->RTSR|=1<<BITx;//line BITx上事件上升降沿觸發
- }
這個函式完成了兩個步驟:
2、開啟IO口複用時鐘,設定IO口與中斷線的對映關係
3、開啟與該IO口相對的線上的中斷/時間,設定觸發條件
- //設定NVIC
- //NVIC_PreemptionPriority:搶佔優先順序
- //NVIC_SubPriority :響應優先順序
- //NVIC_Channel :中斷編號
- //NVIC_Group :中斷分組 0~4
- //注意優先順序不能超過設定的組的範圍!否則會有意想不到的錯誤
- //組劃分:
- //組0:0位搶佔優先順序,4位響應優先順序
- //組1:1位搶佔優先順序,3位響應優先順序
- //組2:2位搶佔優先順序,2位響應優先順序
- //組3:3位搶佔優先順序,1位響應優先順序
- //組4:4位搶佔優先順序,0位響應優先順序
- //NVIC_SubPriority和NVIC_PreemptionPriority的原則是,數值越小,越優先
- //CHECK OK
- //100329
- void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel,u8 NVIC_Group)
- {
- u32 temp;
- u8 IPRADDR=NVIC_Channel/4; //每組只能存4個,得到組地址
- u8 IPROFFSET=NVIC_Channel%4;//在組內的偏移
- IPROFFSET=IPROFFSET*8+4; //得到偏移的確切位置
- MY_NVIC_PriorityGroupConfig(NVIC_Group);//設定分組
- temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
- temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
- temp&=0xf;//取低四位
- if(NVIC_Channel<32)NVIC->ISER[0]|=1<<NVIC_Channel;//使能中斷位(要清除的話,相反操作就OK)
- else NVIC->ISER[1]|=1<<(NVIC_Channel-32);
- NVIC->IPR[IPRADDR]|=temp<<IPROFFSET;//設定響應優先順序和搶斷優先順序
這個函式完成了:
4、配置中斷分組(NVIC),並使能中斷
補充
在實驗18--觸控式螢幕實驗中,中斷初始化沒有呼叫這個函式,它是這樣配置的:
- MY_NVIC_Init(2,0,EXTI1_IRQChannel,2);
- RCC->APB2ENR|=0x01; //使能io複用時鐘 <