文章目录

  • 外部中断
  • 什么是外部中断
  • 外部中断的触发
  • 上升沿触发
  • 下降沿触发
  • 上升下降沿触发
  • 外部中断初始化
  • 初始化思路
  • 1.初始化GPIO
  • 2. 打开时钟
  • 3.GPIO和外部中断的连接
  • 4.外部中断初始化结构体
  • 5.初始化外部中断
  • 6.配置NVIC
  • 7.中断服务函数
  • 外部中断的使用



文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。

外部中断

什么是外部中断

广义上是指外设所产生的中断。
一般在实践中习惯将外部引脚的电平变化所引发的中断叫外部中断。
在标准库中将GPIO所产生的中断,PVD(电压检测),RTC(实时时钟),USB等等,合在一切叫做外部中断。

注意:STM32的所有GPIO均可作为外部中断使用,这和51单片机不一样
外部中断只有24个,0-15是GPIO的外部中断,剩下的中断号的对应关系请看标准库的定义*
本文是关于GPIO所产生的外部中断的。

外部中断的触发

GPIO的外部中断是由于电平变化所引起的,为了避免误触和重复中断,STM32使用边沿触发模式来触发中断。

上升沿触发

当GPIO的电平由低电平到高电平时会触发一次中断

就像这样

STM32CubeMX配置外部中断按键_STM32CubeMX配置外部中断按键

下降沿触发

当GPIO的电平由高电平到低电平时会触发一次中断

就像这样

STM32CubeMX配置外部中断按键_arm_02

上升下降沿触发

当GPIO的电平由低电平到高电平或者从高到低时会触发一次中断

就像这样

STM32CubeMX配置外部中断按键_arm_03

外部中断初始化

初始化思路

  1. 初始化GPIO为输入模式(上下拉和浮空都可,不能模拟输入)
  2. 打开时钟
  3. 将GPIO和中断连接起来
  4. 配置外部中断初始化结构体
  5. 用函数初始化外部中断
  6. 配置NVIC
  7. 编写中断服务函数
1.初始化GPIO

详情见之前关于GPIO的文章
GPIO 这里只放个例子

GPIO_InitTypeDef GPIO_Initstruct;						//声明GPIO初始化结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);	//打开GPIOB的时钟
GPIO_Initstruct.GPIO_Mode=GPIO_Mode_IN;					//设置为输入模式
GPIO_Initstruct.GPIO_OType=GPIO_OType_OD;				//设置为开漏模式
GPIO_Initstruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;			//引脚选择0和1
GPIO_Initstruct.GPIO_PuPd=GPIO_PuPd_UP;					//上拉模式
GPIO_Initstruct.GPIO_Speed=GPIO_High_Speed;				//高速模式
GPIO_Init(GPIOB,&GPIO_Initstruct);						//初始化GPIO
2. 打开时钟

这个不用解释了吧,只需要知道外部中断的控制器的时钟挂载在APB2时钟组下
因此使用这个函数配置时钟

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)

这个外设的打开时钟命令基本上是固定的,也就是这句

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
3.GPIO和外部中断的连接

上文提到过,STM32的GPIO都可以当作外部中断使用,但GPIO外部中断只有16个
因此我们需要一个对应关系,让GPIO和外部中断对应起来。
STM32的GPIO端口号对应着外部中断号
例如:所有GPIO组的0管脚对应外部中断0,所有GPIO组的1管脚对应外部中断1
因此不能同时使用2个组的同一管脚号来触发中断。
例如:能用A组的0引脚触发外部中断0,B组的1引脚触发外部中断1.
但不可以使用A组的0引脚和B组的0引脚同时触发外部中断0

我们使用这个函数来配置连接关系

void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex)

第一个参数是有关GPIO的信息
取值可以是

EXTI_PortSourceGPIOA 到 EXTI_PortSourceGPIOK

第二个参数是选择哪个外部中断的
取值可以是

EXTI_PinSource0 到 EXTI_PinSource15

例子

SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB,EXTI_PinSource1);

第一句是指将GPIOA组的引脚0设置为外部中断0
第二句是指将GPIOB组的引脚1设置为外部中断1

4.外部中断初始化结构体

结构体的定义是这个

typedef struct
{
  uint32_t EXTI_Line;  							//选择需要配置的中断
  EXTIMode_TypeDef EXTI_Mode; 					//选择模式
  EXTITrigger_TypeDef EXTI_Trigger;				//选择触发模式
  FunctionalState EXTI_LineCmd; 				//打开或者关闭
}EXTI_InitTypeDef;

EXTI_Line
是需要配置的中断

可以使用或命令( | )同时初始化多个值
取值可以是

EXTI_Line0 到 EXTI_Line23
其中EXTI_Line0 到 EXTI_Line15是关于GPIO的外部中断

EXTI_Mode
是选择模式

取值可以是

EXTI_Mode_Interrupt				//中断模式
EXTI_Mode_Event					//事件模式

中断模式就是我们在这里配置需要用的
事件模式是不产生中断的,可以用这个模式产生一个信号来驱动某些外设,以此来做到不占用CPU的算力,之后使用时会提到
另外中断本身也是事件的一种,其实就是将某些需要CPU响应的事件叫做中断,并进入某个函数。


EXTI_Trigger
是选择中断触发方式的

取值可以是

EXTI_Trigger_Rising					//上升沿触发
EXTI_Trigger_Falling				//下降沿触发
EXTI_Trigger_Rising_Falling			//上升沿和下降沿触发

具体上升沿和下降沿是什么请看上文的解释。


EXTI_LineCmd
使能开关

取值可以是

DISABLE				//关闭(失能)
ENABLE				//打开(使能)
5.初始化外部中断

就是使用这个函数进行初始化

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

需要传入的是配置外部中断的结构体的地址
请使用取址符(&)
例子

EXTI_Init(&EXTI_Initstruct);
6.配置NVIC

这部分在之前的文章中介绍过了
中断和NVIC 关于NVIC配置结构体的外部中断中断名称(NVIC_IRQChannel)
可以取值为

EXTI0_IRQn				//外部中断0
EXTI1_IRQn				//外部中断1
EXTI2_IRQn				//外部中断2
EXTI3_IRQn				//外部中断3
EXTI4_IRQn				//外部中断4
EXTI9_5_IRQn			//外部中断5-9
EXTI15_10_IRQn			//外部中断10-15

注意外部中断0 到 外部中断4有独立的中断优先级和中断服务函数,而外部中断5-9共用一个中断优先级和中断服务函数,外部中断10-15同理,
可以在外部中断5-9中使用检测中断标志位的方式来区分是哪个中断触发

7.中断服务函数

因为所以的中断服务函数都在启动的汇编文件中定义过了,因此名字不能错误
注意:区分大小写
中断服务函数的定义时应该这样写

void EXTI0_IRQHandler(void){/*这里写函数内容*/}
void EXTI1_IRQHandler(void){/*这里写函数内容*/}
void EXTI2_IRQHandler(void){/*这里写函数内容*/}
void EXTI3_IRQHandler(void){/*这里写函数内容*/}
void EXTI4_IRQHandler(void){/*这里写函数内容*/}
void EXTI9_5_IRQHandler(void){/*这里写函数内容*/}
void EXTI15_10_IRQHandler(void){/*这里写函数内容*/}

外部中断的使用

当外部中断触发时,就会在一个叫做外部中断挂起寄存器的特点位置写入数据
也就是一个标志位,可以使用标准库函数读取和更改标准位
每当这个标志位置为设置状态时()就会自动跳转到对应的中断服务函数

为了避免出现异常调用,一般在中断服务函数里做一个验证,读取这个标志位是否为置位状态
而且每当中断服务函数结束并且需要下次中断执行中断服务函数,
则需要在函数的最后清除中断标志位

用这个函数读取中断标志位

ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)

返回值是

RESET			//没触发中断
SET				//触发了中断

这是清除外部中断的标志位的函数

void EXTI_ClearITPendingBit(uint32_t EXTI_Line)

这两个函数的输入值可以是

EXTI_Line0 到 EXTI_Line23
其中EXTI_Line0 到 EXTI_Line15是关于GPIO的外部中断

这是一个使用外部中断的模板

//GPIO初始化
void GPIO(void)
{
	GPIO_InitTypeDef GPIO_Initstruct;						//声明GPIO初始化结构体
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);	//打开GPIO时钟
	GPIO_Initstruct.GPIO_Mode=GPIO_Mode_IN;					//输入模式
	GPIO_Initstruct.GPIO_OType=GPIO_OType_OD;				//开漏输入模式
	GPIO_Initstruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;			//引脚0,1
	GPIO_Initstruct.GPIO_PuPd=GPIO_PuPd_UP;					//上拉模式
	GPIO_Initstruct.GPIO_Speed=GPIO_High_Speed;				//高速模式
	GPIO_Init(GPIOB,&GPIO_Initstruct);						//初始化GPIO
}
//外部中断初始化
void EXTI_init(void)
{
	EXTI_InitTypeDef EXTI_Initstruct;								//创建外部中断初始化结构体
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);			//打开时钟
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB,EXTI_PinSource0);	//将GPIO与外部中断连接
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB,EXTI_PinSource1);	//同上
	EXTI_Initstruct.EXTI_Line=EXTI_Line0|EXTI_Line1;							//配置的是外部中断0,1
	EXTI_Initstruct.EXTI_LineCmd=ENABLE;							//使能
	EXTI_Initstruct.EXTI_Mode=EXTI_Mode_Interrupt;					//选择中断模式
	EXTI_Initstruct.EXTI_Trigger=EXTI_Trigger_Falling;				//下降沿模式
	EXTI_Init(&EXTI_Initstruct);									//初始化外部中断0,1
}
//配置NVIC
void EXTI_NVIC(void)
{
	NVIC_InitTypeDef NVIC_Initstruct;						//声明NVIC初始化结构体
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//选定NVIC分组
	NVIC_Initstruct.NVIC_IRQChannel=EXTI0_IRQn;				//配置的外部中断0
	NVIC_Initstruct.NVIC_IRQChannelCmd=ENABLE;				//使能
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority=1;	//主优先级
	NVIC_Initstruct.NVIC_IRQChannelSubPriority=1;			//副优先级
	NVIC_Init(&NVIC_Initstruct);							//初始化外部中断0的NVIC
	NVIC_Initstruct.NVIC_IRQChannel=EXTI1_IRQn;				//配置的外部中断1
	NVIC_Initstruct.NVIC_IRQChannelCmd=ENABLE;				//同上
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority=1;	//同上
	NVIC_Initstruct.NVIC_IRQChannelSubPriority=2;			//同上
	NVIC_Init(&NVIC_Initstruct);							//初始化外部中断1的NVIC
}
//初始化函数
void Init(void)
{
	GPIO();
	EXTI_init();
	EXTI_NVIC();
}
//外部中断0的中断服务函数
void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0)!=RESET)			//标志位被值位(产生中断)
  	{
  		/*需要操作的内容*/
  		EXTI_ClearITPendingBit(EXTI_Line0);			//清除中断标志位
  	}
}
//外部中断1的中断服务函数
void EXTI1_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line1)!=RESET)			//标志位被值位(产生中断)
  	{
  		/*需要操作的内容*/
  		EXTI_ClearITPendingBit(EXTI_Line1);			//清除中断标志位
  	}
}