11. 外部中断
一、什么是外部中断
EXTI 即是外部中断和事件控制器。对于互联型产品,外部中断/事件控制器由 20 个产生事件/中断请求的边沿检测器组成,对于其它产品,则有 19 个能产生事件/中断请求的边沿检测器。每一条输入线都可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿都触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
中断:要进入 NVIC,有相应的中断服务函数,需要 CPU 处理;
事件:不进入 NVIC,仅用于内部硬件自动控制的,如:TIM、DMA、ADC;
二、EXTI的工作原理
从 EXTI 功能框图可以看到有两条主线,一条是 由输入线到 NVIC 中断控制器,一条是 由输入线到脉冲发生器。这就恰恰是 EXTI 的两大部分功能,产生中断与产生事件,两者从硬件上就存在不同。
【1】、EXTI 功能框图的产生中断的线路:
最终信号是流入 NVIC 控制器中。输入线是线路的信息输入端,它可以通过配置寄存器设置为任何一个 GPIO 口,或者是一些外设的事件。输入线一般都是存在电平变化的信号。
标号 ① 是一个边沿检测电路,包括 边沿检测电路,上升沿触发选择寄存器(EXTI_RTSR)和 下降沿触发选择寄存器(EXTI_FTSR)。边沿检测电路以 输入线 作为 信号输入端,如果检测到有边沿跳变就输出有效信号‘1’,就输出有效信号‘1’到标号 ② 部分电路,否则输入无效信号‘0’。边沿跳变的标准在于开始的时候对于上升沿触发选择寄存器或下降沿触发选择寄存器对应位的设置。
标号 ② 是一个 或门电路,它的两个信号输入端分别是 软件中断事件寄存器(EXTI_SWIER)和 边沿检测电路的输入信号。或门电路只要输入端有信号‘1’,就会输出‘1’,所以就会输出‘1’到标号 ③ 电路和标号 ④ 电路。通过对 软件中断事件寄存器 的读写操作就可以启动 中断/事件线,即相当于输出有效信号‘1’到或门电路输入端。
标号 ③ 是一个 与门电路,它的两个信号输入端分别是 中断屏蔽寄存器(EXTI_IMR)和 标号 ② 电路输出信号。与门电路要求输入都为‘1’才输出‘1’。如果中断屏蔽寄存器(EXTI_IMR)设置为 0 时,不管从标号 ② 电路 输出的信号特性如何,最终标号 ③ 电路 输出的信号都是 0;假如中断屏蔽寄存器(EXTI_IMR)设置为 1 时,最终 标号 ③ 电路 输出的信号才由标号 ② 电路 输出信号决定,这样就可以简单控制 EXTI_IMR 来实现中断的目的。标号 ④ 电路输出‘1’就会把请求挂起寄存器(EXTI_PR)对应位置 1。
最后,请求挂起寄存器(EXTI_PR)的内容就输出到 NVIC 内,实现系统中断事件的控制。
【2】、EXTI功能框图的产生事件的线路:
产生事件线路是从标号 ② 之后与中断线路有所不用,之前的线路都是共用的。标号 ④ 是一个与门,输入端来自标号 ② 电路 以及来自于事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR 寄存器设置为 0,那不管标号 ② 电路 输出的信号是‘0’还是‘1’,最终标号 ④ 输出的是‘0’;如果 EXTI_EMR 寄存器设置为 1,最终标号 ④ 电路 输出信号就由标号 ③ 电路 输出的信号决定,这样子就可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
标号 ④ 电路 输出有效信号‘1’就会使脉冲发生器电路产生一个脉冲,而无效信号就不会使其产生脉冲信号。脉冲信号产生可以给其他外设电路使用,例如定时器,模拟数字转换器等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换。
三、外部中断/事件线路映像
112 个通用 I/O 端口以下图的方式连接到 16 个外部中断/事件线上:
另外四个 EXTI 线的连接方式如下:
- EXTI 线16 连接到 PVD 输出
- EXTI 线17 连接到 RTC 闹钟事件
- EXTI 线18 连接到 USB 唤醒事件
- EXTI 线19 连接到 以太网唤醒事件(只适用于互联型产品)
GPIO 和中断线映射关系是在寄存器 AFIO_EXTICR1 ~ AFIO_EXTICR4 中配置的。AFIO_EXTICR1 寄存器配置 EXTI0 到 EXTI3 线,包含的外部中断的引脚包括 PAx 到 PGx,x = 0 到 3。AFIO_EXTICR2 寄存器配置 EXTI4 到 EXTI7 线,包含的外部中断的引脚包括 PAx 到 PGx,x = 4 到 7。AFIO_EXTICR3 寄存器配置 EXTI8 到 EXTI11 线,包含的外部中断的引脚包括 PAx 到 PGx,x = 8 到 11。AFIO_EXTICR4 寄存器配置 EXTI12 到 EXTI5 线,包含的外部中断的引脚包括 PAx 到 PGx,x = 12 到 15。
配置 AFIO 寄存器之前要使能 AFIO 时钟:__HAL_RCC_CLK_ENABLE();
四、EXTI寄存器介绍
4.1、中断屏蔽寄存器(EXTI_IMR)
4.2、事件屏蔽寄存器(EXTI_EMR)
4.3、上升沿触发选择寄存器(EXTI_RTSR)
4.4、下降沿触发选择寄存器(EXTI_FTSR)
4.5、软件中断事件寄存器(EXTI_SWIER)
4.6、挂起寄存器(EXTI_PR)
五、外部中断配置步骤
5.1、使能GPIO时钟
/** @defgroup RCC_APB2_Clock_Enable_Disable APB2 Clock Enable Disable
* @brief Enable or disable the High Speed APB (APB2) peripheral clock.
* @note After reset, the peripheral clock (used for registers read/write access)
* is disabled and the application software has to enable this clock before
* using it.
* @{
*/
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOD_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\
UNUSED(tmpreg); \
} while(0U)
/** @defgroup RCCEx_APB2_Clock_Enable_Disable APB2 Clock Enable Disable
* @brief Enable or disable the High Speed APB (APB2) peripheral clock.
* @note After reset, the peripheral clock (used for registers read/write access)
* is disabled and the application software has to enable this clock before
* using it.
* @{
*/
#if defined(STM32F100xE) || defined(STM32F101xB) || defined(STM32F101xE)\
|| defined(STM32F101xG) || defined(STM32F100xB) || defined(STM32F103xB)\
|| defined(STM32F103xE) || defined(STM32F103xG) || defined(STM32F105xC)\
|| defined(STM32F107xC)
#define __HAL_RCC_GPIOE_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPEEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPEEN);\
UNUSED(tmpreg); \
} while(0U)
#endif /* STM32F101x6 || STM32F101xB || STM32F101xE || (...) || STM32F105xC || STM32F107xC */
#if defined(STM32F101xE) || defined(STM32F103xE) || defined(STM32F101xG)\
|| defined(STM32F103xG)
#define __HAL_RCC_GPIOF_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPFEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPFEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOG_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPGEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPGEN);\
UNUSED(tmpreg); \
} while(0U)
#endif /* STM32F101xE || STM32F103xE || STM32F101xG || STM32F103xG*/
5.2、外部中断初始化配置
【1】、HAL_GPIO_Init() 函数
HAL 库的 EXTI 外部中断的设置功能整合到 HAL_GPIO_Init() 函数里面,而不是单独独立一个文件。所以我们的外部中断的初始化函数也是用 HAL_GPIO_Init() 函数。
/**
* @brief Initializes the GPIOx peripheral according to the specified parameters in the GPIO_Init.
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Init: pointer to a GPIO_InitTypeDef structure that contains
* the configuration information for the specified GPIO peripheral.
* @retval None
*/
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
- 函数形参:
GPIOx 是 端口号,其中 x 可以是 A-G
/** @addtogroup Peripheral_declaration
* @{
*/
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE)
GPIO_Init 是 GPIO_InitTypeDef类型的结构体变量
/**
* @brief GPIO Init structure definition
*/
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
- 函数返回值:
无;
5.3、设置优先级,使能中断
设置 NVIC 分为三步,即:设置优先级分组、设置优先级、使能中断。
中断分组 使用 HAL_NVIC_SetPriorityGrouping() 函数设置,中断优先级 使用 HAL_NVIC_SetPriority() 函数设置,中断使能 使用 HAL_NVIC_EnableIRQ() 函数设置。
【1】、HAL_NVIC_SetPriorityGrouping() 函数
/**
* @brief Sets the priority grouping field (preemption priority and subpriority)
* using the required unlock sequence.
* @param PriorityGroup: The priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PRIORITYGROUP_0: 0 bits for preemption priority
* 4 bits for subpriority
* @arg NVIC_PRIORITYGROUP_1: 1 bits for preemption priority
* 3 bits for subpriority
* @arg NVIC_PRIORITYGROUP_2: 2 bits for preemption priority
* 2 bits for subpriority
* @arg NVIC_PRIORITYGROUP_3: 3 bits for preemption priority
* 1 bits for subpriority
* @arg NVIC_PRIORITYGROUP_4: 4 bits for preemption priority
* 0 bits for subpriority
* @note When the NVIC_PriorityGroup_0 is selected, IRQ preemption is no more possible.
* The pending IRQ priority will be managed only by the subpriority.
* @retval None
*/
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
- 函数参数:
PriorityGroup 是中断优先级分组号,可以选择范围:NVIC_PRIORITYGROUP_0 到 NVIC_PRIORITYGROUP_4(共5组);
/** @defgroup CORTEX_Preemption_Priority_Group CORTEX Preemption Priority Group
* @{
*/
#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
0 bits for subpriority */
- 函数返回值:
无;
【2】、HAL_NVIC_SetPriority() 函数
/**
* @brief Sets the priority of an interrupt.
* @param IRQn: External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xx.h))
* @param PreemptPriority: The preemption priority for the IRQn channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority
* @param SubPriority: the subpriority level for the IRQ channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority.
* @retval None
*/
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);
- 函数参数:
IRQn 是中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f103xe.h;
/**
* @brief STM32F10x Interrupt Number Definition, according to the selected device
* in @ref Library_configuration_section
*/
/*!< Interrupt Number Definition */
typedef enum
{
/****** STM32 specific Interrupt Numbers *********************************************************/
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
} IRQn_Type;
PreemptPriority 是 抢占优先级,可以选择范围:0 到 15;
SubPriority 是 响应优先级,可以选择范围:0 到 15;
- 函数返回值:
无;
【3】、HAL_NVIC_EnableIRQ() 函数
/**
* @brief Enables a device specific interrupt in the NVIC interrupt controller.
* @note To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
* function should be called before.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))
* @retval None
*/
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
- 函数参数:
IRQn 是中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f103xe.h;
/**
* @brief STM32F10x Interrupt Number Definition, according to the selected device
* in @ref Library_configuration_section
*/
/*!< Interrupt Number Definition */
typedef enum
{
/****** STM32 specific Interrupt Numbers *********************************************************/
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
} IRQn_Type;
- 函数返回值:
无;
5.4、设计中断服务函数
每开启一个中断,就必须编写其对应的中断服务函数,否则将导致死机(CPU 将找不到中断服务函数)。中断服务函数接口厂家已经在startup_stm32f103xe.s 中做好了,STM32F1 的 IO 口外部中断函数只有 7 个,分别为:
void EXTI0_IRQHandler();
void EXTI1_IRQHandler();
void EXTI2_IRQHandler();
void EXTI3_IRQHandler();
void EXTI4_IRQHandler();
void EXTI9_5_IRQHandler();
void EXTI15_10_IRQHandler();
中断线 0-4,每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler(),中断线 10-15 共用中断函数 EXTI15_10_IRQHandler()。
一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装。HAL 库为了用户使用方便,提供了一个中断通用入口函数 HAL_GPIO_EXTI_IRQHandler(),在该函数内部直接调用回调函数 HAL_GPIO_EXTI_Callback()。
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_tGPIO_Pin)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin)!=0x00U)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); // 清中断标志位
HAL_GPIO_EXTI_Callback(GPIO_Pin); // 外部中断回调函数
}
}
该中断服务函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback() 实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler(),然后在回调函数 HAL_GPIO_EXTI_Callback() 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。
5.5、编写中断处理回调函数
【1】、HAL_GPIO_EXTI_Callback() 函数
HAL 库为了用户使用方便,提供了一个 中断通用入口函数 HAL_GPIO_EXTI_IRQHandler(),在该函数内部直接调用 回调函数 HAL_GPIO_EXTI_Callback()。
/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
六、通过EXTI控制LED状态
6.1、原理图
6.2、程序设计
根据原理图分析,KEY_UP 所连接的引脚应该配置为 下拉模式(GPIO_PULLDOWN),上升沿触发(GPIO_MODE_IT_RISING),所读取的电平与 GPIO_PIN_SET(高电平)作比较;K1、K2 和 K3 所连接的引脚应该配置为 上拉输入(GPIO_PULLUP)、下降沿触发(GPIO_MODE_IT_FALLING),所读取的电平与 GPIO_PIN_RESET(低电平)作比较。
外部中断初始化函数:
void EXTI_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能对应的GPIO的时钟
GPIO_InitStruct.Pin = GPIO_PIN_0; // 选择GPIO的引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 设置触发模式为上升沿触发
GPIO_InitStruct.Pull = GPIO_PULLDOWN; // 下拉输入
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // GPIO初始化函数
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 设置中断优先级分组,HAL库默认分组为4
HAL_NVIC_SetPriority(EXTI0_IRQn,2,0); // 设置中断优先级
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能中断
}
中断服务函数:
void EXTI0_IRQHandler()
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用HAL库中断处理公共函数
}
中断处理公共函数:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
HAL_GPIO_EXTI_Callback(GPIO_Pin); // 外部中断回调函数
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); // 清中断标志位
}
}
中断回调函数:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_Delay(20); // 按键消抖
if(GPIO_Pin == GPIO_PIN_0) // 判断中断线
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_SET) // 如果按键按下
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0); // LED状态翻转
}
}
HAL_Delay(20); // 按键消抖
}
LED 初始化函数 请在 点亮 LED 篇章查看;
main() 函数:
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
LED_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
EXTI_Init();
while (1)
{
}
}