当前位置:   article > 正文

一文读懂STM32中断NVIC(超详细+中断CubeMX配置指南)_stm32 nvic

stm32 nvic

image-20231231175604434

1. 中断基础概念

STM32F407的嵌套向量中断控制器(Nested Vectored Interrupt Controller,NVIC)管理所有中断,它有82个可屏蔽中断,还有13个系统中断。82个可屏蔽中断和部分系统中断可配置中断优先级,总共有16个优先级。

  1. 触发条件: 中断是由外部事件触发的,这些事件可以是来自硬件(比如传感器输入、定时器结束等)或软件(比如系统调用或特定指令)的信号。
  2. 响应过程: 当中断事件发生时,处理器暂停当前正在执行的任务,保存当前状态(比如程序计数器、寄存器状态等),然后跳转执行预定义的中断服务程序(ISR,Interrupt Service Routine)。
  3. 中断服务程序: 这是一段专门设计来处理特定中断的代码。它会执行必要的操作以响应中断事件,可能会读取外部设备状态、清除中断标志、执行特定操作并最终恢复被打断的程序执行。
  4. 中断优先级: 中断可以具有不同的优先级,允许系统在有多个中断发生时确定哪个中断应该首先被处理。

NVIC是ARM Cortex-M处理器内部的模块,负责管理处理器的中断。它的特性包括:

  1. 嵌套性: 允许不同优先级的中断按照其优先级被嵌套处理,优先级高的中断可以中断优先级低的中断。
  2. 向量表: 每个中断都有一个唯一的编号(向量),这些向量存储在向量表中。当中断发生时,处理器通过查找向量表中的特定入口点来确定要执行的中断服务程序。
  3. 优先级和控制: NVIC允许配置每个中断的优先级,并可以通过控制寄存器来使能或禁用特定的中断。

在STM32系列中,要使用中断,通常的步骤包括配置相关的外部硬件(如果需要)、配置NVIC以及编写中断服务程序。在初始化过程中,需要确保配置中断优先级和使能相应的中断。

2. 中断向量表

STM32F4系列使用的是ARM Cortex-M内核,中断向量表是处理器中的一组特殊地址,存储着每个中断服务程序的入口地址。当发生中断时,处理器会根据中断编号查找中断向量表,并跳转到相应中断服务程序的入口地址执行。

STM32F4的中断向量表结构:
  1. 存储位置: 中断向量表存储在处理器的内存中,通常位于微控制器的Flash起始地址处。在STM32F4中,中断向量表通常位于0x00000000的地址处,是一个连续的地址空间。
  2. 向量表项: 每个中断有一个向量表项。这些项是连续存储的,每个项占据4个字节。
  3. 中断服务程序入口地址: 每个向量表项存储着对应中断服务程序的入口地址。当中断发生时,处理器会根据中断编号找到相应的入口地址并跳转执行。
STM32F4的中断向量表组成:

在STM32F4中,中断向量表包括了两种类型的向量表项:

  1. ARM Cortex-M处理器核心向量表: 这部分向量表项包含了Cortex-M处理器的核心中断服务程序入口地址,如:
    • 初始栈指针(Initial Stack Pointer)
    • 复位(Reset)
    • NMI(Non-Maskable Interrupt)
    • Hard Fault等
  2. STM32特定的外设中断向量表: 在ARM Cortex-M处理器核心向量表后面,是STM32特定的外设中断向量表项。这些项存储着与STM32F4系列相关的外设中断服务程序的入口地址,比如TIM1、TIM2、USART1等。如下图所示:

image-20240101233803447

用STM32CubeMX生成代码后,在 startup_stm32f407zgtx.s 汇编文件中,我们可以看到中断向量表具体的定义和中断服务函数。

image-20240102074829476

3. 中断优先级

在STM32F4系列中,中断优先级用于确定当多个中断同时发生时,处理器应该先处理哪个中断。这个优先级系统是由ARM Cortex-M内核提供支持的。

3.1 中断优先级的特点:
  1. 可编程性
    STM32F4允许针对每个可中断的外设配置中断优先级。这使得开发者可以根据系统需求对中断进行优先级管理。

  2. 优先级位数
    Cortex-M内核中的中断优先级分为抢占优先级(Preemption Priority)和子优先级(Subpriority)。在STM32F4中,通常抢占优先级占据较高的位数,子优先级占据较低的位数。

  3. 数值越小,优先级越高
    优先级数值越小,表示优先级越高。比如,抢占优先级数值为0的中断优先级最高。

3.2 中断优先级的配置:

在STM32F4中,使用NVIC(Nested Vectored Interrupt Controller)来配置中断优先级。配置中断优先级的步骤如下:

  1. 确定优先级组分配
    Cortex-M内核允许将中断优先级分为不同的组,主要分为4-2-2(4位抢占优先级、2位子优先级)和3-1-4(3位抢占优先级、1位子优先级)两种模式。这决定了中断优先级的位分配,应根据系统需求进行选择。

  2. 设置抢占优先级和子优先级
    使用库函数或直接操作NVIC寄存器来为每个中断分配优先级。通常有函数如NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)用于设置中断的抢占优先级和子优先级。

  3. 优先级的影响
    在相同的优先级组中,较低位的抢占优先级数值越小,优先级越高;较低位的子优先级数值越小,优先级越高。

3.3 示例:
// 设置外设中断优先级为抢占优先级2,子优先级1
NVIC_SetPriority(USART1_IRQn, 2, 1);
// 启用USART1中断
NVIC_EnableIRQ(USART1_IRQn);
  • 1
  • 2
  • 3
  • 4

这个示例中,针对USART1的中断被配置为抢占优先级为2,子优先级为1。请注意,优先级配置必须在使能相应中断之前完成。

了解并正确配置中断优先级对于确保在系统中正确处理中断,并按照预期的顺序处理多个中断事件至关重要。

4. 中断相关的HAL驱动程序

中断管理相关驱动程序的头文件是 stm32f4xx_hal_cortex.h ,常用函数如下图所示

函数名功能
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);设置4位二进制数的优先级分组策略
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);设置某个中断的抢占优先级和此优先级
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);启用某个中断
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);禁用某个中断
uint32_t HAL_NVIC_GetPriorityGrouping(void);返回当前的优先级分组策略
void HAL_NVIC_GetPriority(IRQn_Type IRQn, uint32_t PriorityGroup, uint32_t* pPreemptPriority, uint32_t* pSubPriority);返回某个中断的抢占优先级、次优先级数值
uint32_t HAL_NVIC_GetPendingIRQ(IRQn_Type IRQn);检查某个中断是否被挂起
void HAL_NVIC_SetPendingIRQ(IRQn_Type IRQn);设置某个中断的挂起标志,表示发生了中断
void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn);清除某个中断挂起标志

5. 外部中断EXTI

外部中断(External Interrupt,通常称为EXTI)在STM32F4系列微控制器中是一个重要的功能,它允许外部事件(例如按键、传感器输入等)引发处理器的中断。STM32F4通过外部中断线(External Interrupt Lines)来处理外部事件,而这些中断线可以与GPIO引脚相连。外部I/O端口的电平可以有三种触发中断的方式:上升沿、下降沿和双边沿触发,如下图所示:

STM32F4的外部中断EXTI的特点:
  1. 支持多个外部中断线: STM32F4具有多个外部中断线,每个中断线可以连接到特定的GPIO引脚。
  2. 多种触发方式: EXTI可以配置为触发不同的边沿或状态,例如上升沿触发、下降沿触发、上升和下降沿触发、低电平触发、高电平触发等。
  3. 中断优先级: EXTI也可以配置其优先级,允许在多个中断发生时确定哪个中断应该首先被处理。
  4. 软件和硬件触发: 可以通过软件手动触发外部中断,也可以通过外部引脚的电平或边沿触发中断。

image-20231231181514810

其原理如下图所示,外部中断是由外部I/O端口产生中断触发,单片机内核中止当前工作,并处理中断。这时就进入了中间部分,这里包括了“CPU参与”方框部分,有CPU进行参与。在中断模式下,CPU参与处理中断服务函数,进而点亮LED灯。

image-20240101233255207

STM32F407有23个外部中断,每个输入线都可以单独配置触发事件,如上跳沿触发、下跳沿触发或双边沿触发。每个EXTI中断可以单独屏蔽,有独立的中断标志,可以单独清除或保持其中断标志。如下图所示,是外部有信号从右边输入线输入,通过边缘检测电路,判断其中断标志位,进而一步步输送到NVIC中断控制器中。

image-20231231181535941

​ STM32F407有23个外部中断,每个输入线都可以单独配置触发事件,其中EXTI0至EXTI4的每个中断有独立的ISR,EXTI线[9:5]中断共用一个中断号,也就是共用ISR,EXTI线[15:10]中断也共用ISR,见下表所示。若共用的ISR,需要在ISR里在判断具体是哪个EXTI线产生的中断,然后做相应处理。

另外7个EXTI线连接的不是某个实际的GPIO引脚,二十其他外设产生的事件信号。这7个EXTI线的中断有单独的ISR:

  • EXTI线16连接PVD输出;
  • EXTI线17连接RTC闹钟事件;
  • EXTI线18连接USB OTG FS唤醒事件;
  • EXTI线19连接以太网唤醒事件;

image-20240102080839382

6. STM32 CubeMX配置实战

1. 环境准备
  1. 正点原子探索者 V2 (STM32F407ZGT6)
  2. STM32CubeMX 6.2.0
  3. STM32CubeIDE 1.14.0
  4. ST-Link V2
2. 硬件配置

从正点原子的原理图中,选择4个按键,2个LED灯作为本次实验的对象,用4个按键来控制2个LED灯实现不同功能的开关LED灯的效果。

image-20231214223415136

下图所示是4个按键KEY和LED对应的原理图,及相应的配置功能

名称端口引脚功能特性初始电平
KeyUp(WK_UP)PA0GPIO_EXTI0Pull-down 下拉N/A
KeyLeft(KEY2)PE2GPIO_EXTI2Pull-up 上拉N/A
KeyDown(KEY1)PE3GPIO_EXTI3Pull-up 上拉N/A
KeyRight(KEY0)PE4GPIO_EXTI4Pull-up 上拉N/A
LED1PF9OutputPushpull推挽输出,初始低电平
LED2PF10OutputPushpull推挽输出,初始低电平

image-20231214223643623

image-20231214223745496

image-20231214224526507

image-20231214224538399

3. CubeMX配置
  1. 按上一节的配置,我们GPIO的名字改为 “上下左右” 命名,更符合板子的命名方式

image-20231228215158642

  1. 将PA0端口改为GPIO_EXTI0,由外部中断来进行信号输入时的采集,如果是采集到的信号是设定的信号,(例如:上升沿或下降沿等),则会进入相应的中断中

image-20231228220031603

  1. 同理剩下三个按键都改为GPIO_EXTIx的中断采集信号模式

image-20231228220407150

  1. 根据实际硬件原理图,将Key按键的GPIO端口设置为外部上升沿/下降沿触发

image-20231228221447267

  1. GPIO mode如下图所示

image-20231228220940924

  1. GPIO端口内部上下拉电阻选择

image-20231228221003436

  1. 按照下图所示选择相应的上升沿/下降沿的中断触发方式

image-20240103075153650

  1. 设置抢占优先级和非抢占优先级,并使能GPIO EXTI外部中断

image-20231228222451066

注:4个GPIO的中断禁止设置成0,由于代码编写时用到了Systick系统时钟,系统时钟计时是靠这个中断来产生1ms的精准定时,如果4个GPIO设置成0,则会导致Systick计时被抢占,导致陷入HAL_Delay()的死循环中,无法出来

image-20231228222947257

  1. 设置RCC时钟

image-20231229081611163

  1. 调试口本例中选用ST-Link,则选择JTAG(4 PIN)image-20231215081805184
  2. 进行时钟树配置,外部晶振为8M,时钟主频为168MHz

image-20231229081730771

  1. Project Manager 配置,工具链选择STM32CubeIDE

image-20231229081935547

  1. 代码生成配置

image-20231229082016245

  1. 点击右上角生成代码

image-20231229082146613

7. CubeIDE编程实战

7.1 主程序

我们在CubeMX中配置完成后生成CubeIDE项目的代码,然后在CubeIDE中贷款刚生成的代码。

所生成的代码已经完成了GPIO引脚的初始化,包括外部中断的初始化配置,还生成了外部中断ISR的代码框架。

文件main.c中的主程序代码如下图所示,他调用了函数MX_GPIO_Init()进行GPIO引脚的初始化:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

HAL_Init()函数用于HAL初始化,在CubeMX中设置的中断优先级分组策略是在这个函数里用代码实现的。HAL_Init()调用了一个弱函数HAL_MspInit(),在CubeMX生成的代码中有一个文件stm32f4xx_hal_msp.c,在这个文件里重新实现了函数HAL_MspInit(),其代码如下:

/**
  * Initializes the Global MSP.
  */
void HAL_MspInit(void)
{
  /* USER CODE BEGIN MspInit 0 */

  /* USER CODE END MspInit 0 */

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_RCC_PWR_CLK_ENABLE();

  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

  /* System interrupt init*/

  /* USER CODE BEGIN MspInit 1 */

  /* USER CODE END MspInit 1 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们在CubeMX中为LED和按键的引脚都定义了用户标签,因此文件main.h中生成了这些引脚的引脚号、端口的宏定义,并且对于4个外部中断引脚,还有中断号的宏定义,全部定义如下:

/* Private defines -----------------------------------------------------------*/
#define KeyRight_Pin GPIO_PIN_2
#define KeyRight_GPIO_Port GPIOE
#define KeyRight_EXTI_IRQn EXTI2_IRQn
#define KeyDown_Pin GPIO_PIN_3
#define KeyDown_GPIO_Port GPIOE
#define KeyDown_EXTI_IRQn EXTI3_IRQn
#define KeyLeft_Pin GPIO_PIN_4
#define KeyLeft_GPIO_Port GPIOE
#define KeyLeft_EXTI_IRQn EXTI4_IRQn
#define LED1_Pin GPIO_PIN_9
#define LED1_GPIO_Port GPIOF
#define LED2_Pin GPIO_PIN_10
#define LED2_GPIO_Port GPIOF
#define KeyUp_Pin GPIO_PIN_0
#define KeyUp_GPIO_Port GPIOA
#define KeyUp_EXTI_IRQn EXTI0_IRQn
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
7.2. GPIO和EXTI中断初始化

文件gpio.c中的函数MX_GPIO_Init()实现了GPIO引脚和EXTI中断的初始化,代码如下:

/** Configure pins as
        * Analog
        * Input
        * Output
        * EVENT_OUT
        * EXTI
*/
void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOF, LED1_Pin|LED2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pins : PEPin PEPin PEPin */
  GPIO_InitStruct.Pin = KeyRight_Pin|KeyDown_Pin|KeyLeft_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pins : PFPin PFPin */
  GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = KeyUp_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(KeyUp_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);

  HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(EXTI2_IRQn);

  HAL_NVIC_SetPriority(EXTI3_IRQn, 1, 2);
  HAL_NVIC_EnableIRQ(EXTI3_IRQn);

  HAL_NVIC_SetPriority(EXTI4_IRQn, 1, 1);
  HAL_NVIC_EnableIRQ(EXTI4_IRQn);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

这个函数前半部分是对LED和按键GPIO引脚的初始化设置,函数代码的后半部分是对4个外部中断的设置,主要是设置中断的优先级和开启中断,用到了函数HAL_NVIC_SetPriority()和HAL_NVIC_EnableIRQ()。

7.3 EXTI中断的ISR

EXTI0到EXTI4都有独立的ISR,在文件stm32f4xx_it.c中自动生成了这4个ISR的代码框架,代码如下:

/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles EXTI line0 interrupt.
  */
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}

/**
  * @brief This function handles EXTI line2 interrupt.
  */
void EXTI2_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI2_IRQn 0 */

  /* USER CODE END EXTI2_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
  /* USER CODE BEGIN EXTI2_IRQn 1 */

  /* USER CODE END EXTI2_IRQn 1 */
}

/**
  * @brief This function handles EXTI line3 interrupt.
  */
void EXTI3_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI3_IRQn 0 */

  /* USER CODE END EXTI3_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
  /* USER CODE BEGIN EXTI3_IRQn 1 */

  /* USER CODE END EXTI3_IRQn 1 */
}

/**
  * @brief This function handles EXTI line4 interrupt.
  */
void EXTI4_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI4_IRQn 0 */

  /* USER CODE END EXTI4_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
  /* USER CODE BEGIN EXTI4_IRQn 1 */

  /* USER CODE END EXTI4_IRQn 1 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

我们在前文分析了外部中断ISR的执行原理,这些ISR追钟都要调用回调函数HAL_GPIO_EXTI_Callback(),因此用户需要重新实现这个回调函数,实现设计功能。

7.4 编写用户功能代码
1. 重现实现中断回调函数

我们要处理外部中断,只需要重新实现回调函数HAL_GPIO_EXTI_Callback()。此外,我们可以在任何一个文件内重新实现这个回调函数(例如在main.c中实现,也可以在gpio.c内实现),并且无须在头文件中声明其函数原型。

我们在文件gpio.c中重新实现这个函数,但需要注意,这个函数的代码必须写在一个代码沙箱内。在文件gpio.c中重新实现这个函数的代码如下:

/* USER CODE BEGIN 2 */

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if (GPIO_Pin == KeyUp_Pin)
	{
		HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
		HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
		HAL_Delay(500);
	}
	else if (GPIO_Pin == KeyRight_Pin)
	{
		HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
		HAL_Delay(1000);
	}
	else if (GPIO_Pin == KeyLeft_Pin)
	{
		HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
		HAL_Delay(1000);
	}
	else if (GPIO_Pin == KeyDown_Pin)
	{
		__HAL_GPIO_EXTI_GENERATE_SWIT(GPIO_PIN_0);
		HAL_Delay(1000);
	}

}

/* USER CODE END 2 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

函数的参数GPIO_Pin是触发外部中断的中断线,可用于判断发生了哪个外部中断。函数代码的功能很直观,就是实现一下预想的示例功能:

  • 按下KeyUp键时,使两个LED灯输出翻转,后面的延时是为了消除按键抖动的影响
  • 按下KeyRight键时,使LED2输出翻转
  • 按下KeyLeft键时,使LED1输出翻转
  • 按下KeyDown键时,产生EXTI0软中断,模拟KeyUp键按下
2. 改写函数HAL_GPIO_EXTI_IRQHandler()的代码

完成回调函数代码后,我们就可以构建项目,并将其下载到开发板上进行测试了。但是运行时,按键按下后的响应不如预期,,例如按下KeyUp按键后,两个LED会亮灭两次,虽然加了延时进行消抖处理,但是还是有按键抖动的影响。分析后发现,这是由ISR中调用的外部中断通用处理函数HAL_GPIO_EXTI_IRQHandler()的代码引起的,这个函数的代码如下:

/**
  * @brief  This function handles EXTI interrupt request.
  * @param  GPIO_Pin Specifies the pins connected EXTI line
  * @retval None
  */
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

他检测到中断挂起标志后,先清除了中断挂起标志,然后再执行回调函数。一般的中断通用函数都是这样的处理流程,是为了硬件能够及时响应下一次中断。但是对于检测按键输入的外部中断,这是有问题的,因为清除中断挂起标志位后,按键的抖动就会触发下一次中断,并将中断挂起标志位置位,就会在执行一次回调函数。

所以对于外部输入的中断按键检测,需要修改一下HAL_GPIO_EXTI_IRQHandler()的代码,将清除中断挂起标志位的功能放到后面,先执行回调函数,进行相应的按键功能检测,然后在清除中断标志位,所以修改的代码如下所示,这样,代码就没有问题了:

/**
  * @brief  This function handles EXTI interrupt request.
  * @param  GPIO_Pin Specifies the pins connected EXTI line
  * @retval None
  */
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {

    HAL_GPIO_EXTI_Callback(GPIO_Pin);

    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

但是要注意,函数HAL_GPIO_EXTI_IRQHandler()时文件stm32f4xx_hal_gpio.c中HAL驱动的原始文件,这个函数里面没有代码沙箱。修改这个函数后,在CubeMX重新生成代码时,这个函数又变回原来的样子。所以,在使用CubeMX时,用户一定要将代码写在沙箱内,如果实在要修改HAL的原始代码,在CubeMX重新生成代码后又会还原回去,要记得再次该回去。

8. 代码开源

工程代码地址:https://download.csdn.net/download/qq_35057766/88694009
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供代码和问题解答。欢迎大家关注和交流
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/973146
推荐阅读
相关标签
  

闽ICP备14008679号