
中断是嵌入式开发中最重要的基础概念之一也是 STM32 从入门到进阶必须掌握的核心能力。本文面向 STM32 初学者从零开始系统讲解中断的完整知识体系先介绍中断的基础概念与作用再深入 STM32 中断系统架构NVIC、中断向量表、处理流程接着讲解中断优先级的配置方法最后通过外部中断EXTI实战带你完整走通标准库和 HAL 库的配置流程并给出按键控制 LED 的完整工程示例。建议阅读顺序如果你是第一次接触中断请按章节顺序依次阅读先理解「什么是中断」和「中断执行过程」再进入架构与优先级部分如果你已有一定基础可以直接跳到「五、中断配置完整步骤」和「七、常见问题与注意事项」对照实践。文中所有代码示例基于 STM32F103 、STM32F411系列建议边读边在开发板上动手验证遇到问题可随时回看对应章节的排查清单。1.1 什么是中断中断Interrupt是指 CPU 在正常执行程序的过程中由于内部或外部事件的触发暂时中止当前程序的执行转而去处理该事件对应的服务程序处理完毕后再回到原程序被打断的位置继续执行的过程。类比理解你正在看书主程序电话铃响了中断触发你在书页上夹个书签保存现场去接电话中断服务函数接完电话后回到书签位置继续看书恢复现场、返回主程序。1.2 中断的作用提高 CPU 效率避免 CPU 不断轮询外设状态让 CPU 在等待时处理其他任务。实时响应紧急事件可以立即得到处理满足实时系统的要求。实现多任务调度操作系统利用 SysTick 等定时器中断实现任务切换。低功耗支持CPU 可进入休眠模式由中断唤醒。1.3 中断 vs 轮询对比项中断方式轮询方式CPU 占用低事件到来时才响应高持续查询状态响应速度快实时性好取决于轮询周期实现复杂度较高需配置中断和服务函数简单循环判断即可适用场景实时性要求高、事件频率低事件频繁、实时性要求低1.4 中断执行过程二、STM32 中断系统架构EXTI 中断线一共 23 个其中外部 GPIO 的中断有 16 个。EXTI流程EXTI 可分为两大部分功能一个是产生中断另一个是产生事件这两个功能从硬件上就有所不同。首先我们来看图中红色虚线指示的电路流程。它是一个产生中断的线路最终信号流入到 NVIC 控制器内。编号 1 是输入线EXTI 控制器有 19 个中断/事件输入线这些输入线可以通过寄存器设置为任意一个 GPIO也可以是一些外设的事件这部分内容我们将在后面专门讲解。输入线一般是存在电平变化的信号。编号 2 是一个边沿检测电路它会根据上升沿触发选择寄存(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路否则输出无效信号0。而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。编号 3 电路实际就是一个或门电路它一个输入来自编号 2 电路另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER允许我们通过程序控制就可以启动中断/事件线这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1所以这两个输入随便一个有有效信号 1就可以输出 1 给编号 4和编号 6电路。编号 4 电路是一个与门电路它一个输入是编号 3 电路另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1导致的结果是如果 EXTI_IMR 设置为 0 时那不管编号 3 电路的输出信号是 1 还是 0最终编号 4 电路输出的信号都为 0如果EXTI_IMR设置为1时最终编号4电路输出的信号才由编号3电路的输出信号决定这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内从而实现系统中断事件控制。接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路最终输出一个脉冲信号。产生事件线路是在编号3电路之后与中断线路有所不同之前电路都是共用的。编号6电路是一个与门它一个输入来自编号 3 电路另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR设置为 0时那不管编号 3电路的输出信号是 1还是 0最终编号 6 电路输出的信号都为 0如果 EXTI_EMR 设置为 1 时最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。编号 7 是一个脉冲发生器电路当它的输入端即编号 6 电路的输出端是一个有效信号 1 时就会产生一个脉冲如果输入端是无效信号就不会输出脉冲。编号 8 是一个脉冲信号就是产生事件的线路最终的产物这个脉冲信号可以给其他外设电路使用比如定时器 TIM、模拟数字转换器 ADC 等等这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。产生中断线路目的是把输入信号输入到 NVIC进一步会运行中断服务函数实现功能这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用并且是电路级别的信号传输属于硬件级的。另外EXTI是在 APB2总线上的在编程时候需要注意到这点。2.1 NVIC 嵌套向量中断控制器STM32基于 Cortex-M 内核的中断管理由 NVICNested Vectored Interrupt Controller嵌套向量中断控制器负责。NVIC 是内核的一部分与 CPU 紧密耦合支持可嵌套中断高优先级中断可打断低优先级中断向量中断自动定位中断服务函数入口无需软件查询动态优先级调整中断屏蔽除 HardFault 外所有中断均可被屏蔽2.2 中断向量表中断向量表是一个存放中断服务函数入口地址的数组位于存储器起始地址默认 0x08000000。每个中断源对应一个固定的向量编号和偏移地址。以 STM32F103 为例向量表前几项如下向量编号中断源说明0—初始栈顶地址MSP1Reset复位中断程序入口2NMI不可屏蔽中断3HardFault硬件错误中断4-10MemManage / BusFault / UsageFault 等内核异常11SVCall系统服务调用14PendSV可挂起系统调用常用于 OS 任务切换15SysTick系统滴答定时器16IRQn外部中断外设中断如 EXTI、USART、TIM 等编号 0-15 为内核异常部分可配置优先级编号 16 及以后为外部中断IRQ由片上外设触发。2.3 中断处理流程中断请求外设或外部事件产生中断信号置位对应中断标志位。中断使能判断NVIC 检查该中断是否被使能以及 CPU 当前是否允许响应中断PRIMASK 等。优先级仲裁若同时有多个中断请求NVIC 按优先级高低决定先响应哪个。保存现场CPU 自动将 xPSR、PC、LR、R12、R3-R0 压入栈中硬件自动完成。取向量从向量表中取出对应中断服务函数的入口地址跳转执行。执行中断服务函数ISR用户编写的中断处理逻辑通常需要清除中断标志位。恢复现场执行完 ISR 后CPU 从栈中弹出之前保存的寄存器恢复到被打断的位置继续执行。三、中断优先级3.1 两级优先级机制Cortex-M 内核的中断优先级分为两个层级抢占优先级Preemption Priority决定中断是否可以嵌套。高抢占优先级的中断可以打断正在执行的低抢占优先级中断。子优先级Sub Priority / 响应优先级当两个中断的抢占优先级相同时子优先级高的先响应。但子优先级不能实现中断嵌套。关键规则只有抢占优先级不同才能嵌套抢占优先级相同、子优先级不同时高优先级先执行但不能打断正在执行的同抢占级中断。3.2 优先级分组STM32 使用 4 位共 16 级来表示中断优先级通过优先级分组决定这 4 位中几位用于抢占优先级、几位用于子优先级。优先级分组抢占优先级位数子优先级位数抢占优先级范围子优先级范围NVIC_PriorityGroup_00 位4 位0仅1级0-15NVIC_PriorityGroup_11 位3 位0-10-7NVIC_PriorityGroup_22 位2 位0-30-3NVIC_PriorityGroup_33 位1 位0-70-1NVIC_PriorityGroup_44 位0 位0-150仅1级数值越小优先级越高例如抢占优先级 0 高于抢占优先级 2。整个工程中优先级分组通常只设置一次建议在系统初始化时统一配置。3.3 优先级配置标准库// 设置优先级分组整个工程只调用一次 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占2位子 // 配置具体中断的优先级 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; // 中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; // 使能 NVIC_Init(NVIC_InitStructure);3.4 优先级配置HAL 库// 设置优先级分组整个工程只调用一次 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置具体中断的优先级 HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 1); // (IRQn, 抢占优先级, 子优先级) HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能中断四、外部中断/事件控制器EXTI4.1 EXTI 概述EXTIExternal Interrupt/Event Controller外部中断/事件控制器负责管理 GPIO 引脚的外部中断和事件。它支持每个 EXTI 线可独立配置触发方式上升沿、下降沿、双边沿每个 EXTI 线可独立使能或屏蔽产生中断请求或事件请求事件用于唤醒低功耗模式不进入 ISR软件触发中断/事件4.2 EXTI 线与 GPIO 映射STM32F1 系列共有 19 条 EXTI 线EXTI0-EXTI18其中 EXTI0-EXTI15 对应 GPIO 引脚EXTI 线对应 GPIO 引脚中断向量EXTI0Px0PA0/PB0/PC0...EXTI0_IRQnEXTI1Px1EXTI1_IRQnEXTI2Px2EXTI2_IRQnEXTI3Px3EXTI3_IRQnEXTI4Px4EXTI4_IRQnEXTI5-EXTI9Px5-Px9EXTI9_5_IRQn共用EXTI10-EXTI15Px10-Px15EXTI15_10_IRQn共用EXTI16PVD 输出PVD_IRQnEXTI17RTC 闹钟事件RTCAlarm_IRQnEXTI18USB 唤醒事件USBWakeUp_IRQn重要限制同一编号的引脚如 PA0、PB0、PC0在同一时刻只能有一个映射到 EXTI0。也就是说EXTIx 只能选择一个端口的第 x 号引脚。EXTI5-9 和 EXTI10-15 共用中断向量需要在 ISR 中通过标志位判断具体是哪条线触发的。4.3 触发方式上升沿触发信号从低电平跳变到高电平时触发。下降沿触发信号从高电平跳变到低电平时触发。双边沿触发上升沿和下降沿均触发。五、中断配置完整步骤以PA0 引脚下降沿触发外部中断为例完整配置流程如下5.1 标准库配置流程步骤 1使能相关时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 复用功能时钟EXTI 需要步骤 2配置 GPIO 为输入模式GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入按键接 GND 时用 GPIO_Init(GPIOA, GPIO_InitStructure);步骤 3配置 EXTI 线映射选择端口GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // PA0 映射到 EXTI0步骤 4配置 EXTI 触发方式和使能EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line EXTI_Line0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; // 中断模式非事件模式 EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure);步骤 5配置 NVIC 优先级并使能NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 通常在 main 开头统一设置 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);步骤 6编写中断服务函数void EXTI0_IRQHandler(void) { // 第一步判断中断标志位是否置位必须 if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { // 中断处理逻辑 // 例如翻转 LED、发送数据、设置标志位等 // 最后一步清除中断标志位必须否则会反复进入中断 EXTI_ClearITPendingBit(EXTI_Line0); } }5.2 HAL 库配置流程// 1. 使能时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 2. 配置 GPIO GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; // 下降沿中断HAL 库一步配置 GPIOEXTI GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 3. 配置 NVIC HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 1); HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 4. 中断服务函数HAL 库风格 void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // HAL 库统一入口内部清标志 } // 5. 回调函数实际处理逻辑写在这里 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { // 中断处理逻辑 } }5.3 HAL库按键控制LED实验配置 GPIO 引脚 PA0 为 EXTI 复用功能GPIO 配置NVIC 使能配置中断优先级找中断向量表——中断服务程序跳转到中断服务程序跳转再跳转中断回调函数中断回调函数里写消抖5.4 完整工程示例下面给出一个基于 HAL 库的按键中断控制 LED 的完整 main.c 示例。硬件连接按键接 PA0另一端接 GND使用上拉输入LED 接 PB0低电平点亮。按下按键触发下降沿中断在回调函数中翻转 LED 状态并加入简单的软件消抖。#include main.h #include gpio.h /* 按键消抖计数在回调中置位主循环中做延时消抖 */ volatile uint8_t key_pressed_flag 0; /** brief 系统时钟配置此处以 72MHz 为例实际由 SystemClock_Config 生成 */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; /* 使能 HSE 外部高速晶振 / RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; / 8MHz * 9 72MHz */ if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } /* 配置 AHB、APB1、APB2 总线时钟分频 / RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; / APB1 36MHz / RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; / APB2 72MHz */ if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2) ! HAL_OK) { Error_Handler(); } } /** brief GPIO 初始化PA0 按键输入下降沿中断PB0 LED 输出 */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; /* 使能 GPIOA、GPIOB 时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /* 配置 PA0 为下降沿中断输入上拉按键接 GND / GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; / 下降沿触发中断 / GPIO_InitStruct.Pull GPIO_PULLUP; / 上拉输入 */ HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /* 配置 PB0 为推挽输出用于驱动 LED */ GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); /* 默认熄灭 LEDPB0 输出高电平 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); } /** brief NVIC 初始化使能 EXTI0 中断并设置优先级 / static void MX_NVIC_Init(void) { / 设置优先级分组2 位抢占优先级 2 位子优先级 */ HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); /* 配置 EXTI0 中断抢占优先级 1子优先级 1 */ HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 1); HAL_NVIC_EnableIRQ(EXTI0_IRQn); } /** brief 主函数 / int main(void) { / 复位所有外设初始化 Flash 接口和 SysTick */ HAL_Init(); /* 配置系统时钟为 72MHz */ SystemClock_Config(); /* 初始化 GPIO 和 NVIC */ MX_GPIO_Init(); MX_NVIC_Init(); while (1) { /* 主循环检测到按键标志后做软件消抖 / if (key_pressed_flag) { / 延时 20ms 跳过机械抖动区间 */ HAL_Delay(20); /* 再次确认按键确实被按下低电平有效 */ if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { /* 翻转 LED 状态 */ HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); } /* 清除按键标志等待下一次按下 */ key_pressed_flag 0; } } } /** brief EXTI0 中断服务函数HAL 库统一入口内部自动清除标志位 */ void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } /** brief EXTI 中断回调函数实际处理逻辑写在这里 param GPIO_Pin 触发中断的引脚 / void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { / 置位按键标志由主循环完成消抖和 LED 翻转 */ key_pressed_flag 1; } } /** brief 错误处理函数程序出错时进入死循环 */ void Error_Handler(void) { __disable_irq(); while (1) { } }代码要点说明时钟配置通过SystemClock_Config()将系统时钟配置为 72MHzAPB2 总线GPIO、EXTI 所在总线为 72MHz。GPIO 初始化PA0 配置为GPIO_MODE_IT_FALLING下降沿中断模式并启用上拉PB0 配置为推挽输出驱动 LED。NVIC 配置使用HAL_NVIC_SetPriorityGrouping()设置优先级分组再通过HAL_NVIC_SetPriority()和HAL_NVIC_EnableIRQ()配置并使能 EXTI0 中断。中断服务函数EXTI0_IRQHandler()调用HAL_GPIO_EXTI_IRQHandler()HAL 库内部自动清除中断标志位。回调函数HAL_GPIO_EXTI_Callback()中只置位标志位不在中断中做耗时操作符合快进快出原则。软件消抖主循环检测到标志后延时 20ms再确认引脚电平避免机械抖动导致 LED 多次翻转。六、中断服务函数ISR编写规范6.1 函数命名中断服务函数的名称是固定的由启动文件startup_xxx.s中的向量表定义决定不能随意命名。常见的中断服务函数名中断源服务函数名EXTI0EXTI0_IRQHandlerEXTI1EXTI1_IRQHandlerEXTI9_5EXTI9_5_IRQHandlerEXTI15_10EXTI15_10_IRQHandlerUSART1USART1_IRQHandlerTIM2TIM2_IRQHandlerSysTickSysTick_Handler如果函数名写错中断触发后 CPU 会跳到默认的空循环Default_Handler中表现为程序卡死。务必检查函数名拼写6.2 编写原则快进快出ISR 应尽可能短避免在中断中执行耗时操作如延时、串口打印大量数据、复杂算法。耗时操作应在 ISR 中设置标志位在主循环中处理。必须清除中断标志位如果不清除中断会反复触发导致 CPU 一直停留在 ISR 中。先判断标志位再处理对于共用中断向量的情况如 EXTI9_5必须先判断是哪个标志位置位再做对应处理。避免在 ISR 中调用可能阻塞的函数如 HAL_Delay()依赖 SysTick若 SysTick 优先级低于当前中断则会卡死。共享变量使用 volatile在 ISR 和主循环之间共享的变量必须声明为volatile防止编译器优化导致主循环看不到变量变化。6.3 共用中断向量的处理以 EXTI9_5 为例EXTI5 到 EXTI9 共用同一个中断向量需要在 ISR 中分别判断void EXTI9_5_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line5) ! RESET) { // EXTI5 的处理逻辑 EXTI_ClearITPendingBit(EXTI_Line5); } if (EXTI_GetITStatus(EXTI_Line6) ! RESET) { // EXTI6 的处理逻辑 EXTI_ClearITPendingBit(EXTI_Line6); } // ... EXTI7/8/9 同理 }七、常见问题与注意事项7.1 中断不触发的排查清单时钟是否使能GPIO、AFIO/外设时钟GPIO 模式是否正确配置为输入EXTI 线映射是否正确GPIO_EXTILineConfig触发方式是否与实际信号匹配上升沿/下降沿NVIC 是否使能了对应中断通道中断服务函数名是否正确检查 startup 文件全局中断是否被关闭__disable_irq() 后忘记 __enable_irq()引脚是否有实际信号输入用万用表/示波器测量7.2 中断抖动问题机械按键按下/释放时会产生抖动导致一次按键触发多次中断。解决方案硬件消抖按键并联电容通常 0.1μF。软件消抖在 ISR 中检测到中断后延时 10-20ms 再判断引脚电平确认后再执行逻辑。但注意 ISR 中不宜用 HAL_Delay可用定时器或在主循环中消抖。定时器消抖ISR 中只置标志位主循环中用定时器做延时消抖判断。7.3 中断嵌套与优先级冲突确保需要嵌套的中断具有不同的抢占优先级。高优先级中断中不要调用依赖低优先级中断的函数如在高优先级 ISR 中调用 HAL_Delay而 SysTick 优先级更低会导致死等。FreeRTOS 中调用 OS API 的中断优先级必须低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY定义的优先级即数值要大于等于该值。7.4 中断标志位清除顺序中断标志位的清除时机直接关系到中断是否会被反复触发需要根据标志位的类型和中断信号的状态来选择合适的清除时机。1. 两种清除时机及适用场景处理前清除在进入 ISR 后、执行具体处理逻辑之前先清除标志位。这种方式适用于中断信号是边沿触发且信号持续时间较短的场景例如按键按下产生的下降沿。由于边沿信号在触发后很快消失先清除标志位不会导致标志位再次置位还能避免在处理逻辑执行期间被同一次事件再次打断。但它的风险在于如果中断信号是电平触发且电平持续有效先清除标志位后硬件会立即再次置位导致 ISR 被反复进入处理逻辑可能永远执行不完。处理后清除在 ISR 的处理逻辑全部执行完毕之后最后再清除标志位。这是最稳妥、最常用的方式适用于大多数场景尤其是电平触发或信号持续时间较长的情况。它的好处是只要标志位还置位同一次事件就不会再次触发中断保证处理逻辑能完整执行。它的风险在于如果在处理逻辑执行期间又来了新的事件新事件会被挂起需要等本次 ISR 退出后才能响应可能造成一定延迟。总结边沿触发且信号短暂两种方式都可以电平触发或信号持续有效必须采用处理后清除否则会反复进入中断。2. 以 EXTI 为例的清除操作标准库EXTI 的标志位是写 1 清零类型使用EXTI_ClearITPendingBit()清除指定线的挂起标志位。void EXTI0_IRQHandler(void) { // 处理逻辑 // ... // 最后清除标志位写 1 清零 EXTI_ClearITPendingBit(EXTI_Line0); }HAL 库HAL 库在HAL_GPIO_EXTI_IRQHandler()内部已经自动完成了标志位清除用户只需在回调函数中编写处理逻辑无需手动清除。void EXTI0_IRQHandler(void) { // HAL 库统一入口内部自动清除标志位 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { // 处理逻辑无需手动清标志 } }3. 典型错误示例与正确写法下面以按键控制 LED 为例演示清除时机不当导致中断反复触发的问题。错误写法在 ISR 开头就清除标志位随后执行耗时较长的处理逻辑。如果按键信号仍处于有效电平标志位会被立即重新置位导致 ISR 反复进入LED 状态不断翻转表现为按键一次却多次翻转。// 错误示例先清标志再处理 void EXTI0_IRQHandler(void) { EXTI_ClearITPendingBit(EXTI_Line0); // 先清除标志位 // 模拟耗时处理如延时、复杂计算 for (volatile int i 0; i 100000; i); // 翻转 LED GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0))); }正确写法先完成处理逻辑最后再清除标志位确保同一次事件只被处理一次。// 正确示例先处理最后清标志 void EXTI0_IRQHandler(void) { // 判断标志位是否置位必须 if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { // 翻转 LED GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0))); // 最后清除标志位写 1 清零 EXTI_ClearITPendingBit(EXTI_Line0); } }注意对于读即清零的标志位如 USART 的 RXNE读取数据寄存器即自动清零无需也不能手动清除否则可能误清其他标志。清除标志位的时机很重要对于写 1 清零类标志建议在处理完中断逻辑之后清除。如果在处理之前清除而中断信号仍然有效可能导致标志位再次置位逻辑还没执行完就被再次打断。但对于某些读即清零的标志如 USART 的 RXNE读取数据寄存器即自动清零。7.5 全局中断开关// 关闭全局中断除 HardFault、NMI 外所有中断被屏蔽 __disable_irq(); // 或 PRIMASK 1 // 打开全局中断 __enable_irq(); // 或 PRIMASK 0 // 注意关闭全局中断期间所有中断请求会被挂起pending // 打开后会按优先级依次响应不会丢失。八、其他常用外设中断简介外设常见中断事件中断标志位标准库USART接收完成、发送完成、空闲总线USART_IT_RXNE / TXE / TC / IDLETIM更新溢出、输入捕获、输出比较TIM_IT_Update / CC1 / CC2ADC转换完成、注入转换完成、模拟看门狗ADC_IT_EOC / JEOC / AWDDMA传输完成、半传输、传输错误DMA_IT_TC / HT / TEI2C事件中断、错误中断I2C_IT_EVT / ERRSPI接收缓冲区非空、发送缓冲区空、错误SPI_IT_RXNE / TXE / ERRRTC秒中断、闹钟中断、溢出中断RTC_IT_SEC / ALR / OW所有外设中断的配置思路都是一致的使能时钟 → 配置外设 → 使能外设中断位 → 配置 NVIC → 编写 ISR判断标志 处理 清标志。掌握了 EXTI 的配置方法其他外设中断可以举一反三。九、串口发送中断使能NVIC·main函数·发送完成回调函数十、参考资料以下资料可作为深入学习 STM32 中断系统的参考建议结合本文内容按需查阅。《STM32F10x 参考手册》其中第 8 章「中断和事件」详细介绍了 EXTI 的寄存器配置、触发方式与事件/中断线路第 9 章「嵌套向量中断控制器NVIC」说明了中断优先级分组、向量表及中断使能的相关寄存器。该手册是 STM32F103 系列开发最权威的硬件参考。《Cortex-M3 权威指南》Joseph Yiu 著第 7 章「异常」和第 8 章「NVIC 与中断控制」系统讲解了中断向量表的结构、异常优先级模型、嵌套中断机制以及尾链Tail-Chaining等内核级概念有助于从架构层面理解 STM32 中断的工作方式。STM32 官方标准外设库用户手册即《STM32F10x Standard Peripheral Library Documentation》其中对 EXTI、NVIC、GPIO 等外设的驱动函数、数据结构和使用示例做了完整说明是使用标准库开发时的重要参考。STM32 HAL 库用户手册即《Description of STM32F1 HAL and Low-layer drivers》详细介绍了 HAL_GPIO_EXTI_IRQHandler、HAL_NVIC_SetPriority 等中断相关 API 的用法与回调机制是使用 HAL 库开发时的必备文档。正点原子 / 野火教程正点原子《STM32F1 开发指南》和野火《STM32F103 霸道/指南者开发指南》中均有「外部中断实验」「NVIC 中断优先级详解」等章节配有完整的工程源码和图文讲解适合入门实践时对照学习。