首页 > 其他分享 >05 详细的中断讲解

05 详细的中断讲解

时间:2024-07-28 22:50:33浏览次数:14  
标签:配置 05 中断 InitStruct NVIC 讲解 GPIO EXTI

目录

前言

又鸽了几天的文章,最近在做一个手表项目,这个项目用到了很多知识,特别是中断的知识特别的多,所以这一篇文章来讲讲外部中断,等下一章说一下内部中断。

一、什么是中断

例如你现在在搞一个项目,然后突然看到你自己写的便条,上面写着今天该写文章了,然后你就会停下手中的项目,转去写文章,当文章写完后又继续的做项目,这个过程就是一个中断。

也就是说中断就是一个打断当前执行的任务,转去执行另外一个任务,当这个任务执行完成后就会返回执行被打断的任务。

img

上面就是中断执行的一个逻辑。

二、如何使用中断

上面我们简单的了解了一下中断,大概了解了中断是需要外部信号的,但是光有外部信号进来也不行,我们还需要配置内部,就比如上面举的例子,你看到了便条,但是你忘记这件事了,是不是就会忽略掉那个便条,stm32也是一样,你设置了外部的信号,但是你没设置stm32内部,让它知道这个信号是中断,这样是没用的,所以我们要对stm32进行配置。

在配置之前首先要了解stm32处理中断的硬件环境,这样我们才知道如何配置。

1.stm32中断结构

img

因为我们这里讲的是外部中断,在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.书写中断服务函数

这里中断服务函数的函数名不是想叫什么就可以叫什么的,而是在启动文件中已经标注好了的,我们只需要给它拿下来就可以了

img

这里可以看到很多,我们找在NVIC配置好的那个IRQ通道的名称的就可以了,比如是EXTI2_IRQn,那这里我们要的是EXTI2_IRQHandler,一样的道理,这里就以这个EXTI2_IRQHandler举例子,那我们开始写函数:

void EXTI2_IRQHandler(void)
{

}

这样中断函数就写好了,现在就是需要在里面填写内容了,内容一般是自己来规定的,但是有一些内容需要写好,首先是判断中断标志位,要判断一下是不是这个引脚触发的,函数如下:

EXTI_GetITStatus(EXTI_Linex);

EXTI_Linex是设置的通道,然后这个函数的返回值是SETRESET,当触发时返回SET,没触发时返回RESET

然后就是清除中断标志位,要手动进行一次复位,使得下一次能够正常的进行触发,函数如下:

EXTI_ClearITPendingBit(EXTI_Linex);

我们在中断服务函数中就先写好这样的代码:

void EXTI2_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Linex) == SET)
    {
        // 你自己的代码

        // ===========
        EXTI_ClearITPendingBit(EXTI_Linex);
    }
}

中断服务函数可以写在任意的位置,不需要在main函数中进行调用,这个中断函数的调用又系统来进行。

总结

在中断函数中尽量不要执行一些耗费大量时间的内容,我一般是使用变量来进行控制,控制变量然后在main函数中判断这个变量的值,然后才执行相应的操作,一般我用中断来进行按键的控制,因为这个效率比较高,但是抖动问题还是比较严重的,可以用做其他的地方。

标签:配置,05,中断,InitStruct,NVIC,讲解,GPIO,EXTI
From: https://www.cnblogs.com/Lavender-edgar/p/18329053

相关文章

  • zzuli1057: 素数判定
    题目描述输入一个正整数n,判断n是否是素数,若n是素数,输出”Yes”,否则输出”No”。注意:1不是素数。输入输入一个正整数n(n<=1000)输出如果n是素数输出"Yes",否则输出"No"。输出占一行。样例输入2样例输出Yes本题考察求素数的方法,我在文章结束列举了3种方法,以......
  • zzuli1055: 兔子繁殖问题
    题目描述这是一个有趣的古典数学问题,著名意大利数学家Fibonacci曾提出一个问题:有一对小兔子,从出生后第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子。按此规律,假设没有兔子死亡,第一个月有一对刚出生的小兔子,问第n个月有多少对兔子?输入输入月数n(1<......
  • 05_BigDecimak类
    一、前言publicclassdemo01{publicstaticvoidmain(String[]args){doubled1=1.0;doubled2=0.9;System.out.println(d1-d2); }}上述代码的结果为0.09999999999999998,出现这个结果是因为在java中"double"类型是浮点数,由于浮点......
  • LeetCode1005. K 次取反后最大化的数组和
    题目链接:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/题目叙述:给你一个整数数组nums和一个整数k,按以下方法修改该数组:选择某个下标i并将nums[i]替换为-nums[i]。重复这个过程恰好k次。可以多次选择同一个下标i。以这种......
  • 【linux】【设备树】具有 GPIO 控制器和连接器的硬件配置的备树(Device Tree)代码讲解
    具有GPIO控制器和连接器的硬件配置的备树(DeviceTree)代码讲解背景-学习Linux设备树代码soc{soc_gpio1:gpio-controller1{#gpio-cells=<2>;};soc_gpio2:gpio-controller2{#gpio-cells=<2>;};};connector:connect......
  • luogu P1896 [SCOI2005] 互不侵犯 题解
    luoguP1896[SCOI2005]互不侵犯题解题目传送门思路状态压缩dp。状态压缩dp对于每一行,用一个\(n\)位二进制数表示每行的状态,则对于上下两行之间,设上行的数字为\(a\),下行的数字为\(b\),状态不合法有三种情况:\(a\operatorname{and}b\neq0\),即存在上行与下行同......
  • 视频解码基础讲解
    视频解码流程视频解码的具体步骤如下:查找指定的解码器avcodec_find_decoder根据指定的解码器ID初始化相应裸流的解析器av_parser_init分配解码器上下文avcodec_alloc_context3打开解码器和关联解码器上下文avcodec_open2读取原始裸流fread解析出一个完整的数据包av_p......
  • 音频解码基础讲解
    音频解码流程音频解码的总体流程如下:输入音频格式(例如AAC)通过音频解码器进行解码得到PCM数据FFmpeg解码流程音频解码的具体步骤如下:查找指定的解码器avcodec_find_decoder根据指定的解码器ID初始化相应裸流的解析器av_parser_init分配解码器上下文avcodec_alloc_conte......
  • GYM105139C Lili Likes Polygons
    记矩形的并为\(P\),定义多边形的大小为它的顶点个数\(|P|\),其\(90\)°的顶角为凸角,\(270\)°的顶角为凹角并记凹点构成的集合为\(C\),称竖直或水平在多边形内部分割开矩形的线为割线,连接了两个凹点的割线为好割线贪心可以发现对于\(P\)的任意极小矩阵划分,所有的割线至少有一......
  • 【C++进阶学习】第九弹——哈希的原理与实现——开放寻址法的讲解
    前言:在前面,我们已经学习了很多存储机构,包括线性存储、树性存储等,并学习了多种拓展结构,效率也越来越高,但是是否有一种存储结构可以在大部分问题中都一次找到目标值呢?哈希可能能实现目录一、哈希的概念二、哈希冲突三、哈希冲突解决3.1开放寻址法节点结构插入操作查......