一、中断简介
中断概念:程序在运行过程中发生了外部或内部事件时,导致中断了正在执行的程序,让CPU转到外部或内部事件中去执行。
中断的作用:大量节约CPU资源,提高程序的效率,即避免重要事件被错过。
中断入口:函数名----中断服务函数名,为中断服务的函数名。
思考:主函数和中断服务函数的关系?
主函数和中断服务函数之间是相同级别(并行)的而不是从属关系。即执行中断服务函数其实是抢占CPU的时间片,而不是主函数调用中断服务函数。所以中断服务函数不是写在主函数中。
中断优先级:中断是为了处理紧急情况的机制,为了描述多个中断同时发生哪一个中断优先执行的顺序。
(1)优先级表示:优先级别用数值来表示,数值越小,优先级越高。
(2)优先级分类:每个中断优先级可以分为硬件(自然)和软件两种。
(3)硬件优先级(自然优先级):中断自带的优先级编号。
(4)软件优先级:就是可以由程序员自行修改的优先级。
(5)中断优先级的意义和目的:为了区分当多个中断同时到来哪一个中断优先执行。
每一个中断都具有3种类型的优先级:软件优先级(抢占优先级,响应优先级),自然(硬件)优先级。
(1) 抢占优先级:当其中一个中断正在执行时,其他(多个中断都发生)的中断是否可以打断正在执行的中断;
概念补充:
中断嵌套:一个中断在执行的过程中又出现了一个优先级更高的新中断。
中断嵌套目的:处理更紧急的事情。中断嵌套中需要将嵌套的中断优先级设置的更高。因为高优先级的任务可以打断或抢占低优先级的任务。(该抢占只发生在抢占优先级不一样的模情况下)
(2) 响应优先级:决定多个抢占优先级都相同的中断同时到来的时候,CPU使用权归谁的问题。
(3) 自然优先级(硬件固定):按照自带的优先级编号(硬件固定)在抢占和响应优先级相同的情况下决定先执行哪个中断;
问题 1:事件A抢占优先级为2,响应为3,事件B抢占3响应3同时发生先执行?
答:A
问题 2:事件A抢占优先级为2,响应为3,事件B抢占2响应4同时发生先执行?
答:A
问题 3: 如果在处理中断 B (抢占1,响应4)的过程中, 发生了中断 A(抢占0,响应3), 发生了什么事情?
答:中断嵌套
问题4:事件A抢占优先级为2,响应为2,事件B抢占2响应2同时发生先执行?
答:比较自然优先级
总结:
1.中断嵌套问题只有抢占优先级不同时才会出现。
2. 同时多个异常事件发生时, 先看抢占优先级, 如果一样再看响应优先级, 如果还一样, 再看自然优先级(硬件)。
优先级大小:抢占优先级>响应优先级>自然优先级
3. 每个中断源的抢占优先级和响应优先级由用户决定(软件设置),而自然优先级已经被硬件固定, 不可更改。
二、中断介绍
在STM32内核里有一个管理中断的模块---NVIC控制器(嵌套向量中断控制器) 是M4内核中专门负责处理中断相关事物的一个机构。
因为NVIC属于内核管理具体内核手册查看,其结构如下:
2、NVIC控制器中断来源
NVIC控制器内部结构如上图所示,由上图可以看出NVIC控制器所管理的的中断来源包括:GPIO口外部中断,片上外设,系统核心,SysTick,非掩蔽中断等。
NVIC控制器总共提供了255个中断入口。
来自系统的中断入口共15个(内核固定的)
注意:以上15个是内核级中断NVIC控制器必须响应且不能够被打断。
来自片上外设或IO口中断从16到255总共240个,如下图所示:
注意:该部分中断是属于内核之外的且由芯片的生产者决定。
STM32-NVIC控制器中断来源如下:
总共92个中断源10个系统中断,82个外设中断。
STM32 的 NVIC 是 Cortex-M4 的 NVIC 的一个子集,因为各个芯片厂商在设计芯片的时候会对 Cortex-M4 内核里面的 NVIC 进行裁剪,把不需要的部分去掉内核之外的中断也是芯片生产者更具使用要求来设置的。
三、NVIC中断管理方式
NVIC管理中断的机制
设置软件优先级的范围和大小
在NVIC里面,系统会给每一个中断源都分配一个8位寄存器来存放它的优先级(抢占优先级和响应优先级)。在这8位寄存器里面,一部分用于存放抢占优先级,另一部分存放响应优先级。即响应+抢占=8位。
如下图所示ARM将内核中的NVIC的抢占和响应的优先级分组了:
Cortex-M4-NVIC 中得知抢占优先级+响应优先级最多占八位。
抢占优先级有128级别,响应优先级有256级别。
实际芯片在使用时用不了那么多,所以ARM公司规定:不是所有的芯片都要使用8位来设置优先级,可以根据实际需要选择3bit~ 8bit使用,而ST公司使用4位即如下图所示:
由上表得知STM32优先级使用4~7共4个位控制且分组范围:3~7,该值是实际写入分组寄存器的值。
写入寄存器的值 | 抢占优先级位数 | 响应优先级位数 | 抢占优先级允许的设置范围 | 响应优先级允许的设置范围 |
3 | 【7:4】 | 【无】 | 0 ~ 15 | 0 |
4 | 【7:5】 | 【7】 | 0 ~ 7 | 0 ~ 1 |
5 | 【7:6】 | 【7:6】 | 0 ~3 | 0 ~3 |
6 | 【7】 | 【7:5】 | 0 ~ 1 | 0 ~ 7 |
7 | 【无】 | 【7:4】 | 0 | 0 ~ 15 |
设置分组
优先级分组由NVIC控制器管理,设置优先级分组就是要找到NVIC相关寄存器。而NVIC是属于系统管理的,所以找到SCB(系统控制块),只要往SCB->AIRCR的PRIGROUP三个位中写入不同值即可决定分组如下表:
如何设置一个中断的抢占优先级和响应优先级呢??
答案: NVIC控制器是通过中断优先级分组,从而确定抢占优先级位数和响应优先级的位数,然后设置各个中断的优先级。
四、NVIC控制器相关函数及配置方法
见CORE.CM4.h文件1498行
设置分组
函数原型:void NVIC_SetPriorityGrouping (uint32_t PriorityGroup)
函数功能:设置优先级的分组
函数形参:分组值(3~7);
函数返回值:无
备注:组编号0~4
使用方法:NVIC_SetPriorityGrouping(5);
NVIC_SetPriorityGrouping(5);//抢占优先级:0~3;响应优先级:0~3
注意:整个项目中,分组形式只能是一种 (优先级分组由寄存器中的三个bit决定,重新设置分组,会改变这个寄存器的值,从而会影响到抢占和响应的范围),可以改变抢占和响应优先级。
注意:分组值在一个项目中只能是一种。一旦确定就不能够改变。
合成优先级
函数原型:uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t Sub priority)
函数功能:将分组&抢占优先级&响应优先级合成最终的优先级结果
函数形参:分组值(3~7);抢占优先级:(不确定,通过优先级分组+程序员自己定义);响应优先级:(不确定,通过优先级分组+程序员自己定义)
函数返回值:返回一个合成的结果
使用方法:Priority = NVIC_EncodePriority(7 – 2, 2, 1);
设置中断优先级
函数原型:void NVIC_SetPriority (IRQn_Type IRQn, uint32_t priority)
函数功能:设置某一个中断的优先级
函数形参:中断的编号;中断优先级(步骤2中合成的优先级)
函数返回值:无
使用方法:示例:USART1
NVIC_SetPriority (USART1_IRQn, Priority);
中断编号见下图:
使能中断
函数原型:void NVIC_EnableIRQ (IRQn_Type IRQn)
函数功能:开启某一个中断的NVIC控制器开关。(当前给某一个授权,可以产生中断)
函数形参:中断的编号;
函数返回值:无
使用方法:NVIC_EnableIRQ (USART1_IRQn);
NVIC中对于内核之外的中断默认中断是关闭的,想要使用必须手动打开。
示例:
NVIC_SetPriorityGrouping (7-2);//抢占2响应4-2
U32 prioity = NVIC_EncodePriority (7-2, 2, 2);
NVIC_SetPriority(USART1_IRQn, prioity);
NVIC_EnableIRQ (USART1_IRQn);//核心级中断使能
打开外设的中断开关位
总结:真正的使用一个中断需要以下步骤:
1设置优先级分组---决定抢占优先级和响应优先级的可设置范围。
2设置该中断的抢占优先级和响应优先级。
3开启核心级中断使能;(NVIC中打开某个外设的开关)。
4开启模块级中断使能(将外设内部的中断打开)。
5.编写中断服务函数(发生中断时CPU会跑到中断中,在中断里面执行对应的操作)。
5.1如何查找中断源
在STM32中断源在stm32f4xx.h文件中
如何查找中断服务函数名
在STM32中,中断服务函数名在相应的汇编文件中有。STM32F40VGT6 startup_stm32f40_41xxx.s
上面的中断地址名字和下面的中断入口地址是一一对应的。
注意事项
(1)中断服务函数名尽量用复制,不要自己写,因为只要写错一个字母,这个函数就变成普通函数了。
(2)如果中断服务函数是公共入口即如果是全局中断,进入到中断服务函数后先要查询是哪种中断
(3)中断服务函数应该尽量简短,一般是做一些标识,不要在中断中做延时之类的占用CPU很长时间的工作。----快进快出
(4)中断服务函数不会被任何一个函数调用,当中断条件满足后,NVIC控制把CPU拉到中断服务函数中执行。
所以:正常的写法是:void 中断服务函数(void)(5)中断服务函数中尽量不要使用printf打印函数(很消耗时间)
(6)中断设置错误会导致程序发生异常。