stm32外部中断详解
相比较于51单片机来说,stm32拥有大量的外部中断资源,利用好外部中断能够带来很多便利STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),STM32F103RCT6(51),下图会给出答案112通用I/O端口以下图的方式连接到16个外部中断/事件线上EXTI线16连接到PVD输出EXTI线17连接到RTC闹钟事件EXTI线1
目录
4.有一个问题,既然Px0全部连接到一条线上,那么能不能有假设PA0和PB0,PC0都用中断呢
前言
相比较于51单片机来说,stm32拥有大量的外部中断资源,利用好外部中断能够带来很多便利
一、外部中断是什么
EXTI(External interupt/event Controller外部中断/事件控制器)控制,在 APB2 总线上,F1系列有20个EXTI线。好比于你在烧一壶水,你并不知道什么时候烧开,这时你需要不断的去查看是否烧开了,这种在单片机中就是查询,若是你使用了定时烧水,那么定时时间到了会提醒你,就不用你一次一次的去查看了,这种就是单片机中使用了外部中断,即在外部事件发生时,产生一个中断,提醒CPU去处理这个中断
二、外部中断概述
1.外部中断线
STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),STM32F103RCT6(51),下图会给出答案
112通用I/O端口以下图的方式连接到16个外部中断/事件线上

EXTI线16连接到PVD输出
EXTI线17连接到RTC闹钟事件
EXTI线18连接到USB唤醒事件
EXTI线19连接到以太网唤醒事件(只适用于互联型产品)
我们可以给每一条中断线配置边沿触发方式
2.框图分析

红线是中断--属于软件级的
- 是GPIO的任意一个引脚,一般是存在电平变化的信号,使用AFIO的寄存器进行配置
- 是一个边沿检测电路,通过上升/下降沿触发选择器寄存器,对应位给1开启边沿信号检测,检测到了就在3或门处给个1,而两个寄存器可以配置上升沿/下降沿,或者上升沿和下降沿都触发
- 是一个或门电路,软件中断事件寄存器由软件置位 1,这一操作会把请求挂起寄存器的相应位置1
- 是一个与门电路,中断屏蔽寄存器设置为1表示开放中断,编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR(挂起寄存器) 对应位置 1,在该位中写入’1’可以清除它,也可以通过改变边沿检测的极性清除。
- 发送到内核NVIC中请求产生中断,并需求中断服务函数
绿线是事件-最终输出一个脉冲信号--属于硬件级的
6.是一个与门电路,那么可以简单的控制 EXTI_EMR 来实现是否产生事件的目的,EXTI_EMR事件屏蔽寄存器控制, 写1使能, 来到脉冲发生器
7.脉冲发生器,输入1就 产生脉冲
8.脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换
3.中断向量表
在中断向量表里给我们列出了各种外部中断
下图只是部分,具体请看参考手册

黑色部分是内核水平的中断,白色部分是外部中断
4.中断优先级配置
从表中可以看出,外部中断线5~9分配一个中断向量,共用一个服务函数
外部中断线10~15分配一个中断向量,共用一个中断服务函数。

现在NVIC中配置优先级分组,然后在利用库函数配置每个的优先级
如下,我配置的是两个按键的中断,优先级分组为0,那根据上图可知主优先级没有,只有子优先级
我又给两个中断配置了一样的优先级,这叫软件优先级,当软件优先级相同时,就比较硬件优先级(中断向量表里面的排序就是硬件优先级,越小,优先级越高)
static void EXTI_NVIC_Config(void)
{
NVIC_InitTypeDef NVICA_InitStructure,NVICC_InitStructure;
//配置优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
//配置优先级
//配置按键KA0
NVICA_InitStructure.NVIC_IRQChannel = KEYA_INT_EXTI_IRQChanned;
NVICA_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVICA_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVICA_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVICA_InitStructure);
//配置按键C13
NVICC_InitStructure.NVIC_IRQChannel = KEYC_INT_EXTI_IRQChanned;
NVICC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVICC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVICC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVICC_InitStructure);
}
下面是中断服务函数 ,名字一定要正确!
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
三、库函数使用
步骤
1.开启时钟-->AFIO,GPIO
2.配置GPIO结构体,EXTI结构体
3.选择中断源 --void GPIO_EXTILineConfig(参数);
4.初始化NVIC--优先级分组,配置优先级(初始化结构体)
5.中断服务函数,记得清除标志位
1.配置初始化
由于选择中断线需要用到AFIO的寄存器,所以这里要开启AFIO的时钟
#ifndef __BSP_KEY_H
#define __BSP_KEY_H
#define KEYA_INT_GPIO_PIN GPIO_Pin_0
#define KEYA_INT_GPIO_PORT GPIOA
#define KEYA_INT_GPIO_CLK RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO
#define KEYA_INT_EXTI_Mode EXTI_Mode_Interrupt
#define KEYA_INT_EXTI_Line EXTI_Line0
#define KEYA_INT_EXTI_TRIGGER EXTI_Trigger_Rising
#define KEYA_INT_EXTI_IRQChanned EXTI0_IRQn
#define KEYA_INT_EXTI_PinSource GPIO_PinSource0
#define KEYC_INT_GPIO_PIN GPIO_Pin_13
#define KEYC_INT_GPIO_PORT GPIOC
#define KEYC_INT_GPIO_CLK RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO
#define KEYC_INT_EXTI_Mode EXTI_Mode_Interrupt
#define KEYC_INT_EXTI_Line EXTI_Line13
#define KEYC_INT_EXTI_TRIGGER EXTI_Trigger_Falling
#define KEYC_INT_EXTI_IRQChanned EXTI15_10_IRQn
#define KEYC_INT_EXTI_PinSource GPIO_PinSource13
#endif
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef KEYA_InitStruct,KEYC_InitStruct;
EXTI_InitTypeDef EXTI0_InitStruct,EXTI13_InitStruct;
//初始化NVIC
EXTI_NVIC_Config();
//初始化KA0
//GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;速度不用配置
RCC_APB2PeriphClockCmd(KEYA_INT_GPIO_CLK,ENABLE);//开启Key_A0相关的外设时钟和AFIO时钟
KEYA_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//配置成浮空输入
KEYA_InitStruct.GPIO_Pin = KEYA_INT_GPIO_PIN;
GPIO_Init(KEYA_INT_GPIO_PORT,&KEYA_InitStruct);
//初始化KC13
//GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;速度不用配置
RCC_APB2PeriphClockCmd(KEYC_INT_GPIO_CLK,ENABLE);//开启Key_C13相关的外设时钟和AFIO时钟
KEYC_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//配置成浮空输入
KEYA_InitStruct.GPIO_Pin = KEYC_INT_GPIO_PIN;
GPIO_Init(KEYC_INT_GPIO_PORT,&KEYC_InitStruct);
//初始化EXTI0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,KEYA_INT_EXTI_PinSource);//配置EXTI信号源
EXTI0_InitStruct.EXTI_Line = KEYA_INT_EXTI_Line;//EXTI0
EXTI0_InitStruct.EXTI_Mode = KEYA_INT_EXTI_Mode;//中断
EXTI0_InitStruct.EXTI_Trigger = KEYA_INT_EXTI_TRIGGER; //上升沿
EXTI0_InitStruct.EXTI_LineCmd = ENABLE;//使能中断
EXTI_Init(&EXTI0_InitStruct);
//初始化EXTI13
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,KEYC_INT_EXTI_PinSource);//配置EXTI信号源
EXTI13_InitStruct.EXTI_Line = KEYC_INT_EXTI_Line;//EXTI13
EXTI13_InitStruct.EXTI_Mode = KEYC_INT_EXTI_Mode;//中断
EXTI13_InitStruct.EXTI_Trigger = KEYC_INT_EXTI_TRIGGER; //下降沿
EXTI13_InitStruct.EXTI_LineCmd = ENABLE;//使能中断
EXTI_Init(&EXTI13_InitStruct);
}
2.中断服务函数
注意:中断服务函数一定要正确,在起始文件中寻找
void EXTI0_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line0) != RESET) //判断标志位
{
LED_G_TOGGLE;//翻转LED
EXTI_ClearFlag(EXTI_Line0);//清除标志位
}
}
void EXTI15_10_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line13) != RESET)
{
LED_R_TOGGLE;
EXTI_ClearFlag(EXTI_Line13);
}
}
3.主函数
void main()
{
LED_GPIO_Config();
EXTI_Key_Config();
while(1);
}
4.有一个问题,既然Px0全部连接到一条线上,那么能不能有假设PA0和PB0,PC0都用中断呢
我认为是可以的,虽然理论上不能同时使用
思路是这样的:在中断服务函数中进行IO电平的查询,即对GPIO的IDR寄存器进行读取
我这里是对给PA0和PB0,PC13都配置中断,来检测PA0和PB0能不能通过查询IDR的方式来一起使用,然后我在中断服务函数中进行GPIO->IDR的读取操作,判断是哪个IO口请求了中断,分别进行中断的处理
本人试了一下,并没有实现,有可能是翻转LED的IO口来进入中断不好用,也可能是就不能这样子进行配置,目前不知道具体原因,希望可以和大家多多交流,找出答案
//中断配置NVIC
static void NVIC_Config()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
//初始化代码
EXTI_InitTypeDef EXTI_InitStruct;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA ,GPIO_PinSource0);
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;//下降沿触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13);
EXTI_InitStruct.EXTI_Line = EXTI_Line13;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
//中断服务函数
void EXTI0_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)//读取按键1
{
LED_R_TOGGLE;//翻转红灯
EXTI_ClearFlag(EXTI_Line0);
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == RESET)//判断PB0是否请求中断
{
LED_B_TOGGLE;//翻转蓝灯
EXTI_ClearFlag(EXTI_Line0);
}
}
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line13) == SET)//读取按键2
{
LED_G_TOGGLE;//翻转PB0
EXTI_ClearFlag(EXTI_Line13);
}
}
总结
本章讲解的是STM32的外部中断,通过外部中断的原理、STM32外部中断概述以及外部中断库函数配置流程来详细讲述了STM32的外部中断这一外设,希望读者能够认真学习。
更多推荐

所有评论(0)