1、中断基础概念:中断的定义、中断向量表、中断优先级、中断嵌套

各位同学,咱们今天聊聊中断。说实话,中断这玩意儿是嵌入式系统的灵魂。没有中断,CPU就得像个傻子一样轮询——你想想看,那得多浪费算力?我刚开始做驱动时,对中断的理解也就停留在“按个键触发个事情”的层面。后来踩了不少坑,才真正搞明白这里面的门道。

1.1 中断到底是什么?

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

举个生活中的例子:你正在看书(主程序),突然电话响了(中断事件),你放下书去接电话(中断服务程序),接完电话回来继续看书(恢复执行)。嗯,就是这么个道理。

在嵌入式系统里,中断的来源五花八门:

  • 外部中断:GPIO引脚电平变化、按键按下
  • 定时器中断:定时器溢出、PWM周期到达
  • DMA中断:数据传输完成
  • 通信外设中断:UART收到数据、SPI传输完成、I2C仲裁丢失
  • 软件中断:程序主动触发的中断(比如系统调用)

核心要点:中断机制让CPU从“被动轮询”变成“主动响应”,效率提升不止一个数量级。我在一个物联网项目里,把轮询改成了中断驱动,CPU占用率直接从85%降到了12%。

1.2 中断向量表——中断的“通讯录”

中断向量表,你可以把它理解成一本通讯录。每个中断源都有一个唯一的编号(中断号),CPU收到中断信号后,就去查这个通讯录,找到对应的处理函数地址,然后跳过去执行。

在ARM Cortex-M系列处理器里,中断向量表通常放在Flash的起始地址(0x00000000)。前16个是系统异常,从第16个开始才是外部中断。我习惯把向量表叫做“中断的入口地图”——地图画错了,你永远到不了目的地。

向量位置 中断号 类型 描述
0x00000000 - 栈顶指针 初始SP值
0x00000004 - Reset_Handler 复位中断
0x00000008 - NMI_Handler 不可屏蔽中断
0x0000000C - HardFault_Handler 硬错误
... ... ... ...
0x00000040 0 IRQ0_Handler 第一个外部中断
0x00000044 1 IRQ1_Handler 第二个外部中断

避坑指南:我曾经在一个项目里,因为中断向量表地址配置错了,导致中断触发后程序直接跑飞。查了两天才发现是链接脚本里向量表偏移设错了。记住:中断向量表地址必须与硬件设计一致,尤其是做Bootloader时,向量表重定位(VTOR)一定要配对。

1.3 中断优先级——谁更紧急谁先上

多个中断同时来了怎么办?总得有个先后顺序吧。中断优先级就是干这个的。

ARM Cortex-M处理器支持可编程的中断优先级,一般用4位(0-15)或8位(0-255)来表示优先级数值。注意:数值越小,优先级越高。这个跟直觉相反,我刚开始也老搞混。

优先级分组也是个有意思的设计。Cortex-M把优先级分为抢占优先级和子优先级:

  • 抢占优先级:决定能否打断正在执行的中断
  • 子优先级:当抢占优先级相同时,决定谁先执行

举个例子:中断A的抢占优先级是1,中断B的抢占优先级是2。A正在执行,B来了——B得等着,因为A的优先级更高。但如果A和B的抢占优先级都是1,那就看子优先级,子优先级小的先执行。

实际经验:我在做电机控制驱动时,把电流环中断设为最高优先级(0),速度环设为次高(1),位置环设为最低(2)。这样电流环永远不会被其他中断打断,保证了控制的实时性。你想想看,如果电流环被耽误了,电机可能直接就烧了。

1.4 中断嵌套——中断里还能有中断?

中断嵌套,就是高优先级的中断可以打断低优先级的中断服务程序。这听起来有点递归的味道,但硬件帮你处理好了。

当CPU正在执行中断A的服务程序时,来了一个优先级更高的中断B:

  1. CPU暂停中断A的执行
  2. 保存中断A的上下文(寄存器、返回地址等)
  3. 跳转到中断B的服务程序
  4. 中断B执行完毕,恢复中断A的上下文
  5. 继续执行中断A

这个过程,硬件自动完成了大部分工作。Cortex-M处理器有硬件压栈机制,发生中断嵌套时,硬件会自动把当前中断的寄存器压入栈中,然后切换到新中断的栈空间。

注意:中断嵌套虽然好用,但别滥用。嵌套层数太多会导致栈溢出,而且中断响应延迟会变大。我见过一个项目,嵌套了5层中断,结果栈空间不够,程序跑着跑着就挂了。我的建议是:中断嵌套控制在2-3层以内,能用轮询解决的别用中断嵌套。

1.5 中断处理流程全景图

下面这张图,把中断从触发到处理完成的整个流程串起来了。我画图时习惯把关键节点标清楚,这样调试时一眼就能看出问题出在哪。

中断处理流程全景图 中断处理流程全景图 中断源触发 中断控制器仲裁(优先级判断) CPU响应中断 查中断向量表,获取入口地址 执行中断服务程序(ISR) 恢复现场,返回主程序 关键点 • 中断源可以是外设或软件 • 优先级决定谁先被响应 • 向量表地址必须正确 嵌套场景 • 高优先级可打断低优先级 • 硬件自动压栈/出栈 • 嵌套层数不宜过多

1.6 中断服务程序(ISR)的编写要点

写ISR跟写普通函数不一样。我见过太多新手在ISR里干各种“大事”,结果系统卡得跟幻灯片似的。记住几个原则:

  • 快进快出:ISR里别做耗时操作,比如打印、malloc、延时。我一般只在ISR里设置标志位或拷贝数据,真正的处理放到主循环或任务里。
  • 不可重入:除非你明确知道自己在做什么,否则别在ISR里调用不可重入函数。比如printf,很多实现都不是线程安全的。
  • 关中断时间要短:如果需要在ISR里关中断保护临界区,关中断的时间越短越好。我曾经在一个项目里关了50微秒的中断,结果UART丢数据了。

个人习惯:我写ISR时,第一件事就是清除中断标志位。为什么?因为如果不先清标志,同一个中断可能会反复触发,导致ISR被重入,逻辑就乱了。这个坑我踩过,现在每次写ISR都条件反射地先清标志。

1.7 中断与Linux内核的关联

咱们这门课讲的是嵌入式Linux中断处理,所以得提一句Linux里的中断机制。Linux把中断分成了上半部(Top Half)和下半部(Bottom Half):

  • 上半部:在中断上下文执行,要求快,只做最紧急的事(比如清标志、读硬件寄存器)
  • 下半部:在软中断或工作队列中执行,可以做耗时操作(比如数据处理、协议解析)

这个设计思路,说白了就是把中断处理拆成“紧急”和“不紧急”两部分。紧急的立刻处理,不紧急的等会儿再说。我在做网络驱动时,上半部只把收到的数据包拷贝到缓冲区,下半部才做协议栈处理——这样网卡就不会因为处理协议而错过下一个数据包。

总结一下:中断是嵌入式系统的“紧急通道”,向量表是“通讯录”,优先级是“排队规则”,嵌套是“紧急打断紧急”。把这些基础概念吃透了,后面写中断驱动才能游刃有余。