
图1 中断程序图

图2 嵌套中断程序图
STM32F1系列
NVIC在STM32中,它是用来统一分配中断优先级和管理中断的,是一个内核外设,NVIC的结构图如下图3所示

图3 NVIC结构图 由于CPU是主要用来运算的,故把中断分配的任务外包给了NVIC,他有很多个输入口,可以接很多的中断口。
在STM32中,NVIC中断分组可以分为两个级别:全局中断分组和子优先级分组。
在全局中断分组中,将中断的优先级分为4组,每组优先级数目不同。可根据实际需要选择合适的分组方式。具体的分组方式如下:
在选择中断分组时,需要权衡系统的可靠性和中断响应速度。如果需要更快的中断响应速度,则应当选取更高的优先级;如果需要更稳定的系统,则应降低优先级。
在STM32单片机中,可以使用外部中断输入线(EXTI)来实现外部中断的响应。EXTI线为双边沿触发,可以同时支持上升沿和下降沿触发中断,还支持软件触发和信号线电平状态查询等多种功能。
在使用STM32中的EXTI外部中断时,需要注意以下几点:
以下是一个简单的STM32EXTI外部中断的示例:
#include "stm32f10x.h"
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
/* 处理中断事件 */
/* 关闭中断 */
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
int main()
{
/* 使能GPIO引脚和EXTI线 */
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/* 配置GPIO引脚 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置EXTI线 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; /* 上升沿触发 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* 配置中断优先级 */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
while(1)
{
/* 正常运行代码 */
}
}定义了一个名为EXTI0_IRQHandler的中断服务函数,通过读取EXTI_GetITStatus函数来检测是否有中断请求产生,执行完中断服务函数后,需要使用EXTI_ClearITPendingBit清除中断标志位并关闭中断。
上面的例子可能还是有点晦涩,我们先来看一下EXTI的基本结构,如图4所示:

图4 EXTI基本结构图
在上图中AFIO、EXTI、NVIC都有其特定的作用:
在STM32外部中断的使用中,这三个组件经常一同出现:
下面我们通过一个红外传感器计次的代码来实际感受一下,外部中断:
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void CountSensor_Init(void)
{
//使用的GPIOB端口和复用中断功能,为外部中断线提供供电
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//定义的GPIO_InitTypeDef结构体为GPIO配置结构体,用来给GPIOB14配置输入模式和上拉输入(Internal Pull Up)。GPIOB14的GPIO_Pin定义为GPIO_Pin_14,其它参数与对应引脚电路方案有关,根据需要进行修改,例如GPIO_Speed为GPIO_Speed_50MHz,这是为了配置管脚输出的最大时钟速度,也就是所有的管脚寄存器(ODR和BSR等,不包括MODER)都运行在50MHz的时钟下
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//告诉NVIC如何映射中断线。使用GPIOB14时,可以将其用于EXTI15线路上,以便将此引脚映射到正确的线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//EXTI_InitTypeDef结构体类型和EXTI_Init()函数来初始化外部中断功能。针对 GPIOB14,需要使用EXTI_Line14枚举类型来选择要使用的中断线路。ENABLE命令行参数用于使能配置好的中断线路。EXTI_Mode设置为EXTI_Mode_Interrupt来使用EXTI的中断模式,而EXTI_Trigger则表示使用下降沿触发中断模式。这些配置后,再调用EXTI_Init()函数注册到 NVIC 的 EXTI 列表中,以确保能够正确响应中断事件
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//在第二组中断中优先级分组,一共可以实现16个不同的优先级级别,包括4个抢占优先级和4个响应优先级。该函数可以用于设置 NVIC 的中断优先级分组,在 NVIC 中按照优先级的设置顺序执行相应中断服务程序。NVIC_PriorityGroup_2 表示在一个共享优先级中,两位用于变换优先级设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//首先定义了一个 NVIC_InitTypeDef 结构体类型,用于存储 NVIC 的各项参数信息。然后通过 .NVIC_IRQChannel 参数设置了 EXTI15_10 对应的中断通道,这表示配置的是 EXTI 产生的中断事件,其中 EXTI15_10 表示相关管脚的最大ID号,表示将配置EXTI的从15到10的中断响应优先级。接着,NVIC_InitStructure.NVIC_IRQChannelCmd设置为ENABLE以告诉 NVIC 此中断通道该项参数已经完成配置,可以开始中断处理。而 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 和NVIC_InitStructure.NVIC_IRQChannelSubPriority 这两项参数则是设置相应的抢占优先级和响应优先级。抢占优先级从 0 到 15,数值最小的优先级最高,而响应优先级的取值范围与抢占优先级的取值范围相同,因为这里两个通道的具体优先级是一样的,所以都设置为 1,。最后,调用函数 NVIC_Init(),向 NVIC 注册中断服务函数,这样 NVIC 就知道该如何优先处理 EXTI 捕获到的中断事件了。执行这些代码后,配置的 EXTI 引脚成功启动,产生中断信号后外部中断的优先级也正常被处理。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}