
“中断”几乎是每个硬件工程师绕不开的第一道坎。我这些年带新人、做面试最常问的一个问题就是“你说说看什么是中断”得到的回答大多是背概念比如“CPU暂停当前任务转去处理突发事件处理完再回来”听起来没毛病但太干。今天我想用一个能记一辈子的例子把中断从概念到代码、再从代码到PCB这条线彻底讲透。因为中断真不只是软件工程师关心的事它背后还牵扯着硬件电路、PCB布局、系统实时性是硬件工程师判断一块板子靠不靠谱的底层思维。无论你刚入门还是做了几年硬件把这套逻辑理清楚后面写代码画板子都会顺手很多。1. 中断到底是什么一个电话打断比你想象的更复杂1.1 生活比喻之外单片机里的完整流程先来一个最经典、也最容易理解的例子。你在办公室写周报写到一半电话响了。你会怎么做停下手头的活记住自己刚写到哪一行然后接电话处理完电话里的问题挂掉电话再回到工位上继续写。这个场景和CPU的中断机制几乎一一对应。单片机里的中断流程实际上是这样走的电话铃响对应“中断请求信号”也就是外设或者外部引脚给CPU拉了一个电平变化。你停下手头的活对应“CPU暂停当前正在执行的主程序”。你记下自己写到哪一行对应“保护现场”也就是把当前执行的地址、寄存器状态压入堆栈。接电话交谈、处理事情对应“执行中断服务函数”行业内叫ISR。挂电话对应“清中断标志”告诉CPU这件事你处理完了。你回到工位继续写周报对应“恢复现场”CPU回到刚才被中断的地方接着往下执行。这个比喻能覆盖百分之八九十的中断流程概念。但单片机里的实际场景比接电话复杂得多比如来了两个电话你接哪个、接电话途中又来一个新电话怎么处理、下次再开会忘了写哪行怎么办这些在中断里都有对应的机制后面我会挨个讲。1.2 为什么不用轮询中断的意义与代价有人会问我不用中断程序里反复检查某个引脚有没有变化或者一直查串口接收标志位不行吗这叫轮询。轮询在简单场景下确实能用但有两个致命问题。第一个问题是浪费CPU。你让一个主频几百兆的CPU干等一个可能几十毫秒才来一次的信号大部分算力都空转了。第二个问题是实时性差。如果你的主程序里有延时、有耗时操作轮询可能根本赶不上处理突发事件。举个实际例子一个编码器信号在高速转动时脉冲间隔可能只有几微秒你用轮询去查一个主循环还没跑完信号已经丢了好几拍。中断的意义就是把“主动问”变成“被通知”。信号来了硬件自动通知CPUCPU可以随时放下手里的事去处理。但中断不是免费的它也有代价。CPU响应中断需要时间从响应到跳转要经过压栈、取向量、跳转这些操作再加上中断服务函数本身执行的时间都会占用CPU。比如STM32F103在72MHz主频下一次外部中断从触发到进入ISR一般需要十几个到几十个CPU周期算下来大概是几百纳秒到几微秒不等。如果中断触发频率特别高ISR又做得太长CPU大部分时间都在忙着切换任务主程序反而跑不动。这个“中断开销”的概念是硬件工程师评估系统实时性时必须心里有数的。2. 中断相关的基本概念面试题里最常出现的那几个2.1 中断向量、中断号与中断服务函数很多初学者啃芯片手册时看到一长串以IRQ结尾的枚举就蒙了比如STM32里的EXTI0_IRQn、TIM2_IRQn。这些东西到底是什么意思在Cortex-M系列内核的单片机里每一个中断源都对应中断向量表里的一个入口。向量表就相当于一张“通讯录”CPU一收到中断信号就去查这张表找到该跳转的地址。每个中断源都有一个编号这个编号在芯片数据手册和启动文件里写得清清楚楚中断号其实就是这张“通讯录”里的索引。以STM32F103为例部分常见中断向量如下中断向量名中断号示例对应外设/功能WWDG_IRQn0窗口看门狗EXTI0_IRQn6外部引脚中断0TIM2_IRQn28定时器2全局中断USART1_IRQn37串口1全局中断写代码时中断服务函数的名字不是随便起的必须和启动文件里的向量表对齐。你写错名字中断触发了也跳不到你的函数程序就会跑飞。这也是新手最容易踩的坑之一函数名少个字母编译不报错但中断就是不进。2.2 抢占优先级与子优先级谁先跑谁后跑前面说“两个电话同时响接哪个”对应到中断系统里就是优先级的问题。以Cortex-M3/M4内核为例NVIC支持中断优先级分组优先级分为抢占优先级和子优先级两组概念。抢占优先级的意思是当一个高抢占优先级的中断正在执行时它可以打断正在执行的低抢占优先级中断这就是中断嵌套。子优先级不能打断它只在多个同抢占优先级的中断同时到达时决定谁先响应。我接触过不少项目中断一多优先级没配好各种神奇问题就出现了A中断还没执行完B中断进来把共享数据改了低优先级的中断永远抢不到CPU最要命的是某个中断服务函数里用了延时而这个中断又被更高优先级的中断打断整个时序全部乱掉。我的经验是一个项目里中断优先级不要规划得太花哨。简单项目统一用一个优先级分组开局先把所有中断设成同级靠子优先级区分先后跑通了再根据实时性要求慢慢调。单片机中断优先级永远是“够用就好”别为了炫技搞太复杂。2.3 ISR的三个铁律短、快、不碰共享数据中断服务函数有自己的一套规矩我在带新人时反复强调总结下来就是三条铁律。第一ISR要尽量短。ISR里只做最重要的事比如置一个标志位、拷贝一个字节到缓冲区真正复杂的逻辑放到主循环里做。你在ISR里停留时间越长其他中断被耽误得越久系统实时性就越差。第二ISR里千万不要调用delay、printf、malloc这类函数。这些函数要么会阻塞等待要么本身不可重入在中断里调用极容易把系统搞死。我见过太多新人在USART中断接收函数里直接调用printf打印调试信息结果串口还开着中断却又来了直接死锁。第三如果ISR和主程序共享同一个变量这个变量必须加volatile修饰。原因在于编译器优化时可能把变量值暂存在寄存器里主程序改了值ISR读到的却是旧值或者ISR改了值主程序一直以为没变。这个问题后面实战部分还会详细说。3. 中断不只在代码里中断引脚背后的PCB设计门道3.1 外部中断的电路设计按键、边沿、抖动的纠缠很多人学中断是从按键开始的按键中断也是最容易出问题的场景之一。原因就是机械按键在按下去的瞬间金属触点会反复接触弹开几次这个过程叫抖动。抖动会产生一串宽度极窄的脉冲如果直接接在中断引脚上一个按键动作会触发好多次中断。在原理图和PCB设计阶段就要把这个问题解决掉。最常见的做法是RC滤波电路用一个电阻和一个电容组成低通滤波把窄脉冲吸收掉。比如10kΩ电阻串联100nF电容到地时间常数算下来约1ms基本能把机械按键的抖动毛刺滤干净。同时按键还要接上拉或下拉电阻确保没有按键动作时引脚电平固定不会因为悬空导致噪声误触发。PCB布局上这个RC滤波网络和上拉电阻要尽量靠近MCU的中断引脚摆放。我见过一些板子干扰源离MCU很近但滤波电容离得很远走线从干扰源脚下绕过一整圈再到MCU引脚结果滤波器形同虚设按键一按隔壁的PWM信号就串进来误触发中断。好的布局思路是干扰源在板边滤波器件紧贴MCU引脚中间走线短而粗尽量避开晶振、电感这类强辐射元件。3.2 中断信号线的布局布线从连接器到MCU的距离如果你的中断信号来自板外的连接器、光耦、编码器或者另一块板子布线时就要格外小心了。我调过一块电机驱动板编码器Z信号零位信号一接上就疯狂触发中断查了半天发现是这条信号线在PCB上走了将近十厘米还贴着开关电源的MOS管和电感。编码器信号本身边沿很陡高速切换时会产生振铃再加上电源噪声耦合MCU的输入引脚根本分不清高低电平。处理这类信号的思路有几种。一是从源头加匹配电阻在发送端串一个22Ω到33Ω的小电阻可以明显抑制信号反射和过冲。二是在接收端加一个施密特触发器或者RC滤波把边沿变缓、毛刺滤掉。三是在PCB布局上中断信号线尽量远离开关电源、晶振、高频PWM走线有条件的话让信号线两侧用地线包围也就是加“保护地”走线。特别提醒一点高速编码器这类脉冲信号如果传输距离较长最稳的方案是用差分信号比如RS422差分编码器抗共模干扰能力远远强于单端信号。如果硬要用单端信号线长别超过二三十厘米否则就要考虑加一级总线缓冲器或者整形电路。3.3 电源去耦和地回路莫名其妙的误中断多数是这个原因很多时候中断引脚周围的电路画得干干净净信号源也正常但MCU就是会误进中断。这时候你就要怀疑电源和地了。数字电路在电平翻转的瞬间会从电源抽取一个瞬态电流。如果MCU电源引脚旁的去耦电容放得不够近、容量不够电源电压就会出现毛刺。这个毛刺通过地回路传到中断引脚的内部门限电路就能触发一次外部中断。这是典型的“电源噪声导致逻辑错乱”。去耦电容的摆放有个基本原则0.1μF的陶瓷电容要尽量靠近每个电源引脚距离不超过两三毫米最佳电容和电源引脚之间不要打过孔最好在同一层直接相连。大容量的电解电容可以放得远一点但也要保证充电回路足够宽。地平面同样重要中断引脚走线不要跨越分割的地平面模拟地和数字地尽量做到单点连接避免形成巨大的地环路。还有一个容易被忽略的问题板子电源输入端的电压跌落或者短时中断也会让MCU复位或者误判引脚状态。如果你的产品有低功耗唤醒功能靠中断唤醒MCU那么电源跌落时产生的毛刺极有可能把系统从睡眠中“假唤醒”。这种问题在原理图阶段就要考虑清楚电源入口要加TVS管和足够的储能电容保证轻微跌落下MCU不掉电、不误触发。4. 中断和DMA怎么选以串口、CAN为例的工程实践4.1 串口中断接收爽快但要注意这些坑串口接收是中断用得最多的场景之一。用中断接收的好处很明显CPU不用一直盯着接收标志位数据来了自动通知。做AT指令应答、遥控器解码这类小数据量交互中断接收完全够用。但中断接收也有坑最典型的是数据处理速度跟不上。如果你的串口波特率是115200每秒钟大约能接收11520字节算下来每字节间隔不到87微秒。如果在中断服务函数里做太多事比如解析数据、调用其他函数下一次中断来了还没来得及退出数据就会溢出丢失。我在调试一块NB-IoT模组的板子时就吃过这个亏。模组上电瞬间会连续输出一大串日志和配置信息我的中断接收函数里做了简单的数据拼接结果日志一多就丢字节。正确的做法是中断服务函数里只做一件事把收到的字节丢进接收缓冲区置一个标志位剩下的事情全部放到主循环里慢慢处理。缓冲区用环形队列实现这样即使主循环偶尔卡顿几十毫秒数据也不会丢。4.2 DMA加空闲中断处理不定长数据的省心方案如果数据量很大比如固件升级、文件传输中断接收就会让CPU忙于搬数据。这时候就该DMA出场了。DMA就是“数据搬运工”外设接收到数据后DMA直接把数据搬到内存不用CPU一行一行地参与。串口接收不定长数据时一个常见的做法是“DMA加空闲中断”DMA负责接收数据串口空闲中断负责在总线空闲时告诉CPU“这一帧数据收完了”。具体流程是初始化DMA接收使能串口空闲中断DMA处于持续接收状态。当一帧数据发送完成后总线上出现空闲状态串口触发空闲中断这时CPU读取DMA剩余长度算出实际收到的数据长度处理完这一帧后再重新启动DMA。这样一帧数据只打扰一次CPU效率远高于逐字节中断。用这个方法有几个关键细节。第一DMA缓冲区的长度必须大于可能收到的最大帧长否则会溢出。第二要处理好DMA的循环模式和普通模式普通模式下接收满后DMA会自动停止循环模式下则会从头继续写容易覆盖旧数据所以一般用普通模式加空闲中断。第三空闲中断的参数要合理设置有的芯片支持配置空闲检测的时间设太短会在帧内数据延迟稍大时误判。4.3 一个经典问题LIN模式下串口发送会触发接收中断吗群里经常有人问“用LIN模式串口发送出去的数据会触发接收中断吗”答案是会的而且这是很多新人第一次接触LIN总线时必然踩的坑。LIN是单线总线典型接法是把MCU的UART_TX和UART_RX都接到同一个LIN收发器上总线上的数据同时会被接收端采样。当你发送一帧数据时这帧数据也会“原路返回”到自己的接收寄存器里。如果在这期间开启了接收中断MCU会以为自己收到了数据进而触发接收中断把你发出去的字节又读回来一遍。这在实时性要求高的LIN通信场景下会造成数据混乱。解决办法有几个第一种是发送期间屏蔽接收中断等发送完成后再打开第二种方法是在接收中断里判断收到的字节是不是自己发出去的如果是就丢弃但这种方式浪费CPU而且容易出错第三种方法是很多MCU支持关闭回环模式或者配置发送时接收不使能从硬件上避免自回环。工程上我建议优先用第一种简单直接逻辑清晰。这个问题的本质其实是“半双工总线的自我回环”。如果你做RS485通信也面临类似的坑只是RS485收发器需要方向控制发送和接收严格分时反而更不容易出错。5. 中断相关Bug现场现象、原因与排查方法5.1 中断里干重活程序为什么“卡死”我曾经处理过一个设备频繁死机的案例现象是设备运行一会儿就死掉看门狗也救不回来。排查了很久最后定位到串口接收中断里做了一大段协议解析其中还有循环等待某些标志位。结果在一次通信异常时ISR里等待的标志位一直不来CPU就卡在中断里出不来主循环和看门狗全部停摆。中断服务函数里干重活轻则丢数据、丢中断重则死机重启。如果你的程序出现“偶尔卡死”且卡死前正在通信先查ISR里有没有耗时的循环等待、有没有调用阻塞函数。正确的做法是ISR里只收数据、置标志位协议解析永远放在主循环。实在需要在中断里处理紧急事件也一定要加超时退出机制不能无限等下去。5.2 按键按一下却进了好几次中断另一个常见bug是按键中断触发次数不对。明明按了一次系统却像被按了好几次时而多执行一次时而少执行一次。原因前面说过机械按键的抖动没处理好。排查思路分三步第一步看硬件用示波器抓按键按下瞬间的引脚波形如果看到密密麻麻的毛刺说明RC滤波没起作用或者参数不合适。第二步看软件如果硬件已经滤波软件里也建议加一次性延时消抖比如检测到边沿后延时10到20毫秒再去读电平双保险。第三步看代码逻辑检查中断触发方式是上升沿、下降沿还是低电平触发有的按键电路常闭常常开触发沿选反了也会表现为“时灵时不灵”。我在PCB上还遇到过一种情况按键确实做了RC滤波但滤波电容的位置离按键插针太远引入的走线寄生电感把滤波效果抵消了。这种问题原理图看不出来只能在Layout阶段规避所以再次强调滤波器件要紧贴受保护的引脚。5.3 中断标志没清程序疯狂重入“中断只触发一次之后就再也不响应了”这个问题多半是中断标志没清。很多MCU外设的中断标志一旦置位就会持续向CPU申请中断。如果你的ISR没有在开头清除标志CPU会不断重入同一个中断表面上看起来像是程序卡死在这个中断里。处理方法很简单进入ISR后第一时间清除中断标志然后再做数据处理。但不同外设清除方式不一样有些是写0清除有些是读寄存器清除有些必须先读状态寄存器再清除细节去看芯片参考手册。我见过有人把标志清除放在ISR最后结果执行到清标志之前又来了一个中断标志被重新置位然后死循环重入憋了半天才找到原因。5.4 共享变量冲突优化一开Bug就来还有一类很隐蔽的bug程序不开优化一切正常一开-O2优化就莫名其妙出错。这种问题十有八九是中断和主程序共享的变量没加volatile。我解释一下原因编译器优化时会把一些变量暂时放在CPU寄存器里而不是每次使用都从内存读。如果主程序里有一个循环在等一个变量变化而这个变量是在中断里被改变的编译器优化后发现这个循环体里没有修改该变量就直接把它当成不变的常量优化掉了那你等一万年也等不到变化。解决办法很简单所有在中断和主程序之间共享的变量定义时加volatile修饰比如volatile uint8_t rx_flag。这个关键字就是在告诉编译器这变量随时可能被外部改变别给我优化掉。另外如果共享变量是多字节的比如32位变量还要考虑访问的原子性必要时在临界区里读取或写入避免出现读到一半被中断打断的“撕裂”情况。下面把常见中断问题整理成一个速查表方便排查时对照现象可能原因排查与解决程序随机死机复位ISR耗时过长、死循环等待简化ISR去掉阻塞调用增加超时机制按键触发次数不对机械抖动、触发沿错误加RC滤波和软件消抖核对触发沿中断执行一次后不再响应中断标志未清除ISR开头清标志查参考手册高优先级中断频繁打断低优先级优先级配置不合理按实时性需求重新规划抢占优先级开优化后程序出错共享变量缺volatile加volatile必要时临界区保护发送数据引发接收中断半双工总线自回环发送时屏蔽RX中断或关闭回环模式6. 从理论到PCB中断这条线怎么学才通6.1 先跑通三个实验按键、定时器、串口如果你正准备系统性地学中断我建议先别急着看一堆理论直接上手跑实验。三个实验跑通中断的底子就打牢了。第一个实验是按键外部中断。用开发板接一个按键按下点亮LED再按一次熄灭。这个实验要搞明白三个细节按键按下时电平怎么跳变、用上升沿还是下降沿触发、软件和硬件怎么配合消抖。第二个实验是定时器中断。用定时器产生周期1ms的中断在中断里累加计数让LED每500ms翻转一次。这个实验要理解定时器的预分频系数、自动重装载值怎么计算以及如何通过标志判断溢出。第三个实验是串口中断接收。PC发一串字符给单片机单片机收到后用中断方式原路返回。这个实验要理解串口接收标志位、数据寄存器读操作对标志的影响以及DMA和中断怎么配合。三个实验都跑通了你已经能处理绝大部分中断应用场景。这时候再往前走一步把每个实验对应的原理图拿出来自己在纸上重新画一遍标出每个引脚的滤波电阻、上拉电阻、去耦电容这就是从代码往硬件方向过渡的开始。6.2 从一块自己的小板开始把中断电路画到PCB上很多硬件新人对中断的理解停留在“写代码使能中断”这一步从来不关心中断引脚在PCB上是什么状态。但实际产品里中断能否可靠触发往往取决于PCB设计。我建议你尽早进入“自己画板子”这个环节。找一个小项目比如做一个带按键、OLED屏幕、旋转编码器的桌面小仪表。原理图上按键和编码器的中断引脚必须加上下拉电阻和RC滤波MCU每个电源引脚旁放0.1μF去耦电容晶振下方铺地铜皮。PCB布局时按键和编码器尽量靠近MCU中断信号线走短不要在板子边缘绕圈。打样回来后用示波器实测按键按下时的波形看看有没有抖动毛刺再用信号发生器给中断引脚灌一个高速脉冲序列看看MCU会不会漏中断。这一步做完你才能真正理解“中断的可靠性设计”是什么意思。它不是一句口号而是具体到每一颗电阻、每一厘米走线的事情。6.3 面试和笔试里中断相关的常见套路最后聊聊求职面试。无论你面硬件工程师、嵌入式工程师还是单片机工程师中断几乎是必考话题。面试官问中断其实是在考察三样东西基础概念、工程经验、系统思维。基础概念类问题比如“中断和轮询有什么区别”“中断服务函数的执行流程是什么”这类问题把本文第一节的内容吃透就能回答。工程经验类问题比如“中断里能不能调用printf”“多字节变量在中断里为什么可能出错”“如何用DMA加空闲中断接收不定长数据”需要你有实际调试经验支撑。系统思维类问题比如“给你一个实时性要求很高的电机控制系统中断优先级怎么规划”“外部中断信号传输距离长如何设计前端电路”这种题没有标准答案但考察的是你能不能把中断放到整个硬件系统里去思考。面试时给面试官讲清楚“你曾经遇到过一个中断相关的bug最后怎么定位怎么解决的”比背十道题都管用。所以平时做项目时遇到问题多记录多总结这些东西才是你真正能拿出手的硬通货。做完前面这一串实验和设计再看市面上那些从理论到PCB的硬件课程你会发现自己的学习节奏清晰多了不至于东一榔头西一棒子。我个人在这些年实践中最深的体会是中断不是一个孤立的软件知识它把CPU内核、外设寄存器、定时时序、通信协议和PCB布局全都串在了一起。前期在这些细节上考虑得越细后期调试的日子就越清闲。如果你现在刚开始学中断建议花一天时间把文章里的例子在开发板上跑一遍再拿一块旧板子对照原理图查一查中断引脚的去耦和滤波。相信我这一天的投入比刷一百遍概念都值。