4、中断处理流程:中断请求、中断响应、中断服务程序(ISR)、中断返回
中断处理,说白了就是CPU被“打断”后,怎么去处理紧急事件,再回来继续干活。这个流程我拆成四个环节来讲:中断请求、中断响应、中断服务程序、中断返回。每个环节都有坑,咱们一个一个说。
核心一句话:中断处理 = 硬件自动完成 + 软件手动处理。硬件负责“快速响应”,软件负责“具体干活”。
4.1 中断请求:谁在“敲门”?
中断请求,就是外设或内部事件向CPU发信号:“嘿,我有事找你!”
这个信号可以是电平触发,也可以是边沿触发。我个人习惯用边沿触发,因为它不容易被误判。我在项目中遇到过一个问题:按键按下时,电平抖动导致中断被触发了十几次。后来改成下降沿触发,再加个20ms的软件消抖,问题就解决了。
中断请求的来源主要有三类:
- 外部中断:GPIO引脚上的电平变化,比如按键、传感器信号。
- 内部中断:定时器溢出、ADC转换完成、UART接收数据等。
- 软件中断:程序自己触发的中断,比如系统调用、异常处理。
小技巧:中断请求信号要尽量短。我曾经见过一个同事,把中断引脚拉低了500ms,结果CPU一直在处理中断,主循环根本跑不动。记住:中断请求是“敲门”,不是“踹门”。
4.2 中断响应:CPU怎么“开门”?
CPU收到中断请求后,不会立刻响应。它得先把手头的事做完——至少要把当前指令执行完。然后,硬件自动完成以下动作:
- 保存现场:把PC(程序计数器)和状态寄存器压入堆栈。
- 关中断:防止在响应过程中被更高优先级的中断打断。
- 查找中断向量:根据中断号,从中断向量表中找到对应的ISR入口地址。
- 跳转执行:PC指向ISR的第一条指令。
嗯,这里要注意:中断响应时间是硬实时系统的关键指标。我做过一个电机控制项目,要求中断响应必须在5微秒以内。当时选了个Cortex-M4内核的MCU,硬件自动压栈只需要12个时钟周期,完全够用。如果你用51单片机,那响应时间可能要到几十微秒,就得掂量掂量了。
避坑指南:我曾经在STM32上犯过一个低级错误——中断向量表没对齐。结果中断响应时,CPU跳到了一个错误的地址,系统直接跑飞。后来查手册才发现,Cortex-M系列要求中断向量表必须按256字节对齐。血的教训啊!
4.3 中断服务程序(ISR):怎么“干活”?
ISR就是中断处理的核心代码。但这里有个原则:ISR要短、要快、要干净。
你想想看,ISR执行期间,其他中断可能被屏蔽,或者至少低优先级的中断进不来。如果你在ISR里搞个延时、跑个循环,整个系统的实时性就毁了。
我建议ISR只做三件事:
- 读取硬件状态:比如读取UART接收寄存器、ADC转换结果。
- 设置标志位:通知主循环“数据来了,赶紧处理”。
- 清除中断标志:告诉硬件“我已经处理完了,你可以发下一个请求了”。
下面是一个典型的UART接收中断ISR示例:
void USART1_IRQHandler(void)
{
// 1. 检查中断源
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
// 2. 读取数据
uint8_t data = USART_ReceiveData(USART1);
// 3. 存入缓冲区(环形缓冲区)
rx_buffer[rx_write_index] = data;
rx_write_index = (rx_write_index + 1) % RX_BUFFER_SIZE;
// 4. 设置标志位
rx_data_ready = 1;
// 5. 清除中断标志
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
个人经验:ISR里尽量不要调用函数,尤其是printf这种慢速函数。我见过有人直接在ISR里打印调试信息,结果串口输出占了几毫秒,系统直接卡死。真要调试,用GPIO翻转加示波器,比printf靠谱多了。
4.4 中断返回:怎么“关门”?
ISR执行完后,CPU要回到被中断的地方继续执行。这个过程叫中断返回,硬件会自动完成:
- 恢复现场:从堆栈中弹出之前保存的PC和状态寄存器。
- 开中断:重新允许中断响应。
- 继续执行:CPU回到被中断的指令处,继续往下跑。
这里有个容易忽略的点:堆栈平衡。如果你在ISR里手动压栈了,返回前一定要手动出栈。否则堆栈指针错位,程序必崩。
避坑指南:我曾经在ARM汇编里写ISR,忘了在返回前恢复LR寄存器。结果中断返回时,CPU跳到了一个随机地址,系统直接HardFault。查了两天才发现,是LR被ISR里的子函数改写了。记住:ISR里调用的函数,必须保证不破坏现场。
4.5 完整流程:一张图看懂
下面我用SVG画了一张中断处理流程图,把四个环节串起来。你看完应该就全明白了。
4.6 总结:记住这三点
中断处理流程,说白了就是“请求-响应-处理-返回”四个步骤。我总结三个要点:
| 环节 | 谁负责 | 关键动作 | 常见坑 |
|---|---|---|---|
| 中断请求 | 外设/软件 | 发送中断信号 | 信号抖动、电平持续时间过长 |
| 中断响应 | 硬件自动 | 保存现场、查向量表 | 向量表未对齐、响应时间超限 |
| ISR | 软件编写 | 读数据、设标志、清标志 | ISR过长、调用阻塞函数 |
| 中断返回 | 硬件自动 | 恢复现场、开中断 | 堆栈不平衡、LR被破坏 |
最后说一句:中断处理是嵌入式系统的“心脏”。你写得好,系统就稳如老狗;你写得烂,系统就三天两头死机。我建议你每写一个ISR,都问自己三个问题:够短吗?够快吗?够安全吗?