STM32外部中断与(NVIC)中断优先级管理


计算机系统中中断占有及其重要地位,在嵌入式系统中更是如此。中断机制让计算机能有效合理的发挥效能和提高效率,我们这节课将会讲解一下STM32的外部中断与中断优先级的管理。
本篇:理解中断机制及处理过程,了解NVIC(内嵌向量中断控制器)及中断优先级分组与中断优先级等概念,配置STM32的PA1角为外部中断引脚,并写出中断处理函数,熟练配置中断优先级及其分组。
涉及外设:EXIT外部中断,NVIC内嵌向量中断控制器
参考资料:《DragonFly原理图》、《RM0383_STM32F411CCU6_Reference manual 》、《STM32F4xx中文参考手册》、《DS10314_STM32F411CCU6_Datasheet 》、《 CortexM3与M4权威指南》。
开发环境: Keil uVision5.20(MDK_ARM)

1。中断介绍

1.中断的概念

计算机在执行程序的过程中,当出现异常或特殊情况需要紧急处理,会停止程序的进行,转向这些特殊情况的处理,处理结束后返回主程序的执行,这就是“中断”。

2.中断的产生

以CPU与打印机的工作来示意:
请添加图片描述
我们知道计算机处理数据的速度是很快的,而相对外设速度会慢的很多,在外设处理一些情况时,不需要CPU参与,这时CPU就可以执行其他事项,这也是“中断”高效体现的地方之一。有了中断后,只有有了打印请求时,CPU才会处理打印机数据。
其实中断技术不仅仅是为了适应外部设备速度不匹配的问题,他还在实时控制系统中,要求CPU能即时响应外来信号的请求,并能完成各种相应的操作。也就是说中断还可以处理一些比较紧急的事情,STM32中的中断一般都是用于此情况,例如STM32怎么及时的检测按键的按下,串口接收到数据怎么及时处理。都是通过中断实现的

3.中断处理的流程

不同设备的中断处理方法不同,但是他们程序的流程基本是类似的,分为四大部分:保护现场,中断服务,恢复现场,中断返回。
请添加图片描述
保护现场:
保护现场有两个含义,其一是保存程序的断点,保证等会返回后能够继续执;其二是保存通用寄存器和状态寄存器的内容。(第二步只需知道不用操作,由硬件实现)。
中断服务程序:
不同的中断,中断服务程序的处理方法不同。中断服务程序也是我们需要重点设计和编写的,例如我们的四轴(DragonFly)与上位机和ESP8266通信是通过串口实现的,为了保证数据的实时行,都是用中断处理的。
恢复现场:
这是中断服务程序的结尾部分,要求在推出服务程序前,将原程序中断时的“现场”恢复到原来的寄存器。这一步我们不需要操作由硬件实现。
中断返回:
中断服务程序的最后一条指令通常是一条中断返回指令,使其返回到原程序的断点,以便继续执行原程序。

2。STM32内嵌向量中断控制器NVIC

对于上面对中断的描述,我们知道中断是一个很重要并且很常用的机制,对于微控制器(MCU),中断也是一种常见的机制,MCU的中断一般是由硬件(如外部或外部输入引脚)产生的事件,当外设或硬件需要处理器的服务,一般会出现下面流程:
1)处理器确认外设的中断请求。
2)处理器暂停执行当前的任务。
3)处理器执行外设的中断服务程序(ISR),若有必要可以选择软件清除中断请求。
4)处理器继续执行暂停的任务。
所有的Cortex-M处理器都会提供一个用于中断处理的嵌套向量中断控制寄存器(NVIC)。除了中断请求,还有其他服务的事件,将其称为“异常”。异常的概念很广泛,中断其实就是异常的一种,也可以说中断就是异常。
Cortex-M微控制器多中断源 示意图
Cortex-M4的NVIC支持最多240个IRQ(中断请求),1个不可屏蔽中断(NMI),一个Systick定时器中断以及多个系统异常,一共256个中断。并且,多数IRQ由定时器,I/O口和通信接口(USART/TIM)等外设产生。NMI通常由看门狗定时器等外设产生,其余的异常则是来自处理器内核,中断还可以用软件生产。

1.NVIC控制器的寄存器

M4的NVIC共支持256个中断,其中包含16个内核中断和240个外部中断。但是STM32F4没有使用完所有中断,只用了92个,10个不可屏蔽中断(NVIC是只支持1个,32用了10个而已,在这容易矛盾),82个可屏蔽中断。这些可屏蔽中断的打开与关闭,挂起等,都是被寄存器控制,这些已经被标准口封装成NVIC_Type的结构体:

typedef struct
{
	__IO uint32_t ISER[8];//中断使能寄存器组
		 uint32_t RESERVED0[24];
	__IO uint32_t ICER[8];
		 uint32_t RESERVED1[24];
	__IO uint32_t ISPR[8];
		 uint32_t RESERVED2[24];
	__IO uint32_t ICPR[8];
		 uint32_t RESERVED3[24];
	__IO uint32_t IABR[8];
		 uint32_t RESERVED4[56];
	__IO uint32_t IP[240];
		 uint32_t RESERVED5[644];
	__O	uint32_t STIR;
}	NVIC_Type;

ISER[8]:M4支持256个中断,这里用8个32位寄存器来控制,每一个位控制一个中断。但是,32可屏蔽中断只有82个,所以对我们来说,有用的就三个ISER[0~2],共96 个中断。只用了前面的82个,所以是ISER[0]的0~31,ISER[1]的0~31,对应33-64,以此类推。若使能某个中断,必须设置相应的ISER位为1,就OK,这里只是使能,还要配合中断分组,屏蔽,IO口映射等设置具体每一位对应哪个中断,请参考stm32f4xx.h里的196行
ICER[8]:是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和ICER一样。这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。
ISPR[8]:是一个中断挂起控制寄存器组。每个位对应的中断和ISER是一样的。通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写0是无效的。
ICPR[8]:是一个中断解挂控制寄存器组。其作用与ISPR相反,对应位也和ISER是一样的。通过设置1,可以将挂起的中断接挂。写0无效。
IABR[8]:是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP[240]:是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32F4的中断分组与这个寄存器组密切相关。IP寄存器组由240个8bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。而STM32F4只用到了其中的82个。IP[0]IP[81]分别对应中断081。而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和响应优先级。抢占优先级在前,响应优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。
STM32F4将中断分为5组,0-4,该分组的设置是由SCB->AIRCE寄存器的bit10-8来确定的。
中断优先级分组表
从上表我们看出中断优先级控制寄存器一共有5种分法,也就是图中的组0-4,例如我们将SCB->AIRCR的bit10-8配置为101既为分组2,每个可屏蔽中断的IP[x]寄存器的高4位的前2位就代表抢占优先级,后2位代表响应优先级。也就是说每个可屏蔽中断的抢占优先级为0-4级,响应优先级0-4级。
中断优先级的定义:

  1. 优先级的数值越小优先级的级别越高。
  2. 抢占优先级的优先级始终高于响应优先级。
  3. 高级别抢占优先级中断可以打断低级别抢占优先级中断。
  4. 两个中断抢占优先级相同时,高级别响应优先级不能打断低级别响应优先级,但处理器优先处理响应优先级级别最高。
  5. 两个中断抢占优先级相同,响应优先级也相同,根据那个中断先发生就先执
    行。
    举个例子说明一下:假定设置中断优先级组为2,然后设置
    中断A:抢占优先级为2,响应优先级为1;
    中断B:抢占优先级为3,响应优先级为0;
    中断C:抢占优先级为2,响应优先级为0;
    那么这3个中断的优先级顺序为:中断C>中断A>中断B。并且中断A和中断C都可以打断中断B,而中断A和中断C不可以相互扛断。

2.STM32中断配置

上面讲了32的中断控制和中断优先级分组,现在讲32中断管理的具体配置流程:
1)配置中断优先级分组(一个程序中断优先级分组只能配置一次,程序执行时中断分组不能更改)
2)配置对应外设中断(中断的引脚,优先级)
3)编写对应中断服务函数(对中断进行处理)
下面利用32标准库实现中断优先级管理,NVIC控制器属于内核的一部分,所以NVIC的寄存器封装在core_cm4.h中,NVIC中断配置有关函数在misc.c中。
中断分组管理函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
我们发现此函数只有一个参数**“NVIC_PriorityGroup”**,我们下面找到NVIC_PriorityGroupConfig();的实现
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
	/*chek the parameters*/
	assert_param(IS_NVIV_PRIORITY_GROUP(NVIC_PriorityGroup));
	/*Set the PRIGROUP[10:8] bits according to NVIC_Priority*/
	SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
双击**“IS_NVIV_PRIORITY_GROUP”**然后找**NVIC_PriorityGroup**可能取的值,如下:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700)

#define NVIC_PriorityGroup_1 ((uint32_t)0x600)

#define NVIC_PriorityGroup_2 ((uint32_t)0x500)

#define NVIC_PriorityGroup_3 ((uint32_t)0x400)

#define NVIC_PriorityGroup_4 ((uint32_t)0x300)

可知32的中断优先级分组分为以上5组,我们想配置中断优先级分为哪组,可以先将参数传给**“NVIC_PriorityGroup”**即可
设置好啦分组我们该怎么给他设置抢占优先级和相应优先级分组呢?下面我们讲一个重要的函数为中断初始化函数NVIC_Init,其函数声明为:

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

其中NVIC_InitTypeDef是一个结构体,我们可以看看结构体的成员变量:

typedef struct
{
	uint8_t NVIC_IRQChannel;
	
	uint8_t NVIC_TRQChannelPreemptionPriority;
	
	uint8_t NVIC_IRQChannelSubPriority;
	
	FunctionalState NVIC_TRQChannelCmd;
}NVIC_InitTypeDef;

NVIC_InitTypeDef结构体中有4个成员变量,下面我们就看看这些成员变量的含义:
NVIC_IRQChannel:确定是哪个中断,这个我们可以在stm32f10x.h中找到每个中断对应的名字。⚠️:这个成员变量只能在stm32f10x.h中找
NVIC_TRQChannelPreemptionPriority:定义中断的抢占优先级别。
NVIC_IRQChannelSubPriority:定义中断的响应优先级
NVIC_TRQChannelCmd:该通道是否使能
下一步外设中断配置在外部中断(EXIT)下面。

3。STM32外部中断EXIT

1.STM32外部中断简介

外部中断可以理解成,检测外部输入电平的一个机制,如果检测到符合中断的电平,就会产生一次中断,就会进入一次中断服务函数。

2.STM32外部中断寄存器

EXIT配置有关的寄存器用图更好理解:
EXIT功能框图
由图:外设接口是挂在APB总线上。外设的电平由输入线进入,经过边沿检测电路进行分析检测,在此我们可以选择上升沿还是下降沿来触发,具体由自己配置的EXIT_RTSR和EXIT_FTSR寄存器决定,检测的结果会送到后面的或门电路。软件中断事件寄存器一般不用,除非是用软件(EXIT_SWIER)来进行触发产生中断。
上面蓝色部分:中断屏蔽寄存器可以选择是否屏蔽,一般不屏蔽,因为我们想要产生中断,再经过挂起请求寄存器,选择是否挂起(取决于EXTI_PR和EXTI_IMR寄存器),不挂起就直接进入NVIC中断控制器。然后根据你配置的组别,优先级进行产生相应的中断,然后进入中断服务函数处理中断
下面绿色部分:它根据边沿检测电路掺入的信号产生事件,事件是否产生还受控于
EXTI_EMR
的配置。如果它允许产生信号,信号会触发脉冲发生器产生一个脉冲。这个线路最终产物是一个脉冲信号,这个脉冲信号可以给其他外设电路使用。如:TIM,ADC等
总之,可以简单理解为:一个外设需要产生中断,通过输入线发送电平信号,产生了中断,进行中断服务。或者///一个外设在进行到一定程度的时候,需要另一个设备进行工作,走绿色的那条线,产生事件,另一个设备就开始工作。中断的细节配置需要自己牢牢掌握。
输入线:EXIT控制器有23个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个GPIO,也可以是一些外设的事件。
EXIT可分为两大部分,一是产生中断,二是产生事件。这两个功能从硬件上就有所不同
外部中断/事件线:EXIT有23哥中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0~EXTI15,还有另外七根用于特定的外设事件。
EXTI中断/事件
EXTI0~EXTI15用于GPIO,通过编程配置可实现任意一个GPIO作为EXTI的输入源。
映射关系如图:
外部中断/事件GPIO映射
如图所示:EXTI0可以通过SYSCFG外部中断配置寄存器1(SYSCFG_EXTICR1)的EXTI0[3:0]位选择配置为PA0,PB0,PC0,PD0,PG0等等。其他EXTI线(EXTI中断/事件线)使用配置都是类似的。
⚠️:使用外部中断,必须使能SYSCFG时钟。

4。程序设计

根据我们上面写的步骤,我们写外部中断首先要配置NVIC既是确定中断优先级分组和使能对应中断线,代码如下:

void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;//结构体声明
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC为第二组
	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//外部中断1通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//外部中断1通道使能
	NVIC_Init(&NVIC_InitStructure);
}

上面是我们新建一个函数void NVIC_Config(void);用于管理我们程序中的所有中断,以后项目中所有的中断优先级配置都将会在此函数中进行。

中断配置之前首先将中断优先级配置为“NVIC_PriorityGroup_2”分组2,既抢占优先级两位,响应优先级占两位。接着配置中断优先级,如上初始化需要注意的是 NVIC_IRQChannel 需要在 stm32f4xx.h 中查找,我们在这配置其为“EXTI1_IRQn”外部中断线1。

接着初始化GPIO,我们配置PC15循环输出高低电平,配置PA1为输入模式来检测电平变化,代码如下:

void EXTI_GPIOConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//GPIOA时钟初始化
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);//GPIOC时钟初始化
	
	//PC15引脚配置为输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PC15
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
	GPIO_InitStructure.GPIO_OType=GPIO_OType_OD;//开漏模式
	GPIO_InitStructure.GPIO_Speeed = GPIO_Speed_50MHZ;//输出速度
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	//PA1引脚配置为输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PA1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//上拉输入 
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

接着配置EXIT(外部中断),并将PA1配置成中断输入源并连接到exti_linel,代码如下:

void Exti_init(void)
{
	EXTI_InitTypeDef EXTI_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);//SYSCFG时钟初始化

	EXTI_GPIOConfig();//EXTI相关的GIOP配置

	SYSCFG_EXTLLineConfig(EXTI_PoertSourceGPIOA,EXTI_PinSource1);//将GPIOA1配置为EXTI1的中断源
	
	EXTI_InitStructure.EXTI_Line = EXTI_Line1;//外部中断线1
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能外部中断线1
	EXTI_Init(&EXTI_InitStructure);
}

⚠️:配置外部中断(EXTI)时,一定记得初始化系统配置控制器时钟(SYSYCFG),系统配置控制器控制着EXTI线与引脚的连接。还有触发模式需要根据外部电平的变化决定,我们此次实验配置为上升触发(下降也可以)。

外部中断(EXTI)配置就绪后,我们开始编写中断服务函数,Cortex-M4内核的所有中断服务函数的函数名都在“starup_stm32f411xe.s”被定义我们不能自己定义中断服务函数的函数名。意思是,只有名字,没有内容,内容需要我们自己来编写,打开“starup_stm32f411xe.s”后,定位到94行:
请添加图片描述
可看出所有的中断服务函数命名:PPP_IRQHandler();“PPP”代表各种外设。我们次借用的是EXTI1,所以把“PPP”替换为“EXTI1”,然后复制粘贴到,用户文件夹下的“stm32f4xx_it.c”,然后我们编写中断服务函数。

void EXTI1_IRQHandler(void)
{
	if(EXTI_GETitsTATUS(EXTI_Line1) == SET)//	确定中断线1的中断
	{
	printf("中断ok");
	}
	EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志
}

如代码上,这也是所有中断服务函数的一个固定模版:
1)确定中断源
2)中断服务函数,这一段根据不同需求编写
3)清除中断标志为

获取中断源状态和中断清除函数在对应外设库函数文件都有,本次中断服务函数很简单,没检测到一次上升沿,就进入中断,然后中断服务函数进行执行,打印到串口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值