文章目录
1 概念
外部中断/事件控制器(EXTI)是 STM32 系列微控制器中的一个重要模块,负责管理控制器的 23 条中断/事件线。
- 中断/事件线
- EXTI 提供了 23 条中断/事件线,每条线可以独立地进行配置和管理。这使得开发者可以灵活地选择哪些引脚作为中断源。
- 边沿检测
- 每条中断/事件线都配备有边沿检测器,可以实现输入信号的上升沿和下降沿检测。这意味着可以对信号变化进行响应,例如检测按钮按下或释放的瞬间。
- 独立配置
- EXTI 允许对每个中断/事件线进行单独配置
- 中断或事件选择: 每条线可以被配置为触发中断或作为事件处理。
- 触发方式: 可以设置为上升沿触发、下降沿触发或双边沿触发,灵活应对不同的应用场景。
- EXTI 允许对每个中断/事件线进行单独配置
2 EXTI 功能框图
STM32F429 上 EXTI 是在 APB2 总线上的。
图中23 表示表示在控制器内部类似的信号线路有 23 个。

外部中断/事件控制器(EXTI)在 STM32 微控制器中具有两个主要功能:产生中断和产生事件。
2.1 中断产生流程
- 输入线(编号 1): EXTI 控制器有 23 个中断/事件输入线,这些输入线可以通过寄存器配置为任意 GPIO 引脚或某些外设的事件源。这些输入线主要用于监测电平变化的信号。
- 边沿检测电路(编号 2): 该电路根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)的配置来检测信号的边沿变化。如果检测到边沿跳变,电路会输出有效信号(1),否则输出无效信号(0)。EXTI_RTSR 和 EXTI_FTSR 的设置允许选择检测上升沿、下降沿或双边沿。
- 或门电路(编号 3): 该电路的一个输入来自边沿检测电路,另一个输入来自软件中断事件寄存器(EXTI_SWIER)。
- EXTI_SWIER 允许通过程序控制来启动中断/事件线。若任一输入为 1,或门输出为 1。
- 与门电路(编号 4): 该电路的输入为编号 3 的输出和中断屏蔽寄存器(EXTI_IMR)。只有当 EXTI_IMR 设置为 1 时,编号 4 的输出才会受到编号 3 输出的影响。输出信号会被保存在挂起寄存器(EXTI_PR)中,若编号 4 输出为 1,则会将 EXTI_PR 对应位置为 1。
- NVIC 输出(编号 5): EXTI_PR 寄存器的内容最终输出到 NVIC,实现系统中断事件的控制。
2.2 事件产生流程
- 与门电路(编号 6): 该电路的一个输入来自编号 3,另一个输入来自事件屏蔽寄存器(EXTI_EMR)。只有当 EXTI_EMR 设置为 1 时,编号 6 的输出才会受到编号 3 输出的影响。
- 脉冲发生器电路(编号 7): 当编号 6 的输出为有效信号(1)时,该电路会产生一个脉冲信号;若输出无效,则不会产生脉冲。
- 脉冲信号(编号 8): 该脉冲信号是事件产生的最终结果,可以被其他外设使用,例如定时器(TIM)、模拟数字转换器(ADC)等。
2.3 中断与事件的区别
- 中断: 产生中断的目的是将输入信号传输到 NVIC,进而执行中断服务函数,实现软件层面的响应。
- 事件: 产生事件的目的是传输一个脉冲信号给其他外设,属于电路级别的信号传输,通常用于硬件层面的应用。
3 EXTI 中断/事件线
EXTI(外部中断/事件控制器)提供了 23 条中断/事件线,每条线都可以被配置为特定的输入源。
3.1 概述
- EXTI 包含 23 条中断/事件线:
- EXTI0 至 EXTI15: 这 16 条线可以通过编程配置为任意 GPIO 引脚(PA0 到 PI0),实现灵活的输入源选择。
- EXTI16 至 EXTI22: 这 7 条线专门用于特定外设事件
中断/事件线 | 输入源 | 中断处理函数 |
EXTI0 | PX0 (X 可为 A,B,C,D,E,F,G,H,I) | KEY1_IRQHandler |
EXTI1 | PX1 (X 可为 A,B,C,D,E,F,G,H,I) | KEY2_IRQHandler |
EXTI2 | PX2 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI2_IRQHandler |
EXTI3 | PX3 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI3_IRQHandler |
EXTI4 | PX4 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI4_IRQHandler |
EXTI5 | PX5 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI9_5_IRQHandler |
EXTI6 | PX6 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI9_5_IRQHandler |
EXTI7 | PX7 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI9_5_IRQHandler |
EXTI8 | PX8 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI9_5_IRQHandler |
EXTI9 | PX9 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI9_5_IRQHandler |
EXTI10 | PX10 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI15_10_IRQHandler |
EXTI11 | PX11 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI15_10_IRQHandler |
EXTI12 | PX12 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI15_10_IRQHandler |
EXTI13 | PX13 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI15_10_IRQHandler |
EXTI14 | PX14 (X 可为 A,B,C,D,E,F,G,H,I) | EXTI15_10_IRQHandler |
EXTI15 | PX15 (X 可为 A,B,C,D,E,F,G,H) | EXTI15_10_IRQHandler |
EXTI16 | 可编程电压检测器 (PVD) 输出 | PVD_IRQHandler |
EXTI17 | RTC 闹钟事件 | RTC_Alarm_IRQHandler |
EXTI18 | USB OTG FS 唤醒事件 | OTG_FS_WKUP_IRQHandler |
EXTI19 | 以太网唤醒事件 | ETH_WKUP_IRQHandler |
EXTI20 | USB OTG HS(在 FS 中配置)唤醒事件 | OTG_HS_WKUP_IRQHandler |
EXTI21 | RTC 入侵和时间戳事件 | TAMP_STAMP |
EXTI22 | RTC 唤醒事件 | RTC_WKUP_IRQHandler |
3.2 GPIO 配置
EXTI0 至 EXTI15 的每条中断/事件线可以通过 SYSCFG 外部中断配置寄存器 1(SYSCFG_EXTICR1)进行配置。
例如,EXTI0 可以通过 EXTI0[3:0] 位选择配置为 PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或 PI0。其余 EXTI 线的配置类似。
3.3 特定外设事件
EXTI 的其他 7 条中断/事件线(EXTI16 至 EXTI22)由特定外设触发。这些线用于响应外设事件,例如 RTC(实时时钟)、USB OTG、以太网等。
4 EXTI 初始化结构体
4.1 初始化结构体
typedef struct { uint32_t EXTI_Line; // 中断/事件线 EXTIMode_TypeDef EXTI_Mode; // EXTI 模式 EXTITrigger_TypeDef EXTI_Trigger; // 触发事件 FunctionalState EXTI_LineCmd; // EXTI 控制 } EXTI_InitTypeDef;
- EXTI_Line: 选择要配置的 EXTI 中断/事件线,可以选择 EXTI0 至 EXTI22。
- EXTI_Mode: 配置 EXTI 的模式,可以选择产生中断(EXTI_Mode_Interrupt)或产生事件(EXTI_Mode_Event)。
- EXTI_Trigger: 配置 EXTI 的触发方式,可以选择:
- 上升沿触发(EXTI_Trigger_Rising)
- 下降沿触发(EXTI_Trigger_Falling)
- 上升沿和下降沿都触发(EXTI_Trigger_Rising_Falling)
- EXTI_LineCmd: 控制是否使能该 EXTI 线,可以选择使能(ENABLE)或禁用(DISABLE)。
5 中断实验
本实验采用野火STM32F429IGTb V2 开发板,以下代码来自野火:
https://gitee.com/Embedfire-stm32f429-tiaozhanzhe/ebf_stm32f429_tiaozhanzhe_std_code
5.1 硬件设计

5.2 前提准备
创建两个文件:exti.c 和 exti.h 文件用来存放 EXTI 驱动程序及相关宏定义,中断服务函数放在 stm32f4xx_it.h 文件中。
5.3 exti.h
#ifndef __EXTI_H #define __EXTI_H #include "stm32f4xx.h" // KEY1 相关定义 #define KEY1_INT_GPIO_PORT GPIOA /*!< KEY1 中断 GPIO 端口 */ #define KEY1_INT_GPIO_CLK RCC_AHB1Periph_GPIOA /*!< KEY1 GPIO 时钟使能 */ #define KEY1_INT_GPIO_PIN GPIO_Pin_0 /*!< KEY1 中断引脚 */ #define KEY1_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOA /*!< KEY1 EXTI 端口源 */ #define KEY1_INT_EXTI_PINSOURCE EXTI_PinSource0 /*!< KEY1 EXTI 引脚源 */ #define KEY1_INT_EXTI_LINE EXTI_Line0 /*!< KEY1 EXTI 线 */ #define KEY1_INT_EXTI_IRQ EXTI0_IRQn /*!< KEY1 EXTI 中断请求号 */ #define KEY1_IRQHandler EXTI0_IRQHandler /*!< KEY1 中断处理函数 */ // KEY2 相关定义 #define KEY2_INT_GPIO_PORT GPIOC /*!< KEY2 中断 GPIO 端口 */ #define KEY2_INT_GPIO_CLK RCC_AHB1Periph_GPIOC /*!< KEY2 GPIO 时钟使能 */ #define KEY2_INT_GPIO_PIN GPIO_Pin_13 /*!< KEY2 中断引脚 */ #define KEY2_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOC /*!< KEY2 EXTI 端口源 */ #define KEY2_INT_EXTI_PINSOURCE EXTI_PinSource13 /*!< KEY2 EXTI 引脚源 */ #define KEY2_INT_EXTI_LINE EXTI_Line13 /*!< KEY2 EXTI 线 */ #define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn /*!< KEY2 EXTI 中断请求号 */ #define KEY2_IRQHandler EXTI15_10_IRQHandler /*!< KEY2 中断处理函数 */ // 函数声明 void EXTI_Key_Config(void); /*!< 配置外部中断的函数 */ #endif /* __EXTI_H */
5.4 exti.c
#include "./key/bsp_exti.h" static void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 配置 NVIC 为优先级组 1 // 配置中断源:按键 1 NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 配置抢占优先级:1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 配置子优先级:1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道 NVIC_Init(&NVIC_InitStructure); // 配置中断源:按键 2,其他使用上面相关配置 NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ; NVIC_Init(&NVIC_InitStructure); } void EXTI_Key_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK | KEY2_INT_GPIO_CLK, ENABLE); // 开启按键 GPIO 口的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // 使能 SYSCFG 时钟,使用 GPIO 外部中断时必须使能 SYSCFG 时钟 NVIC_Configuration(); // 配置 NVIC // 选择按键 1 的引脚 GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // 设置引脚为输入模式 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 设置引脚不上拉也不下拉 GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure); // 使用上面的结构体初始化按键 SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); // 连接 EXTI 中断源到 key1 引脚 EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; // 选择 EXTI 中断源 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断/事件线 EXTI_Init(&EXTI_InitStructure); // 选择按键 2 的引脚 GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN; GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); // 其他配置与上面相同 SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE); // 连接 EXTI 中断源到 key2 引脚 EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; // 选择 EXTI 中断源 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断/事件线 EXTI_Init(&EXTI_InitStructure); }
5.5 stm32f4xx_it.h
#include "stm32f4xx_it.h" #include "./led/bsp_led.h" #include "./key/bsp_exti.h" void KEY1_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) { // LED1 取反 LED1_TOGGLE; //清除中断标志位 EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE); } } void KEY2_IRQHandler(void) { //确保是否产生了EXTI Line中断 if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) { // LED2 取反 LED2_TOGGLE; //清除中断标志位 EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE); } }
5.6 main.c
#include "stm32f4xx.h" #include "./led/bsp_led.h" #include "./key/bsp_exti.h" #include "core_cm4.h" // 函数声明 void Delay(__IO uint32_t nCount); /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { // 初始化 LED 相关的 GPIO 端口 LED_GPIO_Config(); // 初始化外部中断(EXTI),配置按键的中断功能 // 按下按键时会触发中断,进入相应的中断处理函数 // 中断处理函数在 stm32f4xx_it.c 文件中定义,具体为 KEY1_IRQHandler 和 KEY2_IRQHandler // 这些处理函数负责处理中断事件,比如反转 LED 灯的状态 EXTI_Key_Config(); // 主循环,程序运行在此循环中 // 由于使用中断方式,CPU 不需要轮询按键状态,节省了 CPU 资源 while (1) { // 主循环可以执行其他任务或进入低功耗模式 } } // 简单的延时函数,未使用本代码中,但可以用于其他地方 void Delay(__IO uint32_t nCount) { for (; nCount != 0; nCount--); // 循环计数,消耗时间 }