8. 中断系统入门:外部中断触发 LED 状态翻转
引言:为什么需要中断?
想象一下,你正在厨房里专注地煮汤,突然手机响了。你会怎么做?大概率会关小火,接完电话再回来继续煮。这就是“中断”的思维模型:CPU正在执行主程序,外部事件(比如按键按下)发出“请求”,CPU暂停当前任务,去处理这个紧急事件,处理完再回来继续原来的工作。
如果没有中断,CPU就必须不停地“检查”手机有没有响——这叫轮询。轮询不仅浪费CPU资源,而且响应不及时。而中断系统让CPU能“一心多用”,实时响应外部事件,大大提高了效率和实时性。
本教程将从零开始,带你掌握8051单片机外部中断的配置与实战应用。你将学会如何用外部中断触发LED状态翻转,理解中断的工作机制,并亲手编写完整的嵌入式程序。
---
学习目标
通过本教程,你将能够:
- 理解中断的基本概念:掌握中断与轮询的区别,以及中断的优势。
- 配置外部中断:熟练设置INT0/INT1的触发方式(电平/边沿)、使能中断、设置优先级。
- 编写中断服务函数:使用Keil C51语法编写规范的中断服务程序。
- 完成硬件设计:设计按键输入电路,掌握硬件消抖与软件消抖方法。
- 进阶应用:实现双外部中断与中断嵌套实验。
---
前置知识
在学习本教程前,建议你已掌握:
- 8051单片机基本I/O操作(如点亮LED)
- C语言基础语法(变量、函数、循环)
- 延时函数的编写方法
准备好了吗?让我们开始中断系统的奇妙之旅吧!
中断系统概述与外部中断基础
准备好了吗?让我们开始中断系统的奇妙之旅吧!
什么是中断?为什么需要中断?
想象一下,你正在专心写作业,突然电话铃响了——你放下笔去接电话,接完后回来继续写作业。这个“电话铃”就是中断信号,而“接电话”就是中断服务程序。
在单片机世界里,中断是一种让CPU暂停当前任务,立即响应外部或内部紧急事件的机制。与传统的轮询方式相比,中断的优势显而易见:
- 轮询方式:CPU必须不断检查某个事件是否发生,就像每隔一秒看一眼电话有没有响。这浪费了大量CPU时间。
- 中断方式:CPU正常执行主程序,只有事件发生时才会被“打断”,效率极高。
8051外部中断的硬件资源
8051单片机有5个中断源,其中与外部事件相关的是:
- INT0:外部中断0,引脚位于P3.2
- INT1:外部中断1,引脚位于P3.3
这两个引脚可以检测两种触发方式:
- 电平触发(IT0/IT1 = 0):当引脚为低电平时触发中断
- 边沿触发(IT0/IT1 = 1):当引脚从高电平跳变到低电平时触发中断(下降沿触发)
相关寄存器位:
- IT0/IT1:位于TCON寄存器,设置触发方式
- IE0/IE1:中断请求标志位,硬件自动管理
中断优先级与嵌套
8051的中断有自然优先级顺序:
INT0 > T0 > INT1 > T1 > UART通过配置IP寄存器,可以改变优先级。高优先级中断可以打断低优先级中断,形成中断嵌套。
代码示例:配置INT0下降沿触发
#include <reg52.h> sbit LED = P1^0; // LED连接到P1.0 void main() { IT0 = 1; // 设置INT0为下降沿触发 EX0 = 1; // 使能INT0中断 EA = 1; // 开启全局中断 while(1); // 等待中断发生 } // 中断服务函数:INT0中断号为0 void INT0_ISR(void) interrupt 0 { LED = ~LED; // 翻转LED状态 }这段代码展示了中断配置的核心步骤:
- 设置触发方式(IT0=1)
- 使能外部中断(EX0=1)
- 开启全局中断(EA=1)
- 编写中断服务函数
当按键连接到P3.2并按下时,产生下降沿,触发中断,LED状态翻转。整个过程CPU只在按键按下时响应,其他时间可以执行其他任务,这就是中断的魅力!
---
关键术语回顾:中断、外部中断、INT0、INT1、电平触发、边沿触发、中断优先级
下一节我们将深入讲解中断相关寄存器的详细配置方法。
中断相关寄存器详解与配置方法
上一节我们了解了中断的基本概念,本节将深入讲解与外部中断配置密切相关的三个寄存器:IE、TCON和IP。掌握这些寄存器的每一位,是编写中断程序的基础。
IE寄存器:中断的总开关
IE(Interrupt Enable)寄存器是中断系统的“总闸门”,位于地址0xA8,可位寻址。其各位定义如下:
| 位 | 符号 | 功能描述 |
|----|------|----------|
| 7 | EA | 全局中断使能位,1=开启所有中断,0=关闭所有中断 |
| 6 | — | 保留位,通常置0 |
| 5 | ET2 | 定时器2中断使能(仅8052) |
| 4 | ES | 串口中断使能 |
| 3 | ET1 | 定时器1中断使能 |
| 2 | EX1 | 外部中断1(INT1)使能 |
| 1 | ET0 | 定时器0中断使能 |
| 0 | EX0 | 外部中断0(INT0)使能 |
关键点:EA位必须置1,否则所有中断都不会响应。配置外部中断INT0时,需要同时设置EA=1和EX0=1。
// 开启INT0中断的两种方式 // 方式1:位操作(推荐) EA = 1; // 开启全局中断 EX0 = 1; // 使能外部中断0 // 方式2:直接赋值(不推荐,可能影响其他位) IE = 0x81; // 二进制10000001,开启EA和EX0TCON寄存器:触发方式与中断标志
TCON(Timer Control)寄存器位于地址0x88,同样可位寻址。与外部中断相关的位如下:
| 位 | 符号 | 功能描述 |
|----|------|----------|
| 3 | IE0 | INT0中断请求标志位,硬件置1,边沿触发时硬件自动清零 |
| 2 | IT0 | INT0触发方式选择:0=低电平触发,1=下降沿触发 |
| 1 | IE1 | INT1中断请求标志位 |
| 0 | IT1 | INT1触发方式选择:0=低电平触发,1=下降沿触发 |
触发方式选择:边沿触发(ITx=1)更常用,因为只响应信号的下降沿,避免电平触发时信号持续低电平导致反复触发。边沿触发下,中断标志位IE0/IE1由硬件自动清零,无需软件干预。
// 配置INT0为下降沿触发 IT0 = 1; // 设置触发方式 // 若使用电平触发,则IT0 = 0;IP寄存器:优先级设定
IP(Interrupt Priority)寄存器位于地址0xB8,用于设置中断优先级。与外部中断相关的位:
| 位 | 符号 | 功能描述 |
|----|------|----------|
| 2 | PX1 | INT1优先级:1=高优先级,0=低优先级 |
| 0 | PX0 | INT0优先级:1=高优先级,0=低优先级 |
默认所有中断为低优先级(IP=0x00)。当多个中断同时发生时,高优先级中断优先响应。同级中断按自然优先级顺序:INT0 > T0 > INT1 > T1 > UART。
// 设置INT0为高优先级 PX0 = 1;完整配置示例
以下代码展示如何配置INT0为下降沿触发、高优先级:
#include <reg52.h> void main() { // 1. 配置触发方式:下降沿触发 IT0 = 1; // 2. 使能中断 EX0 = 1; // 使能INT0 EA = 1; // 开启全局中断 // 3. 设置优先级(可选) PX0 = 1; // INT0设为高优先级 while(1) { // 主循环等待中断 } }重要提醒:若使用电平触发(IT0=0),外部中断信号必须保持低电平直到CPU响应中断。此时中断标志位IE0不会自动清零,需在中断服务函数中通过软件清除(如读取引脚电平或关闭中断再开启),否则中断会反复触发。
下一节我们将把这些寄存器配置应用到实际代码中,编写完整的中断服务函数来控制LED翻转。
中断服务函数的编写规范与实战
上一节我们学习了中断寄存器的配置方法,现在将这些理论知识转化为实际代码。本节将重点讲解Keil C51中中断服务函数的编写规范,并通过一个完整的INT0外部中断控制LED翻转的示例,让你掌握中断编程的核心技巧。
中断服务函数的语法规范
在Keil C51中,中断服务函数(ISR)的声明格式如下:
void 函数名(void) interrupt 中断号 using 寄存器组其中:
- interrupt关键字:告诉编译器这是一个中断服务函数,编译器会自动生成中断向量表和现场保护代码
- 中断号:标识该函数对应哪个中断源。INT0的中断号为0,INT1的中断号为2,定时器0为1,定时器1为3,串口为4
- using关键字:可选参数,用于指定该ISR使用哪一组寄存器(8051有4组寄存器,每组R0-R7)。合理使用using可以避免中断与主程序之间的寄存器冲突
>重要原则:中断服务函数应尽量简短快速,避免在ISR中进行复杂运算或长时间延时。ISR的使命是快速响应并处理关键事件,然后立即返回主程序。
完整示例:INT0外部中断控制LED翻转
下面是一个完整的实战代码,实现按下连接到P3.2(INT0)的按键时,P1.0上的LED状态翻转。
#include <reg52.h> // 8051寄存器定义头文件 sbit LED = P1^0; // 定义LED连接到P1.0 /** * 主函数:初始化中断配置 */ void main(void) { // 1. 配置INT0为下降沿触发(IT0=1) // TCON寄存器的IT0位(bit0)控制触发方式 IT0 = 1; // 1=下降沿触发,0=低电平触发 // 2. 使能INT0中断(EX0=1) // IE寄存器的EX0位(bit0)控制INT0中断使能 EX0 = 1; // 3. 开启全局中断(EA=1) // IE寄存器的EA位(bit7)是中断总开关 EA = 1; // 4. 主程序进入无限循环,等待中断发生 while(1) { // 主程序可以执行其他任务 // 这里为空,实际应用中可添加其他代码 } } /** * INT0中断服务函数 * 中断号0对应外部中断0(INT0) * 使用寄存器组1(using 1)避免与主程序冲突 */ void INT0_ISR(void) interrupt 0 using 1 { // 翻转LED状态:LED取反 LED = ~LED; // 注意:对于边沿触发,中断标志IE0由硬件自动清零 // 无需软件干预 }代码解析与注意事项
1. 触发方式配置:IT0 = 1将INT0配置为下降沿触发。这意味着只有当P3.2引脚从高电平跳变到低电平时才会触发中断。如果使用电平触发(IT0=0),按键按下时只要保持低电平就会持续触发中断,导致LED快速闪烁。2. 中断使能:EX0 = 1打开INT0中断的独立开关,EA = 1打开全局中断总开关。这两个条件缺一不可。3. 中断服务函数:interrupt 0指定该函数为INT0的中断服务程序。using 1告诉编译器使用第1组寄存器(R0-R7),这样可以避免与主程序默认使用的第0组寄存器发生冲突。4. 中断标志清除:对于边沿触发方式,中断请求标志IE0(TCON寄存器的bit1)由硬件自动清零,无需在ISR中手动清除。但如果使用电平触发,则需要外部电路确保中断信号不会持续存在,否则中断会反复触发。5. 消抖处理:实际按键存在机械抖动,按下时可能产生多个边沿,导致一次按键触发多次中断。简单的软件消抖方法是在ISR中添加10-20ms延时后再次检测引脚电平:
void INT0_ISR(void) interrupt 0 using 1 { // 软件消抖:延时10ms后再次检测 unsigned char i; for(i = 0; i < 100; i++); // 简单延时(约10ms) if(P3^2 == 0) // 确认按键确实按下 { LED = ~LED; // 翻转LED } }>注意:在ISR中使用延时会影响系统实时性,更推荐使用硬件消抖电路(如RC滤波)或在主循环中处理消抖逻辑。
程序执行流程
通过这个示例,你已经掌握了8051外部中断的基本编程方法。下一节我们将深入探讨硬件设计中的抗干扰措施,让你的中断系统更加稳定可靠。
硬件设计与抗干扰措施
上一节我们成功实现了按键触发LED翻转,但在实际应用中,简单的按键电路往往面临抖动和噪声干扰问题。本节将深入探讨硬件设计中的抗干扰措施,让你的中断系统更加稳定可靠。
按键输入电路设计
最基本的按键电路如下图所示:按键一端连接到INT0引脚(P3.2),另一端接地;同时,INT0引脚通过一个10kΩ上拉电阻连接到VCC(+5V)。
VCC (+5V) | [R1] 10kΩ | +-----> P3.2 (INT0) | [S1] 按键 | GND上拉电阻的作用至关重要:当按键未按下时,它确保INT0引脚处于高电平状态;当按键按下时,引脚被拉低,产生下降沿触发中断。如果没有上拉电阻,引脚会处于悬浮状态,容易受噪声干扰而产生误触发。
硬件消抖与软件消抖
机械按键在按下和释放的瞬间会产生抖动,通常持续10-20ms。抖动会导致多次中断触发,使LED状态翻转不可控。解决抖动有两种常用方法:
硬件消抖:在按键电路上增加RC低通滤波器,利用电容的充放电特性平滑抖动信号。
VCC (+5V) | [R1] 10kΩ | +-----> P3.2 (INT0) | [R2] 1kΩ | +-----> [C1] 10μF -----> GND | [S1] 按键 | GND当按键按下时,电容C1通过R2放电,由于电容电压不能突变,引脚电平缓慢下降,从而滤除抖动。另一种更可靠的方案是使用施密特触发器芯片(如74HC14),它能提供滞回特性,有效抑制噪声。
软件消抖:在中断服务函数中,检测到中断后先延时10-20ms,然后再次读取引脚电平确认按键确实被按下。这种方法简单易行,但会增加中断服务函数的执行时间。
void INT0_ISR(void) interrupt 0 { // 软件消抖:延时20ms unsigned int i; for(i = 0; i < 20000; i++); // 再次确认按键是否按下 if(INT0 == 0) // INT0对应P3.2引脚 { LED = ~LED; // 翻转LED状态 } }电平触发 vs 边沿触发:硬件设计考量
在硬件设计中选择触发方式时需注意:
- 电平触发:外部信号必须保持低电平直到CPU响应中断。如果信号持续时间过短,可能无法被正确检测;如果信号持续过长,中断会反复触发。因此电平触发通常需要外部电路保证信号宽度合适。
- 边沿触发:只关心信号的跳变(下降沿),按键抖动产生的多个边沿会导致多次中断触发。因此边沿触发必须配合消抖措施使用。
对于大多数按键应用,推荐使用边沿触发+软件消抖的组合,既简单又可靠。如果系统对实时性要求较高,则建议采用硬件消抖电路。
通过合理的硬件设计和消抖措施,你的中断系统将能够稳定运行,不受外界干扰影响。下一节我们将进一步探索双外部中断与中断嵌套的进阶应用。
进阶应用:双外部中断与优先级实验
掌握了单个外部中断的控制后,我们来探索更复杂的场景:同时使用 INT0 和 INT1 控制两个 LED,并演示中断嵌套的效果。
双中断配置与优先级设置
本实验的目标是:
- 使用 INT0(P3.2)控制 LED1(P1^0),设置为高优先级
- 使用 INT1(P3.3)控制 LED2(P1^1),设置为低优先级
- 通过延时模拟长中断服务,观察中断嵌套现象
配置的关键在于 IP 寄存器:设置 PX0=1 使 INT0 为高优先级,PX1=0 保持 INT1 为低优先级。
中断嵌套实验代码
#include <reg52.h> sbit LED1 = P1^0; // INT0 控制 sbit LED2 = P1^1; // INT1 控制 // 延时函数(约500ms @12MHz) void delay(void) { unsigned int i, j; for(i = 0; i < 100; i++) for(j = 0; j < 500; j++); } // INT0 中断服务(高优先级) void INT0_ISR(void) interrupt 0 using 1 { LED1 = ~LED1; // 翻转 LED1 delay(); // 模拟长中断服务 } // INT1 中断服务(低优先级) void INT1_ISR(void) interrupt 2 using 2 { LED2 = ~LED2; // 翻转 LED2 delay(); // 模拟长中断服务 } void main(void) { // 配置触发方式:下降沿触发 IT0 = 1; // INT0 边沿触发 IT1 = 1; // INT1 边沿触发 // 设置优先级:INT0 高优先级,INT1 低优先级 PX0 = 1; // INT0 高优先级 PX1 = 0; // INT1 低优先级 // 使能中断 EX0 = 1; // 使能 INT0 EX1 = 1; // 使能 INT1 EA = 1; // 全局中断使能 while(1); // 等待中断 }实验现象分析
实验步骤:
- 先按下 INT1 按键(P3.3),LED2 翻转,进入 INT1 中断服务
- 在 INT1 的 delay() 执行期间,按下 INT0 按键(P3.2)
- 观察现象:INT0 会打断 INT1,LED1 立即翻转
- INT0 服务完成后,恢复 INT1 执行,LED2 完成翻转
中断嵌套的注意事项
- 堆栈深度限制:8051 堆栈最多 128 字节,嵌套过深可能导致堆栈溢出
- 寄存器组切换:使用
using关键字分配不同寄存器组(如 INT0 用using 1,INT1 用using 2),减少现场保护开销 - 中断服务函数要简短:避免在中断中执行复杂运算或长延时,否则会影响系统实时性
通过这个实验,你已掌握了中断嵌套的核心概念。下一节我们将进一步探讨实际应用中的优化策略。
总结
本教程从零开始,带你掌握了8051外部中断的核心知识:中断概念、寄存器配置(IE、TCON、IP)、中断服务函数编写,以及硬件设计与消抖技巧。通过“外部中断触发LED翻转”的实战,你已能独立实现单中断和双中断嵌套应用。
进阶建议:
- 尝试用外部中断驱动步进电机或编码器。
- 学习定时器中断与外部中断协同工作。
- 探索中断在RTOS(如uC/OS-II)中的应用。
中断是嵌入式系统的“心脏”,掌握它将为你打开实时编程的大门。继续动手实验,让代码在硬件上“活”起来!
