概述
- 中断概述
1.1 什么是中断?
1.2 中断的作用 - 中断的实现
2.1 中断的入口
2.2 中断优先级 - ARM单片机中断体系
3.1 NVIC优先级说明
3.2 NVIC优先级分配方式
3.3 STM32fxx中断体系 - STM32中断配置
4.1NVIC相关配置函数
4.2设置优先级分组函数
4.3设置中断优先级函数
4.4NVIC响应中断使能 - 内部中断
- 外部中断
- 软件中断
中断概述
什么是中断?
当开始学习中断时,首先需要了解中断在STM32是什么,他是怎么来的,是用来干什么的,又是怎么干的,这是在学之前心里必须要提出的问题。
那么什么是中断呢?
中断就是程序在正常运行的过程中发生了不正常的事情,必须要暂停一下去处理这个不正常的事情,然后跑回来继续干正常的事情。
对于STM32来说,正常运行的程序是主函数(main),代码是由CPU运行的。CPU在主函数里运行是正常的执行过程,当在这个过程中突然发生了异常事件(中断),CPU必须暂停当前的工作(设下断点),然后跑去能处理这个异常事件的函数中做异常处理(中断服务函数),处理完这个异常事件后(执行完中断服务函数),CPU就会跑回刚才的断点处,继续正常运行下去。然而在程序运行当中,中断发生的时间并不确定、所以一旦发生必须立马处理、处理完异常再执行正常事件。(快进快出)。
中断是用来干什么的?
在这里举个例,例如串口收发数据时,查询式用法在每次应用在主程序当中,都需要经过一个查询是否有数据收发的过程,而在此过程当中,都需要经过一个while()的循环进行查询,而在循环执行的过程当中,就会导致程序卡死在此处,必须等执行完才能进入下一步,而中断便是解决这方面问题,在需要处理的时候让主函数去处理,不需要的时候正常的执行程序。
中断的实现
中断的入口
在主程序的运行过程中,想要执行一些其他的事件,这时候该事件抢占CPU去优先处理它,那要去处理它就必须要有一个入口,而这个入口便是在暂停主程序时产生的断点,通过该断点进入响应的中断服务函数入口,进入中断,而对于中断来说,它不能去干扰主函数中之前做过的事,这边存在了一个将数据保存下来的一个操作,即数据入栈,数据出栈。
中断优先级
既然中断是异常事件,那么难保不会同时出现多个异常事件,那对于程序来说,事件的处理顺序又该怎么办呢,那么优先级这一概念就出来了。
优先级的分类
对于STM32来说,中断优先级分三类,即:占先(抢占)优先级,次级(响应)优先级,自然优先级。
中断优先级数字越小,优先级越高
UCOSII/III: 数字越小优先级越高
FreeRTOS:数字越小优先级越低
中断嵌套
ARM单片机中断体系
在我们了解的中断的相关含义,我们再来思考一下,中断优先级,中断服务函数这些又是什么来定义划分的呢?
首先,我们知道ARM公司是制作芯片架构的公司,而STM32系列单片机作为ARM架构下的单片机所采用的中断体系都是同一套而中断体系就是管理中断的一种体系,在芯片内核里专门有一个管理中断机制的模块–NVIC控制器。
NVIC控制器属于内核级的模块,专门做中断管理,包括中断响应,优先级的设置,相关中断的使能。
NVIC优先级说明
如果同时到来两个事件、先对比占先、占先一致再对比次级
事件 | 占先 | 次级 |
---|---|---|
A | 1 | 5 |
B | 2 | 2 |
C | 2 | 3 |
事件A与B同时到来、先处理A
事件B与C同时到来、先处理B
事件C正在执行、事件B到来、不能打断C的执行、占先一致
事件C正在执行、事件A到来、直接打断执行A
总结:1.占先优先级相同的形况下,事件之间不能进行打断
2.占先优先级相同的情况再比较次级优先级
3.自然优先级不可更改,它是内核当中已经规定好的
4.中断嵌套时,当高占先优先级的中断执行时,低占先优先级或同占先优先级的中断产生了,低优先级的中断不响应。
NVIC优先级分配方式
知道了优先级的作用,我们需要了解一下NVIC中断体系中,又是如何去分配优先级的呢,既然自然优先级不能更改,那占先和次级优先级又是如何分配的呢。
在内核的中断体系当中,对于占先和次级优先级的分配与设置都是在一个8位寄存器当中实现的,在该寄存器来防止设置的占先和次级优先级,它们一起被放在该寄存器中,那对于寄存器中优先级位数的划分又是怎么规定的呢?
在ARM的中断体系当中,因为一共有8位来防止优先级,那么对于占先和次级优先级的分配有8种
STM32fxx中断体系
对于ARM的优先级分配方式,其实不是每个芯片都利用8位来设置优先级,对于STM32单片机而言,它所用的是其中四位[7:4],且在我们编写工程当中,每个工程只能有一个分组号,写优先级时第一步必须先分组、才能写入占先和次级的值。
了解完中断及其体系的理论知识过后,接下来了解STM32单片机相关的中断配置。
STM32中断配置
NVIC相关配置函数
优先级相关的配置函数、只需要配置相关即可
NVIC_SetPriorityGrouping:设置优先级分组
NVIC_EnableIRQ:使能优先级
NVIC_SetPriority:设置优先级
设置优先级分组函数
函数原型:void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
函数功能:优先级分组
形参:PriorityGroup
返回值:无
参数说明:参数+分组号=7 分组号=7-参数
例如需要配置分组为第五组:NVIC_SetPriorityGrouping(7-5)
注: 对于该分组函数的使用,我查阅网上的一些资料,认为此处函数形参就是分组号,但是所学的内容是上面的理解,如果有疑问的可以观察该函数原型,查找相关资料自行理解。
设置中断优先级函数
函数原型:void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
函数功能:设置优先级(占先+次级)
形参:
IRQn:Interrupt number 中断号
Priority:Priority to set 设置优先级
返回值:无
例如:
NVIC_SetPriorityGrouping(7-5)//第五组
NVIC_SetPriority(USART1_IRQn,5) 占先=1 次级=1
NVIC_SetPriorityGrouping(7-6)//第六组
NVIC_SetPriority(USART2_IRQn,6) //占先=0 次级=6
该函数第二参数优先级的设置也可通过 NVIC_EncodePriority 函数合成占先和次级优先级
即:NVIC_SetPriority(EXTI15_10_IRQn,NVIC_EncodePriority(7-5,1,1))
NVIC响应中断使能
函数原型:void __NVIC_EnableIRQ(IRQn_Type IRQn)
函数功能:使能
形参:USART1_IRQn 中断号(中断号可查询系统中断向量表知道)
返回值:
内部中断
了解完NVIC相关的中断配置后,那么我们就要了解下中断的请求是哪来的,又该如何配置。
对于内部中断而言,我所了解到的是关于芯片片上外设上的中断,而且每一种外设不止一种中断,那么对于程序而言,要使中断能实现,需要打开两个中断开关,一个是片上外设的中断开关,一个是NVIC中断控制器上的。例如:串口USART1上的RXNE接受中断使能。
由上可知,串口不知一种中断,那么在对于串口的中断进入的又是同一个中断服务函数,我们要做的就是要在中断服务函数当中要判断是哪种中断。
注: 且外设上产生的中断,我们配置中断之前配置好外设的各种寄存器。
内部中断的相关初始化如下:
配置流程:
1.配置好相关片上外设
2.打开片上外设上相关中断的响应
3.设置优先级分组,优先级和NVIC中断响应等
4.片上外设使能
5.中断服务函数
6.进入中断服务函数先判断是哪种中断,在进行相关操作。
7.最后判断程序是否进入中断,可以自定义相关的功能函数,如电灯,串口通信等操作,调用至中断服务函数中,主函数中不需要进行调用,查看功能是否实现,或定义全局变量中断标志,到主函数中通过标志位的变化,查看是否进入中断。
void Uart_Config(u32 brr)
{
float div;
u32 div_m,div_f; //div_m 分频整数部分 div_f分频比小数部分
NVIC_SetPriorityGrouping(7-5); //设置中断优先级分组 抢占:2位 响应:2位
RCC->AHB1ENR |= (1<<0); //打开GPIOA时钟
RCC->APB2ENR |= (1<<4); //打开USART1时钟
GPIOA->MODER &= ~(0xf<<18); //清零PA9/PA10
GPIOA->MODER |= (0xa<<18); //GPIOA复用功能模式
GPIOA->OTYPER &= ~(3<<9); //输出推挽模式
GPIOA->AFR[1] |= (7<<4); //PA9 复用功能高位寄存器(UART1 AF7)
GPIOA->AFR[1] |= (7<<8); //PA10 复用功能高位寄存器(UART1 AF7)
div = 84000000.0f/16/brr; //84MHz 16倍过采样
div_m = (u32)div; //取出DIV整数部分
div_f = (div-div_m)*16+0.5f; //得出来得小数放不进寄存器当中需乘回16倍过采样,并进行四舍五入
USART1->BRR |= (div_m<<4)|div_f; //BRR寄存器需放入DIV的值
//设置接受中断优先级
NVIC_SetPriority(USART1_IRQn,NVIC_EncodePriority(7-2,1,1));
//使能NVIC响应接受中断
NVIC_EnableIRQ(USART1_IRQn);
USART1->CR1 |= (1<<5); //RXNEIE,USART1中断使能(读取数据寄存器不为空中断)
USART1->CR1 |= (1<<4); //IDLE检测到空闲电路,空闲中断使能
USART1->CR1 |= (1<<2); //接收器使能
USART1->CR1 |= (1<<3); //发送器使能
USART1->CR1 &= ~(1<<10); //禁止奇偶校验
USART1->CR1 &= ~(1<<12); //字长 8数据位
USART1->CR1 &= ~(1<<15); //16倍过采样
USART1->CR2 &= ~(3<<12); //1个停止位
USART1->CR1 |= (1<<13); //USART1使能
}
void USART1_IRQHandler(void) //USART1所有中断共用一个入口
{
u32 temp;
//判断是哪种中断
if(USART1->SR & (0x1 << 5))//接受中断,数据寄存器不为空
{
uart_data.revBuf[uart_data.position] = USART1->DR;//读DR清除标志
uart_data.position++; //标志下一次的位置
}else if(USART1->SR & (0x01<<4)) //空闲中断
{
//清标志 空闲中断标志清零
temp = USART1->SR;
temp = USART1->DR;
temp = temp; //防止警告未使用
uart_data.revOK = 1; //数据接受完成
uart_data.revBuf[uart_data.position] = '