中断
目录
中断
中断的概念
中断的执行过程
中断服务函数
中断的部分专业术语 – 了解
STM32中的中断分类
嵌套向量中断控制器 NVIC
STM32中的中断优先级
中断编程
外部中断(单片机之外)之EXTI中断
相关寄存器
外部中断(EXTI)的配置过程
-
上一章是采用轮询的方式去检测按键是否按下,但实验发现按键的检测并不灵敏
-
所以要想要实现按键检测的灵敏,我们就需要增加按键检测占的时间,减小延时,可无论如何,延时不可能减到0,所以就需要用到中断的方式
中断的概念
对于几乎所有的微控制器,中断都是一种常见的特性。中断一般是由硬件(如外设和外部输入引脚)产生的事件,它会引起程序流偏离正常的流程(如给外设提供服务),转去执行其他的流程。
CPU在正常执行程序的过程中,由于内部/外部事件的触发或程序的预先安排引起CPU暂停当前正在运行的程序,而转去执行中断服务子程序,待中断服务子程序执行完毕后,CPU继续执行原来的程序,这一过程称为中断;
中断的执行过程
- 1、外设发出中断请求
- 2、处理器暂停当前执行的任务,保护现场,将当前位置的PC地址压栈;
- 3、程序跳转到中断服务程序,执行中断服务程序;
- 4、恢复现场,将栈顶的值回送给PC;
- 5、跳转到被中断的位置开始执行下一个指令
中断服务函数
- 中断服务函数:相对于正常子函数,中断服务函数有以下需要注意的地方:
1、中断服务函数不能传入参数;
2、中断服务函数不能有返回值;
3、中断服务函数应该做到短小精悍,快入快出,禁止延时性的过程。
4、不要在中断函数中使用printf函数,会带来重入和性能问题。
中断重入:就是在一个中断程序执行过程中又被另一个中断打断,转而又去执行另一个中断程序。
中断的部分专业术语 – 了解
- 中断源:引起中断的原因,或者能够发出中断请求信号的来源统称为中断源
- 中断优先级:中断同时到来,谁先执行。数字越小,优先级越高。
- 中断响应:中断事件发生,Cortex-M3内核准备执行该事件,即为中断响应。
- 中断嵌套:可嵌套的内核 -- 中断可以被其他中断打断。(一般都是可嵌套内核)(Cortex-M)
- 不可嵌套的内核 -- 不可以打断。
- 中断挂起:中断事件发生了,但是Cortex-M内核还没准备去执行。
- 中断服务函数: 中断发生后,要执行的程序。(固定格式)
- 中断通道:对于 Cortex-M 内核所支持的 240 个外部中断,使用了“中断通道”这个概念,因为尽管每个中断对应一个外围设备,但该外围设备通常具备若干个可以引起中断的中断源或中断事件[外围设备与中断源的关系为1对N]。而该设备的所有的中断都只能通过该指定的“中断通道”向内核申请中断。因此,关于中断优先级的概念都是针对“中断通道”的。当该中断通道的优先级确定后,也就确定了该外围设备的中断优先级,并且该设备所能产生的所有类型的中断,都享有相同的通道中断优先级。至于该设备本身产生的多个中断的执行顺序,则取决于用户的中断服务程序。
STM32中的中断分类
- 内核(CPU)对中断的管理(内核只是一个处理数据的地方,NVIC才是管理中断的地方)
NVIC(嵌套向量中断控制器),支持256个中断(240(内核之外(内核之外就是单片机)的中断)+16(异常:内核自身的中断)),对应的中断通道256个, 对应的中断优先级256个(优先级指的是中断通道的优先级),数字越小,优先级越高,8位数
优先级分组:8组
- 单片机对中断的管理(单片机没有管理中断的地方,单片机只能产生中断)
单片机只能产生中断,(对于中断来说)单片机就是一个大的中断源
并不是所有单片机都能产生240个中断,
例如STM32F103CET6只能产生68个中断,
中断优先级16个
中断,允许优先级相同(优先级相同,谁先发生,谁先处理)
优先级:由两部分组成,抢占和响应
抢占优先级:是否能够发生中断打断的情况(抢占优先级高的能够打断抢占优先级低的,两个抢占优先级一样的不能打断)
响应优先级:抢占优先级一致的中断同时发生。响应优先级高的先执行。(正常情况下没作用,只有一种情况。)
如果两个中断同时发生,两个优先级(抢占和响应)都一样,执行顺序按照表格的顺序,谁在前,谁执行。 优先级分组:5组(内核的3,4,5,6,7)
-
内核优先级是8位数构成,单片机是4位,那么这4位哪几位是抢占,哪几位是响应,引出中断优先级分组
-
优先级分组:抢占和响应各自占据的位数
-
已知,内核优先级是8位数组成,高四位是单片机的优先级(也就是单片机的优先级是4位)
-
中断:异常(内核里的中断)+外部(片上外设)中断
嵌套向量中断控制器 NVIC
-
NVIC :嵌套向量中断控制器,属于内核外设,管理着包括内核和片上所有外设的中断相关的功能。
-
这里解释一下片上外设与内核外设他们都在芯片里面,但内核外设是在内核CPU里面,片上外设就是内核之外咯。
异常是指由于执行指令时的一个错误条件而产生的故障。按照ARM的说法,中断也是与一种异常。
异常:可以理解为是Cortex-M内核的中断,包含错误异常以及其他用于OS支持的系统异常。Stm32中的Cortex-M处理器的异常架构具有多种特性,支持多个系统异常和外部中断。编号1~15的为系统异常,16及以上的则为中断输入(处理器的输入,不必从封装上的I/O引脚上访问)。包括所有中断在内的多数异常﹐都具有可编程的优先级,一些系统异常则具有固定的优先级。
不同Cortex-M3或Cortex-M4微控制器的中断源的编号(1~240)可能会不同,优先级也可能会有所差异。这是因为为了满足不同的应用需求,芯片设计者可能会对Cortex-M3或Cortex-M4设计进行相应的配置。
- 内核的中断
- 外部中断
STM32中的中断优先级
对于Cortex-M处理器(包括ARMv6-M和ARMv7-M)异常是否能被处理器接受以及何时被处理器接受并执行异常处理,是由异常的优先级和处理器当前的优先级决定的。
更高优先级的异常(优先级编号更小)可以抢占低优先级的异常(优先级编号更大),这就是异常/中断嵌套的情形。
有些异常(复位,NMI和 HardFault)具有固定的优先级,其优先级由负数表示,这样,它们的优先级就会比其他的异常高。其他异常则具有可编程的优先级﹐范围为0~255。
NVIC 支持由软件指定的优先级。通过对中断优先级寄存器的8位 IPR_N 区执行写操作,来将中断的优先级指定为 0~255,见中断优先级寄存器。硬件优先级随着中断号的增加而降低。 0 优先级最高, 255 优先级最低。指定软件优先级后,硬件优先级(默认优先级)无效。
利用系统控制块(SCB)中一个名为优先级分组的配置寄存器(属于SCB中的应用中断和复位控制寄存器)将每个具有可编程优先级的优先级配置寄存器可被分为两部分。
上半部分(左边的位)为分组(抢占)优先级﹐而下半部分(右边的位)则为子优先级。事实上stm32(单片机)只用了4位优先级(内核的高4位),分组时从优先级高位向低位方向分组。
子优先级就是响应优先级 抢占和响应各自占据的位数,例如0分组,抢占7位(0~2^7-1),响应是1位(0-1)
单片机的优先级分组是在内核分组的基础上开始分组的,因为单片机优先级的位数是内核优先级位数的高4位,也就是4~7,所以单片机优先级分组从第三组开始,3~7(一共5组),如第三个分组,在单片机上给抢占的是4位,响应的是0位
中断编程
- 一般中断使能一般有两个门,外设使能相应的中断然后送入NVIC再使能,外设使能中断是小门,NVIC使能中断是大门,只有都使能才能响应中断。
-- 1.使能外设某个中断,这个具体由每个外设的相关中断使能位控制。
-- 2.配置中断优先级分组,然后初始化 NVIC_InitTypeDef 结构体,设置抢占优先级和子优先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。
1、配置中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
2、初始化 NVIC_InitTypeDef 结构体
-- NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考stm32f10x.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定。
NVIC_IRQChannelSubPriority:子优先级(响应优先级),具体的值要根据优先级分组来确定 。
NVIC_IRQChannelCmd:中断使能(ENABLE)或者(DISABLE)。操作的是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
NVIC_InitTypeDef NVIC_InitStructure = {0};
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)
NVIC_Init(&NVIC_InitStructure);
外部中断(单片机之外)之EXTI中断
-- 这里的外部中断是指由外部条件触发例如按键触发(GPIO),对于互联型产品(F107),外部中断/事件控制器由20个产生事件/中断请求的边沿检测器组成,对于其它产品(我们这里是F103),则有19个能产生事件/中断请求的边沿检测器。
-- 中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。112通用I/O端口以下图的方式连接到16个外部中断/事件线上:
-- 16个中断线的不是每个中断都有独立的中断服务函数,IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数
-- 对应的中断服务函数,直接去启动文件里面找以防写错。
-- 信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 19 个线路原理也就知道了
中断与事件的区别:
中断:需要CPU参与,需要调用软件的中断服务函数才能完成中断后产生的结果
事件:靠脉冲发生器产生一个脉冲,进而由硬件自动完成这个事件产生的结果,当然相应的联动部件需要先设置好,触发TIM计时,AD转换等,事件不要软件的参与,降低了CPU的负荷,而且硬件速度快于软件速度
相关寄存器
-
外部中断配置寄存器
-
选择中断线
-- 在配置中断线时一定要先使能AFIO外设的时钟,因为配置中断线是用到AFIO外设的寄存器,我们知道配置寄存器必须要有时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //exti的时钟(外设也有自己的时钟)
外部中断(EXTI)的配置过程
-
1、配置要中断检测的IO引脚模式为:浮空输入模式,具体查阅参考手册8.1.11以及自己的硬件电路来设置
-
2、把要中断检测的IO引脚映射到对应的EXTI中断线上,通过AFIO来设置。
-
3、配置对应的EXTI中断线,触发的边沿、使能屏蔽位
-
4、通过NVIC配置EXTI中断的优先级、使能NVIC的EXTI中断
-
exti.c
#include "exti.h"
void exti_init(void)
{
//时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //exti的时钟(外设也有自己的时钟)
//IO
GPIO_InitTypeDef GPIO_InitStructure = {0}; //定义结构体变量,并且将结构体变量赋初值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//外设 EXTI
EXTI_InitTypeDef EXTI_InitStructure = {0};//初始化
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//EXTI比较特殊,这里设置为中断模式,就不用设置中断源了
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//中断 NVIC
NVIC_InitTypeDef NVIC_InitStructure = {0};
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)
NVIC_Init(&NVIC_InitStructure);
//其他
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
}
//只要使能中断通道,就一定要写中断服务函数
uint8_t key_it_flag = 0;
//中断服务函数
void EXTI0_IRQHandler(void)
{
//1.判断哪个中断发生
if(EXTI_GetITStatus(EXTI_Line0) == SET)//SET就是1
{
//2.处理中断
key_it_flag = 1;
//3.清理中断 (一定要清除,否则中断会一直触发)
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
- main.c
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置优先级分组(相当于内核的第五组)因为在整个工程里只用分一次,所以写在main函数