资讯动态

中断详解:从原理到NVIC优先级嵌套

发布时间:2026/8/14 14:11:39 来源:尧图企业网站定制
假设你在做一个智能多士炉项目STM32在主循环里刷OLED屏幕同时要实时响应按键中断、温度报警中断。没有中断的话主循环刷屏时没法及时检测按键——屏幕刷新要几十毫秒按键可能错过。有了中断按键一按硬件立刻打断主循环先处理按键再回去刷屏。这就是中断的作用。但中断也是最容易出bug的地方优先级配错、ISR里调了不该调的函数。这篇帮你彻底搞懂。一、什么是中断1.1 一句话定义中断 CPU正在执行程序时硬件突发事件打断它让它先去处理紧急事情处理完再回来。主程序 AAAA──────►CCCC ↓ ↑ 中断 BBBB 触发返回1.2 和轮询的对比/* 轮询CPU不停查有没有数据 */while(!(USART1-SR(15)));/* 死等CPU空转 */dataUSART1-DR;/* 中断来数据了硬件通知CPU */voidUSART1_IRQHandler(void)/* 自动触发CPU不用等 */{dataUSART1-DR;}对比轮询中断CPU占用100%空转仅触发时占用响应速度取决于轮询频率即时微秒级实时性差好复杂度简单较复杂二、STM32中断的处理流程1. 硬件触发中断如UART收到字节 ↓ 2. CPU保存现场把当前寄存器压栈 ↓ 3. 查向量表找到中断服务函数(ISR)地址 ↓ 4. 跳转到ISR执行 ↓ 5. ISR处理完毕清除中断标志 ↓ 6. CPU恢复现场出栈返回主程序2.1 中断向量表向量表是一张中断号→函数地址的映射表存在Flash起始地址/* 启动文件里的向量表汇编 */__Vectors:DCD Initial_SP;0:初始栈指针 DCD Reset_Handler;1:复位 DCD NMI_Handler;2:不可屏蔽中断 DCD HardFault_Handler;3:硬件错误...DCD USART1_IRQHandler;53:USART1中断2.2 ISR的写法ISRInterrupt Service Routine中断服务函数就是你写的处理中断的函数。函数名必须和启动文件里的向量表一致CubeMX会自动生成。/* 函数名必须和向量表里一致启动文件定义CubeMX自动生成框架 *//* 场景多士炉项目串口收到蓝牙模块发来的控制指令 */voidUSART1_IRQHandler(void){if(__HAL_UART_GET_FLAG(huart1,UART_FLAG_RXNE)){uint8_tdataUSART1-DR;/* 读DR自动清RXNE标志 */rx_buf[rx_idx]data;/* 存入接收缓冲区 */}}在CubeMX里怎么开启中断在CubeMX的Connectivity → USART1 → NVIC Settings标签页勾选 “USART1 global interrupt” → Enabled生成代码后CubeMX会自动在stm32f1xx_it.c里生成空的USART1_IRQHandler()函数你在里面写处理逻辑或者用HAL库的回调函数方式关键ISR里必须清除中断标志否则会无限重入执行完立刻又进中断主程序永远卡住。三、NVIC与中断优先级3.1 NVIC是什么NVICNested Vectored Interrupt Controller嵌套向量中断控制器是STM32芯片内部的一个硬件模块管理所有中断的优先级和嵌套。你可以把它理解为一个交通指挥员当多个中断同时发生时它决定谁先执行、谁能打断谁。NVIC不是软件是MCU内部的硬件电路。你只需要通过寄存器配置它的优先级参数它就会自动管理中断的嵌套和调度。3.2 优先级分组STM32用4位表示优先级分成抢占优先级和子优先级/* HAL库设置分组 */HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);/* 4位全给抢占优先级0位给子优先级 → 16级抢占 */分组抢占位数子优先级位数抢占级别数GROUP_0041级不能嵌套GROUP_1132级GROUP_2224级GROUP_3318级GROUP_44016级推荐推荐用GROUP_416级抢占优先级足够用且概念简单没有子优先级。3.3 抢占优先级 vs 子优先级类型作用抢占优先级高能打断低嵌套子优先级抢占级相同时谁先执行不能嵌套抢占优先级2正在运行 ↓ 抢占优先级1触发 → 打断2执行1嵌套 ↓ 1执行完回到2 ↓ 2执行完回到主程序数字越小优先级越高。优先级0是最高的。四、中断嵌套规则4.1 能嵌套的条件只有抢占优先级更高的中断才能打断当前中断。/* 假设GROUP_4全是抢占优先级 */voidconfig_interrupts(void){/* UART1抢占优先级3 */HAL_NVIC_SetPriority(USART1_IRQn,3,0);/* 定时器2抢占优先级1更高 */HAL_NVIC_SetPriority(TIM2_IRQn,1,0);/* 外部中断0抢占优先级2 */HAL_NVIC_SetPriority(EXTI0_IRQn,2,0);}/* 嵌套情况 UART1(3)正在执行 → TIM2(1)能打断 → 执行完回UART1 UART1(3)正在执行 → EXTI0(2)能打断 → 执行完回UART1 TIM2(1)正在执行 → 谁都不能打断最高 */4.2 同级中断不能嵌套两个中断抢占优先级相同后触发的要等先触发的执行完。这叫 pending 等待——pending是挂起等待的意思中断已经发生了但还没轮到执行就先排队等着。4.3 具体场景多士炉项目的优先级设计假设你在做多士炉项目有这些中断 - 温度报警中断EXTI优先级0最高过热要立刻断电 - 按键中断EXTI优先级2用户操作100ms内响应就行 - UART接收中断优先级3蓝牙指令不急 - SysTick定时器优先级15最低系统滴答别抢别人的五、中断里的能做与不能做5.1 ISR铁律❌ 不能做原因调用HAL_Delay等阻塞函数中断里不能等待会死锁调用printfprintf很慢可能几百微秒会让其他中断等太久调用malloc/free内存分配函数不是中断安全的可能崩溃长时间循环中断要快进快出长时间占用会让低优先级中断饿死调用不带FromISR后缀的RTOS APIFreeRTOS规定ISR里只能用FromISR版本的API✅ 应该做说明清除中断标志否则会无限重入中断读/写少量数据比如把收到的字节存入缓冲区通知任务处理如果用了FreeRTOS用xQueueSendFromISR等带FromISR的API把数据发给任务保持尽可能短几十微秒内完成复杂逻辑交给主循环或任务5.2 ISR的标准模式FreeRTOS场景以下代码涉及FreeRTOS。如果你还没学FreeRTOS看裸机版即可——裸机版只做清标志读数据不用发通知。裸机版ISR没学FreeRTOS的看这个voidUSART1_IRQHandler(void){if(__HAL_UART_GET_FLAG(huart1,UART_FLAG_RXNE)){uint8_tdataUSART1-DR;/* 1. 读数据自动清标志 */rx_buf[rx_idx]data;/* 2. 存入缓冲区 *//* 主循环里检查rx_idx来处理数据 */}}FreeRTOS版ISR学了FreeRTOS的看这个/* 场景多士炉项目串口收到蓝牙控制指令通知通信任务处理 */voidUSART1_IRQHandler(void){BaseType_t wokenpdFALSE;/* 用于判断是否需要触发任务切换 */if(__HAL_UART_GET_FLAG(huart1,UART_FLAG_RXNE)){uint8_tdataUSART1-DR;/* 1. 读数据 *//* 2. 把数据发送到队列通知通信任务处理 * xQueueSendFromISR是FreeRTOS中断版API普通版xQueueSend不能在ISR里用 * woken记录是否唤醒了更高优先级任务 */xQueueSendFromISR(rx_queue,data,woken);/* 3. 如果唤醒了更高优先级任务立刻切换过去 */portYIELD_FROM_ISR(woken);}}什么是FromISRFreeRTOS的API分两种普通版如xQueueSend只能在中断外调用FromISR版如xQueueSendFromISR专门在中断里用。在ISR里调普通版会直接崩溃。原则ISR只做清标志、读数据、发通知复杂处理交给任务。六、临界区6.1 什么是临界区临界区 暂时关闭中断保护一段代码不被打断。临界区是指通过关中断等方式保护的一段代码防止被其他任务或中断打断。在FreeRTOS中用taskENTER_CRITICAL()进入用taskEXIT_CRITICAL()退出。/* 进入临界区关中断 */taskENTER_CRITICAL();/* FreeRTOS *//* 或 */__disable_irq();/* 裸机 *//* 这段代码不会被任何中断打断 */shared_varnew_value;/* 退出临界区开中断 */taskEXIT_CRITICAL();/* 或 */__enable_irq();6.2 什么时候用多任务共享变量的非原子操作操作链表/环形缓冲区修改关键配置6.3 临界区的原则尽可能短——关中断期间所有中断都进不来影响实时性不能阻塞——临界区里不能调用任何可能阻塞的API不能调用printf——太慢七、常见踩坑坑1ISR里忘了清中断标志voidTIM2_IRQHandler(void){/* 没清标志 */do_something();/* ISR执行完立刻又进中断无限循环主程序卡死 */}/* 正确 */voidTIM2_IRQHandler(void){if(TIM2-SR(10)){TIM2-SR~(10);/* 清标志 */do_something();}}坑2ISR里调用了HAL_DelayvoidEXTI0_IRQHandler(void){HAL_Delay(100);/* 死锁HAL_Delay依赖SysTick中断ISR里中断被阻塞 */}/* 解决用定时器或RTOS的vTaskDelay任务里 */坑3优先级配错高优先级中断饿死把SysTick系统滴答设成最高优先级会抢占所有其他中断。建议SysTick设较低优先级如15。坑4FreeRTOS的API调用限制ISR里只能调用带FromISR后缀的API。调用普通版本会崩溃。八、总结要点内容中断本质硬件打断CPU处理完返回NVIC管理中断优先级和嵌套优先级分组推荐GROUP_416级抢占嵌套规则高抢占优先级能打断低抢占优先级ISR铁律只清标志读数据发通知不阻塞临界区关中断保护共享资源尽量短一句话总结中断的核心是高抢占打断低抢占ISR只做三件事——清标志、读数据、发通知。下一篇预告《STM32时钟树详解配错一个全乱套》如果这篇文章对你有帮助点赞 收藏 关注这是我持续更新的动力有问题欢迎评论区交流我会逐条回复。作者嵌入式阿蔡

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价