文章目录
- 外部中断
- 什么是外部中断
- 外部中断的触发
- 上升沿触发
- 下降沿触发
- 上升下降沿触发
- 外部中断初始化
- 初始化思路
- 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的电平由低电平到高电平时会触发一次中断
就像这样
下降沿触发
当GPIO的电平由高电平到低电平时会触发一次中断
就像这样
上升下降沿触发
当GPIO的电平由低电平到高电平或者从高到低时会触发一次中断
就像这样
外部中断初始化
初始化思路
- 初始化GPIO为输入模式(上下拉和浮空都可,不能模拟输入)
- 打开时钟
- 将GPIO和中断连接起来
- 配置外部中断初始化结构体
- 用函数初始化外部中断
- 配置NVIC
- 编写中断服务函数
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); //清除中断标志位
}
}