嵌入式学习笔记——STM32的中断控制体系

前言

上一篇中,借着串口接受的问题,简要说了一下串口中断的作用和用法,本文将对STM32的中断控制体系做个介绍。

STM32中断的概念

关于中断的概念,在上一篇中已经做了介绍了,说通俗点就是程序正常情况下是在while(1)内运行着相关的任务,例如下图中的任务1,任务2,在没有中断的时候,整个代码的运行流程就是完成初始化,然后在while(1)内一直循环里面的任务,此时如果任务1内有大的阻塞延时,类似串口等待接收,LED灯while死等延时这些都会阻塞整个while(1)的运行时间,导致执行效率严重低下,这种情况是不能满足日常需求的,所以在实际产品中很少有使用阻塞式的延时和while死等的情况。为了解决这个问题,这时就需要有中断的加入了。
在这里插入图片描述
加入中断后的运行流程图就变成了下图的情况,首先,中断是一个内核功能,不属于片上外设,但是使用过程中也需要初始化;其次,初始化后,会有一个与之对应的中断服务函数,上面的那些具有阻塞的功能通过中断服务函数内的各种标志位就可以实现变阻塞为不阻塞了;当初始化中断对应的条件满足时就会产生一个中断信号,这时CPU会停止当前任务,转而去到对应的中断服务函数里面,将中断服务函数内的任务先完成,完成后再回来继续执行原本的任务。编程时只需要判断对应的标志位即可解决上面的阻塞问题。
需要注意的是,中断是可以解决阻塞的问题,但是中断服务函数的本身也不能有延时、循环、阻塞程序,紧急事件是实时响应的,紧急事件不能执行太久。
在这里插入图片描述
在这里插入图片描述
总的来说,中断对于正常运行的CPU来说就是一个异常事件,而且异常事件的优先级高于正常运行的事件的优先级,发生异常必须先解决异常才可以重新回到正常运行。上一篇中的空闲中断和接收中断就是这样,那么,STM32的中断到底是怎么运行和管理的呢。

中断类型

首先,中断的控制是在内核里面的,中断的相关内容是有ARM公司设计的,在处理器内部会有一个叫做嵌套向量中断控制器NVIC得中断处理器,他就是专门用来管理整个STM32的中断问题的,其中中断的主要来源有两个,一个是处理器的内核出现了故障会触发系统异常,进而进入中断二是片上外设触发了其相关的中断信号也会进入中断,像上一篇的出口接收中断和空闲中断就是属于后者,是外设产生的中断。
在这里插入图片描述

中断控制

在上一篇的配置中,已经使用到了NVIC的相关控制,关于这部分控制是官方封装好的函数,在使用过程中调用即可。也就是说,对于开发者来说,中断的控制只需要调用函数就可以了,这里借用上一篇调用的代码:
NVIC中断控制器做配置的时候流程:
第一步:配置优先级分组 (主函数配置)
第二步:合成优先级
第三步:分配优先级
第四步:使能对应的中断源 (在对应的片上外设初始化函数中)

//NVIC控制器配置
NVIC_SetPriorityGrouping(7-2);//设置中断的优先组别(111-110-101-100)
u32 pri=NVIC_EncodePriority(7-2,0,0);//设置抢占优先级为0,响应优先级为0
NVIC_SetPriority(USART1_IRQn,pri);   //将对应的优先级设置映射到对应的中断
NVIC_EnableIRQ(USART1_IRQn);         //使能中断源

在这里插入图片描述

常用控制函数

1.中断分组函数,此函数用于对整个工程进行中断分组
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
位置:core_cm4.h------1435行
返回值:空
参数:uint32_t PriorityGroup   分组中需要写入的值
传参:7-占先的位数
分组整个工程都要一样,可以只配置一次,只需要在主函数使用一次即可
2.使能中断源配置函数,此函数用于使能对应的中断源
void NVIC_EnableIRQ(IRQn_Type IRQn)
位置:core_cm4.h------1467行
返回值:空
形参:IRQn_Type IRQn很明显此处形参是个结构体,需要使用结构体内规定好的名称 
传入对应的片上外设中断源----放中断源名字  例如:串口:USART1_IRQn
作用:使片上外设的中断能够使用   核心级别中断使能

关于此函数的形参:IRQn_Type IRQn很明显此处形参是个结构体,需要使用结构体内规定好的名称 需要用时直接跳转过去复制即可。
注意,此处的形参编程时一定要跳转到stm32f4xx.h中找到对应的,不可以自己随意修改。
在这里插入图片描述

3.中断优先级配置函数,此函数用于设置对应中断源的中单优先级
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
位置:core_cm4.h------1550行
返回值:空
形参:IRQn_Type IRQn, uint32_t priority
IRQn_Type IRQn:中断源和上一个一样的中断源名称,需要去stm32f4xx.h复制
uint32_t priority:优先级(占先和次级一起的优先级)
4.  优先级合成函数,由于优先级写入时是一起写入的,但是分类时又是分开计算的,为了避免写入出错,官方给了一个合成函数,将分组、抢占优先级和响应优先级传入后此函数会返回需要写入的优先级值。
uint32_t  NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)
位置:core_cm4.h------1592行
返回值:u32类型的值   优先级uint32_t priority
形参:uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority
uint32_t PriorityGroup:优先级分组
uint32_t PreemptPriority:占先优先级
uint32_t SubPriority:次级优先级

用法:u32 pri= NVIC_EncodePriority(7-2,3,2);
      NVIC_SetPriority(USART1_IRQn, pri);

从上面的注释中,可以看出NVIC控制器的作用:
①对中断优先级做分组
② 接收来自中断的请求
③ 配置中断的优先级
④ 使能外设的中断源
具体的代码在"core_cm4.h"中,查看步骤如下图所示,点击main.c前方1号框所在位置的"+“号,然后选择"core_cm4.h”,下滑就可以找到相关函数,对应的函数描述就在其上方注释。
在这里插入图片描述

区分中断源与中断信号

仔细回想上一篇中的配置过程,关于中断配置,除了上面的NVIC控制器的相关操作,还在USART的出事后配置中,使能了接收中断和空闲中断这两个控制位,那么这个使能与上面的中断源使能有什么区别呢。
中断源:一个片上外设 USART1独立的中断源 USART2也是独立的中断源
中断信号:指的是每一个中断源下,会有多个触发条件,例如串口1的接收中断和空闲中断。
也就是说,中断源相当于房子里的总电闸,而中断信号相当于房间里灯的开关,插座的开关,电视的开关。要开灯,不仅要打开总闸,还需要开启对应的分开关才行。
所以在中断配置过程中不仅需要配置对应的中断源使能,还需要使能具体的中断信号。
知道了中断的管理方式,接下来就是怎么管理的问题了。

配置中断

在上述管理的相关函数中,涉及了多个中断里面的名词,像分组,占先优先级、次级优先级、占先位数等等,看起来是有些混乱,接下来就来解释一下这些东西的具体作用。

优先级

首先,需要搞清楚优先级的定义,对于Cortex-M处理器的异常是否能被处理器接受以及何时被处理器接受并执行异常处理,是由异常的优先级和处理器当前的优先级决定的。
更高优先级的异常可以抢占低优先级的异常,这就是异常/中断嵌套的情形,也就是上面提及的占先优先级。
有些异常(例如:复位,Sys_Tick等)具有固定的优先级,其优先级由负数表示,这样,它们的优先级就会比其他的异常高。
在这里插入图片描述
其他异常则具有可编程的优先级﹐范围为0~255。依据以上描述优先级被分为三类:
**1.占先优先级:程序员进行配置的,具备抢占功能,就是当一个中断正在进行时,另外一个中断也产生了,高抢占优先级的中断可以打断低抢占优先级的中断优先运行,高优先级的运行结束后才会继续运行低优先级的。**类似下图,中断1的占先优先级是1,次级优先级为0;中断2的占先优先级为0,次级优先级为0;
此时,中断1已经在运行了,但是由于它的占先为1优先级低于中断2的0占先优先级,所以中断1会被中断2给打断,优先运行中断2,中断2运行完毕了才会回到中断1运行,中断1运行后再回到while(1)中运行。
在这里插入图片描述

**2.次级优先级:程序员进行配置的,不具备抢占功能,也就是说当一个中断正在进行时,另外一个中断也产生了,此时由于不具有抢占功能,所以需要等当前中断运行结束后再进入后面产生的中断中运行。**如下图,中断1的占先优先级为1,次级优先级为1,;中断2的占先优先级为1,次级优先级为0;
当中断1正在运行时,中断2也产生了,虽然此时中断2的次级优先级高于中断1的次级优先级,但是其不具有抢占功能,所以CPU会先运行完中断1后再去运行中断2。
次级优先级的作用在于,当不同优先级的中断同时到达时,CPU会优先执行次级优先级高的。
在这里插入图片描述
3.自然优先级:系统自己配置(不需要我们管),这个优先级是官方给的,最后的一道防线。
优先级:数字越小,优先级越高

分组问题

解决了优先级,接下来就要高清楚分组的问题了。这个分组其实就是决定上面提到的占先优先级的在优先级控制寄存器中的位数和次级优先级在优先级控制寄存器的位数,先看看M3和M4的权威指南关于分组的描述:
在这里插入图片描述
在这里插入图片描述
ARM在涉及的时候一共预留了一个8位寄存器来实现优先级控制的编程,理论上占先优先级和次级优先级会有2^8=256种组合方式。
但是,由于过多的中断优先级分组会影响单片机的性能,所以ST公司在实际设计STM32芯片的时候就丢弃了LSB也就是这个八位寄存器的第四位,只保留了四位用来做优先级的控制编程,这样一来,STM32的优先级分组就只有2的四次方种,也就是16种组合。
在这里插入图片描述
ARM体系下优先级配置寄存器总共由8位,占先和次级同时使用这个寄存器
中断的优先级共有256种
ST体系下,ST公司对ARM公司的中断进行了裁剪,只是用寄存器的高4位
中断的优先级共有16种。
可能还是有点没说清楚,来看下表吧,由于舍去了第四位,占先优先级和次级优先级这两个一起瓜分这四位,通过这四位来配置中断的优先级,其中的配置方式如下表:

写入的分组数 占先在优先级寄存器中的位数 次级在优先级寄存器中的位数 此模式下中断的具体优先级数
7(111) 0位 (范围:无) 4位(范围:0-15) 16种
6(110) 1位 (范围:0-1) 3位(范围:0-7) 16种
5(101) 2位 (范围:0-3) 2位(范围:0-3) 16种
4(100) 3位(范围:0-7) 1位(范围:0-1) 16种
3(011) 4位(范围:0-15) 0位(范围无) 16种
2(011) 4位(范围:0-15) 0位(范围无) 16种
1(011) 4位(范围:0-15) 0位(范围无) 16种
0(011) 4位(范围:0-15) 0位(范围无) 16种

举几个栗子吧:
1.当对分组写入101也就是5时,占先优先级和次级优先级会分别占用优先级控制寄存器的两位,每一个都有0-3三种,组合起来一共就有16个优先等级,其中00为最高优先级,33位最低优先级,且0x(0,1,2,3)可以打断1x(0,1,2,3)、2x(0,1,2,3)以及3x(0,1,2,3)的中断优先运行,其他的也是依次类推。
在这里插入图片描述
2.假设在这个系统中,我们不需要占先优先级,此时就可以将占先的位置零,也就是对分组写入7(111)让优先级控制寄存器独享四位,这样的话,整个系统的中断之间就不会出现打断和抢占,当同一时刻有多个中断到来时,会优先执行次级优先级高的中断,执行完毕后才会转去下一个优先级相对高的中断,此时的16组中断都不具有抢占功能,最高优先级为0最低优先级为15.
在这里插入图片描述
3.假设我们在整个系统中需要各种中断嵌套,这时就可以不为次级优先级分配优先级控制寄存器的位数,让占先优先级独享四位,也就是写入3(011);这样就可以有(0-15)16种中断嵌套,其中优先级最高的可以打断任意中断,最高优先级为0,最低优先级为15。
在这里插入图片描述
其他的情况也就是依照上面进行类推即可,这里再做两个练习吧,
1.占先写入3 次级写入2 优先级分组能够是?5
2.占先写入4 次级写入1 优先级分组能够是?4
3.占先写入5 次级写入2 优先级分组能够是? //不能选择这种写入方式
需要注意的是,一个系统只能有一个中断分组,所以整个设置分组的函数也只需要在初始化时调用一次即可,具体的写入方式为:
写入值=7-占先的位数

NVIC_SetPriorityGrouping(7-2);//设置中断的优先组别(111-110-101-100)
//此时写入的分组为5对应的分组,占先占两位取值范围0-3,次级占两位取值范围0-3

上面的优先级设置函数中,最终的中断优先级的参数只有一个,也就是说,所有的分组、占先的值,次级的值都需要整合计算成一个数值写入,为了减轻计算的麻烦,也为了降低错误率,官方给了一个优先级计算函数,只需传入对应的分组、占先数和次级数即可计算出对应的优先级值,

u32 pri= NVIC_EncodePriority(7-2,3,2);//传入参数进行计算
      NVIC_SetPriority(USART1_IRQn, pri);//写入对应的优先级

中断使能

在弄清楚中断优先级和中断分组后,整个关于中断的操作就剩下了中断使能这最后一个步骤,这里的中断使能是指的对中断源的使能,官方已经给程序员提供了每个外设的中断源名称,使用时只需要在stm32f4xx.h内查询即可,具体的查询步骤在上面已经介绍过了,想看的自己往上翻一下。
注:此处只是打开了外设中断的总开关,具体的中断信号配置还需要在对应的片上外设设置时打卡。

中断服务函数

再经过上面的中断配置后,中断已经可以产生了,那么怎么起作用呢,这是就需要使用到中断服务函数了,中断服务函数就是中断产生后,CPU处理异常事件的代码入口, 具体执行的内容就是中断服务函数中的程序代码;和前面的中断源使能一样,官方也提供了对应片上外设的中断服务函数名,使用时直接在startup_stm32f40_41xxx.s复制即可。
在这里插入图片描述
中断服务函数也分为两个大类,一种是内核的中断,一种是外设的中断,这与中断的分类是一致的。
注意事项:
1.中断服务函数必须复制,不可以自己想当然的写,写错了不会报错,会很难查错误;
2.中断服务函数不需要声明、调用,也没有形参和返回值;
3. 具体的使用只需要触发中断信号就进入中断服务函数 为了能够实时的响应
格式;
4.中断服务函数中不能出现大量的延时和循环 尽量不使用printf,尽量简短;

格式:
    void 中断服务函数名(void)
    {
    
    
     //判断是哪一个中断信号
        {
    
    
           //清除标志位------防止CPU重复进入中断
           //紧急事件程序
		}

		//判断为另一个中断信号
		{
    
    
	 	 	 //清除标志位
	   		//紧急事件
		}
}

举个栗子:
中断的执行过程:
以串口1为例,接收中断,接收到了数据进入中断
空闲中断,数据接收结束了会进入中断

void  串口1中断服务函数(void{
    
    
    //判断是否为接收中断
    {
    
    

	}
	//判断是否为空闲中断
	{
    
    
	  //清除标志位
	}
} 

总结

好了,关于中断的介绍就记录到此,文中如有不足欢迎指出。本文重点就是详细描述一下中断的配置过程,以及中断的运行流程。

猜你喜欢

转载自blog.csdn.net/qq_41954556/article/details/129568570