ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32系列2:中断

STM32系列2:中断 1. 中断与轮询的区别1.1 什么是轮询轮询是一种程序主动查询外部设备状态的方式。微控制器按照固定的顺序反复读取GPIO引脚或外设寄存器的状态以判断是否有事件发生。这种方式实现简单、逻辑直观但CPU需要持续占用时间去检查状态即使没有事件发生也会空转消耗资源。1.2 什么是中断中断是一种由硬件事件驱动的响应机制。当GPIO引脚电平发生变化或外设产生特定事件时微控制器会暂停当前正在执行的任务自动跳转到对应的中断服务程序ISR进行处理处理完成后再返回原来的任务继续执行。这种方式让CPU在无事可做时可以休眠或执行其他任务只在真正需要时被唤醒。1.3 中断与轮询的核心区别对比维度轮询中断触发方式CPU主动查询硬件事件被动触发实时性取决于查询周期可能错过瞬时事件事件发生时立即响应实时性高CPU 占用持续占用即使无事件也在空转无事时释放CPU仅在事件发生时占用实现复杂度简单只需循环读取状态需要配置中断控制器、编写ISR并注意优先级和嵌套适用场景事件频率高、响应要求不苛刻、逻辑简单的任务事件稀疏但要求快速响应的任务如按键、传感器报警1.4 如何选择在实际开发中轮询和中断并非互斥而是可以结合使用。对于高频且简单的状态读取轮询往往更直接对于低频但需要及时响应的关键事件中断是更合适的选择。理解两者的区别有助于在 STM32 项目中合理分配CPU资源提升系统的实时性和稳定性。1.4.1 轮询方式轮询方式在主循环中反复读取按键引脚的电平状态实现简单但CPU需要持续占用。// 主函数 int main() { //1.硬件初始化 LED_Config(); KEY_Config(); BEEP_Config(); //2.进入死循环 for(;;) { //3.KEY检测 //4.LED控制 //5.BEEP驱动 } }1.4.2 外部中断方式中断方式在按键按下时由硬件触发EXTI中断CPU平时可以执行其他任务或进入低功耗模式。//定义中断的服务程序 void XXX(void) { //KEY检测 } // 主函数 int main() { //1.硬件初始化 //2.注册中断 //3.进入死循环 for(;;) { //4.LED控制 //5.BEEP驱动 } }5.3 两种方式的适用场景总结实现方式适用场景轮询按键检测频率高、逻辑简单、对实时性要求不苛刻的场景如简单的状态读取。中断按键事件稀疏但需要及时响应的场景如唤醒系统、紧急报警、低功耗应用。在实际项目中轮询和中断可以结合使用用中断唤醒关键事件用轮询处理高频简单任务从而在实时性和资源占用之间取得平衡。2. 中断的基本概念中断Interrupt是嵌入式系统中一种由硬件事件驱动的响应机制。当GPIO引脚电平发生变化或外设产生特定事件时微控制器会暂停当前正在执行的任务自动跳转到对应的中断服务程序ISR进行处理处理完成后再返回原来的任务继续执行。这种方式让CPU在无事可做时可以休眠或执行其他任务只在真正需要时被唤醒。在STM32中中断机制主要依赖以下几个核心组件协同工作NVIC嵌套向量中断控制器负责管理中断的优先级、使能与屏蔽是STM32中断系统的调度核心。EXTI外部中断/事件控制器负责检测GPIO引脚上的电平变化并根据配置的触发方式上升沿、下降沿或双边沿产生中断请求。中断服务程序ISR当中断被触发且优先级允许时CPU会跳转到对应的ISR中执行处理逻辑处理完成后返回主程序继续运行。根据触发条件的不同STM32的GPIO中断主要分为两种模式上升沿触发当引脚电平从低变高时触发中断常用于检测按键释放或信号由无效变为有效的时刻。下降沿触发当引脚电平从高变低时触发中断常用于检测按键按下或信号由有效变为无效的时刻。此外STM32还支持双边沿触发即引脚电平无论是从低变高还是从高变低都会触发中断适用于需要同时捕获两种状态变化的场景。合理选择触发方式可以有效避免误触发提高系统的稳定性和响应准确性。3. 中断源分析中断源是指能够触发CPU进入中断处理流程的事件来源。分析中断源是配置中断系统的第一步只有先明确系统中有哪些中断源、由什么事件产生以及它们分别对应哪个中断通道才能正确完成中断使能、优先级分配和中断服务程序的编写。按照中断信号的来源STM32的中断源主要可以分为以下几类内核异常由Cortex-M内核产生例如复位、不可屏蔽中断NMI、硬件错误HardFault、系统定时器SysTick等。外部中断源由GPIO引脚状态变化经EXTI产生通常用于按键、传感器信号、外部触发信号等。外设中断源由片内外设产生例如定时器更新中断、USART收发中断、SPI和I2C通信中断、ADC转换完成中断、DMA传输完成中断等。每个中断源都有一个固定的中断通道对应中断向量表中的一个入口。对于外部GPIO中断STM32通过EXTI控制器把引脚信号映射到EXTI线再连接到对应的中断通道。EXTI0至EXTI15分别对应各GPIO端口的0至5号引脚但同一时刻同一条EXTI线只能连接一个GPIO端口。例如PA0、PB0、PC0都使用EXTI0如果已经选择PA0作为EXTI0的中断源就不能同时直接使用PB0触发EXTI0否则会产生通道冲突。在编写程序时中断服务函数的名称必须与启动文件中的中断向量表保持一致。例如外部中断0对应的服务函数通常为EXTI0_IRQHandler当中断发生时CPU才能跳转到我们编写的处理逻辑。4. 中断的管理中断管理是指在STM32开发过程中对中断使能、优先级、嵌套关系以及中断服务程序进行统一规划和控制以确保多个中断源能够有序、及时地响应。如果中断管理不到位容易出现中断响应延迟、优先级冲突或服务程序执行时间过长等问题。在HAL库开发中中断配置主要通过NVIC相关接口和GPIO的EXTI功能共同完成。4.1 NVIC中断优先级管理使用NVIC嵌套向量中断控制器统一管理中断。每个中断源都可以配置抢占优先级和子优先级抢占优先级用于决定中断之间能否嵌套优先级数值越小优先级越高子优先级用于在抢占优先级相同时比较中断响应顺序。HAL库中通常会先用HAL_NVIC_SetPriorityGrouping()设置优先级分组例如配置为NVIC_PRIORITYGROUP_4表示全部4位都用作抢占优先级再使用HAL_NVIC_SetPriority(IRQn, PreemptPriority, SubPriority)为某个中断通道设置抢占优先级和子优先级最后通过HAL_NVIC_EnableIRQ()使能对应中断。4.2 中断使能与屏蔽中断源只有在使能后才能产生中断请求。HAL 库中可以使用HAL_NVIC_EnableIRQ(IRQn)使能单个中断通道使用HAL_NVIC_DisableIRQ(IRQn)关闭单个中断通道需要临时屏蔽全部中断时仍然可以使用CMSIS提供的__disable_irq()和__enable_irq()。对于外部中断只需要在GPIO_InitTypeDef中把引脚模式配置为GPIO_MODE_IT_FALLING、GPIO_MODE_IT_RISING或GPIO_MODE_IT_RISING_FALLING再通过完成初始化。中断发生后在对应的IRQHandler中调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_x)处理EXTI请求。4.3 中断服务程序设计原则尽量精简ISR中只做必要处理耗时任务放到主循环中通过标志位触发执行。HAL库中用户逻辑通常写在回调函数HAL_GPIO_EXTI_Callback()中。及时清除标志HAL库在HAL_GPIO_EXTI_IRQHandler()内会自动清除EXTI中断标志对于定时器、串口等外设中断需要根据外设状态寄存器及时清除对应标志位。避免阻塞调用不要在ISR中执行长时间延时、复杂打印或阻塞式通信。注意临界区保护在修改共享变量时关闭中断或使用临界区保护防止数据竞争。4.4 中断的响应顺序当多个中断同时到来或一个中断正在处理时STM32并不是随意决定执行顺序的而是按照固定的仲裁规则选择先响应哪一个。理解这个顺序有助于在项目中合理安排外设中断避免重要事件被延迟处理。中断响应的完整流程可以概括为事件发生 → EXTI或外设产生中断请求 → NVIC根据优先级进行仲裁 → CPU保存现场并跳转到对应的IRQHandler → 在回调函数中处理用户逻辑 → 清除中断标志并恢复现场。当存在多个中断请求时NVIC按照以下顺序进行比较先比较抢占优先级抢占优先级数值越小优先级越高优先响应。抢占优先级高的中断可以打断抢占优先级低的中断从而形成中断嵌套。再比较子优先级当多个中断的抢占优先级相同时子优先级数值越小越先响应。子优先级只在抢占优先级相同时才起作用不能决定中断嵌套。最后比较自然优先级当抢占优先级和子优先级都相同时按照中断向量表中的固定编号决定响应顺序编号越小优先级越高。例如EXTI0的自然优先级通常高于EXTI1。以按键外部中断为例HAL库中的典型响应顺序是GPIO引脚电平变化触发EXTI请求对应的被调用处EXTI0_IRQHandler理函数内部执行EXTI0_IRQHandler(GPIO_PIN_0)随后进入HAL_GPIO_EXTI_Callback(GPIO_PIN)完成按键消抖和业务处理。整个过程由硬件自动完成现场保护和恢复开发时只需关注回调函数中的逻辑。此外Cortex-M内核还支持尾链机制和晚到中断等优化当一个ISR即将结束时出现新的中断请求CPU不必完整退出再重新进入而是可以直接“尾链”执行下一个ISR若更高优先级中断在一个中断入栈阶段到达则CPU会直接处理这个晚到的高优先级中断。这些机制进一步减少了中断切换开销。5. EXTI外设的基本概念EXTIExternal Interrupt/Event Controller外部中断/事件控制器是STM32中专门负责把GPIO引脚的电平变化转换为中断请求或事件请求的外设。它不直接操作引脚而是检测引脚的边沿信号再向NVIC发起中断请求。在按键检测、传感器报警、编码器计数和低功耗唤醒等场景中EXTI都是非常常用的基础外设。EXTI控制器主要由边沿检测电路和中断/事件通道两部分组成。配置好GPIO引脚后引脚电平会进入EXTI输入线经过上升沿或下降沿检测如果符合触发条件就会产生中断请求。整个过程可以概括为GPIO电平变化 → 边沿检测 → EXTI产生请求 → NVIC仲裁 → CPU执行对应IRQHandler。6. 按键中断翻转LED实战解析下面通过一个“双按键控制双LED”的完整工程本例使用PA0和PE2两个按键分别触发EXTI0与EXTI2的下降沿外部中断在回调函数中翻转连接在PF9、PF10上的LED。对应的引脚与中断线关系如下表所示按键引脚触发中断线控制的 LED 引脚PA0EXTI0PF9PE2EXTI2PF10示例代码如下/** ****************************************************************************** * #文件: main.c * #简要描述: 通过按键来实现LED灯的翻转(中断方式) * #详细描述: 该程序通过按键PA0和PE2来控制LED灯PF9和PF10的翻转使用中断方式实现。 * #作者 游走于孤独的码农 * #时间 2026-10-06 ****************************************************************************** */ /* 头文件 */ #include main.h /* 函数声明 */ void SystemClock_Config(void); static void Key_Init(void); static void Led_Init(void); /** * #简要描述所有程序的入口点 * #返回值 void */ int main(void) { /* MCU配置 */ /* 重启所有的外设 */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); /* 初始化所有配置外设 */ Key_Init(); Led_Init(); /* 死循环 */ while (1) { } } /** * #简要描述系统时钟配置默认 * #返回值 void */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState RCC_PLL_NONE; if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_0) ! HAL_OK) { Error_Handler(); } } /** * #简要描述 按键初始化 * #详细描述 该函数用于初始化按键引脚PA0和PE2配置为输入模式。 * #返回值 void */ static void Key_Init(void) { /* GPIOA时钟使能 */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); /* 配置PA0为输入模式, 下降沿触发 */ GPIO_InitTypeDef Btn_InitStruct_PA0 { .Pin GPIO_PIN_0, .Mode GPIO_MODE_IT_FALLING, .Pull GPIO_PULLUP }; HAL_GPIO_Init(GPIOA, Btn_InitStruct_PA0); /* 配置PE2为输入模式, 下降沿触发 */ GPIO_InitTypeDef Btn_InitStruct_PE2 { .Pin GPIO_PIN_2, .Mode GPIO_MODE_IT_FALLING, .Pull GPIO_PULLUP }; HAL_GPIO_Init(GPIOE, Btn_InitStruct_PE2); /* 配置中断优先级并使能中断 */ HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI2_IRQn); } /** * #简要描述 led初始化 * #详细描述 该函数用于初始化LED引脚PF9配置为输出模式。 * #返回值 void */ static void Led_Init(void) { /* GPIOF时钟使能 */ __HAL_RCC_GPIOF_CLK_ENABLE(); /* 配置PF9和PF10为输出模式 */ GPIO_InitTypeDef Led_InitStruct { .Pin GPIO_PIN_9 | GPIO_PIN_10, .Mode GPIO_MODE_OUTPUT_PP, .Pull GPIO_NOPULL, .Speed GPIO_SPEED_FREQ_LOW }; HAL_GPIO_Init(GPIOF, Led_InitStruct); } /** * #简要描述 led翻转回调函数 * #详细描述 该函数用于处理按键中断回调根据按下的按键翻转对应的LED灯。 * #返回值 void */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { /* 翻转LED灯 */ HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9); } else if (GPIO_Pin GPIO_PIN_2) { /* 翻转LED灯 */ HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10); } } /** * #简要描述 EXTI0中断处理函数 * #详细描述 该函数用于处理EXTI0中断请求。 * #返回值 void */ void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } /** * #简要描述 EXTI2中断处理函数 * #详细描述 该函数用于处理EXTI2中断请求。 * #返回值 void */ void EXTI2_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2); } /*----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /** * brief This function is executed in case of error occurrence. * retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * param file: pointer to the source file name * param line: assert_param error line source number * retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf(Wrong parameters value: file %s on line %d\r\n, file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */6.1 代码整体结构这段main.c基于HAL库的完整按键中断工程。其核心思路是主循环不轮询按键LED的翻转完全由外部中断触发。工程主要包含系统时钟配置、按键中断初始化、LED输出初始化、EXTI中断入口函数以及HAL库回调函数这几个部分。6.2 各函数职责解析HAL_Init()HAL库的基础初始化负责复位所有外设、初始化SysTick、配置Flash预取和中断优先级分组等基础工作。使用HAL库开发时main函数第一步通常都要调用它。SystemClock_Config()系统时钟配置函数。本例使用HSI内部高速时钟作为系统时钟源关闭PLLAHB、APB1和APB2总线都不分频Flash延迟配置为FLASH_LATENCY_0。时钟配置完成后系统在默认频率下运行。Key_Init()按键初始化函数。它使能GPIOA和GPIOE时钟把PA0、PE2都配置为下降沿触发的外部中断输入模式并开启内部上拉。这样按键未按下时引脚保持高电平按下瞬间产生下降沿触发EXTI中断。最后通过HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ配置并开启EXTI0、EXTI2中断。Led_Init()LED初始化函数。它使能GPIOF时钟把PF9和PF10配置为推挽输出模式、无上下拉、低输出速度供后续调用HAL_GPIO_TogglePin翻转电平。HAL_GPIO_EXTI_Callback()HAL库提供的GPIO外部中断回调函数。EXTI中断处理完成后HAL库会自动调用这个函数。程序在回调函数中根据GPIO_Pin判断是PA0还是PE2产生的中断再翻转对应的 LED。EXTI0_IRQHandler()和EXTI2_IRQHandler()分别是EXTI0和EXTI2的中断入口函数。它们内部调用HAL_GPIO_EXTI_IRQHandler()由HAL库完成中断标志清除和回调函数派发。函数名必须与启动文件中的中断向量表保持一致否则中断无法正确跳转。Error_Handler()和assert_failed()错误处理和参数检查函数。当HAL库初始化失败或断言条件不满足时程序会进入对应函数通常在其中关闭全局中断并进入死循环方便开发者排查问题。6.3 中断触发与执行思路以PA0按键为例完整的中断处理流程如下按键PA0未按下时引脚被内部上拉电阻拉到高电平。按下PA0的瞬间引脚电平由高变低产生下降沿。EXTI边沿检测电路捕获到下降沿向NVIC发出EXTI0中断请求。NVIC根据配置的优先级进行仲裁随后CPU保存现场并进入EXTI0_IRQHandler()。EXTI0_IRQHandler()调用HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0)由HAL库清除中断标志并触发回调。HAL_GPIO_EXTI_Callback()判断GPIO_PIN是否等于GPIO_PIN_0如果GPIO_PIN等于GPIO_PIN_0调用HAL_GPIO_TogglePin()翻转PF9上的LED。中断返回主程序恢复执行。由于主循环while(1)为空程序继续等待下一次按键中断。6.4 需要注意的细节主循环保持空闲本示例的while(1)中没有任何检测代码这正体现了中断方式的优势。CPU 不需要反复查询按键状态只有按键事件真正发生时才会被唤醒。按键没有加入消抖示例重点演示中断流程因此没有处理机械按键抖动。实际项目中建议在回调函数中配合定时器或状态机进行消抖避免一次按下触发多次中断。EXTI线不要冲突PA0使用EXTI0PE2使用EXTI2两条中断线相互独立。如果扩展更多按键应注意同一时刻同一条EXTI线只能连接一个GPIO端口避免PA0、PB0、PC0同时接入造成冲突。中断回调保持轻量回调函数中只执行引脚翻转这类快速操作避免加入长时间延时、复杂打印或阻塞式通信符合中断服务程序的设计原则。优先级需要结合项目规划本段代码把EXTI0和EXTI2都配置为抢占优先级0、子优先级0。如果项目中还有其他串口、定时器等中断建议根据业务实时性要求重新规划NVIC优先级避免互相影响。
返回列表