前言
本文主要介紹了正點原子外部中斷實驗的實作方式。本文的代碼仿照正點原子而編寫。其主要功能為:
- key0:LED0和LED1同時熄滅與點亮。
- key1:控制LED1熄滅與點亮。
- key2:控制LED0熄滅與點亮。
- key3:LED0與LED1交替點亮。
本文主要參考文獻為:
- ST.STM32F429開發指南-HAL庫版本_V1.1
文章中使用的代碼工程見:https://github.com/zhenhaiyang/keil
硬體
每根導線與控制器的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進行配置。
在該程式中,總體分成三個部分:
- GPIO時鐘使能。
- 外部中斷GPIO配置。
- 中斷優先級設定與中斷使能。
關于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;
}
}
}
}
這部分程式大緻可以分成三個部分:
- 系統配置時鐘使能。
- 系統配置中外部中斷寄存器對應的GPIO口。
- 配置外部中斷相關的配置。
其中,配置寄存器的流程都是相似的:
- 首先獲得寄存器原有的值,放入臨時變量中。
- 将臨時變量中需要寫入新值的寄存器位清零。
- 将臨時變量中需要寫入新值的寄存器位通過位或的方式寫入。
- 将臨時變量的值寫入寄存器。
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)
可以看到,與下方寄存器中對應值是相同的。
中斷響應
EXTIx_IRQHandler
/**
* @brief 外部中斷0響應函數
* @note 無
* @param {*}無
* @retval 無
*/
void EXTI0_IRQHandler()
{
flagExti0 = 1;
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
關于中斷響應,以EXTI0位例,其餘類似。共有兩句:
- 将全局标志位置1,此标志位是自定義的。該句在本例中并沒有使用,主要是為了以後拓展使用。
- 調用全局外部中斷響應函數
。該函數為HAL庫提供,因為外部中斷相對不會很頻繁且該全局響應很簡單,可以使用該函數。HAL_GPIO_EXTI_IRQHandler()
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);
}
}
該函數分成三句:
- 判斷該中斷是否已經挂起。
- 清除挂起的中斷。
- 調用中斷回調函數
。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的狀态,比較神奇!