天天看點

STM32F429第十七篇之外部中斷實驗詳解前言硬體軟體

前言

本文主要介紹了正點原子外部中斷實驗的實作方式。本文的代碼仿照正點原子而編寫。其主要功能為:

  • key0:LED0和LED1同時熄滅與點亮。
  • key1:控制LED1熄滅與點亮。
  • key2:控制LED0熄滅與點亮。
  • key3:LED0與LED1交替點亮。

本文主要參考文獻為:

  • ST.STM32F429開發指南-HAL庫版本_V1.1

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

硬體

STM32F429第十七篇之外部中斷實驗詳解前言硬體軟體

每根導線與控制器的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第十七篇之外部中斷實驗詳解前言硬體軟體

中斷響應

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的狀态,比較神奇!