【正点原子STM32】中断(NVIC和EXTI、中断优先级基本概念及分组、中断配置步骤API及相关寄存器、AFIO或SYSCFG、EXTI和IO映射、EXTI的配置步骤、HAL库中断回调处理机制)

一、什么是中断?

二、NVIC

三、EXTI

四、EXTI和IO映射关系

五、如何使用中断

六、通用外设驱动模型(四步法)
七、HAL库中断回调处理机制介绍
八、编程实战:通过外部中断控制一个灯亮灭

九、总结

【模拟电路】门电路-逻辑门

一、什么是中断?

在这里插入图片描述
中断是一种计算机体系结构中的机制,它打断了CPU正在执行的正常程序流程,使其临时转而处理紧急或优先级较高的程序,处理完成后再返回到原先暂停的程序,实现了对事件的及时响应。中断主要用于处理异步事件,例如外部设备的输入、时钟信号等。

中断的基本工作流程如下:

  1. 中断发生: 由于某个事件的发生,例如外部设备的输入、定时器计数溢出等,引发了中断请求。

  2. CPU响应: 当中断请求发生时,CPU会在适当的时机检测到中断请求并作出响应,暂停当前正在执行的指令流程。

  3. 中断处理程序执行: CPU跳转到与中断相关联的中断处理程序,执行与中断相关的操作。

  4. 中断处理: 中断处理程序完成相应的任务,可能涉及对寄存器、内存等状态的保存和恢复,处理完后准备返回。

  5. 恢复执行: 处理完成后,CPU将返回到被中断的程序,继续执行原先暂停的指令流程。

中断机制使计算机能够及时响应外部事件,提高了系统的实时性和处理能力。

中断的作用和意义

在这里插入图片描述
中断在计算机系统中具有重要的作用和意义,主要体现在以下几个方面:

  1. 实时控制: 中断允许系统在确定的时间内对特定事件作出及时响应,这对于实时控制系统非常关键。例如,在温度监控系统中,如果温度超过设定阈值,中断可以立即触发相应的处理程序,采取必要的措施以防止系统过热。

  2. 故障处理: 中断机制允许系统检测到故障并迅速作出响应。在电梯门夹人的情况下,中断可以立即中止正常操作并启动紧急停止程序,以确保乘客的安全。

  3. 数据传输: 对于异步事件,如数据传输,中断可以在数据到达时通知系统,并立即处理接收到的数据。在串口数据接收的例子中,中断可以用来触发数据接收处理程序,确保及时处理接收到的数据而不需要系统持续轮询。

  4. 高效处理紧急程序: 中断允许处理紧急程序而不需要等待当前任务的完成。这提高了系统的效率,因为CPU可以在需要时立即切换到处理中断的任务,而不必等待当前任务的结束。

  5. 不占用CPU资源: 中断允许系统在等待事件发生的同时执行其他任务,而不会一直占用CPU资源。系统可以在需要时响应中断,而在没有中断时执行其他任务,提高了系统的并发性和效率。

综上所述,中断在实时响应、故障处理、异步数据传输等方面发挥着重要作用,使计算机系统更加灵活、高效、可靠。

STM32 GPIO外部中断简图

在这里插入图片描述
外部中断的配置和处理,以下是每个步骤的简要解释:

  1. 外部信号: 通常来自于外部世界的信号,例如传感器的状态变化、按键的按下等。

  2. GPIO(通用输入输出): 外部信号被连接到单片机的GPIO引脚上。在这里,你提到了上拉输入、下拉输入和浮空输入,这是指对GPIO引脚进行不同的配置,以适应不同的电路连接方式。

  3. AFIO(Alternate Function I/O)或 SYSCFG(System Configuration): 这是针对不同系列的STM32微控制器的不同名称。这一步的目的是将GPIO引脚映射到外部中断线 EXTI 上。

  4. EXTI(外部中断): EXTI 模块允许你配置外部中断线,以便在输入信号发生变化时生成中断。你可以设置上升沿触发、下降沿触发等条件。

  5. NVIC(嵌套向量中断控制器): NVIC 是用于配置和控制中断的模块。在这一步,你启用了外部中断,并设置了相应的中断优先级。

  6. CPU(中央处理单元): 当外部中断条件满足时,中断被触发,CPU会暂停当前执行的程序,转而执行与中断相关的处理程序。这可能包括保存当前状态、执行中断服务程序,然后在处理完成后恢复原始状态。

总的来说,这个流程描述了外部信号如何通过GPIO、AFIO/SYSCFG、EXTI、NVIC等模块被引导到CPU中进行处理。这种机制使得嵌入式系统能够对外部事件进行实时响应,是许多嵌入式应用中常见的配置和处理流程。

二、NVIC

2.1、NVIC基本概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

什么是中断向量表?

在这里插入图片描述
中断向量表(Interrupt Vector Table,简称IVT)是嵌入式系统中一块用于存储中断服务函数地址的固定内存区域。它通常是在系统启动时被定义和初始化的,以确保当发生中断时,CPU可以迅速找到并执行相应的中断服务函数。

以下是关于中断向量表的一些要点:

  1. 内存中的位置: 中断向量表通常被放置在内存的固定位置,并且其地址需要按照一定的规则对齐。在许多嵌入式系统中,IVT的起始地址是0x00000000,每个中断的入口地址都以4字节对齐排列。

  2. 存储中断服务函数地址: 中断向量表的主要目的是存储每个中断对应的中断服务函数的首地址。每个中断都有一个唯一的位置(向量),其地址保存在中断向量表中。当相应的中断被触发时,CPU会从中断向量表中获取对应中断的地址,并跳转到该地址执行中断服务函数。

  3. 启动文件和初始化: 中断向量表的定义和初始化通常是由系统的启动文件(startup file)负责的。在启动过程中,这个文件会设置中断向量表的内容,将每个中断的服务函数地址写入到相应的位置。

  4. 自动执行中断服务函数: 当中断发生时,CPU会自动根据中断向量表中的地址跳转到相应的中断服务函数,开始执行中断处理。这种自动执行的机制使得系统能够及时响应中断事件。

总体而言,中断向量表是一种组织和存储中断服务函数地址的结构,它在嵌入式系统中是确保中断处理高效、准确的关键组成部分。

2.2、NVIC相关寄存器介绍

在这里插入图片描述
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是一种用于管理中断的硬件模块,常见于ARM Cortex-M微控制器架构中。以下是与 NVIC 相关的一些寄存器:

  1. 中断使能寄存器(ISER):

    • 位数:32
    • 寄存器个数:8
    • 备注:每个位对应一个中断,用于启用相应中断。当某一位被设置为1时,表示相应的中断被使能。
  2. 中断除能寄存器(ICER):

    • 位数:32
    • 寄存器个数:8
    • 备注:每个位对应一个中断,用于禁用相应中断。当某一位被设置为1时,表示相应的中断被禁用。
  3. 应用程序中断及复位控制寄存器(AIRCR):

    • 位数:32
    • 寄存器个数:1
    • 备注:AIRCR 寄存器中的位[10:8]用于控制优先级分组,即确定中断优先级的位数。不同的 ARM Cortex-M 架构可能支持不同的分组方式。
  4. 中断优先级寄存器(IPR):

    • 位数:8
    • 寄存器个数:240
    • 备注:每个中断有一个对应的 8 位寄存器,但在实际应用中,STM32 只使用了高 4 位。这些位用于设置中断的优先级,其中数值越小,优先级越高。

这些寄存器一起协作,允许程序员配置中断的优先级、使能或禁用中断,并对中断进行管理。通过这些寄存器,开发人员可以有效地控制系统对中断的响应,确保对重要事件的及时处理。

2.3、NVIC工作原理

在这里插入图片描述
嵌套向量中断控制器(NVIC)是一种用于管理中断的硬件模块,主要用于 ARM Cortex-M 架构的微控制器。以下是 NVIC 的基本工作原理:

  1. 中断向量表(Interrupt Vector Table,IVT): 在系统启动时,中断向量表被初始化并加载到内存的固定位置。IVT 存储了每个中断的入口地址,即中断服务函数的地址。

  2. 中断触发: 当某个外部事件触发了一个中断条件,例如按键被按下、定时器溢出等,相应的中断请求信号被发送到 NVIC。

  3. 中断请求处理: NVIC 接收到中断请求后,首先检查中断控制寄存器(Interrupt Control Register,ICR)。ICR 存储了当前正在处理的中断以及相关的信息。

  4. 中断优先级判断: NVIC 根据中断优先级判断是否需要中断嵌套。如果当前没有处理其他中断,或者新中断的优先级高于当前正在处理的中断,那么 NVIC 将接受新中断。

  5. 中断嵌套处理: 如果中断允许嵌套(Nested Interrupts),NVIC 将保存当前中断的状态,并跳转到相应的中断服务函数。在服务函数执行期间,如果发生了更高优先级的中断,系统可以中断当前服务函数,处理更高优先级的中断。

  6. 中断服务函数执行: 中断服务函数是用户编写的代码,用于处理特定的中断事件。在服务函数执行完毕后,控制返回到 NVIC。

  7. 中断结束和状态恢复: NVIC 根据中断处理结束后的状态信息,可能会恢复之前被保存的状态(如果发生了嵌套中断),然后继续执行先前中断被中断的地方。

  8. 中断清除: 在中断服务函数执行完成后,如果不再需要处理相同的中断,需要清除相应的中断标志。这通常涉及到特定的寄存器或标志位的操作。

总体来说,NVIC 负责协调中断的优先级、中断服务函数的调用和中断嵌套的处理,确保系统对多个中断事件的有效管理和响应。这种嵌套中断的机制允许系统在处理一个中断时能够及时响应更高优先级的中断,提高了系统的实时性。

2.4、STM32中断优先级基本概念

在这里插入图片描述
中断优先级是指在一个系统中,不同中断之间的执行顺序和竞争关系。在许多嵌入式系统中,中断优先级通常包括抢占优先级(Preemption Priority)和响应优先级(Subpriority)。以下是中断优先级的基本概念:

  1. 抢占优先级(Preemption Priority,pre): 抢占优先级决定了中断在发生时是否可以打断正在执行的其他中断或任务。高抢占优先级的中断可以打断低抢占优先级的中断。在同一抢占优先级下,响应优先级决定执行的顺序。

  2. 响应优先级(Subpriority,sub): 当两个中断具有相同的抢占优先级时,响应优先级用于决定哪个中断先执行。高响应优先级的中断先执行,但它们不会相互打断。在同一响应优先级下,自然优先级决定执行的顺序。

  3. 自然优先级: 自然优先级是中断向量表中每个中断对应的优先级。在没有特别设置抢占和响应优先级的情况下,自然优先级越高的中断会先执行。

  4. 数值越小表示优先级越高: 中断优先级通常使用一个数值表示,数值越小表示优先级越高。例如,在STM32中,优先级值为0表示最高优先级,而值越大表示优先级越低。

总的来说,中断优先级的设置和管理是为了确保系统能够在多个中断事件发生时有序执行,并根据任务的紧急程度和系统需求进行灵活配置。这有助于提高系统的实时性和响应能力。
在这里插入图片描述

2.5、STM32中断优先级分组

在这里插入图片描述
在 ARM Cortex-M 架构的微控制器中,中断优先级分组是通过 AIRCR 寄存器的[10:8]位来配置的。这个配置决定了中断向量表中每个中断的抢占优先级和响应优先级的分配方式。下面是各个优先级分组的配置及其分配结果:

  1. 优先级分组 0:

    • AIRCR[10:8] = 111
    • IPRx bit[7:4]分配:None(即[7:4]都用于抢占优先级)
    • 分配结果:0位用于抢占优先级,4位用于响应优先级
  2. 优先级分组 1:

    • AIRCR[10:8] = 110
    • IPRx bit[7]分配:[6:4](1位用于响应优先级,其余用于抢占优先级)
    • 分配结果:1位用于抢占优先级,3位用于响应优先级
  3. 优先级分组 2:

    • AIRCR[10:8] = 101
    • IPRx bit[7:6]分配:[5:4](2位用于响应优先级,其余用于抢占优先级)
    • 分配结果:2位用于抢占优先级,2位用于响应优先级
  4. 优先级分组 3:

    • AIRCR[10:8] = 100
    • IPRx bit[7:5]分配:[4](3位用于响应优先级,其余用于抢占优先级)
    • 分配结果:3位用于抢占优先级,1位用于响应优先级
  5. 优先级分组 4:

    • AIRCR[10:8] = 011
    • IPRx bit[7:4]分配:None(即[7:4]都用于抢占优先级)
    • 分配结果:4位用于抢占优先级,0位用于响应优先级

这些优先级分组的设置允许开发者根据系统需求和中断的重要性进行灵活的配置,确保中断系统能够满足特定应用场景的实时性和响应性。

STM32中断优先级举例(假设分组是2)

2.6、STM32 NVIC的使用

在这里插入图片描述
NVIC(嵌套向量中断控制器)的使用通常包括设置中断分组、设置中断优先级和使能中断这几个步骤。在一些嵌入式系统中,特别是使用 HAL(Hardware Abstraction Layer)库的系统中,相关的函数通常被提供以简化这些操作。

以下是使用 NVIC 的一般步骤:

  1. 设置中断分组(Set Interrupt Priority Grouping):

    • 使用 HAL_NVIC_SetPriorityGrouping 函数或直接设置 AIRCR[10:8] 寄存器来配置中断分组。这确定了抢占优先级和响应优先级的分配方式。

    示例:

    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_X);
    
  2. 设置中断优先级(Set Interrupt Priority):

    • 使用 HAL_NVIC_SetPriority 函数或直接设置 IPRx bit[7:4] 寄存器来设置中断的抢占优先级和响应优先级。

    示例:

    HAL_NVIC_SetPriority(IRQn, PreemptionPriority, SubPriority);
    
  3. 使能中断(Enable Interrupt):

    • 使用 HAL_NVIC_EnableIRQ 函数或直接设置 ISERx 寄存器来使能特定的中断。

    示例:

    HAL_NVIC_EnableIRQ(IRQn);
    

在上述示例中,IRQn 是中断号,PreemptionPrioritySubPriority 是抢占优先级和响应优先级的数值。NVIC_PRIORITYGROUP_X 是中断分组的配置选项,可以是 0、1、2、3 或 4,分别对应不同的中断分组方式。

这些函数提供了对 NVIC 寄存器的封装,使得中断的配置和管理更加方便。使用 HAL 库的优势在于它提供了对底层硬件的抽象,简化了硬件相关的操作,使代码更具可移植性。在没有 HAL 的情况下,直接对寄存器进行配置也是一种常见的做法,尤其在对资源更加精细掌控的应用中。

中断相关寄存器
这些寄存器与中断控制和管理相关,属于 ARM Cortex-M 架构的 NVIC 模块。以下是对这些寄存器的简要介绍:

  1. SCB_AIRCR(Application Interrupt and Reset Control Register):

    • 作用:控制中断优先级分组。
    • 位域:
      • [10:8]:PRIGROUP,用于设置中断优先级分组。

    示例:

    SCB->AIRCR = (SCB->AIRCR & ~SCB_AIRCR_VECTKEY_Msk) | SCB_AIRCR_VECTKEY | SCB_AIRCR_PRIGROUP_X;
    
  2. NVIC_IPRx(Interrupt Priority Registers):

    • 作用:存储中断的抢占优先级和响应优先级。
    • 位域:
      • [7:4]:抢占优先级(Preemption Priority)。
      • [3:0]:响应优先级(Subpriority)。

    示例:

    NVIC->IP[IRQn] = (PreemptionPriority << 4) | SubPriority;
    
  3. NVIC_ISER(Interrupt Set-Enable Registers):

    • 作用:用于使能特定中断。
    • 位域:
      • 每一位对应一个中断,置1表示使能对应中断。

    示例:

    NVIC->ISER[IRQn / 32] = (1U << (IRQn % 32));
    

这些寄存器用于配置中断优先级、中断分组和中断的使能。在这里,IRQn 表示具体的中断号。需要注意的是,直接访问这些寄存器可能会影响系统的正常运行,因此在使用之前应当谨慎,并且最好使用相关的库函数(如 HAL 库)来进行中断的配置和管理,以确保操作的正确性和可移植性。

1.设置中断分组HAL_NVIC_SetPriorityGrouping

在这里插入图片描述
在这里插入图片描述

2.设置中断优先级HAL_NVIC_SetPriority

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.使能中断HAL_NVIC_EnableIRQ

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、EXTI

3.1、EXTI基本概念

在这里插入图片描述
外部(扩展)中断事件控制器EXTI(External (Extended) Interrupt/Event Controller)基本概念:

EXTI是外部中断/事件控制器,主要用于处理外部信号的中断和事件。在许多嵌入式系统中,它通常包含了多个检测器,用于检测外部信号的状态变化,并触发相应的中断或事件。

以下是一些基本概念和特点:

  1. 边沿检测器: EXTI包含了20个边沿检测器,每一个对应一个外部信号。这些检测器可以检测信号的上升沿、下降沿,或是两者都检测。

  2. EXTI线: 外部中断/事件的触发通过 EXTI线来实现。在某些STM32系列中(如F1系列),有20条 EXTI线,每一条对应一个检测器。当外部信号发生状态变化时,对应的 EXTI线会触发中断或事件。

  3. 中断和事件的区分:

    • 中断: 当一个外部信号触发了中断,系统会跳转到相应的中断服务函数来处理这个中断。这通常需要CPU的直接参与,因此涉及到中断处理的一系列流程,如入栈、保存寄存器状态等。
    • 事件: 与中断不同,事件不涉及中断服务函数。它只是一个内部硬件的状态变化的通知,不需要CPU的直接介入。例如,定时器(TIM)、直接存储器访问(DMA)或模拟数字转换器(ADC)等模块经常会产生事件。
  4. 事件和中断的使用场景:

    • 中断: 适用于需要CPU紧急响应的场景,例如紧急事件或需要及时处理的外部触发信号。
    • 事件: 适用于不需要即时响应,由硬件模块自动处理的场景,如定时器产生的定时事件或DMA传输完成事件。

在STM32系列中,EXTI的使用允许开发者根据实际需求,选择是使用中断处理还是仅仅产生一个事件通知,从而灵活地应对不同的应用场景。

EXTI支持的外部中断/事件请求

在这里插入图片描述
EXTI(External Interrupt/Event Controller)支持的外部中断/事件请求可以根据不同的 EXTI 线进行配置。在STM32系列中,不同的 EXTI 线对应了不同的外部触发源。以下是一些常见的 EXTI 线及其对应的触发源:

  1. EXTI线0~15: 分别对应GPIO PIN 0~15,即可以通过配置相应的 GPIO 引脚来触发这些 EXTI 线。

  2. EXTI线16: PVD(Programmable Voltage Detector)输出,用于检测芯片电源电压是否在预定范围内。

  3. EXTI线17: RTC(Real-Time Clock)闹钟事件,当RTC闹钟发生时触发。

  4. EXTI线18: USB OTG FS(Full-Speed)唤醒事件,当USB OTG FS模块需要唤醒系统时触发。

  5. EXTI线19: 以太网唤醒事件,当以太网模块需要唤醒系统时触发。

  6. EXTI线20: USB OTG HS(High-Speed)唤醒事件,当USB OTG HS模块需要唤醒系统时触发。

  7. EXTI线21: RTC 入侵和时间戳事件,当RTC检测到入侵或产生时间戳时触发。

  8. EXTI线22: RTC 唤醒事件,当RTC模块需要唤醒系统时触发。

  9. EXTI线23: LPTIM1(Low Power Timer 1)异步事件,当LPTIM1产生异步事件时触发。

这些 EXTI 线的设置可以通过相应的寄存器进行配置,以适应不同的外部触发源和应用需求。通过 EXTI,系统可以灵活地对外部事件和中断进行管理和响应。

3.2、EXTI主要特性

在这里插入图片描述
EXTI(External Interrupt/Event Controller)在不同的STM32系列中可能有一些变化,但以下是一些主要的特性,分别对应了F1、F4、F7以及H7系列:

F1/F4/F7系列的主要特性:

  1. 每条EXTI线单独配置: 每一条 EXTI 线都可以独立配置,允许对不同的外部信号进行不同的设置。

  2. 选择类型: 每条 EXTI 线可以配置为中断或事件模式,中断模式会引起中断服务函数的执行,而事件模式则只产生一个事件,不会引起中断。

  3. 触发方式: 可以选择上升沿、下降沿或双边沿触发方式,以适应不同的外部信号变化条件。

  4. 支持软件触发: 可以通过软件触发来模拟外部信号的触发,方便测试和调试。

  5. 开启/屏蔽: 可以通过设置相应的寄存器来开启或屏蔽每一条 EXTI 线。

  6. 挂起状态位: 提供了挂起状态位,可以查询某一条 EXTI 线是否被触发,并且在处理完中断后可以清除该状态。

H7系列的主要特性:

  1. 可配置事件: 提供了和F1/F4/F7系列类似的可配置事件,可以选择中断或事件模式,并配置触发方式。

  2. 直接事件: 对某些外设产生的事件进行分类,有一类是固定上升沿触发的直接事件。

  3. 不支持软件触发: 不同于F1/F4/F7系列,H7系列的直接事件不支持软件触发。

  4. 无挂起状态位: 直接事件没有挂起状态位,其状态由产生事件的外设维护。

总体来说,EXTI在不同的STM32系列中有一些相似的特性,但也可能因为系列的不同而有一些差异。这些特性使得开发者可以根据具体的应用需求配置外部中断和事件的行为。

3.3、EXTI工作原理(F1/F4/F7)

在这里插入图片描述
在STM32 F1、F4、F7系列中,EXTI(External Interrupt/Event Controller)的工作原理涉及到边沿检测、软件触发、中断屏蔽/清除以及事件屏蔽。以下是这些方面的简要说明:

  1. 边沿检测:

    • 上升沿触发选择寄存器 EXTI_RTSR(Rising Trigger Selection Register):用于配置哪些 EXTI 线的中断或事件是上升沿触发的。
    • 下降沿触发选择寄存器 EXTI_FTSR(Falling Trigger Selection Register):用于配置哪些 EXTI 线的中断或事件是下降沿触发的。

    当外部信号发生对应的边沿变化时,符合配置的边沿触发条件,EXTI 模块就会产生相应的中断请求或事件。

  2. 软件触发:

    • 软件中断事件寄存器 EXTI_SWIER(Software Interrupt Event Register):用于通过软件触发 EXTI 线的中断或事件。写入该寄存器的特定位,会导致相应 EXTI 线产生一个中断请求或事件。
  3. 中断屏蔽/清除:

    • 请求挂起寄存器 EXTI_PR(Pending Register):用于标记每一条 EXTI 线是否产生了中断请求。读取该寄存器可以确定中断是否待处理。
    • 中断屏蔽寄存器 EXTI_IMR(Interrupt Mask Register):用于屏蔽/使能每一条 EXTI 线的中断请求。当相应 EXTI 线的中断被屏蔽时,不会进入 NVIC,不会触发中断服务函数。

    通过配置 EXTI_IMR 寄存器,可以选择性地屏蔽或使能某一条 EXTI 线的中断。

  4. 事件屏蔽:

    • 事件屏蔽寄存器 EXTI_EMR(Event Mask Register):用于屏蔽/使能每一条 EXTI 线的事件。事件是不进入 NVIC 的,但可以用于触发其他外设。

    通过配置 EXTI_EMR 寄存器,可以选择性地屏蔽或使能某一条 EXTI 线的事件。

总体来说,通过配置这些寄存器,开发者可以实现对外部信号的中断或事件的精确控制,包括触发条件的选择、软件触发、中断请求的屏蔽和清除,以及事件的屏蔽。这使得 EXTI 模块能够灵活地适应不同的应用场景和外部信号触发条件。
在这里插入图片描述

3.4、EXTI工作原理(H7)

在这里插入图片描述
在这里插入图片描述
嵌入式系统中的外部中断(EXTI)配置,以及与CPU唤醒和低功耗相关的一些设置。以下是一些关键点的解释:

  1. EXTI(外部中断)配置:

    • 通过软件中断事件寄存器(EXTI_SWIER)配置可触发CPU唤醒的事件。
    • 异步边沿检测电路:通过配置下降沿触发选择寄存器(EXTI_FTSR1)和上升沿触发选择寄存器(EXTI_RTSR1),可以选择上升沿、下降沿或双边沿触发外部中断。
  2. CPU唤醒配置:

    • 使用CPU上升沿检测脉冲发生器(rcc_fclk_cpu())来检测中断的上升沿。
    • 通过CPU中断屏蔽寄存器(EXTI_CPUIMR1)配置中断的使能和屏蔽。
    • 通过CPU挂起请求寄存器(EXTI_CPUPR1)来处理CPU挂起请求。
  3. 低功耗相关设置:

    • 与CPU事件相关的寄存器输出到CPU事件寄存器(cpu_event)。
    • CPU唤醒和D3域唤醒设置,包括相关的低功耗模式配置。
    • ck_sys同步是与系统时钟同步。
  4. CPU事件与CPU事件屏蔽寄存器:

    • CPU事件通过CPU事件寄存器输出,用于进一步处理。
    • 通过CPU事件屏蔽寄存器(EXTI_EMR)(cpu_event)可以配置屏蔽特定的事件,以防止其触发CPU中断。
  5. 输出到CPU唤醒和D3唤醒:

    • 输出到CPU唤醒(cpu_wakeup)和D3域唤醒(d3_wakeup)涉及到唤醒相关的处理,与低功耗模式有关。

需要注意的是,上述描述中的具体寄存器和函数名可能是根据您的系统和硬件平台而定的,这里只是一个一般性的描述。在实际应用中,您需要查阅相关的技术文档和手册以获取详细的信息,以确保正确地配置和使用这些功能。

四、EXTI和IO映射关系

4.1、AFIO简介(F1)

在这里插入图片描述
AFIO(Alternate Function IO)是一种在STM32(F1系列)微控制器中使用的模块,主要用于复用功能IO的配置,包括调试IO配置、外设IO重映射配置以及外部中断配置。

  1. 调试IO配置:

    • 通过AFIO_MAPR寄存器的特定位(AFIO_MAPR[26:24])配置JTAG/SWD的开关状态。这允许您选择是否使用调试功能(JTAG或SWD)。
  2. 重映射配置:

    • 使用AFIO_MAPR寄存器进行部分外设IO的重映射配置。这对于某些外设可能需要重新映射到不同的IO引脚是很有用的,以满足特定的硬件设计需求。
  3. 外部中断配置:

    • 使用AFIO_EXTICR1~4寄存器配置外部中断线0-15,将其映射到具体的IO口。这对于将外部中断与特定IO引脚关联是很重要的。

特别需要注意的是,在配置AFIO寄存器之前,需要先使能AFIO时钟。这可以通过调用__HAL_RCC_AFIO_CLK_ENABLE()来实现,该函数通常对应RCC_APB2ENR寄存器的相应位(通常是位0)。这样做是为了确保AFIO模块能够正常工作。

总的来说,AFIO模块为微控制器提供了一种方便的方式,通过配置一些特定的寄存器,来管理IO引脚的不同功能,包括调试、外设IO的重映射以及外部中断的映射。这对于在嵌入式系统中进行底层硬件配置和控制是非常重要的。

4.2、SYSCFG简介(F4/F7/H7)

在这里插入图片描述
SYSCFG(System configuration controller)是在STM32系列微控制器中使用的模块,用于系统配置控制。其功能包括外部中断映射配置等。

  1. 外部中断配置:
    • 使用SYSCFG_EXTICR1~4寄存器配置外部中断线0-15,将其映射到具体的IO口。这对于将外部中断与特定IO引脚关联是很重要的。

特别需要注意的是,在配置SYSCFG寄存器之前,需要先使能SYSCFG时钟。这可以通过调用__HAL_RCC_SYSCFG_CLK_ENABLE()来实现,该函数通常对应RCC_APB2ENR寄存器的相应位。这样做是为了确保SYSCFG模块能够正常工作。

不同系列的STM32微控制器可能在SYSCFG模块的功能上有一些区别,但主要的任务是为外部中断的映射提供配置和控制。SYSCFG允许将外部中断线映射到具体的IO引脚,这对于与外部事件和传感器的交互是非常重要的。

总的来说,SYSCFG模块在STM32系列微控制器中扮演着关键的角色,使得开发者能够更方便地配置系统,特别是在处理外部中断映射时。

4.3、EXTI与IO对应关系

在这里插入图片描述
在STM32微控制器中,外部中断线(EXTI)与IO引脚的对应关系可以通过配置寄存器来进行映射。在不同系列的STM32中,有不同的寄存器用于这一映射。

对于STM32 F1系列,使用的是AFIO模块,具体是AFIO_EXTICR1寄存器,该寄存器的位域 EXTI0[3:0] 用于控制 EXTI0 至 EXTI3 的映射。

AFIO_EXTICR1:
Bit 3:0 EXTI0[3:0]: Port x configuration

对于STM32 F4、F7 和 H7 系列,使用的是SYSCFG模块,具体是SYSCFG_EXTICR1寄存器,同样的,该寄存器的位域 EXTI0[3:0] 用于控制 EXTI0 至 EXTI3 的映射。

SYSCFG_EXTICR1:
Bit 3:0 EXTI0[3:0]: EXTI 0 configuration

上述寄存器的配置允许将特定的IO引脚映射到相应的外部中断线。例如,Px0(IO引脚)可以映射到 EXTI0,Px1 映射到 EXTI1,以此类推,一直到 Px15 映射到 EXTI15。

这样的映射关系允许开发者根据硬件连接和需求,将外部中断映射到特定的IO引脚上,使得外部事件能够触发相应的中断服务程序。这对于处理外部输入信号,如按钮按下、传感器触发等,是非常有用的。
在这里插入图片描述
在这里插入图片描述

五、如何使用中断

在这里插入图片描述

STM32 EXTI的配置步骤(外部中断)

在这里插入图片描述
使用HAL库配置STM32外部中断(EXTI)的步骤如下,特别注意使用 HAL_GPIO_Init 函数一步到位简化配置:

  1. 使能GPIO时钟:

    __HAL_RCC_GPIOx_CLK_ENABLE();  // x 是相应的 GPIO 端口
    
  2. 设置GPIO输入模式:

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_x;  // x 是相应的引脚
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;  // 选择中断触发方式
    GPIO_InitStruct.Pull = GPIO_NOPULL;  // 上拉/下拉/浮空输入
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);  // x 是相应的 GPIO 端口
    
  3. 使能AFIO/SYSCFG时钟:

    __HAL_RCC_AFIO_CLK_ENABLE();  // 或者使用 __HAL_RCC_SYSCFG_CLK_ENABLE(),具体取决于芯片系列
    
  4. 设置EXTI和IO对应关系:

    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_X);  // 设置中断分组(一次即可)
    
    // 设置中断通道的优先级
    HAL_NVIC_SetPriority(EXTIx_IRQn, priority, subpriority);
    
    // 使能中断通道
    HAL_NVIC_EnableIRQ(EXTIx_IRQn);
    
  5. 设置EXTI屏蔽,上/下沿:

    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_X);  // 设置中断分组(一次即可)
    
    // 设置中断通道的优先级
    HAL_NVIC_SetPriority(EXTIx_IRQn, priority, subpriority);
    
    // 使能中断通道
    HAL_NVIC_EnableIRQ(EXTIx_IRQn);
    
  6. 设计中断服务函数:

    void EXTIx_IRQHandler(void)
    {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_x);  // 处理中断
    }
    

上述步骤中, HAL_GPIO_Init 函数用于一步到位地配置GPIO引脚,包括输入模式、上/下拉设置等。使用 HAL_NVIC_SetPriorityHAL_NVIC_EnableIRQ 函数配置中断优先级和使能中断。HAL_GPIO_EXTI_IRQHandler 函数用于处理中断并清除中断标志。

在实际应用中,确保正确配置中断优先级和分组,并根据实际需求选择适当的中断触发方式。

STM32 EXTI的HAL库设置步骤(外部中断)

在这里插入图片描述
使用HAL库配置STM32外部中断(EXTI)相对简化了配置流程。以下是使用HAL库的基本步骤:

  1. 使能GPIO时钟:

    __HAL_RCC_GPIOx_CLK_ENABLE();  // x 是相应的 GPIO 端口
    
  2. GPIO/AFIO(SYSCFG)/EXTI 配置:

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使用HAL_GPIO_Init一步到位
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);  // x 是相应的 GPIO 端口
    
  3. 设置中断分组:

    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_X);
    // 此函数只需设置一次,通常在 main 函数中配置。
    
  4. 设置中断优先级:

    HAL_NVIC_SetPriority(EXTIx_IRQn, priority, subpriority);
    
  5. 使能中断:

    HAL_NVIC_EnableIRQ(EXTIx_IRQn);
    
  6. 设计中断服务函数:

    void EXTIx_IRQHandler(void)
    {
        // 中断服务函数
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_x);  // x 是相应的引脚
    }
    

具体示例:

// 1. 使能GPIO时钟
__HAL_RCC_GPIOA_CLK_ENABLE();

// 2. GPIO/AFIO(SYSCFG)/EXTI 配置
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;  // 示例使用 GPIOA 的引脚 0
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 选择中断触发方式
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 3. 设置中断分组(在 main 函数中进行一次即可)
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_X);

// 4. 设置中断优先级
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);

// 5. 使能中断
HAL_NVIC_EnableIRQ(EXTI0_IRQn);

// 6. 设计中断服务函数
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);  // 处理中断
}

在这个例子中,GPIO_PIN_x 是对应 GPIO 引脚,EXTI0_IRQn 是对应 EXTI 外部中断通道。使用 HAL_GPIO_EXTI_IRQHandler 函数来处理中断并清除中断标志,这是HAL库的一部分。

六、通用外设驱动模型(四步法)

在这里插入图片描述
通用外设驱动模型的四步法是一个常见的设计思路,它通常用于设计和实现针对各种外设的驱动程序。下面对每一步进行简要解释:

1. 初始化

在初始化步骤中,执行以下操作:

  • 时钟设置: 配置外设所需的时钟源和时钟频率。

  • 参数设置: 配置外设的相关参数,例如数据位宽、波特率等。

  • IO设置: 配置与外设通信的引脚,包括引脚的工作模式、上下拉电阻等。

  • 中断设置: 配置外设相关的中断,包括开启中断、设置中断优先级,并在需要时配置NVIC(Nested Vectored Interrupt Controller)。

2. 读函数(可选)

如果外设支持从外设读取数据,设计读函数用于从外设获取数据。

3. 写函数(可选)

如果外设支持往外设写入数据,设计写函数用于向外设发送数据。

4. 中断服务函数(可选)

如果外设产生中断,设计中断服务函数用于处理中断。中断服务函数根据中断标志来执行相应的操作,例如处理接收到的数据、发送数据等。

这个四步法的模型具有灵活性,可以根据外设的特性和需求选择性地实现读函数、写函数和中断服务函数。这种模型的优势在于可以根据具体情况进行定制,使得驱动程序更加灵活和高效。

七、HAL库中断回调处理机制介绍

在这里插入图片描述
在HAL库中,中断处理通常遵循一种回调(Callback)机制,这是一种事件驱动的编程范式。以下是HAL库中断回调处理机制的主要组成部分:

  1. 中断服务函数:

    • 这是由硬件触发的实际中断服务函数。在HAL库中,这些函数通常具有类似 EXTI0_IRQHandler 这样的名称,对应于外部中断 EXTI0 的中断服务函数。
  2. HAL库中断处理公用函数:

    • 在中断服务函数内部,通常会调用HAL库提供的一些公用函数,用于异常处理、清除中断标志以及调用各种回调函数。例如,HAL_GPIO_EXTI_IRQHandler 是一个用于处理GPIO外部中断的公用函数。
  3. HAL库数据处理回调函数:

    • 这些是用户定义的回调函数,允许用户在中断发生时执行特定的操作。HAL库提供了一种机制,允许用户通过注册回调函数的方式,使得在中断发生时能够调用这些函数。这些回调函数包括:
      • HAL_GPIO_EXTI_Callback:用于GPIO外部中断的回调函数。
      • HAL_UART_TxCpltCallback:用于UART传输完成的回调函数。
      • 等等,具体回调函数取决于所使用的外设。

举例来说,考虑一个GPIO外部中断的处理:

void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);  // 处理中断,清中断标志

    // 用户定义的回调函数
    HAL_GPIO_EXTI_Callback(GPIO_PIN_0);
}

用户可以在代码中实现自定义的 HAL_GPIO_EXTI_Callback 函数,以执行在中断发生时期望执行的特定操作。例如,当GPIO引脚上的外部中断触发时,可以在回调函数中执行相应的处理逻辑。

这种回调机制允许用户通过函数指针的方式,将自定义的处理逻辑注册到HAL库中,从而在中断发生时执行相应的用户定义的操作。

八、编程实战:通过外部中断控制一个灯亮灭

在这里插入图片描述
在通过外部中断控制一个灯的亮灭时,首先需要分析IO引脚的配置。以下是对每个IO引脚的模式分析:

  1. PA0(假设是连接到一个按钮或触发器的引脚)

    • 输入下拉模式:当外部设备未连接到这个引脚时,引脚被拉低,确保不会漂浮。当外部设备连接到引脚并产生高电平(例如按钮按下),引脚变为高电平,触发上升沿。
  2. PE4/PE3/PE2(假设是连接到另一个触发器的引脚)

    • 输入上拉模式:当外部设备未连接到这些引脚时,引脚被拉高,确保不会漂浮。当外部设备连接到引脚并产生低电平(例如按钮按下),引脚变为低电平,触发下降沿。

根据上述分析,可以进行如下的配置:

// 配置PA0引脚
GPIO_InitTypeDef GPIO_InitStruct_PA0 = {0};
GPIO_InitStruct_PA0.Pin = GPIO_PIN_0;
GPIO_InitStruct_PA0.Mode = GPIO_MODE_IT_RISING;  // 上升沿触发
GPIO_InitStruct_PA0.Pull = GPIO_PULLDOWN;       // 输入下拉模式
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct_PA0);

// 配置PE4/PE3/PE2引脚
GPIO_InitTypeDef GPIO_InitStruct_PE = {0};
GPIO_InitStruct_PE.Pin = GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2;
GPIO_InitStruct_PE.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_InitStruct_PE.Pull = GPIO_PULLUP;          // 输入上拉模式
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct_PE);

以上代码假设你想通过 PA0 引脚上升沿触发来控制一个灯的亮灭,并通过 PE4/PE3/PE2 引脚下降沿触发来控制另一个灯的亮灭。当 PA0 上的按钮按下时,产生上升沿,触发中断;当 PE4/PE3/PE2 上的按钮按下时,产生下降沿,也触发中断。

具体操作流程中,你需要在中断服务函数中编写相应的逻辑来控制灯的亮灭。例如,在中断服务函数中调用相应的函数来点亮或熄灭灯。

1、编程实战:通过KEY0控制LED0亮灭

为了在外部中断(EXTI)引脚(这里是GPIOE的引脚4)上的下降沿触发时切换另一个引脚(这里是GPIOB的引脚5)的状态。

下面是代码的一些注释和建议:

  1. GPIO初始化:

    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOE_CLK_ENABLE();
    
    gpio_init_struct.Pin = GPIO_PIN_4;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
    gpio_init_struct.Pull = GPIO_PULLUP;          // 上拉模式
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);
    

    这部分初始化了 GPIOE 的引脚4,设置为下降沿触发的输入模式,同时启用了上拉电阻。

  2. 中断优先级设置和使能:

    HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 0);  // 设置中断优先级
    HAL_NVIC_EnableIRQ(EXTI4_IRQn);           // 使能中断
    

    这里设置了 EXTI4_IRQn 中断通道的优先级和使能。

  3. 中断服务函数:

    void EXTI4_IRQHandler(void)
    {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);    // 处理中断
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_4);    // 清除中断标志
    }
    

    这是 EXTI4 的中断服务函数,调用了 HAL_GPIO_EXTI_IRQHandler 来处理中断并清除标志位。

  4. HAL_GPIO_EXTI_Callback:

    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        delay_ms(20);  // 延时 20 毫秒消抖
    
        if(GPIO_Pin == GPIO_PIN_4)
        {
            if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0)
            {
                HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);  // 切换 GPIOB 引脚5的状态
            }
        }
    }
    

    这个回调函数在中断触发后被调用,通过 HAL_GPIO_ReadPin 读取 GPIOE 引脚4的状态,如果为低电平,就切换 GPIOB 引脚5的状态。

请确保 delay_ms 函数实现是可靠的,不会引入额外的延迟或问题。另外,确保 HAL_GPIO_TogglePin 函数的正确性和 GPIOB 引脚5的初始化。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
exit.c

#include "./BSP/EXTI/exti.h"
#include "./SYSTEM/delay/delay.h"


void exti_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOE_CLK_ENABLE();

    gpio_init_struct.Pin = GPIO_PIN_4;
	gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
	gpio_init_struct.Pull = GPIO_PULLUP;           // 上拉模式
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);
    
	HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 0);   // 设置中断优先级
	HAL_NVIC_EnableIRQ(EXTI4_IRQn);           // 使能中断
}

void EXTI4_IRQHandler(void)
{
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);    // 处理中断
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_4);    // 清除中断标志
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(20); // 延时 20 毫秒消抖
    
    if(GPIO_Pin == GPIO_PIN_4)
    {
        if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0)
        {
            HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);  // 切换 GPIOB 引脚5的状态
        }
    }
}

exit.h

#ifndef _EXTI_H
#define _EXTI_H
#include "./SYSTEM/sys/sys.h"


void exti_init(void);

#endif

led.c

#include "./BSP/LED/led.h"


/**
 * @brief       初始化LED相关IO口, 并使能时钟
 * @param       无
 * @retval      无
 */
void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED0_GPIO_CLK_ENABLE();                                 /* LED0时钟使能 */
    LED1_GPIO_CLK_ENABLE();                                 /* LED1时钟使能 */

    gpio_init_struct.Pin = LED0_GPIO_PIN;                   /* LED0引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct);       /* 初始化LED0引脚 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引脚 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引脚 */
    

    LED0(1);                                                /* 关闭 LED0 */
    LED1(1);                                                /* 关闭 LED1 */
}

led.h

#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED0_GPIO_PORT                  GPIOB
#define LED0_GPIO_PIN                   GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)             /* PB口时钟使能 */

#define LED1_GPIO_PORT                  GPIOE
#define LED1_GPIO_PIN                   GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)             /* PE口时钟使能 */

/******************************************************************************************/
/* LED端口定义 */
#define LED0(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)      /* LED0翻转 */

#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)      /* LED1翻转 */

/* LED取反定义 */
#define LED0_TOGGLE()   do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0)        /* 翻转LED0 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻转LED1 */

/******************************************************************************************/
/* 外部接口函数*/
void led_init(void);                                                                            /* 初始化 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/EXTI/exti.h"


int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    exti_init();
    
    while(1)
    {
        LED1(1);                                /* LED1 灭 */
        delay_ms(500);
        LED1(0);                                /* LED1 亮 */
        delay_ms(500);
    }
}

2、解读例程源码:外部中断实验

exit.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/KEY/key.h"
#include "./BSP/EXTI/exti.h"


/**
 * @brief       KEY0 外部中断服务程序
 * @param       无
 * @retval      无
 */
void KEY0_INT_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN);         /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN);         /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

/**
 * @brief       KEY1 外部中断服务程序
 * @param       无
 * @retval      无
 */
void KEY1_INT_IRQHandler(void)
{ 
    HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN);         /* 调用中断处理公用函数 清除KEY1所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN);         /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

/**
 * @brief       KEY2 外部中断服务程序
 * @param       无
 * @retval      无
 */
void KEY2_INT_IRQHandler(void)
{ 
    HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN);        /* 调用中断处理公用函数 清除KEY2所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN);        /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

/**
 * @brief       WK_UP 外部中断服务程序
 * @param       无
 * @retval      无
 */
void WKUP_INT_IRQHandler(void)
{ 
    HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN);        /* 调用中断处理公用函数 清除KEY_UP所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
    __HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN);        /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

/**
 * @brief       中断服务程序中需要做的事情
                在HAL库中所有的外部中断服务函数都会调用此函数
 * @param       GPIO_Pin:中断引脚号
 * @retval      无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(20);      /* 消抖 */
    switch(GPIO_Pin)
    {
        case KEY0_INT_GPIO_PIN:
            if (KEY0 == 0)
            {
                LED0_TOGGLE();  /* LED0 状态取反 */ 
                LED1_TOGGLE();  /* LED1 状态取反 */ 
            }
            break;
        case KEY1_INT_GPIO_PIN:
            if (KEY1 == 0)
            {
                LED0_TOGGLE();  /* LED0 状态取反 */ 
            }
            break;
        case KEY2_INT_GPIO_PIN:
            if (KEY2 == 0)
            {
                LED1_TOGGLE();  /* LED1 状态取反 */ 
            }
            break;
        case WKUP_INT_GPIO_PIN:
            if (WK_UP == 1)
            {
                BEEP_TOGGLE();  /* 蜂鸣器状态取反 */ 
            }
            break;
    }
}

/**
 * @brief       外部中断初始化程序
 * @param       无
 * @retval      无
 */
void extix_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    KEY0_GPIO_CLK_ENABLE();                                  /* KEY0时钟使能 */
    KEY1_GPIO_CLK_ENABLE();                                  /* KEY1时钟使能 */
    KEY2_GPIO_CLK_ENABLE();                                  /* KEY2时钟使能 */
    WKUP_GPIO_CLK_ENABLE();                                  /* WKUP时钟使能 */

    gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;            /* 下升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);    /* KEY0配置为下降沿触发中断 */

    gpio_init_struct.Pin = KEY1_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;            /* 下升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct);    /* KEY1配置为下降沿触发中断 */
    
    gpio_init_struct.Pin = KEY2_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;            /* 下升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct);    /* KEY2配置为下降沿触发中断 */
    
    gpio_init_struct.Pin = WKUP_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_RISING;             /* 上升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);        /* WKUP配置为下降沿触发中断 */

    HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);               /* 抢占0,子优先级2 */
    HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);                       /* 使能中断线1 */

    HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2);               /* 抢占1,子优先级2 */
    HAL_NVIC_EnableIRQ(KEY1_INT_IRQn);                       /* 使能中断线15 */
    
    HAL_NVIC_SetPriority(KEY2_INT_IRQn, 2, 2);               /* 抢占2,子优先级2 */
    HAL_NVIC_EnableIRQ(KEY2_INT_IRQn);                       /* 使能中断线15 */

    HAL_NVIC_SetPriority(WKUP_INT_IRQn, 3, 2);               /* 抢占3,子优先级2 */
    HAL_NVIC_EnableIRQ(WKUP_INT_IRQn);                       /* 使能中断线0 */
}

exit.h

#ifndef __EXTI_H
#define __EXTI_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 引脚 和 中断编号 & 中断服务函数 定义 */ 

#define KEY0_INT_GPIO_PORT              GPIOE
#define KEY0_INT_GPIO_PIN               GPIO_PIN_4
#define KEY0_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */
#define KEY0_INT_IRQn                   EXTI4_IRQn
#define KEY0_INT_IRQHandler             EXTI4_IRQHandler

#define KEY1_INT_GPIO_PORT              GPIOE
#define KEY1_INT_GPIO_PIN               GPIO_PIN_3
#define KEY1_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */
#define KEY1_INT_IRQn                   EXTI3_IRQn
#define KEY1_INT_IRQHandler             EXTI3_IRQHandler

#define KEY2_INT_GPIO_PORT              GPIOE
#define KEY2_INT_GPIO_PIN               GPIO_PIN_2
#define KEY2_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */
#define KEY2_INT_IRQn                   EXTI2_IRQn
#define KEY2_INT_IRQHandler             EXTI2_IRQHandler

#define WKUP_INT_GPIO_PORT              GPIOA
#define WKUP_INT_GPIO_PIN               GPIO_PIN_0
#define WKUP_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
#define WKUP_INT_IRQn                   EXTI0_IRQn
#define WKUP_INT_IRQHandler             EXTI0_IRQHandler

/******************************************************************************************/


void extix_init(void);  /* 外部中断初始化 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/EXTI/exti.h"


int main(void)
{
    HAL_Init();                            /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);    /* 设置时钟, 72Mhz */
    delay_init(72);                        /* 延时初始化 */
    usart_init(115200);                    /* 串口初始化为115200 */
    led_init();                            /* 初始化LED */
    beep_init();                           /* 初始化蜂鸣器 */
    extix_init();                          /* 初始化外部中断输入 */
    LED0(0);                               /* 先点亮红灯 */

    while (1)
    {
        printf("OK\r\n");
        delay_ms(1000);
    }
}

九、总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值