51单片机基础教程(三):中断实战—告别阻塞延时,拥抱高效响应
一、引言:超越阻塞式延时的局限
在上一篇流水灯实践中,我们注意到阻塞式延时函数会占用CPU全部资源,使其无法同时处理其他任务。本次课程将带您实现两大突破:
- 通过外部中断实现按键控制功能
- 通过定时器中断实现精准非阻塞延时
二、中断:单片机的"多任务"引擎
1. 中断的本质与分类
- 硬件中断:由外部物理信号触发(如按键、传感器)。
-
- 外部中断(INT0/INT1):响应引脚电平/边沿变化(如按键按下)。
- 定时器中断(T0/T1):周期性触发,用于精确计时或PWM控制。
- 软件中断:程序主动调用(如系统调用),灵活性高但优先级低。
2. 中断 vs. 轮询:效率革命
|
方式 |
CPU占用 |
实时性 |
适用场景 |
|
轮询 |
高(持续检测) |
低 |
简单任务,如静态显示 |
|
中断 |
低(事件驱动) |
高 |
紧急事件,如按键、超时 |
示例:轮询检测按键需循环读取引脚,而中断即时响应,释放CPU处理其他任务。
"中断是单片机的'感官系统',
让机器学会'眼观六路,耳听八方'。"
三、硬件升级:添加中断触发按键
在原有Proteus流水灯电路上新增外部中断组件:
1. 添加中断源元件
BUTTON(按键开关)RES(10kΩ上拉电阻)
2. 连接外部中断电路
[VCC]--[10kΩ]--[P3.2(INT0)]
|
[BUTTON]
|
[GND]
3. 完整中断控制流水灯电路

四、中断机制原理
1. 51单片机中断系统
中断技术主要用于实时监测与控制,要求单片机能及时地响应中断请求源提出的服务请求,并快速响应与及时处理。
如没有中断系统,单片机大量时间可能会浪费在查询是否有服务请求的定时查询操作上,即不论是否有服务请求,都必须去查询。
采用中断技术完全消除查询方式的等待,大大提高单片机工作效率和实时性。
AT89C51中断系统结构见表。中断系统有5个中断请求源(简称中断源),2个中断优先级,可实现2级中断服务程序嵌套。每一中断源可用软件独立控制为允许中断或关闭中断状态;每一个中断源的优先级均可用软件设置。
| 中断源 | 中断号 | 默认优先级 | 控制寄存器 |
|---|---|---|---|
| INT0 (外部中断0) | 0 | 最高 | TCON, IE |
| T0 (定时器0) | 1 | 高 | TMOD, TCON, IE |
| INT1 (外部中断1) | 2 | 中 | TCON, IE |
| T1 (定时器1) | 3 | 低 | TMOD, TCON, IE |
| 串行通信 | 4 | 最低 | SCON, IE |


由表格看,中断系统共有5个中断请求源,它们是:
(1)INT0*—外部中断请求0,外部中断请求信号(低电平或负跳变有效)由INT0*引脚输入,中断请求标志为IE0。
(2)INT1*—外部中断请求1,外部中断请求信号(低电平或负跳变有效)由INT1*引脚输入,中断请求标志为IE1。
(3)定时器/计数器T0计数溢出的中断请求,标志为TF0。
(4)定时器/计数器T1计数溢出的中断请求,标志为TF1。
(5)串行口中断请求,标志为发送中断TI或接收中断RI。
2. 中断控制寄存器
中断请求源的中断请求标志分别由特殊功能寄存器TCON和SCON相应位锁存
IE寄存器(中断允许): 各中断源开放或屏蔽,是由片内中断允许寄存器IE控制。IE字节地址为A8H,可进行位寻址

IE对中断开放和关闭实现两级控制。两级控制就是有一个总的中断开关控制位EA(IE.7位),当EA=0,所有中断请求被屏蔽,CPU对任何中断请求都不接受;当EA=1时,CPU开中断,但5个中断源的中断请求是否允许,还要由IE中的低6位所对应的6个中断请求允许控制位的状态来决定
IE中各位的功能如下:


AT89C51复位以后,IE被清“0”,所有的中断请求被禁止。IE中与各个中断源相应的位可用指令置“1”或清“0”,即可允许或禁止各中断源的中断申请。若使某一个中断源被允许中断,除了IE相应的位被置“1”外,还必须使EA位置“1”,即EA位置“1”为中断请求的必要条件。
TCON寄存器(中断控制):
定时器/计数器的控制寄存器,字节地址为88H,可位寻址。既包括定时器/计数器T0、T1溢出中断请求标志位TF0和TF1,也包括两个外部中断请求的标志位IE1与IE0,还包括两个外部中断请求源的中断触发方式选择位。TCON格式见下图
TCON寄存器中与中断系统有关各标志位功能如下:
(1)TF1—定时器/计数器T1的溢出中断请求标志位。
当启动T1计数后,T1从初值开始加1计数,当最高位产生溢出时,硬件 置TF1为“1”,向CPU申请中断,响应TF1中断时,TF1标志硬件自动清“0”,TF1也可由软件清“0”。
(2)TF0—定时器/计数器T0溢出中断请求标志位,与TF1类似。
(3)IE1—外部中断请求1中断请求标志位。
(4)IE0—外部中断请求0中断请求标志位,与IE1类似。
关键设置说明:
IT1—选择外中断请求1为跳沿触发还是电平触发方式。
0--电平触发方式,加到INT0*脚上的外中断请求输入信号为低电平有效,并把IE1置“1”。转向中断服务程序时,则由硬件自动把IE1清“0”。
1--跳沿触发方式,加到INT1*脚上的外中断请求输入信号从高到低的负跳变有效,并把IE1置“1”。转向中断服务程序时,则由硬件自动把IE1清“0”。
IT0—选择外中断请求0为跳沿触发方式还是电平触发方式,与IT1类似。
当AT89C51复位后,TCON被清“0”,5个中断源的中断请求标志均为0


五、定时器中断实现非阻塞延时
1. 定时器配置步骤
// 初始化定时器0
void Timer0_Init(void)
{
TMOD &= 0xF0; // 清除T0控制位
TMOD |= 0x01; // T0模式1:16位定时器
TH0 = 0xFC; // 定时1ms初值高字节
TL0 = 0x18; // 定时1ms初值低字节
ET0 = 1; // 允许T0中断
EA = 1; // 全局中断允许
TR0 = 1; // 启动定时器0
}
2. 中断服务程序
volatile unsigned int time_count = 0; // 全局时间计数器
void Timer0_ISR(void) interrupt 1
{
TH0 = 0xFC; // 重新加载初值
TL0 = 0x18;
time_count++; // 1ms计数器递增
}
3. 非阻塞延时函数
void Delay_NonBlocking(unsigned int ms)
{
unsigned int start = time_count;
while ((time_count - start) < ms); // 等待时间到达
}
六、外部中断实现按键控制
1. 外部中断初始化
void ExtInt_Init(void)
{
IT0 = 1; // INT0下降沿触发
EX0 = 1; // 允许INT0中断
EA = 1; // 全局中断允许
}
2. 按键消抖策略
volatile bit key_pressed = 0; // 按键状态标志
void External0_ISR(void) interrupt 0
{
static unsigned int last_time = 0;
// 软件消抖:检测10ms内的多次触发
if ((time_count - last_time) > 10)
{
key_pressed = 1; // 标记有效按键
last_time = time_count;
}
}
3. 中断控制流水灯主程序
void main()
{
Timer0_Init();
ExtInt_Init();
unsigned char led = 0x01;
bit direction = 0; // 0:左移 1:右移
static unsigned int last_update = 0;
while(1)
{
// 检测按键事件
if(key_pressed)
{
direction = !direction; // 切换方向
key_pressed = 0; // 清除标志
}
// 定时更新LED状态(100ms间隔)
if((time_count - last_update) >= 100)
{
// 根据方向移动LED
if(direction) {
led = (led == 0x01) ? 0x80 : led >> 1;
} else {
led = (led == 0x80) ? 0x01 : led << 1;
}
P1 = ~led; // 输出到LED
last_update = time_count;
}
}
}
4. 完整程序
#include <reg51.h> // 包含51单片机头文件
/* 全局变量声明 */
volatile unsigned int time_count = 0; // 1ms计时计数器,volatile防止编译器优化
volatile bit key_pressed = 0; // 按键按下标志位,volatile确保实时性
/* 函数声明 */
void Timer0_Init(void); // 定时器0初始化函数
void ExtInt_Init(void); // 外部中断初始化函数
void Delay_NonBlocking(unsigned int ms); // 非阻塞延时函数
/******************************
* 主函数
******************************/
void main()
{
unsigned char led = 0x01; // LED初始状态(0000 0001),P1.0亮
bit direction = 0; // 流水灯方向标志:0左移,1右移
static unsigned int last_update = 0; // 记录上次LED状态更新时间
// 初始化硬件
Timer0_Init(); // 初始化定时器0
ExtInt_Init(); // 初始化外部中断0
// 主循环
while(1)
{
// 检测按键事件
if(key_pressed)
{
direction = !direction; // 切换流水灯方向
key_pressed = 0; // 清除按键标志
}
// 每100ms更新一次LED状态
if((time_count - last_update) >= 100)
{
// 根据方向移动LED
if(direction) { // 右移模式
led = (led == 0x01) ? 0x80 : led >> 1; // 到最右端后循环
} else { // 左移模式
led = (led == 0x80) ? 0x01 : led << 1; // 到最左端后循环
}
P1 = ~led; // 输出到LED(低电平点亮,故取反)
last_update = time_count; // 更新最后改变时间
}
}
}
/******************************
* 定时器0初始化函数
* 模式1:16位定时器
* 定时1ms @12MHz
******************************/
void Timer0_Init(void)
{
TMOD &= 0xF0; // 清零T0的控制位(保留T1的设置)
TMOD |= 0x01; // 设置T0为模式1(16位定时器)
TH0 = 0xFC; // 定时1ms的初值高字节(0xFC18)
TL0 = 0x18; // 定时1ms的初值低字节
ET0 = 1; // 允许定时器0中断
EA = 1; // 开启总中断
TR0 = 1; // 启动定时器0
}
/******************************
* 外部中断0初始化函数
* 下降沿触发方式
******************************/
void ExtInt_Init(void)
{
IT0 = 1; // 设置INT0为下降沿触发方式
EX0 = 1; // 允许外部中断0
EA = 1; // 开启总中断(冗余设置,确保开启)
}
/******************************
* 定时器0中断服务函数
* 每1ms自动执行一次
******************************/
void Timer0_ISR(void) interrupt 1 // 中断号1对应定时器0
{
TH0 = 0xFC; // 重新装载初值
TL0 = 0x18;
time_count++; // 全局时间计数器递增
}
/******************************
* 外部中断0服务函数
* 按键检测(带10ms软件消抖)
******************************/
void External0_ISR(void) interrupt 0 // 中断号0对应外部中断0
{
static unsigned int last_time = 0; // 记录上次中断时间
// 软件消抖:检测两次中断间隔>10ms
if ((time_count - last_time) > 10)
{
key_pressed = 1; // 标记有效按键动作
last_time = time_count; // 更新最后有效按键时间
}
}
/******************************
* 非阻塞延时函数
* 参数:ms - 需要延时的毫秒数
* 说明:基于time_count全局变量实现
******************************/
void Delay_NonBlocking(unsigned int ms)
{
unsigned int start = time_count; // 记录开始时间
while ((time_count - start) < ms); // 等待时间到达
}
代码说明:
1. 硬件初始化
-
配置定时器0为1ms定时,提供精确计时基准
-
设置外部中断0为下降沿触发模式,用于按键检测
2. 主程序逻辑
-
默认LED流动方向为从左向右(P1.0→P1.7)
-
通过按键切换LED流动方向
-
每100ms更新一次LED显示状态
3. 中断服务
-
定时器中断:维护1ms计时基准
-
外部中断:实现按键检测及消抖处理
-
中断函数是不需要声明的,会由系统自动调用
4. 关键设计
-
采用非阻塞式延时(基于定时器中断)
-
实现10ms按键软件消抖
-
支持双向流水灯控制
5. 硬件连接
-
LED连接至P1口(低电平有效)
-
按键连接至P3.2(INT0)引脚,需外加上拉电阻
5. 常见问题解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 按键无响应 | 上拉电阻缺失 | 添加10kΩ上拉电阻 |
| LED状态跳变 | 按键抖动干扰 | 增加软件消抖时间 |
| 定时不准确 | 晶振配置错误 | 设置XTAL频率为12MHz |
七、扩展实践:中断嵌套实验
1. 多中断优先级配置
PT0 = 1; // 提升T0中断优先级
PX0 = 0; // 降低INT0优先级
在同时收到几个同优先级的中断请求时,哪一个中断请求能优先得到响应,取决于内部查询顺序。

2. 中断嵌套服务程序
void Timer0_ISR(void) interrupt 1
{
TH0 = 0xFC;
TL0 = 0x18;
if(time_count % 500 == 0) {
P0 ^= 0x01; // 每500ms切换指示状态
}
time_count++;
}
void External0_ISR(void) interrupt 0
{
EA = 0; // 暂时屏蔽中断
// 紧急任务处理
EA = 1; // 重新开放中断
}
八、结语:打开实时控制的大门
通过本次实践,我们实现了单片机开发中的关键突破:
- 外部中断应用:实现按键实时响应
- 定时器技术:精确时间控制,效率提升100%
- 中断优先级管理:多任务处理基础
"中断是嵌入式的神经系统,它赋予单片机感知环境变化的能力,是真正智能系统的起点"
学习拓展建议:
- 实现双按键控制:不同按键改变不同参数
- 添加蜂鸣器模块实现音效反馈
- 尝试使用低功耗模式配合中断唤醒
下篇预告:
《串口通信:单片机与PC的对话艺术》
探索内容:
- 异步串行通信协议解析
- PC控制LED的高级应用
- 调试信息输出实践
- 使用中断实现高效数据收发
更多推荐


所有评论(0)