什么是中断
中断定义
中断是指在主程序运行过程中,出现了特定的中断触发条件(中断源),使得 CPU 暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
具象理解
中断和生活中的中断差不多,比如我们正在闲适地看书呢(主程序),突然导师发来一条消息(中断源),说论文写怎么样了,这时候就不得不放下书本夹好书签,赶紧转去写论文(处理中断)好跟老师交差,然后再重新享受读书时光(返回主程序继续执行)。
中断系统的好处是效率更高,只有出现中断触发条件,才会去执行中断。
还是上面的例子,你的计划是在导师催促写论文之前看会书,没有中断机制的系统 belike:
只能边看书边捧着手机打开聊天框,盯着老师的头像反复查看有没有消息过来(轮询)
但是有中断就不一样啦,只管看书就行,收到导师消息再处理就可以啦。
中断优先级
中断也是有优先级的,好比看书的时候同时来了两件事,一边是导师的Push信息,一边是好哥们的海克斯大乱斗邀请,你选哪个先处理?在这个例子中前者的优先级明显更高,所以会优先处理。这就是优先的概念。
抢占 / 响应优先级
优先级分为两种:抢占优先级和响应优先级,抢占可以理解成能不能插队,响应则是怎么排队。两种优先级都遵循数字越小,优先级越高的规律,且抢占优先级高于响应优先级,或者说先比较抢占再比较响应。
在CPU的世界里没有先来后到,完全按轻重缓急来决定执行顺序。
这里我们以医院叫号的例子来理解,每一位来医院就诊的病人在系统中都有两种信息,第一条信息表示患者是急诊还是常规就诊(抢占优先级),第二条信息表示患者的具体病情(响应优先级)。
诊室里有位病人正在就诊中,走的常规就诊通道,但这时候突然送来了一位急诊患者,情况更加紧急,就会打断当前患者的就诊过程优先治疗;当多名患者都是急诊或常规就诊时,具体的就诊顺序就由第二条信息决定,比如一位患者感冒一位患者骨折,则骨折患者优先叫号。但是如果感冒患者已经在就诊中,骨折患者是没法插队的。
中断嵌套
还有一种现象叫中断嵌套,中断嵌套就是你正看书呢,然后收到了好哥们的大乱斗邀请,转到玩游戏中去了,打着打着收到了老师的消息 ...
EXTI 外部中断学习笔记
EXTI 简介
-
第三点:上升沿指低电平变为高电平触发中断(下降沿相反);双边沿指只要电平发生变化,就触发中断;软件触发是指
CPU主动让EXTI假装被触发了一次,然后完整地走一遍正常触发中断的流程,区别如下:正常流程:电平变化 --- 触发中断 --- 执行中断程序
软件触发:代码设置 --- 触发中断 --- 执行中断程序
-
第四点:相同的
Pin不能同时触发中断是由内部硬件结构决定的,STM32里只有16条EXTI线,不是PA0一条,PB0一条,而是所有的x0引脚共用EXTI0。就像只有一个插座,但是有多个插头都可以插。 -
第六点:当中断条件触发时,既可以选择触发中断,也可以选择触发一个事件,此时外部中断的信号不会通向
CPU,而是通向其他外设,用来触发其他外设的操作。这里的事件是什么还没搞清楚,只知道不是我们想的自己编写的函数
STM32之编写中断函数
在编写中断函数时,借助内部的结构图可以更好地理清思绪,逻辑上就是搭建一个完整的从GPIO外设到NVIC的链路。
具体步骤
- 配置RCC,把我们这里涉及的外设的时钟都打开;
- 配置GPIO,选择我们的端口为输入模式;
- 配置AFIO,选择我们用的这一路GPIO,连接到后面的EXTI;
- 配置EXTI,选择边沿触发方式,比如上升沿,下降沿或者双边沿;还有选择触发相应方式,可以选择中断响应(一般都是这个) 和事件响应;
- 配置NVIC,给我们这个中断选择一个合适的优先级;
最后通过NVIC,外部中断信号就能进入CPU了;
通过对射式红外传感器计次的具体代码实现可以看得更清楚:
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void CountSensor_Init(void)
{
// 1、配置RCC,把我们这里涉及的外设的时钟都打开(GPIO AFIO EXTI NVIC)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// EXTI和NVIC两个外设的时钟是一直开着的,不需要设置
// 2、配置GPIO,和前面一样
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 3、配置AFIO,选择我们用的这一路GPIO,连接到后面的EXTI
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
// 4、配置EXTI,选择边沿触发方式,选择触发响应方式等
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14; // EXTI14号线
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 开启中断
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 响应方式为中断响应
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿
EXTI_Init(&EXTI_InitStruct);
// 5、配置NVIC,给我们这个中断选择一个合适的优先级
// 在配置中断之前,先指定一下中断的分组,这里选了2位抢占2位响应
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 因为我们设置的EXTI14
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_Init(&NVIC_InitStruct);
}
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
// 中断函数的名字都是固定,建议直接从启动文件(Start第一个)复制
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET) // 判断标志位
{
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14); // 清除标志位
}
}
代码小记
-
和使用
GPIO外设是一样的流程,都是打开时钟然后初始化,而EXTI和NVIC的时钟本来就是开着的,所以没有打开时钟这一步; -
初始化都是同样的套路,甚至名字形式上也是统一的,除了
AFIO的配置外,另外NVIC初始化之前需要指定优先级的分组类型。其套路如下:
// 根据要配置的外设来定义结构体变量
Which__InitTypeDef Which_InitStruct;
// 用.的方式访问结构体数据(这里会自动弹出,不用死记)
Which_InitStruct.Which = ... ;
...
Which_InitStruct.Which = ... ;
// 对结构体变量取值作为参数传入Init函数(这里的参数根据自动弹出的提示来填就行,有时候不止一个,比如GPIO)
Which_Init(&Which_InitStruct);
-
配置完各个外设之后就是写中断函数,需要注意中断函数的头是固定的(两个
void),对应的函数名也是固定的,需要根据我们使用的EXTI线路来对应选择,比如我们使用了EXTI14号线,对应的就是EXTI15_10_IRQHandler; -
和标志位有关的四个函数
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
EXTI线触发之后(Flag标志位),会分成两条路径走,一种是事件,一种是中断;前两个函数处理的标志位是用于说明某条
EXTI线是否被触发过,后两个函数处理的标志位是用于说明某条EXTI线是否被配置为中断,并且真的走了中断这条路;经验法则:在中断函数中推荐用后两个函数。
- 关于中断函数
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET) // 判断标志位
{
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14); // 清除标志位
}
}
为什么这里要判断一下中断标志位呢?因为EXTI 10 ~ 15号线,共用同一个中断入口,我们需要判断是不是14号线触发的,然后执行相应的逻辑,再清除对应的标志位(不清除就卡死在中断里了!!)。
其内部活动类似下面:
CPU:我被叫进 EXTI15_10 中断了
CPU:先看看是不是 14 号线在叫我
如果是:执行和 14 号线相关的逻辑 + 把 14 号线的小红灯关掉