STM32中断EXTI
- 中断的介绍
- 中断简介
- 中断优先级
- 中断嵌套
- STM32中断
- NVIC介绍
- 作用
- 功能
- 如何分组
- EXTI简介
- EXTI结构
- EXTI框图
- AFIO介绍
- 主要功能和作用:
- 中断配置步骤
- 一个中断时的代码初始化
- 两个中断时的代码和错误提示
中断的介绍
中断简介
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
举例
:当你正在吃饭,你发现电话响了,你要停下吃饭,转而去接电话,接完电话后继续回到最初的吃饭的状态。这个电话响了就是中断源。你吃饭才吃了半个饼子这时电话响了,你去接电话这个时刻时间点就是断点,接完电话继续吃没吃完的饼子也即是回到主程序。
中断优先级
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
举例:
假如一个饼子。你还没有吃完,刚吃到一半,这时候电话响了,同时呢,烧水器的水烧开了,有两个事情需要你去处理,这时候你就需要抉择一下先执行哪个。很显然,烧水器的水是比较重要的,因为如果烧水器的水烧开了。不去拔掉的话可能不去拔掉的话,可能会引起着火。这时候优先级就出来了。
中断嵌套
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
举例
假如你现在正在吃饼子饼子吃到一半的时候。突然间,电话响了,这时候你去另一个房间去接电话但是当走到一半的时候,你发现烧水器在你走到一半路的时候也烧开了,这时候呢,你转而去把烧水器的接头拔了。然后再去另一个房间接电话。等电话接完,你又继续吃剩下的半张饼。
STM32中断
STM32有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。
或许您有这个疑惑: stm32有那么多的中断,那么CPU是怎么进行管理的呢?
答:
他当然需要一个小助手来帮他处理一些中断的事情,而CPU继续忙他的事情。中断则交给了他的小助手。那么他的小助手是谁呢?他的小助手就是NVIC。
NVIC介绍
NVIC (Nested Vectored Interrupt Controller) 是 ARM Cortex-M 处理器架构中的一个重要组件,在STM32系列微控制器中被广泛应用。它是一个硬件模块,用于管理中断请求(IRQ)和异常处理。
作用
-
中断管理: NVIC 负责管理外部中断和内部异常的优先级和触发。
-
中断控制: 它允许您配置和控制每个中断的优先级、使能状态和其他相关设置。
-
中断处理: 当发生中断请求时,NVIC 根据预先配置的中断优先级和相关设置,确定中断的处理顺序。
-
异常处理: NVIC 还负责处理与系统异常相关的操作,如硬件错误、系统调试等。
-
嵌套处理: NVIC 支持嵌套中断,即在处理一个中断时,如果另一个中断发生,系统能够通过中断优先级来判断是否需要暂时挂起当前中断并处理更高优先级的中断。
功能
-
优先级管理: NVIC 允许每个中断都分配一个优先级,这样可以确保在发生多个中断时,系统按照优先级处理。
-
中断使能: NVIC 控制每个中断的使能状态,允许开启或关闭特定中断。
-
异常处理: 对于内部异常,如硬件错误或系统异常,NVIC 负责相应的异常处理。
-
中断向量表: NVIC 维护中断向量表,用于指示每个中断的处理程序地址。
总结:NVIC 是 STM32 系列微控制器中负责管理中断和异常的重要组件,它的功能和作用包括中断管理、优先级控制、异常处理等。
为了理解NVIC和CPU的关系,这里也给出NVIC的基本结构和举例:
CPU是医生,NVIC是医生的小助手也即是叫号系统,门外TIM,ADC等都是病人,病人病的程度不一样,这时优先级不同。这里线上的n是值得有n个通道,但最终NVIC叫号系统只通过一根线告诉cpu医生下一位是哪个病人,这样相比医生自己既叫号又给病人看病的效率就高很多了
如何分组
上面分组应注意:
- 分组方式中的数字表示优先级分组的编号,数字越小,分组的位数越少。
- 抢占优先级和响应优先级的位数决定了可以表示的优先级级别的数量,因此位数越少,可以表示的优先级级别越少,优先级的细粒度也就越高。
- 抢占优先级和响应优先级的取值表示了优先级的具体数值。通常来说,数值越小表示的是优先级越高。所以,取值越小的优先级级别越高。
抢占优先级和响应优先级中断优先顺序:
- 高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的
- 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断
- 抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行
- 如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
- 优先级数字越小,优先级越高,越先被执行
EXTI简介
- EXTI(Extern Interrupt)外部中断
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒,故而通道数有20个 - 触发响应方式:中断响应/事件响应
EXTI结构
所以在STM32配置的时候,需要先配置gpio,然后再配置AFIO再配置EXTI,完成四个配置。首不要忘记打开时钟线RCC。
EXTI框图
AFIO介绍
AFIO(Alternate Function I/O)是在STM32系列微控制器中的一个重要模块,用于管理芯片的引脚复用和外设功能分配。
主要功能和作用:
-
引脚复用: AFIO 允许用户为每个引脚选择不同的功能。由于微控制器的引脚通常具有多种功能,比如 GPIO、USART、SPI 等,AFIO 可以配置引脚以选择适当的功能。
-
外设功能分配: 有些外设可能需要与多个引脚连接,AFIO 可以配置这些外设与引脚之间的映射关系,以确保外设正常工作。
-
复用模式配置: AFIO 允许配置每个引脚的复用模式,包括推挽、开漏和复用功能选择等。
-
中断和事件控制: AFIO 还可以配置一些特殊的中断和事件触发方式,比如外部中断线的映射、中断触发方式等。
-
Debug 功能: AFIO 也与调试功能相关,可以配置调试接口和引脚。
中断配置步骤
一个中断时的代码初始化
这里使用的是EXTI10-15的,,即10~15共用一个中断,当中断来时,并不确定哪个来的。所以中断函数内需要判断下是不是所需的中断。
#include "stm32f10x.h" // Device headervoid CountSensor_Init(void)
{//看该模块的原理图可知,默认有个上拉电阻,故而通电时端口DO是高电平,也即是B14高电平,低电平指示灯亮RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//配置AFIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//因为EXTI和NVIC的时钟是一直在开着的,所以不需要再开启时钟//配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//设置为上拉,这里看红外对射的电路原理图和GPIO参考手册8.2的EXTI推荐可知可知GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);//配置AFIOGPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); //配置EXTI,打开Library分组/找到stm32f10x_exti.h查看里面的函数EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line14;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt ;//选择事件模式为中断触发EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//设置为下降沿触发EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能EXTI_Init(& EXTI_InitStructure);//NVIC的配置,因为NVIC是内核外设,所以他的库函数被分配到杂项这里了,也即是不misc.h里NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//注意,分组代码在整个芯片中只需要分组一次就行,可以将其放在int main里NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;//从f103.h的md后缀找//因为上面选择了分组二即NVIC_PriorityGroup_2,故而下面的抢占和非抢占都需使用分组二的数值NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//一般优先级在多个中断源同时申请,产生拥挤时才有作用,因为这里中断只有一个,所以设置比较随意NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能NVIC_Init(& NVIC_InitStructure);//初始化}void EXTI15_10_IRQHandler(void) //中断函数 这里的中断函数名字是固定的,参考/Start/startup_stm32f10x_md.s里面带有IRQhandeler的写法
{//因为这个中断,15~10的中断都能进来,所以需要判断下是不是14的进来的。//这时候需要打开exti.h查看EXTI_GetITStatus()函数来判断if(EXTI_GetITStatus(EXTI_Line14)==SET)//14的响应,这里为什么判断==SET看EXTI_GetITStatus函数的说明,他的返回值是SET和RESET{//中断结束后,一定一定要调用一下清除中断标志位的函数。因为上面中断标志位置1了,就会进入中断函数,如果不清除,就会一直申请中断EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志位}}
两个中断时的代码和错误提示
*//切记,不要这样写:因为对端口1使能会把端口0的值覆盖掉,这样只配置了1端口。 //要一个一个的配置,这里NVIC_Init( &NVIC_InitStruct);就把值传递过去了,然后再配置端口0,也就是分开配置,单独配置0的和1的。这样是没有任何问题的,下方给出错误的代码:
错误的代码:
NVIC_InitStructure.NVIC_IRQChannel= EXTI0_IRQn ;//对0端口进行配置NVIC_InitStructure.NVIC_IRQChannel= EXTI1_IRQn ;//对1端口,NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_Init(&NVIC_InitStructure);
正确配置如下:
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;//正反转计次,有正转和反转,所以定义的类型为int16_t有符号类型/*** @brief 旋转编码器初始化/中断* @param 无 * @retval 无*/
void Encoder_Init(void)
{//GPIO时钟配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//AFIO使能配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//GPIO配置GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//配置为上拉输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1;//配置端口GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);//AFIO配置,注意,此处也要分开配置,不要://GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0|GPIO_PinSource1);//GPIO_EXTILineConfig() 函数用于将指定的 GPIO 端口引脚映射到外部中断线上,//但是在一个函数调用中只能映射一个引脚到一个外部中断线上。//结构体能或是因为他不是传递的参数,函数传参或了以后传递的是两个数或操作之后的值,而不是传了两个参数//如果想要将 GPIOB 的 Pin 0 和 Pin 1 映射到外部中断线上,你需要分别调用 GPIO_EXTILineConfig() 函数两次GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//EXTI配置EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line0|EXTI_Line1;EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//选择中断触发EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发EXTI_Init(& EXTI_InitStructure);//NVIC初始化//中断分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//切记,不要这样写:因为对端口1使能会把端口0的值覆盖掉,这样只配置了1端口。//要一个一个的配置,这里NVIC_Init( &NVIC_InitStruct);就把值传递过去了,然后再配置端口0,这样是没有任何问题的/*NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel= EXTI0_IRQn ;//对0端口进行配置NVIC_InitStructure.NVIC_IRQChannel= EXTI1_IRQn ;//对1端口,NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_Init(&NVIC_InitStructure);*/// 对0端口进行配置NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级NVIC_Init(&NVIC_InitStructure);// 对1端口进行配置NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//数值越大,优先级越小NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_Init(&NVIC_InitStructure);//记得写,一定别忘记了,切记切记}/*** @brief 计次函数,正转计次++,反转计数--* @param 无 * @retval temp:次数*/int16_t Encoder_GetCount(void)
{//不清零的话,如果第二次调用这个函数,就是从目前的值开始加减int16_t temp;temp=Encoder_Count;Encoder_Count=0;return temp;
}/*** @brief EXTI0中断函数* @param 无 * @retval 无*/
void EXTI0_IRQHandler(void)//函数名固定的,在startup/start_stm32f10x_md.h可找到函数名
{if(EXTI_GetITStatus(EXTI_Line0)==SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)//反转{Encoder_Count--;}EXTI_ClearITPendingBit(EXTI_Line0);//清除标志位}}/*** @brief EXTI1中断函数 * @param 无 * @retval 无*/
void EXTI1_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line1)==SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)==0)//正转 当A口触发下降沿,此时看B口电平状态{Encoder_Count++;}EXTI_ClearITPendingBit(EXTI_Line1);//清除标志位}}```c
//在主函数里用:
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);//清除中断标志位
完结撒花,来支持吧^ o ^----------------------------------------(doge)