今天来回顾外部中断。我个人外部中断很重要,又不那么重要,重要是因为外部中断是其他中断学习的基础,不重要是因为以后的使用其实很少,至少我用的是非常少甚至说不用外部中断。是不是有点废话文学了,那下面快速进入正题。
实验目的
前面我们学习了IO口最基本的操作,现在我们来掌握IO口作为外部中断的使用方法。
实验内容
这里我们首先 STM32 IO 口中断的一些基础概念。STM32 的每个 IO 都可以作为外部中断的中断输入口,STM32F103 的中断控制器支持 19 个外部中断,每个中断设有状态位,每个中断都有独立的触发和屏蔽设置。STM32F103 的19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
在这个实验里面,我们只用到外部IO口的输入中断。中断线16、17、18的先放在一边。眼睑的小伙伴应该发现了,外部IO口中断线只有16条啊,但是我们的IO口可远远不止16个啊,那这些中断线怎么分配呢?我们来看一个图。
没错,stm32的外部IO口的中断线每一条都是映射多个IO口的,但是中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。
下面我们开始来配置IO口的外部中断。
首先,毋庸置疑的当然就是要开启时钟了。在外部中断实验我们要开启的时钟是AFIO时钟。开启时钟的函数在前面就已经熟悉过了。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启复用功能时钟
在库函数中,配置 GPIO 与中断线的映射关系函数是 GPIO_EXTILineConfig(),该函数有两个参数,第一个参数是中断线的选择,有如下这些,被定义在头文件stm32f10x_gpio.h里面。
#define GPIO_PortSourceGPIOA ((uint8_t)0x00)
#define GPIO_PortSourceGPIOB ((uint8_t)0x01)
#define GPIO_PortSourceGPIOC ((uint8_t)0x02)
#define GPIO_PortSourceGPIOD ((uint8_t)0x03)
#define GPIO_PortSourceGPIOE ((uint8_t)0x04)
#define GPIO_PortSourceGPIOF ((uint8_t)0x05)
#define GPIO_PortSourceGPIOG ((uint8_t)0x06)
第二个参数是IO口引脚序号的选择。有如下这些,同样定义在头文件stm32f10x_gpio.h里面。
#define GPIO_PinSource0 ((uint8_t)0x00)
#define GPIO_PinSource1 ((uint8_t)0x01)
#define GPIO_PinSource2 ((uint8_t)0x02)
#define GPIO_PinSource3 ((uint8_t)0x03)
#define GPIO_PinSource4 ((uint8_t)0x04)
#define GPIO_PinSource5 ((uint8_t)0x05)
#define GPIO_PinSource6 ((uint8_t)0x06)
#define GPIO_PinSource7 ((uint8_t)0x07)
#define GPIO_PinSource8 ((uint8_t)0x08)
#define GPIO_PinSource9 ((uint8_t)0x09)
#define GPIO_PinSource10 ((uint8_t)0x0A)
#define GPIO_PinSource11 ((uint8_t)0x0B)
#define GPIO_PinSource12 ((uint8_t)0x0C)
#define GPIO_PinSource13 ((uint8_t)0x0D)
#define GPIO_PinSource14 ((uint8_t)0x0E)
#define GPIO_PinSource15 ((uint8_t)0x0F)
本实验用到了PE3、PE4、PA0,配置如下。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
配置完了中断线映射,接下来就是要初始化中断了。这里的中断初始化使用的是EXTI_Init()函数来进行初始化。同样的,像前面说的一样所有的初始化函数的使用都是差不多的。下面是PE3的初始化配置。其他的以此类推。先来看EXTI_Init()函数使用到的结构体EXTI_InitTypeDef。
typedef struct
{
uint32_t EXTI_Line;
EXTIMode_TypeDef EXTI_Mode;
EXTITrigger_TypeDef EXTI_Trigger;
FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
第一个成员变量是中断线选择;第二个成员变量是选择外部中断或事件请求;第三个成员变量是触发条件选择;第四个成员变量是是否使能外部中断或事件请求。在这个实验里面我们的配置如下。
EXTI_InitStructure.EXTI_Line = EXTI_Line3; //中断线3(引脚3)
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //外部中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能外部中断
EXTI_Init(&EXTI_InitStructure);
既然是中断,那就必须要安排一波优先级配置啦。因为前面讲过优先级的配置,所以这里就不讲了,这给出来的是PA0的中断优先级配置。
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
配置完优先级,中断的配置就算是完成了。然后就要来写我们的中断服务函数了。这里的是PA0的外部中断服务函数。
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)==RESET) //判断中断标志位(判断中断是否发生)
{
EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位
delay_ms(10);
if(WK_UP==1)
{
LED0=LED1=0;
}
}
}
有小伙伴就发现了,这里的中断标志位清除怎么放在前面,而正点原子的例程是放在最后的。其实,在正常情况下,中断标志位放在最后也是没问题的,但是总有个万一的。因为中断标志位清除也是需要一定的时间的,假设中断执行完了,但是中断标志位没有清除掉,就会一直进入中断,导致程序卡死,所以进入中断必须先清中断标志位,必须先清中断标志位,必须先清中断标志位!
好了,今天就讲到这里了,再见!
如果喜欢的话就记得点个赞,关注关注呗!