STM32F429第十七篇之外部中断实验详解

前言

本文主要介绍了正点原子外部中断实验的实现方式。本文的代码仿照正点原子而编写。其主要功能为:

  • key0:LED0和LED1同时熄灭与点亮。
  • key1:控制LED1熄灭与点亮。
  • key2:控制LED0熄灭与点亮。
  • key3:LED0与LED1交替点亮。

本文主要参考文献为:

  • ST.STM32F429开发指南-HAL库版本_V1.1

文章中使用的代码工程见:https://github.com/zhenhaiyang/keil

硬件

STM32F429第十七篇之外部中断实验详解_第1张图片
每根导线与控制器的GPIO连接方式为:

  • WKUP->PA0
  • key0->PH3
  • key1->PH2
  • key2->PC13

软件

主程序

/**
  ******************************************************************************
  * @file    main.c
  * @author  zhy
  * @version 1.0
  * @date    2021-01-29
  * @brief   验证外部中断
  ******************************************************************************
  */
#include "stm32f4xx.h"
#include "sys.h"
#include "exti.h"
#include "led.h"
#include "usart.h"

int main()
{
    HAL_Init();
    SystemClock_Config();
    LedInit();
    UartInit();
    ExtiInit();

    printf("hello,zhy!");
    while(1)
    {
        
    }

}

该程序的主程序很简单,只有各种模块的初始化。本文重点关注外部中断初始化ExtiInit(),其余的模块在前面的博客中都有介绍。

初始化

ExtiInit

/** 
 * @brief 外部中断初始化
 * @note  无
 * @param {*}无
 * @retval 无 
 */
void ExtiInit()
{
    /* 1.gpio时钟使能 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOH_CLK_ENABLE();

    /* 2.gpio初始化 */
    GPIO_InitTypeDef gpioInit;
    gpioInit.Pin = GPIO_PIN_0;           //Pin0
    gpioInit.Mode = GPIO_MODE_IT_RISING; //上升沿
    gpioInit.Pull = GPIO_PULLDOWN;       //下拉
    gpioInit.Speed = GPIO_SPEED_FAST;    //快速
    HAL_GPIO_Init(GPIOA, &gpioInit);     //PA0

    gpioInit.Pin = GPIO_PIN_2 | GPIO_PIN_3; //PIN2和PIN3
    gpioInit.Mode = GPIO_MODE_IT_FALLING;   //下降沿
    gpioInit.Pull = GPIO_PULLUP;            //上拉
    HAL_GPIO_Init(GPIOH, &gpioInit);        //PH2,PH3

    gpioInit.Pin = GPIO_PIN_13;
    HAL_GPIO_Init(GPIOC, &gpioInit); //PC13

    /* 3.中断优先级与使能 */
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);

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

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

    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 3);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

在HAL库中,外部中断的配置被整合在GPIO功能一起,所以,我们首先对GPIO进行配置。
在该程序中,总体分成三个部分:

  1. GPIO时钟使能。
  2. 外部中断GPIO配置。
  3. 中断优先级设置与中断使能。

关于GPIO时钟使能部分,在之前的博客中已经分析了,此处不再详细描述。
关于中断优先级的设置与中断使能部分,主要涉及到内核编程的知识,通过调用内核相关函数完成,此处也不再详细分析。

HAL_GPIO_Init

重点看一下GPIO初始化部分:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
      /* 省略前面部分代码 */

      /*--------------------- EXTI Mode Configuration ------------------------*/
      /* Configure the External Interrupt or event for the current IO */
      if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)//判断是否设置外部中断相关
      {
        /* Enable SYSCFG Clock */
        __HAL_RCC_SYSCFG_CLK_ENABLE();//使能系统时钟

        temp = SYSCFG->EXTICR[position >> 2U];//四个外部中断线一个寄存器,选择合适的寄存器
        temp &= ~(((uint32_t)0x0FU) << (4U * (position & 0x03U)));//&3其实就是%4,将对应的位置清零
        temp |= ((uint32_t)(GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));//将对应的值写入临时值
        SYSCFG->EXTICR[position >> 2U] = temp;//将临时值存入

        /* Clear EXTI line configuration */
        temp = EXTI->IMR;
        temp &= ~((uint32_t)iocurrent);//iocurrent:当前管脚对应寄存器的位置:Pin0->0b001;Pin1->0b010;Pin2->0b100
        if((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
        {
          temp |= iocurrent;//使能该中断
        }
        EXTI->IMR = temp;

        temp = EXTI->EMR;
        temp &= ~((uint32_t)iocurrent);
        if((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
        {
          temp |= iocurrent;
        }
        EXTI->EMR = temp;

        /* Clear Rising Falling edge configuration */
        temp = EXTI->RTSR;
        temp &= ~((uint32_t)iocurrent);
        if((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
        {
          temp |= iocurrent;
        }
        EXTI->RTSR = temp;

        temp = EXTI->FTSR;
        temp &= ~((uint32_t)iocurrent);
        if((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
        {
          temp |= iocurrent;
        }
        EXTI->FTSR = temp;
      }
    }
  }
}

这部分程序大致可以分成三个部分:

  1. 系统配置时钟使能。
  2. 系统配置中外部中断寄存器对应的GPIO口。
  3. 配置外部中断相关的配置。

其中,配置寄存器的流程都是相似的:

  1. 首先获得寄存器原有的值,放入临时变量中。
  2. 将临时变量中需要写入新值的寄存器位清零。
  3. 将临时变量中需要写入新值的寄存器位通过位或的方式写入。
  4. 将临时变量的值写入寄存器。

GPIO的MODE相关定义:

/** @defgroup GPIO_mode_define GPIO mode define
  * @brief GPIO Configuration Mode 
  *        Elements values convention: 0xX0yz00YZ
  *           - X  : GPIO mode or EXTI Mode
  *           - y  : External IT or Event trigger detection 
  *           - z  : IO configuration on External IT or Event
  *           - Y  : Output type (Push Pull or Open Drain)
  *           - Z  : IO Direction mode (Input, Output, Alternate or Analog)
  * @{
  */ 
#define  GPIO_MODE_INPUT                        ((uint32_t)0x00000000U)   /*!< Input Floating Mode                   */
#define  GPIO_MODE_OUTPUT_PP                    ((uint32_t)0x00000001U)   /*!< Output Push Pull Mode                 */
#define  GPIO_MODE_OUTPUT_OD                    ((uint32_t)0x00000011U)   /*!< Output Open Drain Mode                */
#define  GPIO_MODE_AF_PP                        ((uint32_t)0x00000002U)   /*!< Alternate Function Push Pull Mode     */
#define  GPIO_MODE_AF_OD                        ((uint32_t)0x00000012U)   /*!< Alternate Function Open Drain Mode    */

#define  GPIO_MODE_ANALOG                       ((uint32_t)0x00000003U)   /*!< Analog Mode  */
    
#define  GPIO_MODE_IT_RISING                    ((uint32_t)0x10110000U)   /*!< External Interrupt Mode with Rising edge trigger detection          */
#define  GPIO_MODE_IT_FALLING                   ((uint32_t)0x10210000U)   /*!< External Interrupt Mode with Falling edge trigger detection         */
#define  GPIO_MODE_IT_RISING_FALLING            ((uint32_t)0x10310000U)   /*!< External Interrupt Mode with Rising/Falling edge trigger detection  */
 
#define  GPIO_MODE_EVT_RISING                   ((uint32_t)0x10120000U)   /*!< External Event Mode with Rising edge trigger detection               */
#define  GPIO_MODE_EVT_FALLING                  ((uint32_t)0x10220000U)   /*!< External Event Mode with Falling edge trigger detection              */
#define  GPIO_MODE_EVT_RISING_FALLING           ((uint32_t)0x10320000U)   /*!< External Event Mode with Rising/Falling edge trigger detection       */
/**
  * @}
  */

#define GPIO_MODE             ((uint32_t)0x00000003U)
#define EXTI_MODE             ((uint32_t)0x10000000U)
#define GPIO_MODE_IT          ((uint32_t)0x00010000U)
#define GPIO_MODE_EVT         ((uint32_t)0x00020000U)
#define RISING_EDGE           ((uint32_t)0x00100000U)
#define FALLING_EDGE          ((uint32_t)0x00200000U)
#define GPIO_OUTPUT_TYPE      ((uint32_t)0x00000010U)

还有其中使用的宏定义为:

#define GPIO_GET_INDEX(__GPIOx__)    (uint8_t)(((__GPIOx__) == (GPIOA))? 0U :\
                                               ((__GPIOx__) == (GPIOB))? 1U :\
                                               ((__GPIOx__) == (GPIOC))? 2U :\
                                               ((__GPIOx__) == (GPIOD))? 3U :\
                                               ((__GPIOx__) == (GPIOE))? 4U :\
                                               ((__GPIOx__) == (GPIOF))? 5U :\
                                               ((__GPIOx__) == (GPIOG))? 6U :\
                                               ((__GPIOx__) == (GPIOH))? 7U :\
                                               ((__GPIOx__) == (GPIOI))? 8U :\
                                               ((__GPIOx__) == (GPIOJ))? 9U : 10U)

可以看到,与下方寄存器中对应值是相同的。
STM32F429第十七篇之外部中断实验详解_第2张图片

中断响应

EXTIx_IRQHandler

/** 
 * @brief 外部中断0响应函数 
 * @note  无
 * @param {*}无
 * @retval 无
 */
void EXTI0_IRQHandler()
{
    flagExti0 = 1;
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

关于中断响应,以EXTI0位例,其余类似。共有两句:

  1. 将全局标志位置1,此标志位是自定义的。该句在本例中并没有使用,主要是为了以后拓展使用。
  2. 调用全局外部中断响应函数HAL_GPIO_EXTI_IRQHandler()。该函数为HAL库提供,因为外部中断相对不会很频繁且该全局响应很简单,可以使用该函数。

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. 调用中断回调函数HAL_GPIO_EXTI_Callback

首先,通过宏__HAL_GPIO_EXTI_GET_IT直接读取挂起寄存器(PR)判断该中断是否已经挂起,该宏定义为:

/**
  * @brief  Checks whether the specified EXTI line is asserted or not.
  * @param  __EXTI_LINE__: specifies the EXTI line to check.
  *          This parameter can be GPIO_PIN_x where x can be(0..15)
  * @retval The new state of __EXTI_LINE__ (SET or RESET).
  */
#define __HAL_GPIO_EXTI_GET_IT(__EXTI_LINE__) (EXTI->PR & (__EXTI_LINE__))

然后,通过向挂起寄存器(PR)写1将对应位清0从而完成中断的响应,宏定义为:

/**
  * @brief  Clears the EXTI's line pending flags.
  * @param  __EXTI_LINE__: specifies the EXTI lines flags to clear.
  *         This parameter can be any combination of GPIO_PIN_x where x can be (0..15)
  * @retval None
  */
#define __HAL_GPIO_EXTI_CLEAR_FLAG(__EXTI_LINE__) (EXTI->PR = (__EXTI_LINE__))

最后,调用回调函数HAL_GPIO_EXTI_Callback,该函数实现中断后用户想实现的功能,其函数的具体内容由用户自己定义。而在本文,就是对应的LED灯实现对应的控制。

HAL_GPIO_EXTI_Callback

/** 
 * @brief 外部中断回调函数 
 * @note  在HAL_GPIO_EXTI_IRQHandler()函数中自动调用
 *             WKUP->PA0
 *             key0->PH3
 *             key1->PH2
 *             key2->PC13
 * @param {uint16_t} GPIO_Pin
 * @retval 无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(100); //消抖
    switch (GPIO_Pin)
    {
    case GPIO_PIN_0:
        if (WKUP == 1) //延时后依然按下
        {
            LED1 = LED0;
            LED0 = !LED0;
        }
        break;
    case GPIO_PIN_2:
        if (KEY1 == 0)
        {
            LED1 = !LED1;
        }
        break;
    case GPIO_PIN_3:
        if (KEY0 == 0)
        {
            LED1 = !LED1;
            LED0 = LED1;
        }
        break;
    case GPIO_PIN_13:
        if (KEY2 == 0)
        {
            LED0 = !LED0;
        }
        break;
    }
}

该函数通过Switch判断发生中断对应的按键,然后,通过延时消抖,再次判断按键是否真的按下。若确定触发中断的不是抖动,则响应中断,将LED实现对应的变化。

注意:

本实验将按键对应的GPIO位设置的为外部中断模式,而不是输入模式。然而,在外部中断的模式下,我们依然可以读取该GPIO的状态,比较神奇!

你可能感兴趣的:(ARM,ARM,外部中断,EXTI,HAL库,STM32F429)