一、EXTI中断
STM32有多个可屏蔽中断通道(中断源):
包含EXTI(外部中断)、TIM、ADC(模数转换器)、USART(串口)、SPI、I2C、RTC(实时时钟)等多个外设。(几乎所有模块都能申请中断)
STM32使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。
NVIC就是STM32中用来管理中断、分配优先级的,NVIC的中断优先级共有16个等级
二、NVIC基本结构
NVIC(嵌套中断向量控制器),在stm32中,它是用来统一分配中断优先级和管理中断的,NVIC是一个内核外设,是CPU的小助手(如果把中断全接到cpu上,会很麻烦,毕竟CPU主要是用来运算的)
NVIC有很多输入口,下图中线上划了个斜杠上面写了n(这个意思是:一个外设可能会同时占用多个中断通道,所以这里有n条线),然后NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,之后通过右边这一输出口就告诉CPU该处理哪个中断,对于中断先后顺序分配的任务,CPU不需要知道
举个例子:
比如CPU是医生,如果医院只有一个医生时,当看病人(EXTI、TIM、ADC等就是各类病人)很多时,医生就得先安排一下先看谁后看谁
如果有紧急的病人,那还得让紧急的病人最先来,这个安排先后顺序的任务很繁琐会影响医生看病的效率,所以医院就安排了一个叫号系统(NVIC)
来病人了统一取号并且根据病人的等级,分配一个优先级,然后叫号系统看一下现在在排队的病人,优先叫号紧急的病人,最后叫号系统给医生输出的就是一个一个排好队的病人,医生就可以专心看病了。
三、NVIC优先级分组
为了处理不同形式的优先级,STM32的NVIC可以对优先级进行分组,分为抢占优先级和响应优先级,抢占优先级和响应优先级的区别:
例子理解:
一种是,病人1在看病,外面排队中的病人2比病人1更加紧急,病人2可以不等病人1看完直接冲到医生的屋里,病人1先等待,先给病人2看病,病人2看完病接着病人1看病,然后外面排队的病人再进来,这种形式的优先级就是中断嵌套,这种决定是不是可以中断嵌套的优先级,就叫抢占优先级,抢占优先级高的,可以进行中断嵌套
另一种是病人叫号的例子,对于紧急的病人,其实有两种形式的优先。一种是,病人1在看病,外面排队了很多病人,当病人1看完后,外面排队中病人最先进去看病即使这个病人是最后来的,这种在排队中的插队的就叫响应优先级,响应优先级高的可以插队提前看病。
为了将优先级区分为抢占优先级和响应优先级,就需要对这16个优先级优先级进行分组,NVIC的中断优先级由优先级寄存器的4位(0~15,4位二进制,对应16个优先级)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
因为优先级总共是4位,所以就有(0,4)、(1.3)、(2,2)、(3,1)、(4、0)这五种分组方式,分组0,就是0位的抢占等级,取值为0,4位的响应等级,取值为0~15,分组1234雷同。这个分组方式是我们在程序中自己进行选择的,选好分组方式后,就要注意抢占优先级和响应优先级的取值范围了,不要超出这个表里规定的取值范围。
优先级数字越小,优先级越高
分组方式 | 抢占优先级NVIC_IRQChannelPreemptionPriority | 响应优先级NVIC_IRQChannelSubPriority | 备注 |
分组0 | 0位,取值为0 | 4位,取值为0~15 | 0bit抢占优先级、4bit响应优先级 |
分组1 | 1位,取值为0~1 | 3位,取值为0~7 | 1bit抢占优先级、3bit响应优先级 |
分组2 | 2位,取值为0~3 | 2位,取值为0~3 | 2bit抢占优先级、2bit响应优先级 |
分组3 | 3位,取值为0~7 | 1位,取值为0~1 | 3bit抢占优先级、1bit响应优先级 |
分组4 | 4位,取值为0~15 | 0位,取值为0 | 4bit抢占优先级、0bit响应优先级 |
四、实现EXTI外部中断
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。(简单说:引脚电平变化,申请中断,进入中断程序)
支持的触发方式(引脚电平的变化类型)
1.上升沿(电平从低电平变到高电平的瞬间触发中断)
EXTI_Trigger_Rising
2.下降沿(电平从高电平变到低电平的瞬间触发中断)
EXTI_Trigger_Falling
3.双边沿(上升沿和下降沿都可以触发中断)
EXTI_Trigger_Rising_Fulling
4.软件触发(程序执行代码就能触发中断)
外部中断模式
1.中断模式:AFIO调用EXTI的0-15中断线对GPIO的电平变化进行中断判断
EXTI_Mode_Interrupt
2.事件模式:不会进入中断嵌套程序,会进行事件判断
EXTI线16连接到PVD输出
EXTI线17连接到RTC闹钟事件
EXTI线18连接到USB唤醒事件
●EXTI线19连接到以太网唤醒事件(只适用于互联型产品)
EXTI_Mode_Event
五、EXTI中断常用函数
EXTI初始化函数
//建立结构体
EXTI_InitTypeDef EXTI_InitStructure;
//配置结构体
EXTI_InitStructure.EXTI_Line= EXTI_LineXX; //指定中断线 XX=0-15;
EXTI_InitStructure.EXTI_LineCmd= ENABLE/DISABLE; //使能中断
EXTI_InitStructure.EXTI_Mode= EXTI_Mode_Interrput/EXTI_Mode_event; //指定外部中断的模式;
EXTI_InitStructure.EXTI_Trigger= 指定触发信号的有效边沿; //触发模式,参考上文
EXTI_Init(&EXTI_InitStructure) //调用创建的结构体地址
EXTI配置清除函数
void EXTI_DeInit(void);
EXTI恢复缺省值(默认值)函数
void EXTI_StructInit(EXTI_InitTypeDef *EXTI_InitStructure;)
EXTI_Line= EXTI_LineNone;
EXTI_InitStructure.EXTI_LineCmd= EDISABLE;
EXTI_InitStructure.EXTI_Mode= EXTI_Mode_Interrput;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI软件中断函数
用于软件触发中断,与中断/事件模式不同,不需要外部电平触发就可以使EXTI_Line置位
EXTI_GenerateSWInterrupt(u32 EXTI_LineX)X=0~15
EXTI线路标志位设置与否
用于检测线路的配置是否设置完成
FlagStatus EXTI_GetFlagStatus(u32 EXTI_LineX); X=0~15
//返回值为 SET/RESET
EXTI清除线路标志位的返回值
使用FlagStatus EXTI_GetFlagStatus(u32 EXTI_Line)后标志位会有返回值
该函数用于清除返回值
void EXTI_ClearFlag(u32 EXTI_LineX)
EXTI_ClearFlag(EXTI_LineX)
X=0~15
EXTI中断触发标志位
EXTI线路触发中断的时候标志位置1,函数用于检测该标志位是否置1
无返回值,常配合IF函数使用
ITStatus EXTI_GetITStatus(u32 EXTI_Line X)
EXTI_GetITStatus(EXTI_Line X) ;
X=0~15
清除EXTI中断触发标志位
EXTI线路中断后标志位不会自动复位,用该函数使标志位复位
void EXTI_ClearITPendingBit(u32 EXTI_Line X)
EXTI_ClearITPendingBit( EXTI_Line X);
X=0~15
六、NVIC常用函数
优先级分组设定
可考虑第三大点的“NVIC优先级分组”的信息
分组方式,整个芯片只能用一种。如放在模块中进行分组,要确保每个模块分组都选的是同一个;或者将这个代码放在主函数的最开始
整个系统执行过程中,只设置一次中断分组。(NVIC_PriorityGroupConfig只是对一个寄存器操作。多次操作以最后一次设置为准。)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_X); X=0\1\2\3\4
NVIC初始化函数
1.NVIC_InitStructure.NVIC_IRQChannel
选择的通IRQ通道
2.NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
选择IRQ通道的抢占优先级
3.NVIC_InitStructure.NVIC_IRQChannelSubPriority
选择IRQ通道的响应优先级
针对每个中断,设置对应的抢占优先级和响应优先级。
如果不设置中断优先级分组,则中断优先级分组默认为0,即0位抢占优先级,4位响应优先级。
4.NVIC_InitStructure.NVIC_IRQChannelCmd
选择IRQ通道的使能位
初始化函数示例:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_X); X=0\1\2\3\4
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = XXXXXXXX;
//视分组规划参数范围,可参考NVIC优先级分组
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = X;
//视分组规划参数范围,可参考NVIC优先级分组
NVIC_InitStructure.NVIC_IRQChannelSubPriority =X ;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE\DISABLE;
NVIC_Init(&NVIC_InitStructure);
七、使用实际案例
对射式红外传感器中断测试
当我们的挡光片或者编码盘在对射式红外传感器中间经过时,DO就会输出电平跳变的信号,电平跳变的信号触发STM32 PB14号口的中断,在中断函数中,执行变量++的程序,然后主循环里用OLED显示这个变量
main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#inclued "red-countsensor.h"
int main(void)
{
red-countsensor(); //红外对射传感器初始化
OLED_Init(); //OLED初始化
OLED_ShowString(1,2,"Count:");//第一行第三列开始显示字符串
while(1)
{
OLED_ShowNum(1,8,red_countsersor_get(),5);//显示red_countsersor_get的返回值,长度为5
}
}
red-countsensor.h
#ifndef __RED_COUNTSENSOR_H
#define __RED_COUNTSENSOR_H
void red_countsensor_Init(void) ; //红外传感器初始化
uint16_t red_countsersor_get(void); //红外传感器中断读数
#endif
red-countsensor.c
#include "stm32f10x.h" // Device header
uint16_t red_countsensor_num;
/***
AFIO GPIO初始化函数
***/
void red_countsensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//GPIO时钟口打开
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //AFIO时钟口初始化
//需要开启AFIO时钟后才可进入EXTI和NVIC的中断执行层且EXTI NCIC的时钟为常开
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//设置AFIO调用口,接收GPIO的端口
//表示GPIOB的14端口在EXTI的第14中断线口
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//配置AFIO,接受GPIO的中断需求
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择第14个中断线口
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断打开
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //分组方式,整个芯片只能用一种。如放在模块中进行分组,要确保每个模块分组都选的是同一个;或者将这个代码放在主函数的最开始
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//因为EXTI选择的是GPIO14对应的Line14,所以这里选择EXTI15_10IRQn的IRQ中断通道,具体可参考NVIC_InitStructure.NVIC_IRQChannel的中断通道选择
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//STM32中断函数无参无返回值,有标志位,不需要声明自动执行
//因为EXTI选择的是GPIO14对应的Line14,所以这里选择EXTI15_10的IRQ中断通道,具体可参考NVIC_InitStructure.NVIC_IRQChannel的中断通道选择
void EXTI15_10_IRQHandler()
{
//中断触发
if(EXTI_GetFlagStatus(EXTI_Line14) == SET)
{
red_countsensor_num++; //全局变量增加
EXTI_ClearITPendingBit(EXTI_Line14); //中断触发标志位复位
}
}
uint16_t red_countsersor_get(void)
{
return red_countsensor_num;
}