1. 从“单线程”到“多任务”为什么单片机需要中断如果你刚开始玩单片机比如经典的AT89C51你写的程序大概率是“顺序执行”的。主函数里一个while(1)大循环里面依次检查按键、刷新显示、处理数据……程序就像一条单向行驶的马路车CPU必须从头跑到尾再回到起点周而复始。这时候一个很现实的问题就来了如果我在循环里正忙着用延时函数让一个LED闪烁这时候有个紧急的按键需要立刻响应怎么办答案是等。等这个漫长的闪烁循环结束CPU才能“看到”那个按键。这显然不符合我们对“智能设备”的期待。现实世界的事件是随机、异步发生的我们的单片机程序需要一种机制能够随时打断当前正在执行的“常规任务”去处理那些更紧急、更重要的“突发事件”处理完后还能无缝地回到原来的任务继续执行。这套机制就是中断系统。你可以把它想象成你在看书主程序这时门铃响了中断请求。你会先放下书保存当前阅读位置去开门处理访客执行中断服务程序等访客离开后你再回来接着刚才的段落继续看书恢复现场继续执行主程序。中断系统赋予了单片机类似人类“响应突发事件”的能力是单片机从“顺序执行器”迈向“实时控制系统”的关键一步。对于AT89C51这款经典的8位单片机其中断系统虽然不如现代ARM Cortex-M内核那样复杂和强大但其设计思想一脉相承是理解所有中断概念的绝佳起点。掌握了51的中断再去学习STM32、ESP32等更高级单片机的中断就会有一种“万变不离其宗”的通透感。今天我们就来彻底拆解AT89C51的中断系统并通过一个综合实验让你亲手体验中断如何让单片机的程序“活”起来。2. AT89C51中断系统架构全解析AT89C51的中断系统结构清晰是学习中断原理的理想模型。它总共管理着5个中断源你可以理解为5个不同功能的“门铃”。当中断发生时CPU如何知道是哪个“门铃”在响又该去哪里执行对应的“开门”程序呢这背后有一套完整的硬件逻辑。2.1 五大中断源谁可以打断CPUAT89C51的5个中断源分为两大类外部中断和内部中断。外部中断0 (INT0)由引脚P3.2引入。可以是低电平触发也可以是下降沿触发从高电平跳到低电平的那一瞬间。常用于紧急按键、限位开关等需要立即响应的外部事件。外部中断1 (INT1)由引脚P3.3引入。触发方式同INT0。定时器/计数器0溢出中断 (TF0)当定时器/计数器0T0计满溢出时由内部硬件自动置位一个标志位TF0从而产生中断请求。这是实现精准定时、计数的核心。定时器/计数器1溢出中断 (TF1)功能同T0对应定时器/计数器1T1。串行口中断 (RI/TI)这是一个比较特殊的中断源。当串行口接收完一帧数据时硬件会置位RI接收中断标志当串行口发送完一帧数据时硬件会置位TI发送中断标志。这两个标志位任何一个被置位都会触发同一个串行口中断。所以在串口中断服务程序里我们必须先检查是RI还是TI引起的中断再进行相应处理。这五个中断源每个都对应一个固定的“中断向量地址”。当CPU响应某个中断时它会自动跳转到这个固定的地址去执行程序。这个地址通常只存放一条跳转指令指向你编写的中断服务函数。AT89C51的中断向量地址是固定的外部中断00003H定时器/计数器0000BH外部中断10013H定时器/计数器1001BH串行口中断0023H注意这些地址非常靠前紧挨着复位地址0000H。因此我们通常在0000H放一条跳转到主程序的指令LJMP MAIN然后在各个中断向量地址处存放跳转到对应中断服务程序的指令例如在0003H放LJMP INT0_ISR。2.2 中断相关的特殊功能寄存器SFR单片机内部有很多特殊功能寄存器像一个个开关和控制台用来配置和管理中断。对于AT89C51你需要重点掌握以下四个1. 中断允许寄存器 (IE - Interrupt Enable)这是一个总开关和分开关的结合体。地址A8H可位寻址。EA | - | - | ES | ET1 | EX1 | ET0 | EX0 -----|------|------|------|------|------|------|------ 位7 | 6 | 5 | 4 | 3 | 2 | 1 | 0EA(位7)中断总开关。EA1CPU开放所有中断EA0CPU屏蔽所有中断无论其他位如何设置。这是第一道闸门。ES(位4)串行口中断允许位。ES1允许串口中断。ET1(位3)定时器/计数器1中断允许位。EX1(位2)外部中断1允许位。ET0(位1)定时器/计数器0中断允许位。EX0(位0)外部中断0允许位。 想要某个中断源能工作必须同时打开总开关EA和对应的分开关。2. 定时器/计数器控制寄存器 (TCON)这个寄存器低4位与外部中断相关高4位与定时器相关。地址88H可位寻址。TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 ----|-----|-----|-----|-----|-----|-----|---- 位7 | 6 | 5 | 4 | 3 | 2 | 1 | 0IT0/IT1(位0/位2)外部中断触发方式控制位。这是关键ITx 0低电平触发。只要INTx引脚为低电平就持续产生中断请求。这种方式要小心如果中断服务程序执行期间低电平仍未消失退出中断后会立刻再次进入造成“中断重入”的混乱。ITx 1下降沿触发。只在INTx引脚电平由高变低的那一瞬间锁存一个中断请求。即使此后引脚一直保持低电平也只产生一次中断。这是最常用、最可靠的方式。IEx(位1/位3)外部中断请求标志位。当对应的外部中断引脚上出现了有效的触发信号低电平或下降沿时由硬件自动置1。CPU响应中断后硬件会自动清零对于边沿触发模式。在电平触发模式下需要引脚恢复高电平后该标志位才会自动清零。3. 中断优先级寄存器 (IP - Interrupt Priority)AT89C51只有两个优先级高优先级和低优先级。通过IP寄存器设置。地址B8H可位寻址。- | - | - | PS | PT1 | PX1 | PT0 | PX0 -----|------|------|------|------|------|------|------ 位7 | 6 | 5 | 4 | 3 | 2 | 1 | 0PS串行口中断优先级设定位。PT1定时器1中断优先级设定位。PX1外部中断1优先级设定位。PT0定时器0中断优先级设定位。PX0外部中断0优先级设定位。 某位设为1则该中断源为高优先级设为0则为低优先级。优先级规则不同优先级之间高优先级中断可以打断正在执行的低优先级中断服务程序实现“嵌套”。低优先级中断不能打断高优先级的中断服务程序。相同优先级之间如果多个中断同时发生CPU会按照一个固定的“查询顺序”来响应。这个顺序是从高到低INT0-TF0-INT1-TF1-RI/TI。这个顺序是硬件固定的无法更改。4. 串行口控制寄存器 (SCON)它的两位RI和TI是串行口中断的标志位。地址98H可位寻址。RI(位0)接收中断标志。硬件在接收完一帧数据后置1必须由软件清零。TI(位1)发送中断标志。硬件在发送完一帧数据后置1必须由软件清零。关键点RI和TI不会自动清零如果在中断服务程序中不清零它们退出中断后由于标志位依然为1会立刻再次触发串口中断导致程序“死”在中断里。这是串口中断编程中最常见的坑。2.3 中断响应与处理的完整流程当一个中断事件发生到CPU执行完对应的代码整个过程是硬件和软件协同完成的中断请求中断源事件发生如按键按下产生下降沿对应的中断请求标志位如IE0、TF0等被硬件置1。中断响应条件CPU在每个机器周期的S5P2时刻可以理解为CPU的“心跳”时刻采样这些标志位。如果发现某个中断标志为1且满足以下所有条件则在下一个机器周期响应中断当前指令已执行完毕。当前没有正在处理同级或更高优先级的中断。中断总开关EA1且该中断的分开关已打开。硬件响应CPU一旦决定响应会做三件重要的事保护断点将当前程序计数器PC的值压入堆栈以便中断结束后能回来。清除中断标志对于TF0、TF1和边沿触发模式下的IEx硬件会自动清零。对于RI、TI和电平触发模式下的IEx不会自动清零跳转根据中断源将PC值装入对应的中断向量地址如0003H。执行中断服务程序 (ISR)CPU开始执行你在中断向量处安排的程序。在C语言中我们通过中断号声明函数编译器会帮我们处理跳转和返回。中断返回ISR最后一条指令必须是RETI中断返回指令。CPU执行RETI后会做两件事从堆栈弹出断点地址送回PC。通知中断系统本次中断处理已完成对于可嵌套的中断这一步很重要。恢复执行CPU从原先被打断的指令处继续执行主程序。理解这个流程尤其是哪些标志位硬件清零、哪些需要软件清零是写出稳定中断程序的基础。3. 实战中断与查询的对比实验——按键控制LED理论说得再多不如动手一试。我们设计一个经典对比实验用同一个按键控制同一个LED的亮灭。一组程序用“查询法”实现另一组用“中断法”实现。通过这个实验你能直观感受到中断带来的程序结构优化和实时性提升。3.1 实验硬件与电路设计核心AT89C51单片机最小系统包含晶振、复位电路。输入一个轻触按键一端接地另一端接单片机P3.2引脚即INT0和P1.0引脚用于查询法同时通过一个10KΩ上拉电阻接到VCC确保引脚默认高电平。输出一个LED阳极通过一个220Ω限流电阻接VCC阴极接单片机P2.0引脚。“干扰源”为了放大查询法的弊端我们在主循环中加入一个明显的“忙”操作比如一个让另一个LED接P2.1缓慢闪烁的延时循环。电路原理很简单关键在于编程逻辑的差异。3.2 方案一查询法实现查询法的思路是CPU不断地、主动地去“询问”按键引脚的状态。#include reg51.h sbit KEY P1^0; // 查询按键接P1.0 sbit LED P2^0; // 受控LED sbit BUSY_LED P2^1; // 用于模拟繁忙工作的LED void delay_ms(unsigned int ms) { unsigned int i, j; for(i0; ims; i) for(j0; j123; j); // 粗略的1ms延时实际需校准 } void main() { LED 1; // LED初始熄灭共阳接法 BUSY_LED 1; while(1) { // 模拟一个耗时任务让BUSY_LED闪烁 BUSY_LED ~BUSY_LED; delay_ms(500); // 延时500ms模拟CPU被占用 // 查询按键状态 if(KEY 0) { // 按键按下为低电平 delay_ms(10); // 简单消抖 if(KEY 0) { LED ~LED; // 翻转LED状态 while(!KEY); // 等待按键释放防止连续触发 } } } }存在的问题实时性差CPU必须执行完delay_ms(500)这个“繁忙任务”后才能轮到if(KEY 0)这条查询语句。如果在这500ms内按下按键程序根本无法察觉必须等“忙”完了才处理响应延迟可能高达半秒。CPU效率低即使没有按键动作CPU也在不停地执行if判断做了大量无用功。程序结构耦合按键检测逻辑和主循环的“繁忙任务”紧密耦合在一起任何一方的改动都可能影响另一方。3.3 方案二中断法实现中断法的思路是CPU只管处理它的主任务让BUSY_LED闪烁。按键的状态变化由硬件自动监测一旦按下硬件主动通知CPUCPU暂停主任务立刻去处理按键处理完再回来。#include reg51.h sbit LED P2^0; // 受控LED sbit BUSY_LED P2^1; // 用于模拟繁忙工作的LED void delay_ms(unsigned int ms) { unsigned int i, j; for(i0; ims; i) for(j0; j123; j); } // 外部中断0服务程序 void int0_isr(void) interrupt 0 { // interrupt 0 对应INT0 delay_ms(10); // 中断服务程序中慎用长延时这里仅为消抖演示。 if(INT0 0) { // 再次确认P3.2脚为低电平按键仍按下 LED ~LED; // 翻转LED } // 注意对于边沿触发IE0标志位已由硬件自动清零。 // 无需软件清除IE0。 } void main() { LED 1; BUSY_LED 1; // 1. 配置中断触发方式下降沿触发 IT0 1; // 设置INT0为下降沿触发 // 2. 打开中断开关 EX0 1; // 打开外部中断0允许 EA 1; // 打开总中断允许 while(1) { // 主程序专心“忙”自己的事 BUSY_LED ~BUSY_LED; delay_ms(500); // 长延时不再影响按键响应 } }中断法的优势极高的实时性无论主程序在delay_ms(500)的哪个时刻只要按键按下产生下降沿CPU会立即暂停延时跳转到int0_isr函数执行LED翻转。响应时间在微秒级。CPU效率高主程序不用再轮询按键可以专心处理主要任务。中断只在事件发生时才占用CPU资源。程序结构清晰按键处理逻辑被封装在独立的中断服务函数中与主程序完全解耦。主程序变得简洁功能模块化程度高。对比实验现象 烧录两个程序后你反复按下按键会发现查询法LED的状态变化明显“迟钝”有时按了没反应因为正好在500ms延时内必须等BUSY_LED闪烁一下后按键才生效。中断法LED的响应极其迅速几乎在按下按键的瞬间就完成状态翻转完全不受BUSY_LED缓慢闪烁的影响。这个实验直观地展示了中断在解决“实时响应”问题上的巨大威力。4. 中断服务程序ISR编写核心要点与避坑指南写中断服务程序不是简单地把代码放进去就行。它运行在一个特殊的环境下有很多“规矩”和“坑”。4.1 ISR编写的基本原则短小精悍ISR应该像消防员一样处理完紧急火情就立刻撤离。避免在ISR内进行复杂的运算、调用多层函数、使用长延时如delay_ms(1000)。长时间占用CPU会导致其他中断无法及时响应甚至影响主程序的正常运行。如果确实有大量工作要做通常的做法是在ISR中只设置一个标志位flag然后由主循环根据这个标志位去处理具体任务。bit key_pressed_flag 0; // 定义一个位变量作为标志 void int0_isr(void) interrupt 0 { key_pressed_flag 1; // 仅置位标志 // 硬件自动清除IE0 } void main() { // ... 初始化中断 while(1) { if(key_pressed_flag) { key_pressed_flag 0; // 清除标志 // 在这里执行复杂的按键处理逻辑如菜单切换、数值调整等 do_something_complex(); } // ... 主循环其他任务 } }避免重入在C51中如果中断服务程序A在执行过程中又被更高级别的中断B打断而中断B也调用了同一个函数就可能发生“重入”导致数据错乱。对于会被中断和主程序同时调用的函数应声明为“可重入函数”使用reentrant关键字或者通过开关中断EA0和EA1进行保护。现场保护与恢复编译器在进入和退出ISR时会自动帮我们保存和恢复一些关键寄存器如ACC, B, PSW, DPTR等。但如果你在ISR中使用了其他一些寄存器比如R0-R7且主程序也在用编译器可能不会自动处理。在汇编编程中这需要程序员手动用PUSH和POP指令完成。在C语言中选择正确的“寄存器组”可以优雅地解决这个问题。4.2 关键技巧寄存器组Register Bank的妙用AT89C51内部RAM中有4组通用寄存器R0-R7称为寄存器组0-3。通过修改程序状态字PSW中的RS1和RS0位可以切换当前使用的寄存器组。PSW寄存器 CY | AC | F0 | RS1 | RS0 | OV | - | P 位7| 6 | 5 | 4 | 3 | 2 |1 |0RS1 RS0 00 使用第0组寄存器 (地址 00H-07H)RS1 RS0 01 使用第1组寄存器 (地址 08H-0FH)RS1 RS0 10 使用第2组寄存器 (地址 10H-17H)RS1 RS0 11 使用第3组寄存器 (地址 18H-1FH)应用技巧我们可以让主程序使用一组寄存器例如第0组而让中断服务程序使用另一组寄存器例如第1组。这样进入中断时就不需要手动保存R0-R7了因为用的根本不是同一组物理寄存器实现了硬件级的“现场保护”。在Keil C51中可以在函数声明后使用using n关键字来指定该函数使用的寄存器组。void my_isr(void) interrupt 2 using 1 { // 使用第1组寄存器 // 在这里使用R0-R7不会影响主程序使用第0组的R0-R7 }注意using关键字不能用于普通函数只能用于中断函数。同时不同优先级的中断最好分配不同的寄存器组以防止高优先级中断打断低优先级中断时造成寄存器数据混乱。4.3 常见“坑”与调试心得中断不触发检查总开关和分开关确认EA和对应的EXx、ETx、ES是否已置1。这是最常被遗忘的一步。检查触发方式对于外部中断确认ITx设置是否符合你的硬件电路电平触发还是边沿触发。用示波器或逻辑分析仪看引脚波形最直接。检查硬件连接按键是否接触不良上拉电阻是否接好引脚是否配置正确检查中断标志位对于需要软件清零的标志RI,TI是否在ISR中清除了不清除会导致中断只发生一次。中断响应异常只响应一次或多次电平触发模式的“持续中断”如果设置ITx0低电平触发并且按键按下后一直保持低电平那么CPU会在退出中断后立即再次检测到低电平从而再次进入中断陷入死循环。解决方案改用下降沿触发(ITx1)或者在ISR中等待按键释放但不推荐在ISR中等待。消抖处理不当机械按键的抖动会产生多个边沿可能误触发多次中断。需要在ISR开始进行软件消抖短延时再判断或者使用硬件RC滤波电路。程序跑飞或死机堆栈溢出中断嵌套、函数调用层次太深或者ISR内局部变量过大可能导致堆栈增长超出RAM范围覆盖其他数据。51的堆栈空间很小通常只有128字节需特别留意。未实现的中断向量如果某个中断被打开但你没有编写对应的ISR或者没有在中断向量地址处放置跳转指令当该中断发生时PC会跳转到一个随机地址执行导致程序跑飞。安全的做法是即使不用某个中断也在其向量地址处放一条RETI指令或跳转到错误处理程序的指令。调试建议使用IO口模拟示波器在ISR开始和结束的地方让一个空闲的IO口输出高电平和低电平。用示波器观察这个引脚可以非常直观地看到ISR的进入、执行时间和频率。这是调试中断最有效的方法之一。简化ISR在调试阶段先将ISR精简到只做一个最简单的操作如翻转一个测试引脚确认中断机制本身工作正常后再逐步添加功能逻辑。5. 综合应用实验中断与定时器联动的精准时钟掌握了基本的外部中断后我们再来挑战一个更综合的应用利用定时器中断制作一个精准的数码管时钟并且用外部中断来实现时间的调整。这个实验融合了定时器中断、外部中断、数码管动态扫描等多个核心知识点。5.1 系统设计与工作原理目标制作一个显示时、分、秒的时钟精度要求高。要求能用两个按键调整时间一个按键INT0进入/退出调整模式另一个按键INT1在调整模式下对闪烁的位进行加一操作。核心方案定时器中断提供“心跳”使用AT89C51的定时器0工作在模式116位定时模式计算出产生50ms中断一次的初值。每中断20次就是1秒。通过中断来维护秒、分、时变量保证了计时的精准性不受主程序其他任务的影响。外部中断实现人机交互INT0模式选择键下降沿触发。按下后在正常显示模式和时分秒调整模式间切换。在调整模式下当前调整的位时、分、秒会闪烁。INT1加键下降沿触发。仅在调整模式下有效对闪烁的位进行加一操作并处理进位如59秒加1变为0分同时分钟加1。主程序负责显示主循环的唯一任务就是根据当前的时钟变量和模式状态动态扫描刷新数码管。显示逻辑和计时逻辑通过中断解耦。5.2 代码实现与关键逻辑分析以下是精简后的核心代码框架突出了中断相关的逻辑#include reg51.h #define uchar unsigned char #define uint unsigned int // 假设共阴数码管段选接P0口位选接P2口低3位 uchar code smg_duan[] {...}; // 0-9的段码表 uchar smg_wei[] {0xfe, 0xfd, 0xfb}; // 位选控制对应秒个位、秒十位、分个位... uchar time_buf[6]; // 存储时分秒每位数字的缓冲区 uchar second0, minute0, hour12; // 时间变量 uchar t50ms_cnt0; // 50ms计数器 bit adjust_mode 0; // 0-正常显示1-调整模式 uchar adjust_pos 0; // 调整位置0-秒个位1-秒十位...5-时十位 // 定时器0初始化50ms中断一次 (假设晶振12MHz) void timer0_init() { TMOD 0xF0; // 高4位不变低4位清零 TMOD | 0x01; // 设置T0为模式116位定时器 // 计算初值 2^16 - 50000 15536 0x3CB0 TH0 0x3C; TL0 0xB0; ET0 1; // 允许T0中断 TR0 1; // 启动T0 } // 外部中断初始化 void ext_int_init() { IT0 1; // INT0下降沿触发 IT1 1; // INT1下降沿触发 EX0 1; // 允许INT0中断 EX1 1; // 允许INT1中断 } // 定时器0中断服务程序 - 系统的“心脏” void timer0_isr() interrupt 1 { // 重装初值保证下次中断仍是50ms TH0 0x3C; TL0 0xB0; t50ms_cnt; if(t50ms_cnt 20) { // 20 * 50ms 1000ms 1s t50ms_cnt 0; second; if(second 60) { second 0; minute; if(minute 60) { minute 0; hour; if(hour 24) { hour 0; } } } } } // 外部中断0服务程序 - 模式切换 void int0_isr() interrupt 0 { // 简单消抖实际项目应用更可靠的消抖方法 delay_ms(10); if(INT0 0) { adjust_mode !adjust_mode; // 切换模式 if(adjust_mode) { adjust_pos 0; // 进入调整模式默认从秒个位开始调 TR0 0; // 暂停计时这是调整时间时的关键操作 } else { TR0 1; // 退出调整模式恢复计时 } while(!INT0); // 等待按键释放防止长按连续触发 } } // 外部中断1服务程序 - 调整加一 void int1_isr() interrupt 2 { if(!adjust_mode) return; // 非调整模式直接退出 delay_ms(10); if(INT1 0) { switch(adjust_pos) { case 0: // 秒个位 second (second % 10 1) % 10 (second / 10) * 10; if(second % 10 0) second 10; // 处理9-0的进位 break; case 1: // 秒十位 second (second 10) % 60; break; case 2: // 分个位 minute (minute % 10 1) % 10 (minute / 10) * 10; if(minute % 10 0) minute 10; break; // ... 类似地处理分十位、时个位、时十位 } // 可选调整后让被调整位闪烁一下作为反馈 while(!INT1); } } // 主函数 void main() { EA 1; // 开总中断 timer0_init(); ext_int_init(); while(1) { // 1. 将时分秒变量分解成6个单独数字存入time_buf time_buf[0] second % 10; time_buf[1] second / 10; time_buf[2] minute % 10; time_buf[3] minute / 10; time_buf[4] hour % 10; time_buf[5] hour / 10; // 2. 根据模式处理显示缓冲如让adjust_pos对应的位闪烁 // 3. 动态扫描数码管显示time_buf内容 display_scan(time_buf, adjust_mode, adjust_pos); } }5.3 实验中的深度思考与优化定时精度问题我们采用中断重装初值的方式。在12MHz晶振下机器周期为1μs定时器计一次数需要1μs。要定时50ms需计数50000次。但进入中断、执行重装指令本身也需要时间这会导致微小的误差。对于时钟这种长期运行的程序误差会累积。更精准的做法是使用自动重载的模式28位自动重装但模式2最大定时只有256μs不适合长定时。可以将其作为“定时器节拍”在中断中用一个软件计数器来实现更长定时。补偿法在中断服务程序开始读取当前TL0/TH0的值计算出从定时器溢出到真正进入ISR的延迟时间并在下次重装初值时进行补偿。这对编程要求较高。使用更高精度的晶振如11.0592MHz或22.1184MHz其定时器初值计算更规整。调整模式下的计时暂停代码中TR00暂停了定时器这是调整时间的常见做法。但更友好的设计是在调整模式下定时器继续运行保证时钟基准不停但显示的数字“冻结”在进入调整模式的那一刻直到用户完成调整并退出再将累积的时间差一次性加到当前时间上。这避免了调整期间的时间丢失。中断冲突与优先级在这个实验中定时器中断每50ms一次和外部中断用户按键可能同时发生。默认情况下它们处于同一优先级都是低级按照查询顺序INT0先于TF0。这意味着如果刚好在定时器溢出时按下INT0按键会先响应按键中断。这通常是可以接受的。但如果你的系统对定时精度要求极高不能让任何中断打断定时器中断则可以将定时器中断设为高优先级PT01。功耗考虑主循环中的display_scan是一个死循环会持续刷新数码管消耗较多电流。在实际的低功耗应用中可以在没有按键操作一段时间后关闭数码管显示并让单片机进入空闲Idle模式或掉电Power Down模式由定时器中断或外部中断来唤醒它从而大幅降低系统功耗。通过这个综合实验你将中断从孤立的知识点串联成了一个解决实际问题的完整方案。你会发现中断不再是枯燥的寄存器配置而是构建高效、实时、模块化单片机程序不可或缺的骨架。从按键消抖到计时基准从模式切换到显示驱动中断的身影无处不在。理解并驾驭它你的单片机编程能力便会上一个新的台阶。