关于STM32外部中断
扫描二维码
随时随地手机看文章
以下总结参考原子STM32开发指南
STM32F103 的中断控制器支持 19 个外部中断/事件请求。STM32F103 的19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、 GPIOF.0、 GPIOG.0。
在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:
voidGPIO_EXTILineConfig(uint8_tGPIO_PortSource,uint8_tGPIO_PinSource);
然后看一下这个函数的使用范例:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);//将中断线2与GPIOE映射起来
中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:
voidEXTI_Init(EXTI_InitTypeDef*EXTI_InitStruct);
我们来看看结构体 EXTI_InitTypeDef 的成员变量:
typedefstruct
{
uint32_tEXTI_Line;
EXTIMode_TypeDefEXTI_Mode;
EXTITrigger_TypeDefEXTI_Trigger;
FunctionalStateEXTI_LineCmd;
}EXTI_InitTypeDef;
第一个参数是中断线的标号,取值范围为EXTI_Line0~EXTI_Line15。
第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。
第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling。
最后一个参数就是使能中断线。
下面我们用一个使用范例来说明这个函数的使用:
EXTI_InitTypeDefEXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
这样就设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,我们还需要设置NVIC中断优先级。
下面介绍一下NVIC
首先要讲解的是中断优先级分组函数 NVIC_PriorityGroupConfig,其函数申明如下:
voidNVIC_PriorityGroupConfig(uint32_tNVIC_PriorityGroup);
这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分组确定就最好不要更改。
比如我们设置整个系统的中断优先级分组值为 2,那么方法是:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这样就确定了一共为“2 位抢占优先级,2 位响应优先级”。
下面我们讲解一个重要的函数为中断初始化函数 NVIC_Init,其函数申明为:
voidNVIC_Init(NVIC_InitTypeDef*NVIC_InitStruct);
其中 NVIC_InitTypeDef 是一个结构体,我们可以看看结构体的成员变量:
typedefstruct
{
uint8_tNVIC_IRQChannel;
uint8_tNVIC_IRQChannelPreemptionPriority;
uint8_tNVIC_IRQChannelSubPriority;
FunctionalStateNVIC_IRQChannelCmd;
}NVIC_InitTypeDef;
NVIC_InitTypeDef 结构体中间有三个成员变量,这三个成员变量的作用是:
NVIC_IRQChannel:定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到每个中断对应的名字。例如 USART1_IRQn。
NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别。
NVIC_IRQChannelSubPriority:定义这个中断的子优先级别。
NVIC_IRQChannelCmd:该中断是否使能。
抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
这里需要注意两点:
第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
结合实例说明一下:假定设置中断优先级组为 2,然后设置中断 3(RTC 中断)的抢占优先级为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。
上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!
我们现在就可以设置中断线2的中断优先级:
NVIC_InitTypeDefNVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;//使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//中断优先级分组初始化
我们配置完中断优先级之后,接着我们要做的就是编写中断服务函数。中断服务函数的名字是在 MDK 中事先有定义的。这里需要说明一下,STM32 的 IO 口外部中断函数只有 6 个,分别为:
EXPORTEXTI0_IRQHandler
EXPORTEXTI1_IRQHandler
EXPORTEXTI2_IRQHandler
EXPORTEXTI3_IRQHandler
EXPORTEXTI4_IRQHandler
EXPORTEXTI9_5_IRQHandler
EXPORTEXTI15_10_IRQHandler
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。 在编写中断服务函数的时候会经常使用到两个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位) :
ITStatusEXTI_GetITStatus(uint32_tEXTI_Line);
这个函数一般使用在中断服务函数的开头判断中断是否发生。另一个函数是清除某个中断线上的中断标志位:
voidEXTI_ClearITPendingBit(uint32_tEXTI_Line);
这个函数一般应用在中断服务函数结束之前,清除中断标志位。
常用的中断服务函数格式为:
voidEXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
{
中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line3);//清除LINE上的中断标志位
}
}
固件库还提供了两个函数用来判断外部中断状态以及清除外部状态标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而EXTI_GetFlagStatus 直接用来判断状态标志位。
下面我们再总结一下使用 IO 口外部中断的一般步骤:
1)初始化 IO 口为输入。
2)开启 IO 口复用时钟,设置 IO 口与中断线的映射关系。
3)初始化线上中断,设置触发条件等。
4)配置中断分组(NVIC),并使能中断。
5)编写中断服务函数。
通过以上几个步骤的设置,我们就可以正常使用外部中断了。
注意使用STM32外部中断需要开启AFIO时钟,开启方式为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);