一、51单片机中断技术(以AT89S51为例)
(一)中断技术概述
中断技术主要用于实时监测与控制,要求单片机能及时地响应中断请求源提出的服务请求,并快速响应与及时处理。
当中断请求元发出中断请求时,如果中断请求被允许,单片机暂时中止(中途停止)当前正在执行的主程序,转到中断服务处理程序,处理中断服务请求。处理完请求后,再返回原来被中止的程序之处(断点),继续执行被中断的主程序。
中断响应和处理过程示意图
如没有中断系统,单片机大量时间可能会浪费在查询是否有服务请求的定时查询操作上,即不论是否有服务请求,都必须去查询。
采用中断技术完全消除查询方式的等待,大大提高单片机工作效率和实时性。
(二)中断技术结构
中断系统有5个中断请求源(简称中断源),2个中断优先级。可实现2级中断服务程序嵌套。每一中断源可用软件独立控制为允许中断或关闭中断状态,每一个中断源的优先级均可用软件设置。
AT89S51的中断系统结构示意图
1.中断源(中断请求源)
见中断系统结构示意图,可知中断系统共有5个中断请求源,它们是:
(1)INT0*—外部中断请求0,外部中断请求信号(低电平或负跳变有效)。由INT0引脚输入,中断请求标志为IE0。
(2)INT1—外部中断请求1,外部中断请求信号(低电平或负跳变有效)。由INT1*引脚输入,中断请求标志为IE1。
(3)定时器/计数器T0计数溢出的中断请求,标志为TF0。
(4)定时器/计数器T1计数溢出的中断请求,标志为TF1。
(5)串行口中断请求,标志为发送中断TI或接收中断RI。
2.中断请求标志寄存器
5个中断源的中断请求标志分别由特殊功能寄存器TCON和SCON的相应位锁存。
(1)TCON寄存器
定时器/计数器的控制寄存器,字节地址为88H,可位寻址。既包括定时器/计数器T0、T1溢出中断请求标志位TF0和TF1,也包括两个外部中断请求的标志位IE0与IE1,还包括两个外部中断请求源的中断触发方式选择位。
特殊功能寄存器TCON的格式
(2)SCON寄存器
串行口控制寄存器,字节地址为98H,可位寻址。SCON的低二位锁存串口的发送中断和接收中断的中断请求标志TI和RI。
特殊功能寄存器SCON的格式
(三)中断允许与中断优先级的控制
实现中断允许控制和中断优先级控制分别用中断允许寄存器IE和中断优先级寄存器IP实现。下面介绍这两个特殊功能寄存器。
1.中断允许寄存器IE
各中断源开放或屏蔽,是由片内中断允许寄存器IE控制。IE字节地址为A8H,可进行位寻址。
IE寄存器的格式
2.中断优先级寄存器IP
AT89S51片内有一个中断优先级寄存器IP,字节地址为B8H,可位寻址。只要用程序改变其内容,即可进行各中断源中断优先级设置。
IP寄存器格式
中断请求源有两个中断优先级,每一个中断请求源可由软件设置为高优先级中断或低优先级中断。也可实现两级中断嵌套。
所谓两级中断嵌套,就是AT89S51正在执行低优先级中断的服务程序时,可被高优先级中断请求所中断,待高优先级中断处理完毕后,再返回低优先级中断服务程序。
两级中断嵌套过程
3.中断时的两条基本规则
(1)低优先级可被高优先级中断,高优先级不能被低优先级中断。
(2)任何一种中断(不管是高级还是低级)一旦得到响应,不会再被它的同级中断源所中断。如果某一中断源被设置为高优先级中断,在执行该中断源的中断服务程序时,则不能被任何其他的中断源的中断请求所中断。
二、实验项目
(一)单一外中断的应用
1.实验介绍
在单片机P1口上接有8只LED。在外部中断0输入引脚(P3.2)接一只按钮开关K1。要求将外部中断0设置为电平触发。程序启动时,P1口上的8只LED全亮。每按一次按钮开关K1,使引脚接地,产生一个低电平触发的外中断请求,在中断服务程序中,让低4位的LED与高4位的LED交替闪烁5次。然后从中断返回,控制8只LED再次全亮。
2.C语言代码
#include <reg51.h>
#define uchar unsigned char
// 延时函数Delay( ),i形式参数,不能赋初值
void Delay(unsigned int i)
{
unsigned int j;
for(; i > 0; i--)
for(j = 0; j < 333; j++) // 晶振为12MHz,j的选择与晶振频率有关
{;} // 空函数
}
// 主函数
void main()
{
EA = 1; // 总中断允许
EX0 = 1; // 允许外部中断0中断
IT0 = 1; // 选择外部中断0为跳沿触发方式
while(1) // 循环
{
P1 = 0; // P1口的8只LED全亮
}
}
// 外中断0的中断服务函数
void int0() interrupt 0 using 0
{
uchar m;
EX0 = 0; // 禁止外部中断0中断
for(m = 0; m < 5; m++) // 交替闪烁5次
{
P1 = 0x0F; // 低4位LED灭,高4位LED亮
Delay(400); // 延时
P1 = 0xF0; // 高4位LED灭,低4位LED亮
Delay(400); // 延时
}
EX0 = 1; // 中断返回前,打开外部中断0中断
}
3.Proteus仿真
4.普中开发板实现
(二)两个外中断的应用
1.实验介绍
在单片机P1口上接有8只LED。在外部中断0输入引脚(P3.2)接有一只按钮开关K1。在外部中断1输入引脚(P3.3)接有一只按钮开关K2。要求K1和K2都未按下时,P1口的8只LED呈流水灯显示,仅K1(P3.2)按下再松开时,上下各4只LED交替闪烁10次,然后再回到流水灯显示。如果按下再松开K2(P3.3)时,P1口的8只LED全部闪烁10次,然后再回到流水灯显示。设置两个外中断的优先级相同。
2.C语言代码
#include <reg51.h>
#define uchar unsigned char
void Delay(unsigned int i) // 延时函数Delay( ), i为形式参数,不能赋初值
{
uchar j;
for (; i > 0; i--)
for (j = 0; j < 125; j++)
; // 空函数
}
void main() // 主函数
{
uchar display[9] = {0xff, 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f}; // 流水灯显示数据数组
unsigned int a;
for (;;)
{
EA = 1; // 总中断允许
EX0 = 1; // 允许外部中断0中断
EX1 = 1; // 允许外部中断1中断
IT0 = 1; // 选择外部中断0为跳沿触发方式
IT1 = 1; // 选择外部中断1为跳沿触发方式
IP = 0; // 两个外部中断均为低优先级
for (a = 0; a < 9; a++)
{
Delay(500); // 延时
P1 = display[a]; // 将已经定义的流水灯显示数据送到P1口
}
}
}
void int0_isr(void) interrupt 0 using 1 // 外中断0的中断服务函数
{
uchar n;
for (n = 0; n < 10; n++) // 高、低4位显示10次
{
P1 = 0x0f; // 低4位LED灭,高4位LED亮
Delay(500); // 延时
P1 = 0xf0; // 高4位LED灭,低4位LED亮
Delay(500); // 延时
}
}
void int1_isr(void) interrupt 2 using 2 // 外中断1中断服务函数
{
uchar m;
for (m = 0; m < 10; m++) // 闪烁显示10次
{
P1 = 0xff; // 全灭
Delay(500); // 延时
P1 = 0; // 全亮
Delay(500); // 延时
}
}
3.Proteus仿真
4.普中开发板实现
(三)中断嵌套的应用
1.实验介绍
中断嵌套只发生正执行一个低优先级中断,此时又有一高优先级中断产生,就会去执行高优先级中断服务程序。高优先级中断服务程序完成后,再继续执行低优先级中断程序。
设计一中断嵌套程序:要求K1和K2都未按下时,P1口8只LED呈流水灯显示,当按一下K1时,产生一个低优先级外中断0请求(跳沿触发),进入外中断0中断服务程序,上下4只LED交替闪烁。此时按一下K2时,产生一个高优先级的外中断1请求(跳沿触发),进入外中断1中断服务程序,使8只LED全部闪烁。当显示5次后,再从外中断1返回继续执行外中断0中断服务程序,即P1口控制8只LED,上、下4只LED交替闪烁。设置外中断0为低优先级,外中断1为高优先级。
2.C语言代码
#include <reg51.h>
#define uchar unsigned char
// 延时函数
void Delay(unsigned int i) // Delay function
{
unsigned int j;
for(; i > 0; i--)
for(j = 0; j < 125; j++)
; // 空函数
}
// 主函数
void main( )
{
uchar display[9]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
// 流水灯显示数据组
uchar a;
for(;;)
{
EA=1; // 总中断允许
EX0=1; // 允许外部中断0中断
EX1=1; // 允许外部中断1中断
IT0=1; // 选择外部中断0为跳沿触发方式
IT1=1; // 选择外部中断1为跳沿触发方式
PX0=0; // 外部中断0为低优先级
PX1=1; // 外部中断1为高优先级
for(a=0; a<9; a++)
{
Delay(500); // 延时
P1=display[a]; // 流水灯显示数据送到P1口驱动LED显示
}
}
}
// 外中断0中断函数
void int0_isr(void) interrupt 0 using 0
{
for(;;)
{
P1=0x0f; // 低4位LED灭,高4位LED亮
Delay(400); // 延时
P1=0xf0; // 高4位LED灭,低4位LED亮
Delay(400); // 延时
}
}
// 外中断1中断函数
void int1_isr (void) interrupt 2 using 1
{
uchar m;
for(m=0; m<5; m++) // 8位LED全亮全灭5次
{
P1=0; // 8位LED全亮
Delay(500); // 延时
P1=0xff; // 8位LED全灭
Delay(500); // 延时
}
}
3.Proteus仿真
4.普中开发板实现
(四)定时器控制
1.实验介绍
定时器控制LED灯每隔1s周期性亮灭,并用Keil仿真中的虚拟逻辑仪对LED管脚进行波形观察,测量真实的周期数,并与上次采用软件循环进行周期定时的精度进行对比,看哪一种方式更加精准。
2.C语言代码
#include<reg51.h>
char i = 100;
void main ()
{
TMOD = 0x01; // 定时器T0为方式1
TH0 = 0xee; // 设置定时器初值
TL0 = 0x00;
P1 = 0x00; // P1口8个LED点亮
EA = 1; // 总中断开
ET0 = 1; // 开T0中断
TR0 = 1; // 启动T0
while (1) // 循环等待
{
;
}
}
void timer0 () interrupt 1 // T0中断程序
{
TH0 = 0xee; // 重新赋初值
TL0 = 0x00;
i--; // 循环次数减1
if (i <= 0)
{
P1 = ~P1; // P1口按位取反
i = 100; // 重置循环次数
}
}
3.Proteus仿真
4.普中开发板实现
5.keil波形仿真
显然通过波形观察得到的周期数更精确。
(五)计数器控制
1.实验介绍
T1采用计数模式,方式1中断,计数输入引脚T1(P3.5)上外接按钮开关,作为计数信号输入。按4次按钮开关后,P1口的8只LED闪烁不停。
2.C语言代码
#include <reg51.h>
void Delay(unsigned int i) //定义延时函数Delay( ),i是形 //式参数,不能赋初值
{
unsigned int j;
for(;i>0;i--) //变量i由实际参数传入一个值 //因此i不能赋初值
for(j=0;j<125;j++)
{;} //空函数
}
void main( ) //主函数
{
TMOD=0x50; //设置定时器T1为方式1计数
TH1=0xff; //向TH1写入初值的高8位
TL1=0xfc; //向TL1写入初值的低8位
EA=1; //总中断允许
ET1=1; //定时器T1中断允许
TR1=1; //启动定时器T1
while(1); //无穷循环,等待计数中断
}
void T1_int(void) interrupt 3 //T1中断函数
{
for(;;) //无限循环
{
P1=0xff; //8位LED全灭
Delay(500); //延时500ms
P1=0; //8位LED全亮
Delay(500); //延时500ms
}
}
3.Proteus仿真
4.普中开发板实现
(六)代码改进
volatile uint8_t modeFlag = MODE1; // 假设MODE1是初始模式
void main(void) {
// 初始化LED、按键中断等相关硬件
while (1) {
switch(modeFlag) {
case MODE1:
toggleLEDWithInterval1(); // 对应模式1的LED亮灭间隔控制函数
break;
case MODE2:
toggleLEDWithInterval2(); // 对应模式2的LED亮灭间隔控制函数
break;
// 其他模式...
}
}
}
// 外部中断服务函数
void EXTI_Handler(void) {
static uint8_t lastModeFlag = MODE1; // 记录上一次的模式标志
if (/* 检测到按键按下 */) {
// 根据按键逻辑更改模式标志
if (lastModeFlag == MODE1) {
modeFlag = MODE2;
} else {
modeFlag = MODE1;
}
lastModeFlag = modeFlag;
}
// 清除中断标志位等其他中断处理操作
}
三、总结
本次单片机实验让我体验到51单片机中断系统的灵活性。通过动手实践,我不仅掌握了外部中断和定时器/计数器中断的基本代码,还学习了中断响应的过程、中断优先级的设定以及中断标志位清零的重要性。
我按照教程实例实现了基本的中断响应功能,领悟到了中断处理的核心原则:中断服务程序应尽可能快速高效地完成必要任务。即中断函数执行得时间必须短。