【实训五】中断

前言

1、中断简介

中断:程序在运行过程中发生了外部或内部事件时,中断了正在执行的程序,转到外部或内部事件中去执行别的任务。

(1)中断的作用

  • 中断的作用是大量节约CPU资源,提高程序的效率,即避免重要事件被错过。
  • 中断使用地方:没办法确定什么时候需要处理数据时,采用中断方式处理。

以之前学习的串口接收数据为例,比较使用中断与否的效果:
查询方式:能够实现功能代码,CPU一直在查询是否接收数据标志,如果程序中有延时就会产生丢数据的现象。
中断方式:能够实现功能代码,当产生接收数据中断时,立马告诉MCU,也就是将CPU拉到中断中执行中断任务。(如果中断没有发生变化,MCU可以做其他工作)。

(2)中断入口
在单片机内部有一块空间专门用来存放地址,该地址已经和相应的中断绑定在一起了(芯片厂商处理的)。当中断发生时,CPU会跑到该内存空间中查找中断入口地址,该地址就是中断入口。该地址对于开发人员而言,可读性较差,所以芯片厂商在芯片设计时将该地址重新取名(便于记忆和理解),即中断服务函数名,在stm32里,中断服务函数名是不可自定义,所有的中断服务函数名都已经被设定好了。因此在编写中断服务函数时绝对不能写错,如果写错,CPU就找不到中断的入口。如中断地址0x233947hff取中断服务函数名为USART1_IRQHandler,这对应着同一个中断事件。

中断服务函数在XXX_md.s文件。

主函数和中断服务函数的关系:主函数和中断服务函数之间并不是主函数调用中断服务函数的关系,二者属于同一个级别,即本质是中断服务函数和主函数一起抢占CPU的使用权。所以中断服务函数不是写在主函数中。

(3)中断优先级
把事件的紧急情况或执行事件的先后顺序称为优先级。使用到优先级的目的:为了区分这些事情的重要程度,即事件执行时的先后顺序。优先级就是一个数值,数值越小,优先级越高。每个中断优先级可以分为自然(固定不变的)和可控(程序员可以配置)两种。中断自带的优先级编号称为自然优先级。可控优先级就是可以由程序员自行修改的优先级。

  • 可控优先级分为抢占优先级响应优先级
  • 自然优先级的本质就是一些硬件编号。数字越小优先级越高。
  • 优先级比较顺序:先比较抢占优先级,再比较响应优先级,最后比较自然优先级。

(4)中断嵌套

中断嵌套只发生在抢占优先级不一样的情况下。

中断嵌套:就是中断在执行的时候在中断中又出现了一个中断。
在这里插入图片描述
中断嵌套目的:处理更紧急的事情。中断嵌套中需要将嵌套的中断优先级设置的更高。因为高优先级的任务可以打断或抢占低优先级的任务。

2、Cortex-M4——中断体系

中断体系就是管理中断的一套机制。
(1)Cortex-M4-中断架构
在芯片的内核里专门有一个管理中断机制的模块——NVIC(嵌套向量中断控制器)控制器。M4内核专门负责处理中断相关问题的机构,专门做中断管理相关的事务。

注意:NVIC控制器属于内核级的模块,所以在中文手册找不到,到芯片内核编程手册:《Cortex M3与M4权威指南.pdf》在第7章异常和中断中详细讲中断体系中查看。

所有的中断都是在NVIC控制器中控制的,如下图所示。
在这里插入图片描述

  • NVIC控制器中的中断来源:包括来自GPIO口外部中断,片上外设,系统核心,SysTick,非掩蔽中断。
  • Cortex-M3和Cortex-M4支持多达240个IRQ(中断请求)、一个非掩蔽中断(NMI)、一个SysTick(系统滴答)定时器中断和一些系统异常。NMI通常是由外围设备(如看门狗定时器)生成的,其余的异常来自处理器核心。大部分 IRQ由外围设备(如定时器、I/O端口和通信接口(例如UART、I2C)生成。

(2)Cortex-M4-NVIC控制器中断来源分析
NVIC控制器总共提供了255个中断入口。具体见《Cortex M3与M4权威指南.pdf》
①系统及SysTick中断入口共15个(内核固定的)。

注意:这是内核级中断,中断控制器必须响应,内核中断不能够被打断也不能被设置优先级,具有固定的优先级。这15个中断源是由ARM设计的。

②片上外设或IO口中断入口16到255总共240个。

注意:该部分中断由芯片厂商决定(不是内核极的),由芯片生产者决定。

(3) Cortex-M4中断管理方式
Cortex-M4 NVIC控制器中1 ~ 15个系统中断不能被打断也不可以设置优先级,他们的优先级是固定的,且发生事件时必须要响应。剩下的16~255总共240个中断属于外部或片上外设中断,他们可以自由的设置中断优先级,且每一种中断都具有三种类型的优先级:可控优先级(抢占优先级,响应优先级),自然优先级。

抢占优先级:当其中一个中断正在执行时,其他的中断是否可以打断正在执行的中断。
响应优先级:决定当多个抢占优先级都相同且多个中断同时到来的时候,先执行哪一个中断。
自然优先级(硬件固定):按照自带的优先级编号(硬件固定)在抢占和响应优先级相同的情况下决定先执行哪个中断,数字越小级别越高。

  • 优先级大小:抢占优先级>响应优先级>自然优先级。
  • 每个中断源的抢占优先级和响应优先级由用户决定(软件设置),而自然优先级已经被硬件固定, 不可更改。优先级越高其对应的值越低。数字越小,优先级越高。

M4 NVIC控制器通过分组来设置各个中断的优先级的方式来管理各个中断。

在CM4里面,系统会给每一个中断源都分配一个8位寄存器来存放它的优先级(抢占优先级和响应优先级)。在这8位寄存器里面,一部分用于存放抢占优先级,另一部分存放响应优先级。8位寄存器的理想情况下的分布情况如下(抢占优先级位数+响应优先级位数=8bit):
在这里插入图片描述

Cortex-M4-NVIC 中得知抢占优先级+响应优先级最多占8位,最少3位。(一个范围,具体占几位取决于芯片生产商)

M4内核中的中断可分为8组,不同的组抢占和响应的位数不一样,可以设置的优先级的范围也不一样。如下表所示。
在这里插入图片描述

注意:抢占优先级最大有128级别,响应优先级最大有256级别。

往SCB(系统控制块)->AIRCR寄存器的PRIGROUP3位(8 ~ 10)中写入不同值(分组值0~7)即可决定系统中,中断的抢占优先级位数和响应优先级位数。
在这里插入图片描述
一个系统在使用中断之前,必须确定优先级分组,也就是确定抢占优先级和响应优先级的位数,从而决定抢占优先级和响应优先级的可设置范围。

(4)Cortex-M4-NVIC控制器中断相关函数介绍
在这里插入图片描述

3、STM32——中断体系

(1)STM32-NVIC控制器简介

STM32-NVIC控制器结构和内核的一样,只是中断入口或者来源发生了变化。见《STM32F4xx中文参考手册.pdf》中断章节。

1)设置分组&优先级

M4内核的NVIC控制器抢占+响应优先级设置总共占用8bit表示。而ARM公司规定:不是所有的芯片厂商都要使用8位。最多可以使用8bit,最少不少于3bit。可以根据需要使用(比如自己芯片功能等)。比如ST公司就是使用4位,NXP(恩智浦)公司使用5位。

这里讲的是STM32F40X的中断分组设置(使用4bit)。抢占优先级位数+响应优先级位数=4bit
在这里插入图片描述

知识总结:

  • 由上表得知STM32优先级使用4~7共4个位控制。 如下所示:
    在这里插入图片描述
  • 由上表得知STM32分组值范围:3 ~ 7,上表使用的是二进表示形式,是写入寄存器的值,一般对分组使用组编号的形式,组编号范围:0 ~ 4。组编号中0对应于分组值的7,而组编号中4对应于分组值的3。组编号=抢占优先级位数

问:将某外设分组为第5组,程序如何实现?
答:抢占优先级是几位就7-几,即(7-n),剩下的是响应优先级位数。
注意:设置组编号(5)的目的是为了更清楚的知道抢占优先级和响应优先级的大小,其本身并不存在。

(2)STM32-NVIC控制器相关函数及配置方法
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在NVIC控制器中打开相应中断功能叫做:核心级中断使能

(3)STM32外设中断原理
经过核心级中断使能的中断并未真正的打开,还需要打开相关外设的中断,即模块级中断。即要真正使用中断功能要实现核心级中断使能+模块级中断使能

  • 真正的开启一个外设的中断需要以下步骤:
    ①开启核心级中断使能,即在NVIC控制器当中打开该外设对应的中断(比如 串口接受中断)。
    ②开启模块级中断使能(打开在外设中当前(比如串口接受中断)中断的开关)。

  • 也可以理解为想要真正的使用中断需完成以下几步:
    ①设置分组; (可以在中断函数对应的源文件处设置,也可以在main.c文件设置)
    ②设置抢占和响应的优先级级别;
    ③给中断设置中断功能;
    ④打开中断在NVIC控制器当中的开关;
    ⑤打开中断在模块(外设)中的开关。

中断过程分析:
在这里插入图片描述

书写中断服务函数注意事项:
① 中断服务函数名是固定的,在启动代码里面已经定下来了,不可以由程序员自己更改。
② 在STM32中,中断服务函数名在相应的汇编文件(.s文件)中有,如STM32F407VGT6→startup_stm32f40_41xxx.s。 中断编号即中断源在相应的头文件(.h文件)中有,如STM32F4XX.h。
③ 中断服务函数名尽量用复制,不要自己写,写错就进不了中断。
④(如果中断服务函数是公共入口即如果是全局中断)进入到中断服务函数后先要查询是哪种中断。
先清中断标志,然后再做中断处理,不要把清中断标志放在函数的最后。(如果把清除中断标志放在中断服务函数的最后,会出现当发出清中断标志指令后,硬件还没有把相关标志清除掉,程序就已经跳出了中断服务函数,这个时候NVIC又会识别到标志是1,出现重复中断)。可以清除中断标志命令发出后,等待清除成功再往下执行。
⑥ 中断服务函数应该尽量简短,一般是做一些标识,不要在中断中做延时之类的占用CPU很长时间的工作。(快进快出)
⑦ 中断服务函数不会被任何一个函数调用,当中断条件满足后,NVIC控制把CPU拉到中断服务函数中执行,执行完毕后在回到主函数。
⑧ 中断服务函数写法是:void 中断服务函数 (void),即不可以有参数和返回值。
⑨ 中断服务函数不需要声明,也不需要调用,它的本质和main函数一样,其工作原理是和main函数抢CPU的使用权。

4、说明

本博客设置 USART1接收中断为例。

串口1模块级中断寄存器(USART_CR1):
在这里插入图片描述在这里插入图片描述

一、单片机利用中断接收字符串

现象:串口助手发送字符串到单片机,单片机将接收到的字符串又发送到串口助手。

1、在usart.c文件中编写中断接收字符串函数
先初始化,再编写接收函数。

void Usart1_Init(u32 brr)
{
	//1、开串口USART1和GPIOA组时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1 ,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;              //定义结构体变量
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  //配置为复用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //输出速度为2MHz
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;        //选定管脚PA9
	GPIO_Init(GPIOA,&GPIO_InitStruct);            //调用初始化函数
	
	//2、初始化USART1
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = brr;					//波特率
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//禁止硬件流控
	USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;						//全双工通信
	USART_InitStruct.USART_Parity = USART_Parity_No;		//禁止奇偶校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1;		//1个停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;		//8bit数据位
	USART_Init(USART1, &USART_InitStruct);
	
	//中断
	USART_ITConfig(USART1,USART_IT_RXNE, ENABLE);				//使能USART1接收中断
	USART_ITConfig(USART1,USART_IT_IDLE, ENABLE);				//使能USART1空闲中断  判断通信什么时候结束  
	NVIC_SetPriority (USART1_IRQn, NVIC_EncodePriority (7-2, 1, 2)); //设置抢占和响应的优先级级别,并将合成的优先级设置给USART1中断源
	NVIC_EnableIRQ (USART1_IRQn);				  //使能NVIC控制器中断开关,即打开串口1在NVIC控制器当中的开关,这一步必须要
	
	//3、使能串口1
	USART_Cmd( USART1, ENABLE);	
}
//使用中断方式接收字符串。
u8 buff[50];
u8 rev_ok;
/*
1.中断服务函数不可以有返回值,也不可以有形参
2.中断服务函数名名字已经固定好,不可以由程序员自己更改
3.中断服务函数,不需要声明,也不需要调用,它的本质和main函数一样,其工作原理是和main函数抢CPU的使用权
*/
void USART1_IRQHandler(void)
{
	static u8 i = 0;//static 修饰的静态局部变量只执行初始化一次
	if( USART_GetITStatus(USART1, USART_IT_RXNE) )	//接收中断
	{
		USART_ClearFlag(USART1,USART_FLAG_RXNE);   //清除接收中断标志位
		buff[i] = USART_ReceiveData(USART1);
		i++;
	}
	if( USART_GetITStatus(USART1, USART_IT_IDLE) )	//空闲中断
	{
		if(USART1->DR)							//读DR寄存器  清空闲标志位
		{
			;
		}
		buff[i] = '\0';							//字符串以字符'\0'结尾
		i = 0;									//数组下标清零
		rev_ok = 1;							   //表示一次通信结束
	}
		
}

2、编写main.c文件,引用usart.c文件的中断接收函数。

#include "main.h"
int main()
{
	NVIC_SetPriorityGrouping (7-2);				//分组一般同一个工程只设置一次
	Usart1_Init(115200);
	while(1)	//死循环
	{
		//中断方式
		if(rev_ok == 1)					//这个标志位置1代表一次通信结束
		{
			rev_ok = 0;
			Usart1_Send_Str(buff);
		}
		
	}
	return 0;
}

二、单片机利用中断控制LED灯

现象:串口助手发送命令到单片机,单片机将接收到命令后做出响应。如串口助手发送LED_ON,单片机将打开LED灯。

1、编写main.c文件,引用usart.c文件的中断接收函数。

使用strcmp需包含#include “string.h”。在串口助手里的“发送新行”要取消,否则控制不了。因为它是以“\r\n”结尾。所以也可以在代码上加上“\r\n”就不用取消。

#include "main.h"
int main()
{
	NVIC_SetPriorityGrouping (7-2);				//分组一般同一个工程只设置一次
	Usart1_Init(115200);
	Led_Pin_Init();
	while(1)	//死循环
	{
		if(rev_ok == 1)					//这个标志位置1代表一次通信结束
		{
			rev_ok = 0;
			Usart1_Send_Str(buff);
			if(strcmp("LED_ON",(const char *)buff)==0)
			{
				LED1_ON;
				LED2_ON;
				LED3_ON;
			}
			if(strcmp("LED_OFF",(const char *)buff)==0)
			{
				LED1_OFF;
				LED2_OFF;
				LED3_OFF;
			}
		}
	}
	return 0;
}

控灯部分也可以放在usart.c文件的中断接收函数里。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

念芯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值