STM32 中断非常强大,几乎每个外设都可以产生中断,因此这里我们单独来介绍它,为后面介绍外设中断做铺垫。
目录
一、中断的介绍
1.1 中断的概念
1.2 中断优先级:
1.3 中断的嵌套
1.4 内部中断和外部中断
1.5 中断响应和事件响应
1.6 中断的作用和意义
1.6.1 中断的作用
1.6.2 中断的意义
二、STM32 的中断资源
三、STM32中断管理控制器----NVIC
3.1 NVIC的基本概念
3.2 NVIC相关寄存器介绍
3.3 NVIC 中断配置固件库
3.3 NVIC工作原理
3.4 STM32中断优先级基本概念
3.5 STM32中断优先级分组
3.6 如何使用库函数实现中断分组设置以及中断优先级管理
3.6.1设置中断优先级分组函数
3.6.2 对于每个中断如何确定他的抢占优先级和响应优先级
四、 STM32 中断编程(非常重要)
五、外部中断/事件控制器—EXTI
5.1 STM32F4 GPIO外部中断简介
5.2 EXTI简介
5.2.1 概念
5.2.2 组成
5.2.3 工作模式
5.2.4 支持的触发方式
5.2.5 响应的方式
5.3 EXTI 框图
5.4 外部中断/事件线映射
5.5 SYSCFG的介绍
5.6 EXTI 初始化结构体详解
5.7 如何使用中断
5.8 STM32 外部中断的配置步骤
六、 使用库函数配置STM32外部中断输入的步骤(非常重要)
6.1 第一步:使能GPIO时钟和使能SYSCFG时钟
6.2 第二步:初始化 IO 口为输入模式
6.3 第三步:配置SYSCFG,设置 IO 口与中断线的映射关系。
6.4 第四步:初始化线上中断EXTI
6.5 第五步:配置中断分组(NVIC),并使能中断。
6.6 第六步:编写中断服务函数
一、中断的介绍
1.1 中断的概念
中断其实就是当 CPU 执行程序时, 由于发生了某种随机的事件(外部或内部),引起 CPU 暂时中断正在运行的程序, 转去执行一段特殊的服务程序(中断服务子程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程就称为中断,简而言之:打断CPU执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行,就叫中断。引发中断的称为中断源。比如:看电视时突然门铃响,那么门铃响就相当于中断源。中断示意图如下图所示:
关键概念:
- 事件的发出者--------中断源
- 此次事件本身-------中断
中断是⼀个动作 :是由中断源向CPU发出的/申请的
中断是⼀种状态:主程序被CPU暂停了
中断是⼀个功能:⼀个实现特定功能的函数
1.2 中断优先级:
当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧 急的中断源。
1.3 中断的嵌套
当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断 程序,转而去处理新的中断程序,处理完成后依次进行返回。
1.4 内部中断和外部中断
- 内部中断:由CPU内部事件所引起的中断。也称为软件中断、异常。 系统调用。
- 外部中断:由外部设备事件所引起的中断。 IO中断、时钟中断....。
【注意】我们平常说的中断指的是外部中断。
1.5 中断响应和事件响应
- 中断响应:某个事件发生时,触发了中断,打断了正在执行的CPU,让CPU转而处理此事件。
- 事件响应:某个事件发生时,触发了特定的信号,此信号不经过CPU,而是触发了其他外设模块。
核心区分:有无经过CPU
1.6 中断的作用和意义
1.6.1 中断的作用
- 精准控制:在规定的时间内做出相应的操作,如:温度监控
- 紧急处理:检测到故障,需要第⼀时间处理,如:电梯门夹人了
- 数据传输:当不确定数据何时会到来时,可采用中断的方式,如:串口数据接收
1.6.2 中断的意义
提高CPU的工作效率、节省CPU资源、具有处理突发事件的能力-----------中断使得计算机系统具备处理突发事件的能力,提高了CPU的工作效率,如果没有中断系统,CPU就只能按照原来的程序编写的先后顺序,对各个外设进行查询和处理,即轮询的工作方式,轮询方法貌似公平,但实际工作效率却很低,浪费cpu资源,还不能及时响应紧急事件。
简而言之:高效处理紧急程序,不会一直占用CPU资源
二、STM32 的中断资源
M4内核支持256个中断,包含16个系统(内核)中断和240个外部中断。但是芯片厂商⼀般不会把内核的这些资源全部用完。 对于STM32F407具有82个可屏蔽中断通道(外设中断)。10个系统中断。 中断通道:指的是中断源。
三、STM32中断管理控制器----NVIC
3.1 NVIC的基本概念
NVIC 英文全称是 Nested Vectored Interrupt Controller,中文意思就是嵌套向量中断控制器,它属于 M4 内核的一个外设,控制着芯片的中断相关功能。 由于 ARM 给 NVIC 预留了非常多的功能,NVIC支持:256个中断(16内核 + 240外部),支持:256个优先级,允许裁剪!但对于使用 M4 内核设计芯片的公司可能就不需要这么多功能,于是就需要在 NVIC 上裁剪。ST 公司的 STM32F407 芯片内部中断数量就是 NVIC 裁剪后的结果。 Crotex-M4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断。但 STM32 并没有使用 M4 内核的全部东西,而是只用了它的一部分。 STM32F407 芯片支持 82 个可屏蔽中断通道,除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的。每个中断通道(中断源)都拥有16个可编程的优先等级,可对优先级进行分组。如下图所示:
【注意】:NVIC属于内核中的外设,默认时钟是开启的,因此不需要开启时钟;
异常就是中断,中断就是异常,请不要刻意钻牛角尖较劲. 这些中断通道已按照不同优先级顺序固定分配给相应的外部设备。从 STM32F4xx 中文参考手册的中断向量表可以知道具体分配到那些外设,这里只截取一部分,如需了解更详细可参考《STM32F4xx 中文参考 手册》 中断和向量章节内容,中断向量表如下图所示:
上面说到 NVIC 控制着芯片的中断相关功能,那么肯定有很多对应的寄存器, 在固件库 core_cm4.h 文件内定义了一个 NVIC 结构体,里面定义了相关寄存器, 下面就来介绍下NVIC相关寄存器。
3.2 NVIC相关寄存器介绍
我们的STM32F407芯片的所有中断在NVIC的管理下有序的运行着。那只有了解了他的寄存器才能方便使用这些中断。在固件库中,NVIC 的结构体定义如下:
在配置中断时,我们通常使用的只有 ISER、 ICER 和 IP 这三个寄存器, ISER 是中断使能寄存器,ICER 是中断清除寄存器,IP 是中断优先级寄存器。ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。
ISER[8]:ISER 全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组。上面 说了 CM4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是 STM32F407 的可屏蔽中断最多只有 82 个,所以对我们来说,有用的就是两个(ISER[0~3]),总 共可以表示 128 个中断。而 STM32F407 只用了其中的 82 个。ISER[0]的 bit0~31 分别对应中断 0~31;ISER[1]的 bit0~31 对应中断 32~63; ISER[2]的 bit0~16 对应中断 64~81,这样总共 82 个 中断就可以分别对应上了。你要使能某个中断,必须设置相应的 ISER 位为 1,使该中断被使能 (这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。 具体每一位对应哪个中断,请参考 stm32f407xx.h 里面的第 68 行。
ICER[8]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组 与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ISER 一样。 这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄 存器都是写 1 有效的,写 0 是无效的。
IP [240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄 存器组相当重要!STM32F407 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32F407 只用到了其中的 82 个。IP[81]~IP[0]分别对应中断 81~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和响应优先级。抢占优先级在前,响应优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。关于中断优先级控制的寄存器组下面详细讲解。
3.3 NVIC 中断配置固件库
固件库文件 core_cm4.h 的最后,还提供了 NVIC 的一些函数,这些函数遵循 CMSI 规 则,只要是 Cortex-M4 的处理器都可以使用,具体如下:
3.3 NVIC工作原理
3.4 STM32中断优先级基本概念
在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx(在 F407 中,x=0...981) 用来配置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM4 芯片都会精简设计,以致实际上支持的优先级数减少,在 F407 中,只使用了高 4bit,前面说了 STM32F407 芯片支持 82 个可屏蔽中断通道,每个中断通道都具备自己的中断优先级控制字节, STM32F407 用于表达优先级的高 4 位又被分组成抢占优先级和响应优先级,每个中断源都需要被指定这两种优先级。
3.5 STM32中断优先级分组
对于 NVCI 的中断优先级分组:STM32F407 将中断分为 5 个组,组 0~4。该分组的设置是 由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表所示:
特别提示:一个工程中,一般只设置一次中断优先级分组。
通过这个表,我们就可以清楚的看到组 0~4 对应的配置关系,例如优先级分组设置为 3, 那么此时所有的 82 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级, 低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
3.6 如何使用库函数实现中断分组设置以及中断优先级管理
上面我们讲了中断优先级分组是通过IP寄存器进行分组的,那么,我们是基于库函数开发的,又该如何使用库函数进行中断分组设置以及中断优先级管理?NVIC 中断管理函数主要在 misc.c 文件里面。
3.6.1设置中断优先级分组函数
首先要讲解的是中断优先级分组函数 NVIC_PriorityGroupConfig,其函数申明如下:这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分 组确定就最好不要更改。这个函数我们可以找到其实现:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
从函数体可以看出,这个函数唯一目的就是通过设置 SCB->AIRCR 寄存器来设置中断优先级分组,这在前面寄存器讲解的过程中已经讲到。而其入口参数通过双击选中函数体里面的 “IS_NVIC_PRIORITY_GROUP”然后右键“Go to defition of …”可以查看到为:
#define IS_NVIC_PRIORITY_GROUP(GROUP)
(((GROUP) == NVIC_PriorityGroup_0) ||
((GROUP) == NVIC_PriorityGroup_1) || \
((GROUP) == NVIC_PriorityGroup_2) || \
((GROUP) == NVIC_PriorityGroup_3) || \
((GROUP) == NVIC_PriorityGroup_4))
这也是我们上面表 讲解的,分组范围为 0-4。比如我们设置整个系统的中断优先级分组值 为 2,那么方法如下,这样就确定了一共为“2 位抢占优先级,2 位响应优先级”。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
3.6.2 对于每个中断如何确定他的抢占优先级和响应优先级
设置好了系统中断分组,那么对于每个中断我们又怎么确定他的抢占优先级和响应优先级 呢?下面我们讲解一个重要的函数为中断初始化函数 NVIC_Init,其函数申明为:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
其中 NVIC_InitTypeDef 是一个结构体,我们可以看看结构体的成员变量:
typedef struct
{uint8_t NVIC_IRQChannel; uint8_t NVIC_IRQChannelPreemptionPriority;uint8_t NVIC_IRQChannelSubPriority; FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
NVIC_InitTypeDef 结构体中间有四个成员变量,接下来我们一一来看看这些成员变量的含 义。
- NVIC_IRQChannel:定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到每个中断对应的名字。例如 USART1_IRQn。
- NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别。
- NVIC_IRQChannelSubPriority:定义这个中断的子优先级别,也叫响应优先级。
- NVIC_IRQChannelCmd:该中断通道是否使能。
比如我们要使能串口 1 的中断,同时设置抢占优先级为 1,响应优先级位 2,初始化的方法 是:
NVIC_InitTypeDef NVIC_InitStructure;;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口 1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 响应优先级位 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化 NVIC 寄存器
这里我们讲解了中断分组的概念以及设置单个中断优先级的方法。最后我们总结一下中断优先级设置的步骤:
1. 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和响应优先级的分配位数。调用函数为 NVIC_PriorityGroupConfig();
2. 设置所用到的中断的中断优先级别。对每个中断调用函数为 NVIC_Init();
四、 STM32 中断编程(非常重要)
在配置每个中断的时候一般有 3 个编程要点:
第一步:设置中断分组,调用库函数:NVIC_PriorityGroupConfig();
2、使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
3、初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和响应优先级,使能中断请求,调用NVIC_Init()函数进行初始化。
4、编写中断服务函数:配置好中断后如果有触发,即会进入中断服务函数,那么中断服务函数也有固定的函数名,可以在 startup_stm32f40_41xxx.s 启动文件查看,只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写。关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就 在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数, 并且在里面无限循环,实现不了中断。启动文件内中断服务函数名如下:
有关 NVIC 初始化结构体的成员我们一一解释下:
1)NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序不会报错,只会导致不想要中断。具体的成员配置可参考 stm32f4xx.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
2)NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,具体参考表格 优先级分组真值表 。
3)NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 。
4)NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
通过以上讲解相信已经对中断有了了解,并且掌握了如何配置一个外设的中断,接下来我们将介绍如何将 STM32F4 的 IO 口作为外部中断输入。
五、外部中断/事件控制器—EXTI
5.1 STM32F4 GPIO外部中断简介
STM32F4 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32F4 的强大之处。STM32F407 的中断控制器支持 22 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。GPIO作为外部中断输入口的简要框图如下 :先简单看一下,后面针对SYSCFG和EXTI会有详细讲解,NVIC上面已经详细介绍过。
5.2 EXTI简介
5.2.1 概念
EXTI 即是外部中断和事件控制器, 外部中断/事件控制器(EXTI)管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。不错,EXTI也是STM32F4ZGT6的⼀个片上外设。
【注意】:EXTI外设的时钟默认是开启的
5.2.2 组成
它是由23个边沿检测器组成的。每条EXTI线都可以单独配置:选择类型(中断或者事件)、触发方式(上升沿,下降沿或者双边沿触发)、支持软件触发、开启/屏蔽、有挂起状态位。
5.2.3 工作模式
EXTI可以监测指定GPIO⼝的电平信号,当其指定的GPIO⼝产⽣电平变化时,EXTI将⽴即 向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
5.2.4 支持的触发方式
⽀持上升沿、下降沿、双边沿、软件触发。
5.2.5 响应的方式
事件响应 中断响应
5.3 EXTI 框图
EXTI 的功能框图是最直接把有关 EXTI 的知识点连接起来的图,掌握了该图的来龙去脉, 就会对 EXTI 有了一个整体熟悉,编程时候可以得心应手。EXTI 的功能框图如图所示。可以看到很多在信号线上打一个斜杠并标注“23”字样,这个表示在控制器内部类似的信号线路有 23 个,这与 EXTI 总共有 23 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 22 个线路原理也就知道了。
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。 首先我们来看图中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内。
编号 1 是输入线,EXTI 控制器有 23 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。 输入线一般是存在电平变化的信号。
编号 2 是一个边沿检测电路,EXTI 可以对触发方式进行选择,通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路, 否则输出无效信号 0。而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/ 事件线,这在某些地方非常有用。我们知道或门的作用就是有1就为 1,所以这两个输入随便一个有有效信号 1 就可以输出 1 给编号 4 和编号 6 电路。
编号 4 其实就是一个与门电路,一端输入信号线由标号 3 电路输出提供,一端由 中断屏蔽寄存器提供,只有当两者都为有效信号 1,标号 4 电路才会输出有效信 号 1,否则输出无效。这样我们就可以简单的控制中断屏蔽寄存器来实现是否产生中断的目的。当我们把中断屏蔽寄存器设置为 1 时,标号 4 输出就取决于标号 3 电路的输出。标号 4 电路输出的信号会被保存到挂起寄存器内,如果确定标号 4 电路输出为 1 就会把挂起寄存器对应位置 1.
编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一 个脉冲信号。
产生事件线路是在编号 3 电路之后与中断线路有所不同,之前电路都是共用的。编号 6 电路是一个与门,它一个输入编号 3 电路,另外一个输入来自事件屏蔽寄存器 (EXTI_EMR)。如果 EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0, 最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等。
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。另外,EXTI 是在 APB2 总线上的,在编程时候需要注意到这点。
5.4 外部中断/事件线映射
STM32F4 的 EXTI 具有 23 个中断/事件线,对应连接的外设说明如下表 所示:
EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输 入源。从上表可知,STM32F4 的 EXTI 供外部 IO 口使用的中断线有 16 根,但是我们使用的 STM32F407 芯片却远远不止 16 个 IO 口,那么 STM32F4 芯片怎么解决这个问题的呢?因为 STM32F4 芯片每个 GPIO 端口均有 16 个管脚,因此把每个端口的 16 个 IO 对应那 16 根中断线 EXTI0-EXTI15。比如: GPIOx.0-GPIOx.15(x=A,B,C,D,E, F,G,H,I)分别对应中断线 EXTI0-EXTI15,这样一来每个中断线就对应了最多 9 个 IO 口,比如:GPIOA.0、GPIOB.0、GPIOC.0、 GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0、GPIOH.0、GPIOI.0。但是中断线每次只 能连接一个在 IO 口上,这样就需要通过软件配置 SYSCFG 外部中断配置寄存器中的 EXTIx[3:0]位来决定对应的中断线映射到哪个 GPIO 端口上,对 SYSCFG 外部中断配置寄存器的配置函数在 stm32f4xx_syscfg.c 和 stm32f4xx_syscfg.h 中,所以使用到外部中断时需要添加到工程中,EXTI 的 GPIO 映射图如下图所示:
至此,我们可以用下面一张图可以清晰的看到,GPIO作为外部中断输入口的整体框图如下:
5.5 SYSCFG的介绍
System configuration controller,即系统配置控制器,他也是一个外设,主要⽤于管理对可执⾏代码的存储区域的地址重映射、选择以太⽹ PHY 接 ⼝ 以及用于外部中断映射配置. 所对应的结构体如下:
其中有一个寄存器外部中断配置寄存器中的 EXTIx[3:0]位来决定对应的中断线映射到哪个 GPIO 端口上。SYSCFG_EXTICR0~3,配置EXTI中断线0~15对应具体哪个IO口 ,我们将EXTI0 -EXTI15称为中断标志。
特别注意:SYSCFG它也是一个外设,配置SYSCFG寄存器之前要使能SYSCFG时钟
5.6 EXTI 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如 EXTI_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。 初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f4xx_exti.h 文件中, 初始化库函数定义在 stm32f4xx_exti.c 文件中,编程时我们可以结合这两个文件内注释使用。
- EXTI_Line:EXTI 中断/事件线选择/中断通道/中断源,可选 EXTI0 至 EXTI22。
- EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。
- EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下 降沿触发 ( EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
- EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用 (DISABLE)。
5.7 如何使用中断
5.8 STM32 外部中断的配置步骤
六、 使用库函数配置STM32外部中断输入的步骤(非常重要)
6.1 第一步:使能GPIO时钟和使能SYSCFG时钟
首先,我们要使用 IO 口作为中断输入,所以我们要使能相应的 IO 口时钟, EXTI和 NVIC的时钟默认是打开的,因此,我们不需要使能,但是需要使能SYSCFG的时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能 SYSCFG 时钟 这里大家一定要注意,只要我们使用到外部中断,就必须打开 SYSCFG 时钟。
6.2 第二步:初始化 IO 口为输入模式
使用 IO 口作为中断输入,初始化相应的 IO 口为输入模式,具体上拉还是下拉根据具体项目进行分析确定;
6.3 第三步:配置SYSCFG,设置 IO 口与中断线的映射关系。
配置 GPIO 与中断线的映射关系。在库函数中,配置 GPIO 与中断线的映射 关系的函数 SYSCFG_EXTILineConfig ()来实现的:
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);
该函数将 GPIO 端口与中断线映射起来,使用范例是:
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
将中断线 0 与 GPIOA 映射起来,那么很显然是 GPIOA.0 与 EXTI0 中断线连接了。设置好中断 线映射之后,那么到底来自这个 IO 口的中断是通过什么方式触发的呢?接下来我们就要设置该中断线上中断的初始化参数了。
6.4 第四步:初始化线上中断EXTI
中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
下面我们用一个使用范例来说明这个函数的使用:
EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line4;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure); //初始化外设 EXTI 寄存器
上面的例子设置中断线 4 上的中断为下降沿触发。STM32 的外设的初始化都是通过结构体来设 置初始值的,这里就不再讲解结构体初始化的过程了。我们来看看结构体 EXTI_InitTypeDef 的 成员变量:
typedef struct
{ uint32_t EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTITrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
从定义可以看出,有 4 个参数需要设置。
- 第一个参数是中断线的标号,对于我们的外部中断, 取值范围为 EXTI_Line0~EXTI_Line15。这个在上面已经讲过中断线的概念。也就是说,这个 函数配置的是某个中断线上的中断参数。
- 第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。
- 第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触 发 EXTI_Trigger_Rising_Falling。
- 最后一个参数就是使能中断线了。
6.5 第五步:配置中断分组(NVIC),并使能中断。
我们设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置 NVIC 中断分组和优先级。这个在前面已经讲解过,这 里我们就接着上面的范例, 设置中断线 2 的中断优先级。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //响应优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
6.6 第六步:编写中断服务函数
我们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是在 MDK 中事先有定义的。这里需要说明一下,STM32F4 的 IO 口外部中断函数只有 7 个,分别为:
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中 断线 10-15 共用中断函数 EXTI15_10_IRQHandler。
在编写中断服务函数的时候会经常使用到两个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位),这个函数一般使用在中断服务函数的开头判断中断是否发生。
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
另一个函数是清除某个中断线上的中断标志位:这个函数一般应用在中断服务函数结束之前,清除中断标志位。
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
常用的中断服务函数格式为:
void EXTI3_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line3)!=RESET)判断某个线上的中断是否发生 { …中断逻辑… EXTI_ClearITPendingBit(EXTI_Line3); 清除 LINE 上的中断标志位 } }
在这里需要说明一下,固件库还提供了两个函数用来判断外部中断状态以及清除外部状态标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。 只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而 EXTI_GetFlagStatus 直接用来判断状态标志位。
通过以上几个步骤的设置,我们就可以正常使用GPIO外部中断了。
至此,我们的本次的学习就结束了。学习了 STM32F407 的 IO 作为外部输入中断的使用方法, 下一次,我们将应用我们的中断完成一个实验,加深我们对于外部中断的理解。这一节我们就讲解到这里,希望能对大家的开发有帮助。 如有兴趣,感谢点赞、关注、收藏,若有不正地方,还请各位大佬多多指教!