1. 中断基础概念

各位同学,咱们今天聊聊中断。这东西可以说是嵌入式系统的灵魂之一。我刚开始做嵌入式那会儿,对中断的理解就是“一个信号来了,CPU停下手里的活去处理它”。后来踩了不少坑才明白,事情远没那么简单。

1.1 中断的定义

中断,说白了就是CPU在执行主程序时,被一个内部或外部的事件打断,转而去执行一段专门的处理程序,处理完再回来接着干原来的活。

你想想看,如果没有中断,CPU就得不停地轮询检查各个外设的状态——按键按了没?数据到了没?定时器溢出了没?这得多浪费啊。中断机制让CPU能专心干正事,只在需要的时候才响应外设的请求。

核心要点:中断是一种异步事件处理机制,它让CPU从“被动轮询”变成“主动响应”,大大提高了系统的实时性和效率。

我在一个工业控制项目中遇到过这样的情况:主循环里有个复杂的算法要跑几十毫秒,如果不用中断,按键响应延迟能到上百毫秒,操作员按下去半天没反应。后来改成中断触发,响应时间直接降到微秒级。

1.2 中断向量表

中断向量表,这是每个嵌入式工程师必须搞明白的东西。它本质上是一张地址表,存放在固定的内存区域,每个中断源对应一个表项,表项里存放的是该中断服务函数的入口地址。

以ARM Cortex-M系列为例,向量表通常从地址0x00000000开始。前几个表项是系统异常,后面才是外设中断。

偏移地址 中断号 用途
0x0000 - 栈顶指针(MSP初始值)
0x0004 - 复位向量
0x0008 - NMI(不可屏蔽中断)
0x000C - 硬错误(HardFault)
0x0040 0 系统滴答定时器(SysTick)
0x0044 1 外部中断0(EXTI0)
... ... ...

个人经验:我习惯在工程启动文件里手动确认向量表布局,特别是做Bootloader的时候。有一次芯片升级后向量表偏移没改对,程序一跑就进HardFault,查了两天才发现是SCB->VTOR没设置。

1.3 中断优先级与嵌套

中断优先级,这是个容易出坑的地方。不同架构的优先级管理方式不一样,但核心思想是一致的:高优先级的中断可以打断低优先级的中断,这就叫中断嵌套。

ARM Cortex-M处理器支持可编程的优先级,通常用4位来表示优先级,但具体实现可能是3位或2位,取决于芯片厂商。优先级数值越小,优先级越高——这点和直觉相反,要注意。

避坑指南:我曾经在一个项目中,把两个中断设成了相同优先级,结果它们同时触发时,系统行为变得不可预测。后来查手册才发现,同优先级中断不会互相嵌套,而是按硬件编号顺序响应。所以,如果你需要确定性的嵌套行为,务必给每个中断分配不同的优先级。

中断嵌套的典型场景是这样的:

  1. CPU正在执行主程序,低优先级中断A来了,CPU保存现场,进入ISR_A。
  2. ISR_A执行到一半,高优先级中断B来了,CPU再次保存现场,进入ISR_B。
  3. ISR_B执行完毕,恢复现场,回到ISR_A继续执行。
  4. ISR_A执行完毕,恢复现场,回到主程序。

这里有个关键点:中断嵌套层数不能太深。我见过有人嵌套了五六层,栈空间直接爆掉,系统死得悄无声息。一般建议嵌套不超过3层。

1.4 中断服务函数(ISR)编写规范

ISR的编写规范和普通函数不一样,很多新手在这里栽跟头。我总结了几条铁律:

  • 短小精悍:ISR里只做最必要的事,比如读取数据、置标志位。复杂的计算、打印、内存分配这些,统统放到主循环或任务里去处理。
  • 避免阻塞:不要在ISR里用延时、等待锁、或者调用可能阻塞的函数。你想想看,ISR阻塞了,其他中断怎么办?系统实时性就崩了。
  • 保护共享数据:ISR和主程序共享的变量,要用volatile修饰,必要时加临界区保护。我遇到过最典型的bug就是主程序读一个变量,ISR同时在写,结果读到一半的数据,整个逻辑就乱了。
  • 清除中断标志:进入ISR后第一件事,通常是清除中断标志位,否则退出后又会立即触发,形成死循环。
// 一个规范的ISR示例
volatile uint32_t g_uart_rx_flag = 0;
volatile uint8_t  g_uart_rx_byte = 0;

void USART1_IRQHandler(void)
{
    // 1. 检查中断源
    if (USART1->SR & USART_SR_RXNE)
    {
        // 2. 读取数据(硬件自动清除RXNE标志)
        g_uart_rx_byte = USART1->DR;
        
        // 3. 置标志位,通知主程序处理
        g_uart_rx_flag = 1;
    }
    
    // 其他中断源的处理...
}

我的习惯:ISR里我几乎不用printf,因为printf本身可能依赖中断,而且执行时间不可控。调试时我会用GPIO翻转或者DMA+环形缓冲区的方式,把日志数据先存起来,主循环再慢慢处理。

嗯,这里还要提一个容易被忽略的点:ISR的栈空间。RTOS环境下,每个任务有自己的栈,但中断通常使用系统栈(MSP)。如果中断嵌套层数多,或者ISR里局部变量太大,栈溢出是迟早的事。我建议给系统栈预留足够的空间,至少256字节起步,具体看你的中断复杂度。

知识结构总览

下面这张图把中断的核心概念串起来了,你可以对照着梳理自己的知识体系:

中断基础概念知识结构 中断系统 中断定义 中断向量表 优先级与嵌套 ISR编写规范 异步事件处理机制 CPU被动轮询→主动响应 存放ISR入口地址的表 固定内存区域(如0x0000) 数值越小优先级越高 高优先级可打断低优先级 短小精悍,避免阻塞 保护共享数据,清除标志 核心要点 • 中断是嵌入式实时性的基石,理解中断机制是系统设计的第一步 • 向量表、优先级、ISR规范三者缺一不可,任何一个环节出问题都会导致系统不稳定 • 实际项目中,中断延迟分析和优化是性能调优的关键手段

中断这部分内容,是后续分析中断延迟和DMA传输的基础。你把这些概念吃透了,后面讲中断延迟的测量方法、DMA如何配合中断工作,理解起来就会顺畅很多。