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

目录

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系统异常清单

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

2.2 F103 外部中断清单

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

3. NVIC 简介 

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

3.1 NVIC 寄存器简介

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

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

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

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

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

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

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

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

         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:

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

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


 4.2 优先级分组

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

        设置优先级分组可调用库函数 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;
}

        也就是说,当选择  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 跟中断线的映射关系图:

 5.2 EXTI 功能框图

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

        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-中文参考手册》中对外设的具体说明。

        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 中断/事件线) 使用配置都是类似的。

 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 文件中,编程时我们可以结合这两个文件内注释使用。

        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();      //初始化beep
    key_init();       //初始化按键
    led_init();       //初始化LED
    exit_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 时钟,这是为什么呢?

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

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

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

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

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/241409.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

同义词替换器降低论文重复率的最新技术进展

大家好,今天来聊聊同义词替换器降低论文重复率的最新技术进展,希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧: 标题:同义词替换器降低论文重复率的最新技术进展 一、引言 随着学术…

Android : Room 数据库的基本用法 —简单应用_一_入门

1.Room介绍: Android Room 是 Android 官方提供的一个持久性库,用于在 Android 应用程序中管理数据库。它提供了一个简单的 API 层,使得使用 SQLite 数据库变得更加容易和方便。 以下是 Android Room 的主要特点: 对象关系映射…

一个人全干!之后台管理中的搜索区域的展开收缩组件。

后台管理系统中大多数都有列表的搜索,那么用户的需求又需要必要时收缩搜索区域,需要时再展开。 而且怪的是他还需要一些部分不可收缩,不需要的地方才收缩。使用v-if来解决吧又不咋美观,我们还需要一个简单的动画效果。我们先写一…

.NET医院检验系统LIS源码,使用了oracle数据库,保证数据的隔离和安全性

医院检验系统LIS源码,LIS系统全套商业源码 LIS系统实现了实验室人力资源管理、标本管理、日常事务管理、网络管理、检验数据管理(采集、传输、处理、输出、发布)、报表管理过程的自动化,使实验室的操作人员和管理者从繁杂的手工劳…

vue的computed中的getter和setter

vue的computed中的getter和setter 定义getter写法setter写法 定义 computed 中可以分成 getter(读取) 和 setter(设值),一般情况下是没有 setter 的,computed 预设只有 getter,也就是只能读取&a…

【EI会议征稿】第三届电力系统与电力工程国际学术会议(PSPE 2024)

第三届电力系统与电力工程国际学术会议(PSPE 2024) 2024 3rd International Conference on Power System and Power Engineering(PSPE 2024) 第三届电力系统与电力工程国际学术会议(PSPE 2024)于2024年3月29-31日在中国三亚隆重召…

CVPR 2023 三维重建相关必读论文和代码合集

三维重建涉及将二维图像或视频转换为三维模型的过程,这个过程需要应用到多门学科的知识,比如数学、计算机图形学和多视图几何等,学习门槛较高。但尽管如此,三维重建仍然是CV领域的一个热门方向。 目前三维重建技术已经有了广泛应…

Spring Boot 3.x.x Spring Security 6.x.x @PreAuthorize 失效

Spring Boot 3.x.x Spring Security 6.x.x PreAuthorize 失效 背景问题解决备注 背景 最近在搞一个后端项目,登录、接口权限、token认证。 版本 Spring Boot 3.2.0 JDK 21 Spring Security 6.2.0 问题 PreAuthorize 失效,没有走认证。 解决 给PreAu…

金属制造ERP是什么?可以帮助企业解决什么问题

不同的金属有不同的制造工艺和生产工序,有些金属制造企业并不能按照既有的生产计划执行下去,此外有些工艺还可能受到设备或资源等影响造成部分加工流程出现问题,从而导致物料损耗大,产品交期延误等。 另外,有些金属制…

nodejs微信小程序+python+PHP沧州地区空气质量数据分析系统-计算机毕业设计推荐 django

本系统不仅主要实现了注册登录,系统首页,个人中心,用户管理,城市区域管理,空气状况管理,空气质量管理,系统管理,数据爬取,大屏分析等功能,通过这些功能基本可…

Android---Kotlin 学习005

substring 字符串截取。相加与 java,kt 里面的 substring 函数支持 IntRange 类型(表示一个整数范围的类型)的参数,until 创建的范围不包括上限值。 const val NAME "Jimmys friend" fun main(){val index NAME.ind…

fuxploide,一款针对文件上传的Fuzz检测工具

fuxploide,一款针对文件上传的Fuzz检测工具 1.工具概述2.安装3.参数解析4.使用案例1.工具概述 Fuxploider 是一种开源渗透测试工具,可自动检测和利用文件上传表单缺陷。该工具能够检测允许上传的文件类型,并能够检测哪种技术最适合在所需的 Web 服务器上上传 Web Shell 或任…

Xubuntu16.04系统中使用EDIMAX EW-7822UAC无线网卡开启5G自发AP

目录 1.关于 EDIMAX EW-7822UAC2.驱动安装3.查看无线网卡信息3.通过create_ap配置5G自发AP 1.关于 EDIMAX EW-7822UAC 官网介绍 https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_ac1200_dual-band/ew-7822uac/ 详细参数…

HarmonyOS:NativeWindow 开发指导

场景介绍 NativeWindow 是 HarmonyOS 本地平台化窗口,表示图形队列的生产者端。开发者可以通过 NativeWindow 接口进行申请和提交 Buffer,配置 Buffer 属性信息。 针对 NativeWindow,常见的开发场景如下: ● 通过 NativeWindow…

如何在Android中旋转屏幕时避免重新绘制Activity

如何在Android中旋转屏幕时避免重新绘制Activity 在Android开发中,设备旋转通常导致当前活动(Activity)被销毁并重新创建,这可能导致用户界面重置和不必要的资源重新加载。然而,有时我们希望避免这种行为,…

飞天使-linux操作的一些技巧与知识点4-ansible常用的技巧,配置等

文章目录 ansible配置文件的优先级尝试开始进行操作ansible常用模块ansible 的playbook示例安装phpplaybook中变量的引用 ansible yum install -y ansible 测试是否可用 ansible localhost -m ping /etc/ansible/ansible.cfg :主配置文件,配置 ansible…

每日一题,杨辉三角

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]]

基于Java的学习交流论坛系统论文

摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对学习交流信息管理混乱,出错率高,信息安全性差…

thinkphp 中 关联查询 like 查询失效

controller: public function goodsList(){if (request()->isGet()) {//表单验证//调用发布$where [];$goodname $this->request->param(goodname, );if(!empty($goodname)){$where[] [name,like,$goodname];}return $this->logic->goodsList($where, $this-…

el-date-picker 限制选择范围最大为一年,设置快捷选项,设置默认时间

el-date-picker 限制选择范围最大为一年&#xff1a; 主要代码为&#xff1a;:picker-options"pickerOptions" 以及 blur"pickerBlur" <el-date-pickerv-model"transactionTime"type"daterange"style"width: 200px"size…