本文主要介绍了正点原子外部中断实验的实现方式。本文的代码仿照正点原子而编写。其主要功能为:
本文主要参考文献为:
文章中使用的代码工程见:https://github.com/zhenhaiyang/keil
/**
******************************************************************************
* @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()
,其余的模块在前面的博客中都有介绍。
/**
* @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初始化部分:
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的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)
/**
* @brief 外部中断0响应函数
* @note 无
* @param {*}无
* @retval 无
*/
void EXTI0_IRQHandler()
{
flagExti0 = 1;
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
关于中断响应,以EXTI0位例,其余类似。共有两句:
HAL_GPIO_EXTI_IRQHandler()
。该函数为HAL库提供,因为外部中断相对不会很频繁且该全局响应很简单,可以使用该函数。/**
* @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灯实现对应的控制。
/**
* @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的状态,比较神奇!