- 前言
- 一、什么是中断
- 二、如何使用中断
- 1.stm32中断结构
- 1.1 AFIO中断引脚选择
- 1.2 EXTI边缘检测
- 1.3 NVIC优先级配置
- 2.配置stm32的中断
- 1.打开时钟
- 2.配置GPIO口
- 3.配置AFIO控制
- 4.配置EXTI功能
- 5.配置NVIC
- 6.配置完整代码
- 3.书写中断服务函数
- 1.stm32中断结构
- 总结
前言
又鸽了几天的文章,最近在做一个手表项目,这个项目用到了很多知识,特别是中断的知识特别的多,所以这一篇文章来讲讲外部中断,等下一章说一下内部中断。
一、什么是中断
例如你现在在搞一个项目,然后突然看到你自己写的便条,上面写着今天该写文章了,然后你就会停下手中的项目,转去写文章,当文章写完后又继续的做项目,这个过程就是一个中断。
也就是说中断就是一个打断当前执行的任务,转去执行另外一个任务,当这个任务执行完成后就会返回执行被打断的任务。
上面就是中断执行的一个逻辑。
二、如何使用中断
上面我们简单的了解了一下中断,大概了解了中断是需要外部信号的,但是光有外部信号进来也不行,我们还需要配置内部,就比如上面举的例子,你看到了便条,但是你忘记这件事了,是不是就会忽略掉那个便条,stm32也是一样,你设置了外部的信号,但是你没设置stm32内部,让它知道这个信号是中断,这样是没用的,所以我们要对stm32进行配置。
在配置之前首先要了解stm32处理中断的硬件环境,这样我们才知道如何配置。
1.stm32中断结构
因为我们这里讲的是外部中断,在stm32中控制外部中断的是EXTI控制器,所以这里有GPIO口,GPIO口经过AFIO
引脚选择器后选择出引脚转到EXTI
检测器后会到NVIC
中进行排队触发,如果前面有正在执行的中断,NVIC
会根据这个中断的抢占优先级来进行强占,如果没有正常排队;如果同时有两个中断同时触发,会根据它们之间的响应优先级来进行排队响应。
1.1 AFIO中断引脚选择
这个连接这GPIO口的所有引脚,进入了这个器件后,这个器件会将同一个号码的引脚分为一个引脚,比如说PA1、PB1、PC1....分为一个引脚,其他的引脚也是这样的分类,我们配置引脚为中断接受引脚后,AFIO会将这个引脚设置为对应的名字,然后将这个引脚发送给EXTI进行检测和控制,这里可以看到上图有一个16,代表的是只能接受16个引脚。
1.2 EXTI边缘检测
当配置好后,就可以将配置的引脚设置进EXTI中进行配置模式,配置好后进入NVIC进行配置抢占优先级和响应优先级。
1.3 NVIC优先级配置
到NVIC中就开始配置优先级,当优先级配置好后,NVIC就会让配置好的这个中断进行执行。
这样就是一个中断的执行过程,下面就是介绍一下配置了。
2.配置stm32的中断
了解了上面的那些元器件,接下来就是代码的配置实现了。
1.打开时钟
我们在做32的时候要,无论是做什么都需要打开对应的时钟,打开后才能正常的使用。
在上面的硬件介绍中我们了解到,需要打开GPIO的时钟、AFIO的时钟。
为什么不打开EXTI和NVIC的时钟呢?因为EXTI是和GPIO在一起的,当打开了GPIO的时钟,EXTI的时钟就打开了,而NVIC是在内核中的,这个时钟是在单片机启动有时钟时就可以直接使用,所以不用再麻烦的打开时钟进行操作。
AFIO的时钟是属于APB2中的,所以打开时钟的代码也很简单:
RCC_APB2PriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
其中RCC_APB2Periph_GPIOx
是你要开启的GPIO口的时钟,这里就不具体举例子了。
2.配置GPIO口
打开时钟后就可以配置GPIO口了,一般我们使用GPIO口来接受中断信号,所以需要配置GPIO口,配置的方法之前在将IO口的操作时已经讲了,主要是GPIO口该是什么模式来进行接受呢?
其实和输入一样,如果这个按钮是接地,那这个就设置为上拉输入,如果是接电源,那就下拉输入:
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 这里默认为接地
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_x;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOx, &GPIO_InitStruct);
3.配置AFIO控制
配置完成后我们接下来AFIO
控制,了解过AFIO
的都知道,AFIO
是GPIO重映像的一个部件,在32中有一些引脚有一个重映像功能,如果我们需要使用到这个重映像的话就需要使用AFIO
进行重映像配置。
在这也是,其实你可以理解为将接受中断信号的引脚重映像为中断引脚,配置的方法很简单,只需要使用下面的一个语句就可以了:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOx, GPIO_PinSourcex);
其中GPIO_PortSourceGPIOx
是GPIO组,如果要设置为GPIOB的,那这里就写GPIO_PortSourceGPIOB
。
GPIO_PinSourcex
是GPIO口中需要配置的引脚,比如说是12引脚,那这里就填写GPIO_PinSource12
。
4.配置EXTI功能
配置完成AFIO后现在就要来配置EXTI中断控制器了,配置方法其实和GPIO口的配置一样,首先创建一个结构体,然后配置结构体中的内容,然后交给初始化函数就可以了,配置代码如下:
EXTI_InitTypeDef EXTI_InitStruct = {0};
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 设置中断模式
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 使能中断通道
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 触发模式
EXTI_InitStruct.EXTI_Line = EXTI_Linex; // 设置通道
EXTI_Init(&EXTI_InitStruct);
其中EXTI_Linex
是设置通道,比如上面设置12引脚为中断模式,那这里就填写EXTI_Line12
即可。
5.配置NVIC
现在需要配置个NVIC就可以将stm32的中断配置完成了,配置NVIC的步骤也是和配置GPIO一样,但是在设置之前需要给NVIC一个分组,这个分组的作用是设置抢占优先级和响应优先级的范围,每个组的抢占和响应可选的值不一样,下面是分组对应的优先级的范围:
组名 | 抢占优先级 | 响应优先级 |
---|---|---|
NVIC_PriorityGroup_0 | 0~4 | 0 |
NVIC_PriorityGroup_1 | 0~3 | 0~1 |
NVIC_PriorityGroup_2 | 0~2 | 0~2 |
NVIC_PriorityGroup_3 | 0~1 | 0~3 |
NVIC_PriorityGroup_4 | 0 | 0~4 |
配置的代码如下:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这里设置是组二,这个设置NVIC的组只有设置的第一次有用,如果你在其他再次调用了这个函数,组配置的不一样那也不会进行更改,相当于只要执行了一次这个函数就再也修改不了了,设置完成后就可以开始配置NVIC了:
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 选择启用的IRQ通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 设置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; // 设置响应优先级
NVIC_Init(&NVIC_InitStruct);
这里需要注意一下,这里的抢占和响应的优先级如果超过了配置的组中的优先级,那会出问题,不要超过即可。
这里EXTI15_10_IRQn
是设置启用的通道,前面配置的通道需要在这变为IRQ通道,这里可以在源码中进行查看对应的通道,比如说我配置的是2引脚,那么这里写EXTI2_IRQn
。
这里就写完配置中断的代码了,全部的代码如下:
6.配置完整代码
// 开启时钟
RCC_APB2PriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); // 这里需要对应的GPIO口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 配置GPIO口
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 这里默认为接地
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_x; // 这里需要写响应的引脚
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOx, &GPIO_InitStruct);// 配置AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOx, GPIO_PinSourcex); // 这里需要写具体的GPIO口和引脚// 配置EXTI
EXTI_InitTypeDef EXTI_InitStruct = {0};
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 设置中断模式
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_Line = EXTI_Linex; // 这里需要写具体的通道
EXTI_Init(&EXTI_InitStruct);// 配置NVIC组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_x); // 这里要写具体的组// 配置NVIC
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 选择启用的IRQ通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 设置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; // 设置响应优先级
NVIC_Init(&NVIC_InitStruct);
配置完成中断后,stm32知道要执行中断了,但是中断的内容却没有,就比如你现在看到字条了,你知道要写文章了,已经停止做项目的操作了,但是你不知道该写什么文章,所以我们现在要做的就是告诉32,你触发了这个中断后要进行什么操作。
这一步就叫做写中断服务函数,让它接收到这个中断后能转去执行对应的函数和内容。
3.书写中断服务函数
这里中断服务函数的函数名不是想叫什么就可以叫什么的,而是在启动文件中已经标注好了的,我们只需要给它拿下来就可以了
这里可以看到很多,我们找在NVIC配置好的那个IRQ通道的名称的就可以了,比如是EXTI2_IRQn
,那这里我们要的是EXTI2_IRQHandler
,一样的道理,这里就以这个EXTI2_IRQHandler
举例子,那我们开始写函数:
void EXTI2_IRQHandler(void)
{}
这样中断函数就写好了,现在就是需要在里面填写内容了,内容一般是自己来规定的,但是有一些内容需要写好,首先是判断中断标志位,要判断一下是不是这个引脚触发的,函数如下:
EXTI_GetITStatus(EXTI_Linex);
EXTI_Linex
是设置的通道,然后这个函数的返回值是SET
和RESET
,当触发时返回SET
,没触发时返回RESET
。
然后就是清除中断标志位,要手动进行一次复位,使得下一次能够正常的进行触发,函数如下:
EXTI_ClearITPendingBit(EXTI_Linex);
我们在中断服务函数中就先写好这样的代码:
void EXTI2_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Linex) == SET){// 你自己的代码// ===========EXTI_ClearITPendingBit(EXTI_Linex);}
}
中断服务函数可以写在任意的位置,不需要在main函数中进行调用,这个中断函数的调用又系统来进行。
总结
在中断函数中尽量不要执行一些耗费大量时间的内容,我一般是使用变量来进行控制,控制变量然后在main函数中判断这个变量的值,然后才执行相应的操作,一般我用中断来进行按键的控制,因为这个效率比较高,但是抖动问题还是比较严重的,可以用做其他的地方。