一、中断系统
以上就是中断的概念,简单理解就是:
当程序运行过程中,如果有中断源向CPU打报告,CPU就会暂停手下的事情去处理中断源提交的事情,然后处理完了在返回到CPU原来的位置继续处理手上的事情。如果同时有多个中断源提交报告,这时候CPU就得根据优先级进行抉择,先去处理哪个中断而后再去处理哪个中断?这个过程也就包含了中断嵌套,一层套一层,最后直到所有的中断处理完了才返回到自己的位置。
那么没有中断系统行不行?答案肯定是不行的,如果没有中断,CPU就得一边执行程序一边检测其他地方有没有出现异常,中断系统就相当于一个医院,CPU是医院总裁,医院的各个部门也就是中断源,各个部门负责他们的职位,一有异常就打报告给CPU。
二、STM32中断
那么STM32有多少个中断源(中断通道)?
CM3 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256级的可编程中断设置。但 STM32 并没有使用 CM3 内核的全部东西,而是只用了它的一部分。STM32 有 84 个中断,包括 16 个内核中断和 68 个可屏蔽中断,具有 16 级可编程的中断优先级。而我们常用的就是这 68 个可屏蔽中断,但是 STM32 的 68 个可屏蔽中断,在 STM32F103 系列上面,又只有 60 个(在 107 系列才有 68 个)。
灰色的就是系统异常(中断),中断就是异常,表中的优先级是硬件编号,数字越小优先级越高这里复位中断的编号最小,也就是复位中断,所以一按板子上的复位键会立马执行复位程序。
三、嵌套向量中断控制器 (NVIC)
前面提到中断优先级,那么中断优先级是谁来指定的呢?——NVIC
NVIC是内核上的一个外设,是CUP的助手,如果很多中断源都向CPU打报告,这时候CPU很有可能会处理不过来,CPU只是负责运算的,NVIC的作用就是把64个中断源全部都接在它自己上,因为NVIC有很多个输入口,但是NVIC只有一个输出口连接到CPU上,通过这个输出口,NVIC将优先级分配好之后再之间告诉CPU你应该先去执行哪一个中断。
CPU只负责运算,中断优先级不是CPU去分配和管理的,而是NVIC
NVIC优先级分组
在NVIC中有一个寄存器:应用程序中断及复位控制寄存器(AIRCR),它里面有 一个位段名为“优先级组”,它将每个优先级可配置的异常中断都能进行分组配置,意思就是可以将一个中断进行设置,分为抢占优先级和响应优先级。
这个寄存器是8Bit的长度,同样是使用高四位表示中断的优先级的。然后在这四位中再继续进行划分,分出了用多少个Bit表示抢占优先级和多少个Bit表示响应优先级,上图中的分组方式就是把这四位分别划分给这两种方式的一个不同组合,分组方式的选择是通过函数自行选择。而这高4位就可以用0~15进行排序,也就是我们前面说到的一个中断通道有16种优先级,数字越小,优先级越高,0就是最高优先级。
两者的区别:
抢占优先级就是允许插队并且如果有人在治疗他也可以打断治疗让医生优先看他;
响应优先级就是只能插队,不可以打断上一个的治疗,等上一个治疗结束后才能治疗;
也就是优先比较抢占优先级,谁高就先执行谁;如果抢占优先级一样,就比较响应优先级,谁高就先执行谁;如果没有设置抢占优先级,而响应优先级又是一样,那就按照系统默认的中断优先级,谁高先响应谁,也就是中断表里面的优先顺序。
四、EXTI外部中断
中断响应和事件响应的区别是:中断响应是正常响应,经过NVIC后直接报告给CPU;但事件响应就是相当于外设之间的一个联合,不直接报告给CPU,可能触发到另一个外设中断
五、开启EXTI外部中断的步骤
所以步骤均基于上图
1、配置RCC,把涉及的外设的时钟打开:
涉及的外设有GPIO、AFIO、EXTI、NVIC,其中EXTI不需要我们自己打开,NVIC是内核中的外设,内核的外设都不需要开启时钟,RCC管的都是内核外的外设。
#include "stm32f10x.h" // Device header
void CountSensor(void)
{
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB ,ENABLE );
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO,ENABLE );
}
另外,如果不知道外设对应的总线是哪个,可以右键跳转到定义查看:
2、配置GPIO:
#include "stm32f10x.h" // Device header
void CountSensor(void)
{
//开启外设时钟
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB ,ENABLE );
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO,ENABLE );
//配置GPIO
GPIO_InitTypeDef GPIO_Instructure;
GPIO_Instructure.GPIO_Mode =GPIO_Mode_IPU ;
GPIO_Instructure.GPIO_Pin =GPIO_Pin_14;
GPIO_Instructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOB,&GPIO_Instructure);
}
另外,如果不知道该配置什么输入模式,可以进入手册进行查看:
GPIO配置完成,按照下图我们就应该配置AFIO,进行中断引脚的选择:
3、配置AFIO:
//配置AFIO,选择中断引脚(没有单独的AFIO文件,需要到GPIO.h文件寻找)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
4、配置EXTI:
//在EXTI头文件中查看函数并配置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);
5、配置NVIC:
在misc文件里面查看函数并进行配置:
前面提到过:优先级分组方式是通过函数进行配置而自行选择的,这就需要用到上图中第一个函数,这里因为只有一个中断,所以选择哪个分组方式都可以,为了平均分配选择了第二种分组方式;第二个函数是NVIC的初始化,和GPIO、AFIO一样都需要定义一个结构体,然后再依次把子成员引出赋值;
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
结构体的选中第一个子成员跳转定义之后:
得到如下图所示:因为是中等容量,所以选择展开MD系列,因为是B14引脚,所以选择EXTI15_10_IRQn
同理,第二个操作同理;第三个和第四个子成员进行选中搜索之后,也就是出现了下图:
这和我们前面的分组方式一样,因为我们选择的是第二种分组,可以看到第三和第四子成员的复制范围都在0~3,这里就随便选择,都选择1即可;
直到这里外部中断的程序就结束了,此时CPU已经能够从主程序跳转到中断程序中去了,但接下来我们还要写中断函数:
6、中断函数
在STM32中中断函数的名字都是固定的,每个中断通道对应一个中断函数,在启动文件中查看中断向量表,就确认中断程序名:
这些以IRQHandler结尾的都是中断函数名,其中EXTI15_10_IRQHandler就是通道15-10的中断函数名(因为我用的是B14引脚)
然后在函数里第一步一般是进行中断标志位的判断,这样才能确保是我们想要的中断源触发的中断,因为这个函数EXTI10-EXTI15都能触发中断,我们需要判断是不是EXTI14触发的中断,这时候就要到EXTI头文件进行查找相关的函数去进行判断:
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14)==SET ) //判断标志位是不是1,函数返回值是SET就说明是14引脚
触发的中断,SET就是1,高电平,RESET是低电平
{
EXTI_ClearITPendingBit(EXTI_Line14); //最后需要清除标志位
}
}
7、总代码:
#include "stm32f10x.h" // Device header
void CountSensor(void)
{
//开启外设时钟
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB ,ENABLE );
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO,ENABLE );
//配置GPIO
GPIO_InitTypeDef GPIO_Instructure;
GPIO_Instructure.GPIO_Mode =GPIO_Mode_IPU ;
GPIO_Instructure.GPIO_Pin =GPIO_Pin_14;
GPIO_Instructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOB,&GPIO_Instructure);
//配置AFIO,选择中断引脚(没有单独的AFIO文件,需要到GPIO.h文件寻找)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//在EXTI头文件中查看函数并配置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);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14)==SET )
{
EXTI_ClearITPendingBit(EXTI_Line14);
}
}