一、EXTI 簡介
EXTI(External interrupt/event controller)—外部中斷/事件控制器,管理了控制器的 20個中斷/事件線。每個中斷/事件線都對應有一個邊沿檢測器,可以實現輸入信號的上升沿檢測和下降沿的檢測。EXTI 可以實現對每個中斷/事件線進行單獨配置,可以單獨配置為中斷或者事件,以及觸發事件的屬性。
二、EXTI框圖
在圖中可以看到很多在信號線上打一個斜杠並標注“20”字樣,這表示在控制器內部的信號線有 20 路,我們只要明白其中一路的原理即可。
- 輸入線:EXTI 控制器有 20 個中斷/事件輸入線,這些輸入線可以通過寄存器設置為任意一個 GPIO,也可以是一些外設的事件,輸入線一般是存在電平變化的信號。
- 邊沿檢測電路:它會根據上升沿觸發選擇寄存(EXTI_RTSR)和下降沿觸發選擇寄存器(EXTI_FTSR)對應位的設置來控制信號觸發。邊沿檢測電路以輸入線作為信號輸入端,如果檢測到有邊沿跳變就輸出有效信號 1 ,否則輸出無效信號0。而 EXTI_RTSR 和 EXTI_FTSR 兩個寄存器可以控制器需要檢測哪些類型的電平跳變過程,可以是只有上升沿觸發、只有下降沿觸發或者上升沿和下降沿都觸發。
- NVIC中斷控制器:NVIC中斷控制器接收到EXTI_PR 寄存器發送的內容,實現中斷的發生。
- 脈沖發生器:脈沖發生器發送信號后會產生事件線路,脈沖信號可以給其他外設電路使用,比如定時器 TIM、模擬數字轉換器 ADC等等,這樣的脈沖信號一般用來觸發 TIM 或者 ADC開始轉換。
現在可以知道EXTI 可分為兩大部分功能,一個是產生中斷,另一個是產生事件。產生中斷線路目的是把輸入信號輸入到 NVIC,進一步會運行中斷服務函數,實現功能,這樣是軟件級的。而產生事件線路目的就是傳輸一個脈沖信號給其他外設使用,並且是電路級別的信號傳輸,屬於硬件級的。
三、中斷/事件線
EXTI有 20個中斷/事件線,每個 GPIO都可以被設置為輸入線,每個輸入線的來源如圖所示:
從圖中可以看出STM32的外部中斷只有16個,又是怎么使每個GPIO都可以實現中斷了,通過編程控制可以實現任意一個 GPIO作為 EXTI的輸入源。
從上圖可知EXTI0 可以通過 AFIO 的外部中斷配置寄存器 1(AFIO_EXTICR1)的EXTI0[3:0]位選擇配置為 PA0、PB0、PC0、PD0、PE0、PF0、PG0,其他 EXTI線(EXTI中斷/事件線)使用配置都是類似的。
四、EXTI 寄存器
-
中斷屏蔽寄存器(EXTI_IMR)
-
事件屏蔽寄存器(EXTI_EMR)
-
上升沿觸發選擇寄存器(EXTI_RTSR)
注意: 外部喚醒線是邊沿觸發的,這些線上不能出現毛刺信號。在寫EXTI_RTSR寄存器時,在外部中斷線上的上升沿信號不能被識別,掛起位也不會被置位。在同一中斷線上,可以同時設置上升沿和下降沿觸發。即任一邊沿都可觸發中斷 -
下降沿觸發選擇寄存器(EXTI_FTSR)
注意: 外部喚醒線是邊沿觸發的,這些線上不能出現毛刺信號。在寫EXTI_FTSR寄存器時,在外部中斷線上的下降沿信號不能被識別,掛起位不會被置位。在同一中斷線上,可以同時設置上升沿和下降沿觸發。即任一邊沿都可觸發中斷。 -
軟件中斷事件寄存器(EXTI_SWIER)
-
掛起寄存器(EXTI_PR)
-
外部中斷/事件寄存器映像
五、EXTI 程序分析
- EXTI 初始化程序的結構體
typedef struct
{
uint32_t EXTI_Line; // 中斷/事件線
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 觸發類型
FunctionalState EXTI_LineCmd; // EXTI 使能
}EXTI_InitTypeDef;
- 初始化GPIO
//將GPIOA_0設置為下拉輸入
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能PORTB,PORTE時鍾
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能AFIO復用功能時鍾
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 0 pin
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 設置成下拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA_0
}
- 初始化EXTI
//GPIOA_0 中斷線以及中斷初始化配置 下降沿觸發
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); // 選擇GPIOA_0作為中斷EXTI0的線路
EXTI_InitStructure.EXTI_Line=EXTI_Line0; //中斷/事件線
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中斷觸發
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 下降沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根據EXTI_InitStruct中指定的參數初始化外設EXTI寄存器
}
- 配置 GPIO 中斷
//初始化中斷
void NVICX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按鍵WK_UP所在的外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占優先級2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子優先級2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure);
}
- 中斷程序
這里的功能是當按鍵觸發后切換led的狀態
//外部中斷2服務程序
void EXTI0_IRQHandler(void)
{
Delay(100);//消抖
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1) //檢測按鍵
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==1) //檢測led燈的狀態,進行切換
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}else{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中斷標志位
}
注意:配置中斷函數時,先確保提供的頭文件中有相應的中斷函數,否則會報錯
六、程序源碼
main.c 文件
#include "stm32f10x.h"
#include "stm32f10x_exti.h"
#include "misc.h"
/*************** 配置LED用到的I/O口 *******************/
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口時鍾
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //選擇對應的引腳
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PC端口
GPIO_SetBits(GPIOB, GPIO_Pin_12 ); // 關閉LED
}
void Delay(__IO u32 nCount)
{
for(; nCount != 0; nCount--);
}
//將GPIOA_0設置為下拉輸入
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能PORTB,PORTE時鍾
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能AFIO復用功能時鍾
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 0 pin
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 設置成下拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA_0
}
/* 配置PA0為外部中斷 */
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); // 選擇GPIOA_0作為中斷EXTI0的線路
EXTI_InitStructure.EXTI_Line=EXTI_Line0; //中斷/事件線
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中斷觸發
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 下降沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根據EXTI_InitStruct中指定的參數初始化外設EXTI寄存器
}
//初始化中斷
void NVICX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按鍵WK_UP所在的外部中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占優先級2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子優先級2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure);
}
//外部中斷2服務程序
void EXTI0_IRQHandler(void)
{
Delay(100);//消抖
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1) //WK_UP按鍵
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}else{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中斷標志位
}
int main(void)
{
SystemInit();
LED_GPIO_Config(); //LED 端口初始化
KEY_Init();
EXTIX_Init();
NVICX_Init();
while (1)
{
}
}
七、編譯運行
編譯完成后下載程序並觀察運行現象,當GPIOA_0引腳接地一次,LED燈的狀態便切換一次
參考文獻
STM32系統學習——EXTI(外部中斷):https://blog.csdn.net/zxh1592000/article/details/80280715
STM32之EXTI——外部中斷:https://www.cnblogs.com/alvis-jing/p/3678285.html