中断来了,程序去了哪里?|单片机动画课 09

课程插图

第 04 课让主循环不停查看按钮,这叫轮询。但有些事件发生得突然,我们希望 CPU 得到通知,暂时转去处理,再回来继续。中断就是这样一种机制。

先记住一件事:中断并没有凭空多造一个 CPU。它改变的是当前执行的先后顺序。 对于普通单核例子,执行中断服务程序时,原来的主流程暂时停在一旁。

本文以 Uno R3 的普通中断行为建立概念,不展开嵌套中断、RTOS 或多核调度。GIF 为时序示意,非芯片仿真、非实测;各阶段被放慢,不表达真实响应时间。

轮询:我主动去看

轮询像值班员定时看门口。只有执行到读取位置,程序才检查当前输入。两次检查之间发生又消失的短事件,可能没被发现。

轮询在固定检查点观察,中断由事件请求处理

中断则像门铃触发通知:硬件检测到已配置的条件,提出中断请求。当中断启用、优先级和处理条件允许时,CPU 转去执行相应的处理代码。

“提出请求”和“已经开始处理”不是同一时刻。处理器可能需要完成当前指令,或者正在处理其他事情;如果中断被屏蔽,请求的记录与后续处理还受具体外设机制约束。不能把动画中的箭头理解成零延迟。

暂时离开,然后返回

中断服务程序常缩写为 ISR。它不是主循环里按顺序调用的下一行,而是由中断机制引导进入的执行入口。

主流程暂停,执行 ISR,返回后继续原来的流程

观察橙色高亮:主流程走到一半,中断到来,执行位置转到下方。ISR 完成后,程序回到先前中断点附近继续执行,而不是重新从 setup() 开始。

为了能正确回来,硬件与编译器生成的代码会共同保存、恢复必要的执行上下文。具体保存哪些寄存器、怎样入栈,取决于架构和工具链。这一课先理解“来得了,也回得去”,不把一个框图当作所有芯片的完整内部实现。

ISR 里只记下必要的事

如果 ISR 做很久,主循环就长时间得不到运行机会,还可能影响其他事件的处理。一个常见做法是:ISR 只设置标志,让主循环稍后完成较重的工作。

ISR 设置标志,主循环随后领取任务

例如,ISR 写下“有事件待处理”,马上返回。主循环看到标志后,再决定是否更新界面、记录数据或执行其他业务逻辑。

下面是概念片段。共享标志使用 volatile;假设它由 ISR 写入,在主循环读取后还需要用适合该平台的临界区方式领取并清除,不能忽略并发访问问题。

volatile bool pending = false;
void onEvent() {
pending = true;
}

这个布尔标志只记住“至少发生过一次”。如果主循环还没领取,事件又来了三次,标志仍然只是 true。需要逐次计数时,就要使用合适的计数器或队列,并考虑原子访问、容量和溢出。

volatile 不等于线程锁,也不保证一组读改写操作原子完成。它告诉编译器这个值可能在正常执行路径之外变化,不能据此认为共享数据问题全解决了。

有了中断,就不用消抖了吗?

仍然需要考虑消抖。第 03 课的机械触点可以产生多个边沿,如果把每个边沿都配置为中断,一次按压同样可能产生多次请求。中断不会自动分辨“用户真实想按几次”。

Uno R3 使用 attachInterrupt() 时,常用外部中断引脚是 D2、D3,应通过 digitalPinToInterrupt(pin) 取得对应编号。其他板子的引脚与中断映射可能完全不同。

在普通 AVR ISR 中,millis() 依赖的计时中断不能像主循环中那样继续正常推进,也不应该靠 delay() 在 ISR 里等待。不同平台有各自的 ISR 限制,不能把某个平台的可用调用随意搬过去。

看完再猜

ISR 连续把 pending 设为 true 五次,主循环只看到一次 true,可以据此还原五次事件吗?

不能。这个变量保存的是状态,不是次数。你已经能区分中断请求、处理入口和应用任务了。下一课再把“通知来源”换成计时硬件:时间到了,它怎样产生事件?

资料:Arduino attachInterrupt、Arduino AVR 计时源码、Espressif GPIO 与中断。

发现更多好玩的开源项目

专注 Arduino、开源硬件、端侧 AI 和值得收藏的开发者工具。

课程插图

长按二维码关注「GitStar」

所有动画为教学原理示意,非芯片仿真、非硬件实测。网页发布:2026-09-21。