一、中断基础概念:中断的定义、作用、分类与向量表

各位同学,咱们今天聊中断。说实话,中断这玩意儿是嵌入式系统的灵魂之一。我做了十几年嵌入式,见过太多因为中断没处理好导致系统崩溃的案例。你想想看,一个没有中断的系统,就像一个人只能排队办事,前面的人不走你就得一直等着——这效率,太低了。

1.1 中断到底是什么?

中断,说白了就是CPU正在干一件事的时候,突然被一个紧急事件打断,转去处理这个紧急事件,处理完再回来接着干原来的事。

举个生活中的例子:你正在看书(主程序),突然手机响了(中断事件),你接电话(中断服务程序),挂断后继续看书(返回主程序)。嗯,就是这么个道理。

中断的核心特征:

  • 异步性:中断事件的发生时间不可预测
  • 优先级:紧急程度不同,处理顺序不同
  • 可嵌套:处理一个中断时,可以被更高优先级的中断打断

1.2 中断的作用——为什么我们需要它?

我刚开始做嵌入式开发时,用的是轮询方式——就是主循环里不断检查各个外设的状态。那时候做一个简单的按键检测,CPU 90%的时间都在空转。后来用了中断,CPU利用率直接降到10%以下。

中断的主要作用有这几个:

作用 说明 实际场景
提高CPU利用率 CPU不用一直等待事件发生 串口接收数据,有数据才触发中断
实时响应 对紧急事件快速处理 电机过流保护,必须在微秒级响应
任务切换 操作系统调度的基础 FreeRTOS的SysTick中断
功耗管理 CPU可以进入休眠,中断唤醒 物联网设备待机模式

我的经验:在低功耗项目中,我习惯把不用的外设中断全部关掉,只保留唤醒源的中断。曾经有个项目,就是因为一个GPIO中断没关,设备待机电流从10μA飙到了2mA——电池寿命直接砍半。

1.3 中断的分类——三种中断,各有各的脾气

中断按来源分,主要有三类。我画了个图,方便你理解它们之间的关系:

中断分类结构图 中断 外部中断 内部中断 软件中断 外部中断 来自芯片外部引脚 GPIO、定时器捕获 外部设备请求 内部中断 芯片内部外设触发 定时器、ADC、DMA 串口、I2C、SPI 软件中断 CPU指令触发 SWI/SVC指令 系统调用 三类中断:来源不同,处理方式各有讲究

1.3.1 外部中断

外部中断来自芯片的引脚。比如你按下一个按键,电平变化触发中断。我记得有个项目,客户说设备偶尔死机,查了三天发现是按键抖动触发了多次中断,导致中断栈溢出。后来加了个硬件去抖电路,问题就解决了。

1.3.2 内部中断

内部中断由芯片内部的外设产生。定时器溢出、ADC转换完成、DMA传输结束——这些都是内部中断的典型场景。我建议你在设计系统时,能用DMA的地方尽量用DMA,它可以在不占用CPU的情况下完成数据传输,只在完成时触发一次中断。

1.3.3 软件中断

软件中断是CPU执行特定指令触发的。在ARM Cortex-M上,SVC指令就是典型的软件中断。操作系统用它来实现系统调用——用户程序通过SVC指令请求内核服务,比如创建任务、申请内存。

注意:软件中断的优先级通常由操作系统管理,不要随意修改。我曾经在一个RTOS项目中,手欠改了SVC中断的优先级,结果系统调度直接乱套——任务该跑的不跑,不该跑的抢着跑。调试了整整两天才找到原因。

1.4 中断向量表——CPU的紧急联络簿

中断向量表,你可以把它想象成一本电话簿。CPU收到中断信号后,查这个表就知道该跳到哪里去处理。

以ARM Cortex-M3为例,向量表放在Flash的起始地址:

; 向量表示例(STM32F103)
__Vectors       DCD     __initial_sp          ; 0x00000000 栈顶指针
                DCD     Reset_Handler         ; 0x00000004 复位中断
                DCD     NMI_Handler           ; 0x00000008 NMI中断
                DCD     HardFault_Handler     ; 0x0000000C 硬件错误
                DCD     MemManage_Handler     ; 0x00000010 内存管理
                DCD     BusFault_Handler      ; 0x00000014 总线错误
                DCD     UsageFault_Handler    ; 0x00000018 用法错误
                ; ... 后续是外设中断
                DCD     TIM2_IRQHandler       ; 定时器2中断
                DCD     USART1_IRQHandler     ; 串口1中断

向量表的关键点:

特性 说明 注意事项
固定位置 通常从0地址开始 部分芯片支持重定位
固定大小 每个向量4字节(32位) 64位芯片为8字节
顺序固定 中断号对应固定位置 芯片手册有明确列表
优先级关联 向量位置决定默认优先级 可编程修改

避坑指南:我曾经在Bootloader项目中,把应用程序的向量表重定位到了0x08010000。结果忘了修改SCB->VTOR寄存器,中断一来CPU直接跳到0地址——那里是Bootloader的向量表,应用程序的中断一个都跑不对。嗯,从那以后我每次做Bootloader,第一件事就是检查VTOR的值。

1.5 中断处理流程——CPU到底干了啥?

一个完整的中断处理流程,我习惯把它分成6步:

  1. 中断请求:外设或引脚产生中断信号
  2. 中断判优:CPU检查当前中断优先级,决定是否响应
  3. 保护现场:自动压栈PC、PSR、LR等寄存器
  4. 查向量表:根据中断号找到对应的ISR入口地址
  5. 执行ISR:运行中断服务程序
  6. 恢复现场:出栈,返回被中断的程序

这里有个细节:Cortex-M3/M4处理器在硬件层面自动完成了压栈和出栈,不需要你在ISR里手动保存寄存器。但有些老架构(比如ARM7)需要手动处理——我刚开始做ARM7时,就因为忘了保存R0-R3,导致中断返回后数据全乱了。

核心原则:中断服务程序要短、要快、要干净。我给自己定的规矩是:ISR里只做标志位设置和数据搬运,真正的处理逻辑放到主循环或任务里。超过100条指令的ISR,基本就是设计有问题。

1.6 本章小结

中断是嵌入式系统的核心机制,理解它的基础概念是后续性能调优和调试的前提。外部中断、内部中断、软件中断各有各的应用场景,向量表是CPU响应中断的导航图。记住:中断处理的核心是快进快出,别在ISR里干重活。