前置知识
- 中断:在主程序运行过程中,出现了特定的中断源,使得CPU暂停当前正在运行中的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续执行,可以参考图1所示。
图1 中断程序图
-
中断优先级:当有多个中断源同时请求中断的时候,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
-
中断嵌套:其中一个中断正在运行中,又出现了一个新的更高级的中断,CPU再次暂停当前中断程序,转而运行新的程序,处理完成后依次返回。如图2所示:
图2 嵌套中断程序图
STM32 中断
STM32F1系列
- 68个可屏蔽中断通道,包含EXTI,TIM,ADC,USART,SPI,I2C,RTC等多个外设
- 使用NVIC统一管理中断,每个中断通道都有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
NVIC基本结构
NVIC在STM32中,它是用来统一分配中断优先级和管理中断的,是一个内核外设,NVIC的结构图如下图3所示
图3 NVIC结构图
由于CPU是主要用来运算的,故把中断分配的任务外包给了NVIC,他有很多个输入口,可以接很多的中断口。
NVIC中断分组
在STM32中,NVIC中断分组可以分为两个级别:全局中断分组和子优先级分组。
-
全局中断分组:用于配置所有中断请求的优先级分组方式,可以通过SCB(System Control Block)模块中的AIRCR(Application Interrupt and Reset Control Register)寄存器进行配置。全局中断分组主要影响到普通中断的优先级顺序。
-
子优先级分组:用于配置同一优先级中多个中断之间的响应顺序,通过NVIC模块的IPR(Interrupt Priority Register)寄存器进行配置。
在全局中断分组中,将中断的优先级分为4组,每组优先级数目不同。可根据实际需要选择合适的分组方式。具体的分组方式如下:
-
0位抢占优先级和4位响应优先级(0:4):将16个主优先级分成16/1=16个组,每组只包含一个优先级。
-
1位抢占优先级和3位响应优先级(1:3):将16个主优先级分成16/4=4个组,每组包含4个优先级。
-
2位抢占优先级和2位响应优先级(2:2):将16个主优先级分成16/16=1个组,所有中断的优先级相同。
-
3位抢占优先级和1位响应优先级(3:1):将16个主优先级分成16/64=1/4个组,每组包含64个优先级。
在选择中断分组时,需要权衡系统的可靠性和中断响应速度。如果需要更快的中断响应速度,则应当选取更高的优先级;如果需要更稳定的系统,则应降低优先级。
EXTI外部中断
在STM32单片机中,可以使用外部中断输入线(EXTI)来实现外部中断的响应。EXTI线为双边沿触发,可以同时支持上升沿和下降沿触发中断,还支持软件触发和信号线电平状态查询等多种功能。
在使用STM32中的EXTI外部中断时,需要注意以下几点:
-
配置GPIO引脚:首先需要将要使用的GPIO引脚配置为输入模式,同时使能外部中断线。
-
配置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都有其特定的作用:
-
AFIO(Alternate Function I/O):是一种外设复用功能,可以将单个GPIO端口的复用功能分配给多个外设,例如复用其他串行通信接口或IO端口。在外部中断中,为了允许对每个I/O引脚选择不同的中断线,需要使用AFIO来配置GPIO端口的复用功能,从而定义中断线的连接。
-
EXTI(External Interrupt/Event Controller):是管理外设中断/事件的控制器,通常用于激活处理器中的中断。EXTI外部中断线和GPIO Pin相关联,当线上电平发生变化时,EXTI会触发一次中断请求,并产生中断标志位。通过操作EXTI, 可以设置中断线的触发方式和优先级等参数。
-
NVIC(Nested Vectored Interrupt Controller):是STM32芯片中处理各种中断请求的一个内部模块,支持嵌套中断机制,用于优化系统的多任务管理。NVIC中,优先级分组分为:抢占优先级分组和响应优先级分组。抢占优先级越高,CPU在处理该中断时,会放弃低优先级中断的响应并快速地进入该中断处理函数中。
在STM32外部中断的使用中,这三个组件经常一同出现:
- AFIO和GPIO可以一起配置中断线路和端口,以便将输入引脚映射到正确的外部中断线路。
- EXTI和系统中断溢出控制器协同工作,确定是否还有活动中断,以及确保在执行给定中断的处理程序之前没有丢失或覆盖其他中断。
- NVIC控制中断处理程序的优先级,以确保在中断“堆栈”中按正确顺序处理多个中断。
下面我们通过一个红外传感器计次的代码来实际感受一下,外部中断:
红外传感器计次
#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);
}
}