EXTI外部中断
中断的概念:在主程序运行过程中,出现特定的中断触发条件(也称中断源)时,CPU在执行完当前指令后,会暂停正在运行的程序转而去处理中断程序,处理完成后再返回原来暂停的位置继续运行。
通常,当发生中断时,在执行完当前指令后,芯片内部的中断控制器会检查中断请求,并选择要响应的中断请求,响应该中断请求本质是CPU跳转到指定的指令地址去执行,该指令地址称为中断向量。中断向量在芯片设计时就指定了,也可以在上电后通过配置寄存器进行修改向量表起始地址。
中断优先级:在同一时刻可能有多个中断源请求CPU,此时CPU就会根据中断源的优先级选择其他一个响应,而其他未被响应的中断会在前面的中断完成后再进行响应。
中断嵌套:由于中断源存在优先级,因此高优先级的中断可以打断正在执行的低优先级的中断,当高优先级的中断执行完毕后,再继续执行未完成的低优先级中断响应程序。

芯片的架构和功能通常都是模块化的,因此对于中断功能,在芯片内也有专门的中断控制器对中断请求进行管理。并且,其他功能模块也控制着该模块与中断控制器连接的通路。以STM32为例,管理中断的控制器名称是NVIC。STM32具有68个可屏蔽中断通道,包括EXTI、TIM、ADC、USART、SPI、RTC等多个外设。这些外设也有专门控制器进行配置,并且需要配置这些模块与NVIC中断控制器连接才能够实现触发CPU中断。NVIC统一管理通向CPU的中断通道,每个中断通道有16个可编程(也即可通过程序定义)的优先级,并且可以对优先级进行分组进一步分为抢占优先级和响应优先级。中断系统的结构示意图如下所示。

图中,芯片上各个外设模块通过链路连接到内核中的NVIC控制器(这个其实算是内核中的外设),当这些外设中的某些条件满足时,会驱动连接线产生电平变化,NVIC通过感知连接线的电平变化判断是否产生了中断,并对中断进行管理,然后选择要响应的中断上报给CPU去对应的中断向量地址执行指令,该指令通常是一条跳转指令,跳转到真正的中断处理函数中。
以EXTI外部中断为例,STM32芯片上的中断链路结构如图所示。

外部中断控制器EXTI的结构图如下

从图中可以看出,EXTI控制器挂在在APB外设总线上,并通过寄存器配置通向NVIC中断控制器的信息流、边沿检测电路、脉冲发生器等。
EXTI外部中断支持检测所有GPIO口的电平变化以产生中断,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒。从结构图可以看出,GPIO模块连接到AFIO复用控制器模块,然后连接到EXTI外部中断控制器模块,最后到NVIC中断控制器,最终再由NVIC控制器向CPU上报。因此对于某一个GPIO引脚,通过引脚电平产生外部中断时的配置流程应该遵循信号的流向关系,也即分为以下几步:
(1)配置GPIO控制器,复用引脚为输入模式,以及电气属性、速度等配置;
(2)配置AFIO复用控制器,选择GPIO引脚的中断功能,将引脚连接到AFIO控制器;
(3)配置EXTI外部中断控制器,选择由AFIO通向EXTI的中断控制线(链路);
(4)配置NVIC中断控制器,选择由EXTI控制器流向NVIC控制器的链路;
需要注意的是,不同的GPIO模块中相同序号的pin共用一条通向AFIO的链路,也因此共用同一条最终通向NVIC的链路,至于最终是哪个GPIO模块的pin引起的中断,可以通过在中断处理程序中读取引脚电平来进行判断。具体地,GPIOA的1号引脚和GPIOB、GPIOC、GPIOD、GPIOE的1号引脚共用同一条中断链路,最终通向NVIC控制器的都是EXTI0,触发中断时执行的也是同一个中断处理程序。
图中,PVD输出、RTC闹钟、USB唤醒、以太网唤醒则是直接连接到了EXTI控制器,因此配置过程与GPIO引脚外部中断的配置过程稍有不同,但是流程都是类似的,都要遵循信号的流向进行配置。
下面以具体的代码演示说明外部中断的初始化过程:
点击查看代码
void gpioExitInit(void){
/*
开启时钟:GPIO控制器时钟和
AFIO复用控制器时钟
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//初始化GPIO为输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//AFIO复用控制器选择中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);
//EXTI外部中断控制器初始化
EXTI_InitTypeDef EXTI_InitStructre;
EXTI_InitStructre.EXTI_Line = EXTI_Line10;
EXTI_InitStructre.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructre.EXTI_LineCmd = ENABLE;
EXTI_InitStructre.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructre); //传入配置结构体进行初始化
//NVIC中断控制器配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //优先级分组
NVIC_InitTypeDef NVIC_InitStructer;
NVIC_InitStructer.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructer.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructer.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructer); //传入配置结构体进行初始化
}
从代码中可以看出,GPIO外部初始化过程遵循了上面的结构图和信号链路流向:
(1)开启GPIO模块和AFIO模块的时钟;
(2)初始化GPIO引脚;
(3)通过AFIO复用控制器选择中断引脚,建立GPIO引脚到AFIO控制器的链路;
(4)EXTI外部中断控制器选择中断控制线并初始化,建立AFIO控制器到EXTI控制器的链路;
(5)选择NVIC优先级分组,选择并初始化通向NVIC控制器的中断线,建立EXTI外部中断控制器到NVIC中断控制器的链路。
对于中断处理程序,通常包含以下几步:
(1)判断是否对应想要的中断控制线产生的中断;
(2)如果(1)成立,执行相关的处理程序;
(3)执行完处理程序后清除中断标志位,以便后续能够重新产生中断。
其中一个中断处理函数代码如下:
点击查看代码
//中断处理函数
void EXTI15_10_IRQHandler(void) {
/*
(1)判断是否是所使用的引脚产生的中断
(2)再次判断引脚电平,以防止出现数据抖动导致的错误记录
(3)如果确实是产生了中断,计数值加1
(4)清除中断标志位,以便下一次能够继续进入中断
*/
if (EXTI_GetITStatus(EXTI_Line10) == SET) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0) {
count++;
}
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line10);
}
}
中断处理函数其实是对启动代码中对应中断向量跳转函数的重载,并且中断处理函数无参无返回值。启动代码中中断向量表如下图。


浙公网安备 33010602011771号