(七)STM32 NVIC 中断、优先级管理及 AFIO 时钟的开启

news/2024/2/27 21:45:49

目录

1. 中断相关知识简介

1.1 什么是中断

1.2 什么是内中断、外中断

1.3 什么是可屏蔽中断、不可屏蔽中断

2. CM3 内核中断介绍

2.1 F103系统异常清单

2.2 F103 外部中断清单

3. NVIC 简介 

3.1 NVIC 寄存器简介

3.2 NVIC 相关寄存器的介绍

4. 中断优先级

4.1 优先级定义

​编辑 4.2 优先级分组

4.3 中断编程

5. EXTI —— 外部中断/事件控制器

5.1 STM32 外部中断 EXTI 简介

 5.2 EXTI 功能框图

5.3 中断/事件线

 5.4 EXTI 初始化结构体详解

5.5 外部中断控制实验

6. STM32 什么情况下开始外设复用 AFIO 时钟


1. 中断相关知识简介

1.1 什么是中断

        中断是指处理器运行过程中,出现某些意外情况,CPU 能自动停止正在运行的程序并转入处理新情况的程序(中断服务函数),处理完毕后又返回原被暂停的程序继续运行。

1.2 什么是内中断、外中断

        CPU 内部引发的中断称作内中断,外部引发的中断称为外中断(一般就是STM32片上外设)。而外中断源分为以下两类:可屏蔽中断、不可屏蔽中断。

1.3 什么是可屏蔽中断、不可屏蔽中断

        可屏蔽中断就是 CPU 可以不响应这个中断。CPU 是否要响应这个中断要看标志寄存器中的IF 标志位的值。如果IF标志位等于0,那么 CPU 则不响应这个中断,如果IF标志位为 1 ,CPU 则响应这个中断,所以每次的中断过程中都一个把 IF 设置为 0 的动作,就是让 CPU 在进入中断处理后禁止其他的可屏蔽中断。可屏蔽中断引发的中断过程和内中断的差不多,除第一步获取中断类型码的途径有所不同,内中断的中断类型码是在CPU内部产生的,而外中断的中断类型码是在CPU外部通过数据总线传给CPU的。

我们也有可以设置IF位的指令:  sti  :设置IF为 0 ,cli : 设置IF为 1 。

        不可屏蔽中断就是CPU必须响应的外中断,它的中断类型码固定为 2 ,所以由它引发的中断过程中没有取得中断类型码的那一步。不过后面三步还是一样的。大多数由外设引发的中断都是可屏蔽中断

2. CM3 内核中断介绍

        CM3 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256级的可编程中断设置。但 STM32 并没有使用 CM3 内核的全部东西,而是只用了它的一部分
        STM32 有 84 个中断,包括 16 个内核中断和 68 个可屏蔽中断,具有 16 级可编程的中断优先级(为什么是16级,在 4.1 中有介绍)。
        而我们常用的就是这 68 个可屏蔽中断,但是 STM32 的 68 个可屏蔽中断,在 STM32F103 系列上面,又只有 60 个(在 107 系列才有 68 个)。因为我们开发板选择的芯片是 STM32F103 系列的所以我们就只针对 STM32F103 系列这 60 个可屏蔽中断进行介绍。

        异常就是中断,中断就是异常,请不要刻意钻牛角尖较劲。在下文中体现。

        F103 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中系统异常有 8 个(如果把 Reset 和 HardFault 也算上的话就是 10 个),外部中断有 60 个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。有关具体的系统异常和外部中断可在标准库文件 stm32f10x.h 这个头文件查询到,在 IRQn_Type 这个结构体里面包含了 F103 系列全部的异常声明。

2.1 F103系统异常清单

51d03d3299754f74901d6529a2bb287d.png

在程序中体现,在 stm32f10x.h 文件中可找到这些异常的编号:

4d797003cdf1475988db171b28879e28.png

2.2 F103 外部中断清单

5c5c5baa0ba944589a302ec81e8d935d.png

        在程序中体现,在 stm32f10x.h 文件中可找到这些外部中断的编号,根据宏定义不同的芯片类型,进行不同的中断编号:

7ea88eb3e6544120a440c64151f25d5b.png

3. NVIC 简介 

        NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC 是 Cortex-M3 的 NVIC 的一个子集。

3.1 NVIC 寄存器简介

        在固件库中,NVIC 的结构体定义可谓是颇有远虑,给每个寄存器都预留了很多位,恐怕为的是日后扩展功能。不过 STM32F103 可用不了这么多,只是用了部分而已,具体使用了多少可参考
《Cortex-M3 内核编程手册》-4.3.11:NVIC 寄存器映射。

088455d8ae5547cb9c52dd8576b43800.png

该结构体可在 core_cm3.h 中找见:

9b10799cd354466a9fe91aed425f76c6.png

        在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断ICER 用来失能中断IP 用来设置中断优先级

结构体是声明了,但是我们如何找到 NVIC 控制器的起始地址呢,也就是说这个结构体怎么和地址联系起来?

f93c5eb39e1748c385895939353be16d.png

在对中断初始化时,点击 NVIC_Init(),点击进去,看到 NVIC 的指针访问:

66abe8da5fa34b85ac154c9af8229abb.png点击 NVIC ,在 core_cm3.h 文件中 找到它的宏定义,发现 NVIC_Type 结构体的起始地址:NVIC_BASE

990f41d4a2a0422cb40a3fe5b19ac928.png

点击  NVIC_BASE,看到 NVIC_BASE 的地址是在 SCS_BASE 地址进行偏移得到的

0e1d59a87a6242d6b1969bd6f4f7b44f.png

我们去查看 Cortex-M3 权威指南,NVIC 与中断控制,可以看到 :

fa9ff95c3f9543e5bb40694ba2969aa8.png

         NVIC 嵌套向量中断控制器的起始地址是:0xE000 E100,0xE000 E000 + 0x0100得到。

        因为 NVIC 嵌套向量中断控制器 ,它是一个内核里的外设,所以它的起始地址是和片上外设寄存器组的起始地址是不同的。

3.2 NVIC 相关寄存器的介绍

typedef struct 
{__IO uint32_t ISER[8]; // 中断使能寄存器uint32_t RESERVED0[24];__IO uint32_t ICER[8]; // 中断清除寄存器uint32_t RSERVED1[24];__IO uint32_t ISPR[8]; // 中断使能悬起寄存器uint32_t RESERVED2[24];__IO uint32_t ICPR[8]; // 中断清除悬起寄存器uint32_t RESERVED3[24];__IO uint32_t IABR[8]; // 中断有效位寄存器uint32_t RESERVED4[56];__IO uint8_t IP[240]; // 中断优先级寄存器 (8Bit wide)uint32_t RESERVED5[644];__O uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;

        STM32 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便
的使用 STM32 的中断。下面重点介绍这几个寄存器:

        ISER[8]:ISER 全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。上面
说了 CM3 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是STM32F103 的可屏蔽中断只有 60 个,所以对我们来说,有用的就是两个(ISER[0]和ISER[1]),总共可以表示 64 个中断。而 STM32F103 只用了其中的前 60 位。ISER[0]的bit0~bit31 分别对应中断 0~31。ISER[1]的 bit0~27 对应中断 32~59;这样总共 60 个中断就分别对应上了。你要使能某个中断,必须设置相应的 ISER 位为 1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。具体每一位对应哪个中断,请参考 stm32f10x.h 里面的第 140 行处(针对编译器 MDK5 说)。
        ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ICER 一样。这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。具体为什么这么设计,请看《CM3 权威指南》第 125 页,NVIC 概览一章。
        ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
        ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断接挂。写 0 无效。
        IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
        IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32 只用到了其中的前 60 个。IP[59]~IP[0]分别对应中断 59~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。

4. 中断优先级

4.1 优先级定义

        在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPx,用来配置外部中断的优先级,IP宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数减少,在 F103 中,只使用了高 4bit,如下所示,每个 IP宽度虽然为 8bit,但 F103 中,只使用了高 4 bit:

3fbae04f9b0d467db69e848f15b6dd6c.png

        用于表达优先级的这 4bit,又被分组成抢占优先级子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高

注意:这里的先是比较你的 抢占和子优先级,数值越小,优先级越高;当你的外设,抢占和子优先级都相同的话,这时候比较的是 硬件中断编号,编号越小,优先级越高。这里的硬件中断编号指的是在 stmf103x.h 文件中定义好的:

7ea88eb3e6544120a440c64151f25d5b.png
 4.2 优先级分组

        优先级的分组由内核外设 SCB 的应用程序中断及复位控制这里简单介绍一下 STM32 的中断分组:STM32 将中断分为 5 个组,组 0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表 4.5.1 所示:

13d02bc49af94b6bbdbd0b6bc8f2501c.png        设置优先级分组可调用库函数 NVIC_PriorityGroupConfig() 实现,有关 NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中。

中断优先级分组库函数:NVIC_PriorityGroupConfig()

/**
* 配置中断优先级分组:抢占优先级和子优先级
* 形参如下:
* @arg NVIC_PriorityGroup_0: 0 bit for 抢占优先级
*                             4 bits for 子优先级
* @arg NVIC_PriorityGroup_1: 1 bit for 抢占优先级
*                             3 bits for 子优先级
* @arg NVIC_PriorityGroup_2: 2 bit for 抢占优先级
*                             2 bits for 子优先级
* @arg NVIC_PriorityGroup_3: 3 bit for 抢占优先级
*                             1 bits for 子优先级
* @arg NVIC_PriorityGroup_4: 4 bit for 抢占优先级
*                             0 bits for 子优先级
* @ 注意 如果优先级分组为 0,则抢占优先级就不存在,优先级就全部由子优先级控制
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{// 设置优先级分组SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

9c7b9f403d51497bb9d7ef9bf5f5341e.png

        也就是说,当选择  NVIC_PriorityGroup_0 分组时,无主优先级,优先级全部由子优先级控制,有 0-15 个等级划分;

        当选择  NVIC_PriorityGroup_1 分组时,主优先级有0-1两级划分,子优先级有 0-7 个等级划分;

        注意:NVIC_PriorityGroupConfig() 是整个程序中只需要设置一次,并且从代码布局逻辑来说,NVIC_PriorityGroupConfig() 适合放在 main() 函数中。程序在进入main函数中时,首先进行中断分组,这样只需要一次分组,后续在其他中断配置中,无需再进行分组。一般配置为:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)。

4.3 中断编程

在配置每个中断的时候一般有 3 个编程要点:
        1. 使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
        2. 初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。

typedef struct
{uint8_t NVIC_IRQChannel; // 中断源uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级uint8_t NVIC_IRQChannelSubPriority; // 子优先级FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能
} NVIC_InitTypeDef;

有关 NVIC 初始化结构体的成员我们一一解释下:
        a. NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考 stm32f10x.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
        b. NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 优先级分组真值表。
        c. NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 优先级分组真值表。
        d. NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。

        3. 编写中断服务函数
        在启动文件 startup_stm32f10x_hd.s 中我们预先为每个中断都写了一个中断服务函数,只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写,为了方便管理我们把中断服务函数统一写在 stm32f10x_it.c 这个库文件中。一般我们都直接写在初始化外设的函数后边
        关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数,并且在里面无限循环,实现不了中断。

5. EXTI —— 外部中断/事件控制器

5.1 STM32 外部中断 EXTI 简介

        EXTI(External interrupt/event controller)— 外部中断/事件控制器,管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
        这里我们首先知道 STM32 IO 口中断的一些基础概念。STM32 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103 的19 个外部中断为:

  •         线 0~15:对应外部 IO 口的输入中      断。
  •         线 16:连接到 PVD 输出。
  •         线 17:连接到 RTC 闹钟事件。
  •         线 18:连接到 USB 唤醒事件。

        从上面可以看出,STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止 16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一一对应起来的呢?于是 STM32 就这样设计,GPIO 的管教 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。下面我们看看 GPIO 跟中断线的映射关系图:

28615d5e3dfb4e48af65908a2f7c8a8b.png

 5.2 EXTI 功能框图

        EXTI 的功能框图包含了 EXTI 最核心内容,掌握了功能框图,对 EXTI 就有一个整体的把握,在编程时思路就非常清晰。EXTI 功能框图见图 EXTI 功能框图。
        在图 EXTI 功能框图 可以看到很多在信号线上打一个斜杠并标注 “20” 字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 19 个线路原理也就知道了。

55778ba2965049608f7ae3d9d4676b34.png

        EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
        首先我们来看图 EXTI 功能框图中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内。

产生中断:
        编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线(对应上边的线0-18),这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输入线一般是存在电平变化的信号。
        编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器 (EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而 EXTI_RTSR 和EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
        编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器 (EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线(也就是说,不需要实际的线上产生信号跳变触发中断/事件,也可以通过程序控制触发中断/事件),这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1就可以输出 1 给编号 4 和编号 6 电路。
        编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果 EXTI_IMR设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR) 内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 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 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。
        产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。另外,EXTI 是在 APB2 总线上的,在编程时候需要注意到这点

5.3 中断/事件线

        EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至 EXTI15,还有另外七根用于特定的外设事件,见表 EXTI 中断 _ 事件线。
        4 根特定外设中断/事件线由外设触发,具体用法参考《STM32F10X-中文参考手册》中对外设的具体说明。

e817ff734bf749b285579c7b00f35d61.png

        EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。由表 EXTI 中断 _ 事件线可知,EXTI0 可以通过 AFIO 的外部中断配置寄存器 1(AFIO_EXTICR1) 的 EXTI0[3:0] 位选择配置为 PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或者 PI0,见图 EXTI0 输入源选择。其他 EXTI 线 (EXTI 中断/事件线) 使用配置都是类似的。

a0ddcdf5b40242d794a9cf4e26daa987.png

 5.4 EXTI 初始化结构体详解

        标准库函数对每个外设都建立了一个初始化结构体,比如 EXTI_InitTypeDef、NVIC_InitTypeDef、GPIO_InitTypeDef、USART_InitTypeDef 等等,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init()、NVIC_Init()、GPIO_Init()、USART_Init() 调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
        初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f10x_exti.h 文件中,初始化库函数定义在 stm32f10x_exti.c 文件中,编程时我们可以结合这两个文件内注释使用。

1467d679f28840ff8f230faaac0f5f90.png

        1) EXTI_Line:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,可参考表 EXTI 中断 _ 事件线选择。
        2) EXTI_Mode:EXTI 模式选择,可选为产生中断 (EXTI_Mode_Interrupt) 或者产生事件
(EXTI_Mode_Event)。
        3) EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 (EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
        4) EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。

5.5 外部中断控制实验

        中断管理分组管理在前面有详细的阐述。这里我们将介绍 STM32 外部 IO 口的中断功能,通过中断的功能,达到实验的效果,即:通过板载的 3 个按键,控制板载的两个 LED 的亮灭以及蜂鸣器的发声。这章的代码主要分布在固件库的 stm32f10x_exti.h 和 stm32f10x_exti.c 文件中。

main.c

/* 外部中断测试实验 */
void exit_test(void)
{//优先级分组,数值越小,优先级越高,在misc.h中最后找//优先级分组不同,抢占优先级和子优先级数目也不同,一般选2组//因为抢占优先级有2位(0-3),子优先级有2位(0-3)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init();     //初始化延时函数beep_init();      //初始化beepkey_init();       //初始化按键led_init();       //初始化LEDexit_init();      //初始化外部中断函数usart1_init(115200);//初始化串口1函数while (1){printf("OK\r\n");delay_ms(1000);}
}

exit.c

#include "exit.h"
#include "key.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "sys.h"//①初始化IO口为输入。
//     GPIO_Init();
//②开启IO口复用时钟。
//     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//③设置IO口与中断线的映射关系。
//     void GPIO_EXTILineConfig();
//④初始化线上中断,设置触发条件等。
//     EXTI_Init();
//⑤配置中断分组(NVIC),并使能中断。
//     NVIC_Init();
//⑥编写中断服务函数。
//     EXTIx_IRQHandler();
//     EXTI_ClearITPendingBit();该函数写在中断服务函数中,中断函数执行完后,用来清除中断标志位void exit_init(void)
{EXTI_InitTypeDef  EXTI_InitStructure;NVIC_InitTypeDef  NVIC_InitStructure;/*初始化外部中断线所对应的IO配置*/key_init();/*开启外部中断配置寄存器AFIO的时钟AFIO*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);/*--------------------------KEY0配置-----------------------------*//* 选择EXTI的信号源 */GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4); //PE4  IO口映射到外部中断线4(KEYO)EXTI_InitStructure.EXTI_Line = EXTI_Line4;/* 使能中断 */EXTI_InitStructure.EXTI_LineCmd = ENABLE;/* EXTI为中断模式 */EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;/* 下降沿中断 */EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure);/* 配置中断源:EXTI4 */NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //在stm32f10.h中190行/* 使能中断通道 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;/* 配置抢占优先级 */NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;/* 配置子优先级 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStructure);/*--------------------------KEY1配置-----------------------------*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3); //PE3  IO口映射到外部中断线3(KEY1)EXTI_InitStructure.EXTI_Line = EXTI_Line3;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //在stm32f10.h中190行NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);/*--------------------------WK_UP配置-----------------------------*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //PA0  IO口映射到外部中断线0(WK_UP)EXTI_InitStructure.EXTI_Line = EXTI_Line0;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_Init(&EXTI_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //在stm32f10.h中190行NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);/*--------------------------END OF SECTION-----------------------------*//*-------------------外部中断-- 5-7中断线及中断函数配置-------------------*//*--------------------------GPIOE.5中断配置-----------------------------*///GPIOE.5     中断线以及中断初始化配置 下降沿触发/* 选择EXTI的信号源 */GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);/* 选择EXTI线 */EXTI_InitStructure.EXTI_Line = EXTI_Line5;/* EXTI为中断模式 */EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;/* 下降沿中断 */EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;/* 使能中断 */EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);/*--------------------------GPIOE.6中断配置-----------------------------*///GPIOE.6     中断线以及中断初始化配置 下降沿触发GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource6);EXTI_InitStructure.EXTI_Line = EXTI_Line6;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);/*--------------------------GPIOE.7中断配置-----------------------------*///GPIOE.7     中断线以及中断初始化配置 下降沿触发GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource7);EXTI_InitStructure.EXTI_Line = EXTI_Line7;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);/* 配置中断源:EXTI5-9 */NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;//在stm32f10.h中190行/* 配置抢占优先级 */NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;/* 配置子优先级 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;/* 使能中断通道 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}//值得注意的是EXTI0-4有自己的中断源、中断函数名
//EXTI5-9共用一个中断源EXTI9_5_IRQn、一个中断函数名EXTI9_5_IRQHandler
//EXTI10-15共用一个中断源EXTI15_10_IRQn、一个中断函数名EXTI15_10_IRQHandler
//但EXTI5-15都可以有属于自己的中断,只是中断函数名一样而已/*** @brief  KEY0中断服务函数,翻转红色绿色LED灯的状态* @param  无* @retval 无*/
void EXTI4_IRQHandler(void)//KEY0中断服务函数,中断名字最好不要修改,但可以进行宏定义
{//确保是否产生了EXTI Line中断if (EXTI_GetITStatus(EXTI_Line4) != RESET){delay_ms(20);if (KEY0 == 0){LED0_R = !LED0_R;LED1_G = !LED1_G;}//清除中断标志位EXTI_ClearITPendingBit(EXTI_Line4);}
}/*** @brief  KEY1中断服务函数,翻转红色LED灯的状态* @param  无* @retval 无*/
void EXTI3_IRQHandler(void)//KEY1中断服务函数
{//确保是否产生了EXTI Line中断if (EXTI_GetITStatus(EXTI_Line3) != RESET){delay_ms(20);if (KEY1 == 0){LED0_R = !LED0_R;}//清除中断标志位EXTI_ClearITPendingBit(EXTI_Line3);}
}/*** @brief  WK_UP中断服务函数,翻转绿色LED灯的状态* @param  无* @retval 无*/
void EXTI0_IRQHandler(void)//WK_UP中断服务函数
{//确保是否产生了EXTI Line中断if (EXTI_GetITStatus(EXTI_Line0) != RESET){delay_ms(20);if (WK_UP == 1){LED1_G = !LED1_G;}//清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}
}void EXTI9_5_IRQHandler(void)//5-9上的中断源不管哪个触发都进入中断函数,再进行判断是哪个中断源
{if (EXTI_GetITStatus(EXTI_Line5) != RESET){delay_ms(20);EXTI_ClearITPendingBit(EXTI_Line5); //清除LINE5上的中断标志位}if (EXTI_GetITStatus(EXTI_Line6) != RESET){delay_ms(20);EXTI_ClearITPendingBit(EXTI_Line6); //清除LINE6上的中断标志位}if (EXTI_GetITStatus(EXTI_Line7) != RESET){delay_ms(20);EXTI_ClearITPendingBit(EXTI_Line7); //清除LINE7上的中断标志位}}

注意点:

1)使用外部中断初始化配置时,不仅要配置外部中断 EXTI 外部中断控制器,还要配置 NVIC 嵌套向量中断控制器;

2)每个中断源都有自己的中断函数,值得注意的是EXTI0-4有自己的中断源、中断函数名,而EXTI5-9 共用一个中断源 EXTI9_5_IRQn、一个中断函数名 EXTI9_5_IRQHandler;EXTI10-15共用一个中断源EXTI15_10_IRQn、一个中断函数名EXTI15_10_IRQHandler,但EXTI5-15都可以有属于自己的中断,只是中断函数名一样而已,代码中已经写的很清楚了;

6. STM32 什么情况下开始外设复用 AFIO 时钟

        串口、定时器等,这些都是 STM32 的片上外设,在使用时看作 GPIO 口的一种复用功能。可是在配置这些外设时钟的时候,我们发现,它们只是开启了自己外设的时钟,并没有开启 AFIO 时钟,这是为什么呢?

939d70da7f854506b11d7077a80495e1.png

b11416f7888d4861838091844df6d45b.png        但是,为什么做中断配置时,中断也属于外设,GPIO 的复用,中断除了开启对应的外设时钟,还启了 AFIO 时钟了。

5d4744fcd7f7498cb9a16a13a3e0b0e0.png

针对这个问题,可以参考 STM32 中文参考手册:

991bff1478ee453bb95647ab2fb42fd9.png

ad6c688e08e34d04b93e20005ed532e8.png

        可以看到, 只有当使用 事件控制寄存器、复用重映射和调试寄存器以及外部中断寄存器的时候,才需要提前开启AFIO的时钟,也就是说:当你需要配置 AFIO 这些寄存器的时候,就需要把 RCC_APB2ENR 寄存器的 AFIO 位置‘1’打开 AFIO 时钟。!并不是使用到引脚复用功能 就必须开启AFIO时钟。像定时器、串口这类外设,虽然不需要开启复用时钟,但一定要开启他们自己的相应的外设时钟

        另外,外设确实是引脚功能的一种复用,针对 GPIO 的复用功能,在配置 GPIO 的输出方式时,一定别忘了使用 复用推挽输出 或者 复用开漏输出!!!如下图中的, USART1_TX 的 GPIOA.9 的输出配置,必须使用 复用推挽输出。

397dbc7120364f0b8df6e79f7dfcb7b9.png


http://www.ppmy.cn/news/1279262.html

相关文章

主从复制mysql-replication | Replication故障排除

主从复制mysql-replication 准备环境 #防火墙 selinux systemctl stop firewalld --now &&setenforce 0 #修改主机名:hostnamectl set-hostname 名字 tip:vim /etc/sysconfig/network-scripts/ifcfg-ens33 BOOTPRTOTstatic IPADDR192.168.100.…

Linux命令-查看内存、GC情况及jmap 用法

查看进程占用内存、CPU使用情况 1、查看进程 #jps 查看所有java进程 #top 查看cpu占用高进程 输入m :根据内存排序 topMem: 16333644k total, 9472968k used, 6860676k free, 165616k buffers Swap: 0k total, 0k used, 0k free, 6…

【优化】XXLJOB修改为使用虚拟线程

【优化】XXLJOB修改为使用虚拟线程 新建这几个目录 类&#xff0c; 去找项目对应的xxljob的源码 主要是将 new Thread 改为 虚拟线程 Thread.ofVirtual().name("VT").unstarted 以下代码是 xxljob 2.3.0版本 举一反三 去修改对应版本的代码 <!-- 定…

11-Kafka

1 Kafka Kafka是一个分布式流式数据平台&#xff0c;它具有三个关键特性 Message System: Pub-Sub消息系统Availability & Reliability&#xff1a;以容错及持久化的方式存储数据记录流Scalable & Real time 1.1 Kafka架构体系 Kafka系统中存在5个关键组件 Producer…

如何选择合适的数据库?

数据库常常是应用系统中最大的性能瓶颈。一旦部署到生产环境中&#xff0c;就很难迁移&#xff0c;因此为应用系统选择合适的数据库至关重要。​ 做出正确决定的一个重要部分是知道面临哪些选择。数据库领域在过去几年迅速发生了变化&#xff0c;本文将试图探讨以下几个主题&am…

【ElfBoard】ELF 1 开箱初体验

大家好&#xff0c;我是 Hello阿尔法&#xff0c;最近参与了保定飞凌嵌入式技术有限公司举办的 ElfBoard 共创社招募活动&#xff0c;并有幸成为了一名共创官&#xff0c;官方寄来了一块 ELF 1 开发板&#xff0c;开箱视频看这里 飞凌嵌入式「ElfBoard」开箱体验&#xff01;。…

Huggingface T5模型代码笔记

0 前言 本博客主要记录如何使用T5模型在自己的Seq2seq模型上进行Fine-tune。 1 文档介绍 本文档介绍来源于Huggingface官方文档&#xff0c;参考T5。 1.1 概述 T5模型是由Colin Raffel, Noam Shazeer, Adam Roberts, Katherine Lee, Sharan Narang, Michael Matena, Yanqi…

【Linux笔记】网络操作命令详细介绍

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 前言&#xff1a; 网络操作是Linux系统中常见的任务之一&#xff0c;它涵盖了测试网络连接、配置网络接口、显示网络统计信息以及远程登录和文件传…

ES排错命令

GET _cat/indices?v&healthred GET _cat/indices?v&healthyellow GET _cat/indices?v&healthgreen确定哪些索引有问题&#xff0c;多少索引有问题。_cat API 可以通过返回结果告诉我们这一点 查看有问题的分片以及原因。 这与索引列表有关&#xff0c;但是索引…

【数据结构】五、数组与广义表

目录 一、定义 二、计算数组元素地址 三、稀疏矩阵快速转置 稀疏矩阵的表示 稀疏矩阵快速转置 四、广义表 一、定义 我们所熟知的一维、二维数组的元素是原子类型。广义表中的元素除了原子类型还可以是另一个线性表。当然所有的数据元素仍然属于同一类型。 这里的数组可…

PIC单片机项目(8)——基于PIC16F877A的温度光照检测装置的protues仿真

1.功能设计 使用PIC16F877A单片机&#xff0c;进行温度检测、光照检测。温度使用的是DS18B20&#xff0c;光照检测直接利用的AD转换。 光照太暗就开灯&#xff0c;温度太高就开风扇。温度阈值和光照阈值都实时显示在LCD1602屏幕上面。 完成了protues仿真。文件里面包含代码和仿…

闪存驱动器与机械硬盘与固态硬盘

目录 U盘&#xff08;闪存驱动器&#xff09; 固态硬盘&#xff08;SSD&#xff09; 机械硬盘&#xff08;HDD&#xff09; 目前主流 U盘&#xff08;闪存驱动器&#xff09; U盘是便携式存储设备&#xff0c;内部采用闪存芯片作为存储介质。它们通常具有小巧轻便的外形&am…

系列十四、SpringBoot + JVM参数配置实战调优

一、SpringBoot JVM参数配置实战调优 1.1、概述 前面的系列文章大篇幅的讲述了JVM的内存结构以及各种参数&#xff0c;今天就使用SpringBoot项目实战演示一下&#xff0c;如何进行JVM参数调优&#xff0c;如果没有阅读过前面系列文章的朋友&#xff0c;建议先阅读后再看本篇文…

八股文打卡day6——计算机网络(6)

面试题&#xff1a;GET请求和POST请求的区别 我的回答&#xff1a; 1.作用不同&#xff1a;GET是用来获取服务器资源的;POST是用来向服务器提交资源的&#xff1b; 2.参数传递方式不同&#xff1a;GET请求参数一般写在URL中的&#xff0c;只能接收ASCII字符&#xff1b;POST的…

面试题 01.01. 判定字符是否唯一(优质解法)

链接&#xff1a;面试题 01.01. 判定字符是否唯一 代码&#xff1a; class Solution {public boolean isUnique(String astr) {//s[i]仅包含小写字母&#xff0c;数据范围小于 32 位&#xff0c;我们可以使用 int 变量的比特位来代替数组// 每个小写字符对应 bitMap 中的一个比…

第二百二十三回

我们在上一章回中介绍了"滚动布局的使用实例"相关的内容&#xff0c;本章回中将介绍自定义百分比布局.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的百分比布局主要指按照一定的百分比来控制组件在屏幕垂直方向的位置&#xf…

09.list 容器

9、list 容器 功能&#xff1a; 将数据进行链式存储 链表&#xff08;list&#xff09;是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的 链表的组成&#xff1a; 链表由一系列结点组成 结点的组成&#xff1a; 一个是存…

C_12练习题答案

一、单项选择题《本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) C 语言中程序的执行是从(D)A. 任意函数开始 B. 程序中的第一个函数开始 c.程序的第一条可执行语句开始 D. main函数开始 2,…

SpringSecurity安全框架 ——认证与授权

目录 一、简介 1.1 什么是Spring Security 1.2 工作原理 1.3 为什么选择Spring Security 1.4 HttpSecurity 介绍&#x1f31f; 二、用户认证 2.1 导入依赖与配置 2.2 用户对象UserDetails 2.3 业务对象UserDetailsService 2.4 SecurityConfig配置 2.4.1 BCryptPasswo…

linux系统和网络(二):进程和系统时间

本文主要探讨linux系统进程和系统相关知识&#xff0c;本博客其他博文对该文章的部分内容有详细介绍 main函数 int main(int argc,char *argv[],char *envp[]); 操作系统下main执行前先执行引导代码,编译连接引导代码和程序连接在一起构成可执行程序,加载器将程序加载到内存中…
最新文章