一、中断基础概念
1.1 中断到底是什么?
先说说我对中断的理解。说白了,中断就是让CPU停下当前工作,去处理一个更紧急的事情。
想象一下你正在写代码,突然电话响了。你会怎么做?当然是停下手头的事,接电话。接完再继续写。这就是中断的日常版。
在嵌入式系统里,中断是硬件向CPU发出的一个信号。这个信号告诉CPU:「嘿,有急事,先处理我!」CPU收到信号后,会保存当前的工作状态,跳去执行中断服务程序(ISR),处理完再回来继续原来的工作。
中断的核心三要素:
- 触发源:谁发的中断?可能是定时器、GPIO引脚、串口接收等
- 中断向量:CPU该跳到哪里去执行?每个中断源对应一个入口地址
- 中断服务程序:到了入口地址后,具体要做什么?这就是ISR的内容
我刚开始做嵌入式开发时,总觉得中断是个很玄乎的东西。后来调试一个串口丢数据的问题,折腾了两天,最后发现是中断优先级没配好。嗯,从那以后我对中断就格外上心了。
1.2 中断到底有什么用?
中断的作用,我总结为三点:
- 提高CPU利用率:没有中断时,CPU只能轮询等待事件发生,白白浪费时钟周期
- 实时响应:外部事件发生时,CPU能立刻感知并处理
- 多任务处理:让CPU看起来能同时处理多个事情
举个例子。你写了一个温度采集程序,每100ms读一次传感器。如果用轮询,CPU这100ms里啥也干不了,就死等着。用中断的话,CPU可以去做其他计算任务,定时器到了再通知CPU去读数据。你想想看,这效率差了多少?
我个人习惯:在设计系统时,能用中断的地方尽量用中断。但要注意,中断服务程序要短小精悍,别在里面做复杂运算。我曾经见过有人把FFT计算放在中断里,结果系统直接崩了。
1.3 中断 vs 轮询:到底选哪个?
这个问题我经常被问到。先看个对比表格:
| 特性 | 中断 | 轮询 |
|---|---|---|
| CPU利用率 | 高,无事时CPU可做其他工作 | 低,CPU被白白占用 |
| 响应延迟 | 快,事件发生立即响应 | 慢,取决于轮询周期 |
| 实现复杂度 | 较高,需要配置中断控制器 | 简单,一个while循环搞定 |
| 适用场景 | 实时性要求高、事件不频繁 | 事件频繁、实时性要求低 |
| 代码可维护性 | 需要小心处理共享数据 | 逻辑清晰,容易调试 |
我一般这样选:如果事件发生的频率超过1kHz,或者对延迟要求很苛刻,那就用中断。如果只是偶尔检查一下按键状态,轮询就够了。
举个例子,你写一个UART接收程序。数据随时可能来,用轮询的话CPU得一直盯着接收寄存器。用中断的话,数据来了再通知CPU去取。哪个更合理?一目了然。
我曾经踩过的坑:在一个电机控制项目中,我用轮询检测过零信号。结果电机转速一高,过零信号就丢了。后来改成中断,问题立刻解决。记住:轮询的响应时间取决于轮询周期,而中断的响应时间取决于中断优先级。
1.4 中断在实时系统中的重要性
实时系统最核心的要求是什么?两个字:确定。
确定性能保证系统在规定时间内完成响应。中断是实现确定性的关键手段。
你想想看,一个飞行控制系统,传感器检测到姿态异常,必须在几毫秒内做出调整。如果用轮询,CPU可能正在处理其他任务,等它轮询到传感器时,飞机可能已经翻了。
中断在实时系统中的几个关键作用:
- 事件驱动:系统只在需要时才被唤醒,节省功耗
- 优先级管理:高优先级的中断可以抢占低优先级的任务
- 时间确定性:中断响应时间是可预测的
- 资源隔离:不同中断源之间互不干扰
我参与过一个工业控制项目,要求PLC的IO响应时间不超过100微秒。用轮询根本做不到,最后靠精心设计的中断优先级和嵌套机制才搞定。
1.5 中断处理的基本流程
下面这张图展示了中断从发生到处理完成的完整流程:
这个流程看起来简单,但每个步骤都有讲究。比如保存上下文,要保存哪些寄存器?中断向量表放在哪里?ISR里能不能调用printf?这些问题我在后面的章节会详细讲。
1.6 一个简单的中断代码示例
拿STM32的GPIO外部中断举个例子:
// GPIO外部中断配置
void GPIO_EXTI_Init(void)
{
// 1. 使能GPIO时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 2. 配置PA0为输入模式
GPIOA->MODER &= ~(0x3 << 0);
// 3. 配置中断触发方式:上升沿触发
EXTI->RTSR |= EXTI_RTSR_TR0;
// 4. 使能中断线0
EXTI->IMR |= EXTI_IMR_MR0;
// 5. 配置NVIC优先级
NVIC_SetPriority(EXTI0_IRQn, 2);
NVIC_EnableIRQ(EXTI0_IRQn);
}
// 中断服务程序
void EXTI0_IRQHandler(void)
{
// 检查中断标志位
if(EXTI->PR & EXTI_PR_PR0)
{
// 处理中断事件
// 注意:这里不要做耗时操作
// 清除中断标志位
EXTI->PR = EXTI_PR_PR0;
}
}
避坑指南:ISR里一定要清除中断标志位,否则会一直触发中断。我曾经因为这个原因,调试一个按键中断时,发现按一次按键,串口打印了十几条消息。后来才发现是忘了清标志位。
1.7 中断的优先级与嵌套
实时系统里,中断优先级是个关键概念。高优先级的中断可以打断低优先级的中断,这叫中断嵌套。
举个例子:
- 系统正在执行定时器中断(优先级3)
- 突然来了一个外部紧急中断(优先级1)
- CPU会暂停定时器中断,先去处理紧急中断
- 处理完紧急中断,再回来继续执行定时器中断
我建议在设计系统时,把时间敏感的中断设高优先级,比如电机控制、通信协议等。把不那么紧急的中断设低优先级,比如按键扫描、LED闪烁等。
注意:中断嵌套不是越多越好。嵌套层数太多,会导致栈溢出。而且低优先级中断可能长时间得不到响应。我一般控制在3层以内。
1.8 中断延迟:实时系统的命门
中断延迟是指从中断发生到CPU开始执行ISR的时间。这个时间决定了系统的实时性能。
影响中断延迟的因素:
- CPU正在执行关中断的代码
- 高优先级中断正在执行
- 中断控制器仲裁时间
- CPU保存上下文的时间
我做过一个测试,在Cortex-M4上,中断延迟大约在12个时钟周期左右。如果CPU跑100MHz,那就是120纳秒。这个时间对于大多数实时应用来说足够了。
但要注意,如果你在代码里频繁关中断,那延迟就会大大增加。我曾经接手过一个项目,代码里到处都是__disable_irq(),结果中断响应时间从微秒级变成了毫秒级。嗯,后来我花了一周时间重构了那部分代码。
好了,中断的基础概念就讲到这里。记住一句话:中断是实时系统的灵魂,用好了事半功倍,用不好系统崩溃。
公众号:蓝海资料掘金营,微信deep3321