目录
3.2.3 采用定时计数器控制LED灯每隔1s周期性亮灭,并在keil中对LED管脚进行波形观察,测量真实周期数
一、学习目标
深入了解51单片机中断原理。掌握外部中断、定时器/计数器中断的编程方式;
了解中断响应、中断优先级、中断标志位复位等原理,掌握二级中断、中断嵌套的具体应用编程;
了解定时器/计数器的工作原理,掌握计数器初值计算方法,定时/计数中断服务的编程开发。
二、中断系统的工作原理及应用
2.1 AT89S51中断系统结构
中断系统结构如下图。中断系统有5个中断请求源(简称中断源),2个中断优先级,可实现2级中断服务程序嵌套。每一中断源可用软件独立控制为允许中断或关闭中断状态;每一个中断源的优先级均可用软件设置。
中断系统共有5个中断请求源:
(1)INT0*—外部中断请求0,外部中断请求信号(低电平或负跳变有效)由INT0*引脚输入,中断请求标志为IE0。
(2)INT1*—外部中断请求1,外部中断请求信号(低电平或负跳变有效)由INT1*引脚输入,中断请求标志为IE1。
(3)定时器/计数器T0计数溢出的中断请求,标志为TF0。
(4)定时器/计数器T1计数溢出的中断请求,标志为TF1。
(5)串行口中断请求,标志为发送中断TI或接收中断RI。
2.2 两级中断嵌套与中断优先级
2.2.1 两级中断嵌套
两级中断嵌套,就是AT89S51正在执行低优先级中断的服务程序时,可被高优先级中断请求所中断,待高优先级中断处理完毕后,再返回低优先级中断服务程序。两级中断嵌套如下图:
两级中断嵌套过程
各中断源的中断优先级关系,可归纳为下面两条基本规则:
(1)低优先级可被高优先级中断,高优先级不能被低优先级中断。
(2)任何一种中断(不管是高级还是低级)一旦得到响应,不会再被它的同级中断源所中断。如果某一中断源被设置为高优先级中断,在执行该中断源的中断服务程序时,则不能被任何其他的中断源的中断请求所中断。
2.2.2 AT89S51的中断优先级结构
中断系统有两个不可寻址的“优先级激活触发器”,其中一个指示某高优先级中断正在执行,所有后来中断均被阻止;另一个触发器指示某低优先级中断正在执行,所有同级中断都被阻止,但不阻断高优先级的中断请求。
在同时收到几个同优先级的中断请求时,哪一个中断请求能优先得到响应,取决于内部查询顺序。这相当于在同一个优先级还存在另一辅助优先级结构,其查询顺序见下表。
2.2.3 中断响应过程
首先由硬件自动生成一条长调用指令“LCALL addr16”。即程序存储区中相应的中断入口地址。例如,对于外部中断1的响应,硬件自动生成的长调用指令为: LCALL 0013H 生成LCALL指令后,紧接着就由CPU执行该指令。首先将程序计数器PC内容压入堆栈以保护断点,再将中断入口地址装入PC,使程序转向响应中断请求的中断入口地址。各中断源服务程序入口地址是固定的,如下表。 其中两个中断入口间只相隔8字节,一般情况下难以安放一个完整的中断服务程序。
因此,通常总是在中断入口地址处放置一条无条件转移指令,使程序执行转向在其他地址存放的中断服务程序入口。
2.3 中断标志位复位原理
51单片机的中断系统是其重要的组成部分,它能够使单片机响应和处理各种异步事件。中断标志位是用于指示中断请求是否发生的特殊位,当发生中断时,相应的中断标志位会被置位。
对于51单片机,中断标志位的复位通常是通过硬件自动清零或软件编程清零来实现的。下面是51单片机中断标志位复位的一般原理:
(1)、硬件自动清零:某些中断源在单片机响应中断后,硬件会自动将对应的中断标志位清零。例如,当51单片机的外部中断0(INT0)发生时,CPU响应中断后,硬件会自动将INT0的中断标志位TF0清零。
(2)、软件编程清零:程序员可以通过编写软件指令来清零中断标志位。例如,在使用定时器/计数器时,一旦定时器溢出中断发生,可以通过向定时器控制寄存器写入指令来清零中断标志位。
(3)、中断返回指令:当中断服务程序执行完毕,使用中断返回指令(如RETI)时,单片机会自动复位一些中断相关的寄存器,包括中断标志位。
在编写中断服务程序时,需要注意确保所有可能被置位的中断标志位都在适当的时候被清零,以防止错误的中断请求重复触发。
2.4 中断函数
中断服务函数的一般形式为:
函数类型 函数名(形式参数表)interrupt n using n
关键字interrupt后面的 n是中断号,对于8051单片机,n的取值为0~4,编译器从8×n+3处产生中断向量。AT89S51中断源对应的中断号和中断向量如下表。
中断号n | 中 断 源 | 中断向量(8*n+3) |
0 | 外部中断0 | 0003H |
1 | 定时/计数器T0 | 000BH |
2 | 外部中断1 | 0013H |
3 | 定时/计数器T1 | 001BH |
4 | 串行口 | 0023H |
其他值 | 保留 | 8*n+3 |
编写中断程序,应遵循以下规则:
(1)中断函数没有返回值,如果定义一个返回值,将会得到不正确结果。建议将中断函数定义为void类型,明确说明无返回值。
(2)中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。
(3)任何情况下都不能直接调用中断函数,否则会产生编译错误。因为中断函数的返回是由汇编语言指令RETI完成的。RETI指令会影响AT89S51硬件中断系统内的不可寻址的中断优先级寄存器的状态。如没有实际中断请求情况下,直接调用中断函数,也就不会执行RETI指令,其操作结果有可能产生一个致命错误。
(4)如在中断函数中再调用其他函数,则被调用的函数所用的寄存器区必须与中断函数使用的寄存器区不同。
2.5 中断系统的应用
2.5.1 单一外中断
【1】在单片机P1口上接有8只LED。在外部中断0输入引脚(P3.2)接一只按钮开关K1。要求将外部中断0设置为电平触发。程序启动时,P1口上的8只LED全亮。每按一次按钮开关K1,使引脚接地,产生一个低电平触发的外中断请求,在中断服务程序中,让低4位的LED与高4位的LED交替闪烁5次。然后从中断返回,控制8只LED再次全亮。原理电路及仿真结果见图 a.
图 a
程序代码如下:
#define uchar unsigned char
void Delay(unsigned int i)
{
unsigned int j;
for(;i > 0;i--)
for(j=0;j<333;j++)
{;}
}
void main( )
{
EA=1;
EX0=1;
IT0=1;
while(1)
{ P1=0;}
}
void int0( ) interrupt 0 using 0
{
uchar m;
EX0=0;
for(m=0;m<5;m++)
{
P1=0x0f;
Delay(400) ;
P1=0xf0;
Delay(400);
EX0=1;
}
}
实验效果:
2.5.2 两个外中断
当需要多个中断源时,只需增加相应的中断服务函数即可。【2】是处理两个外中断请求的例子。
【2】如图 b. 所示,在单片机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次,然后再回到流水灯显示。设置两个外中断的优先级相同。
程序代码如下:
#include <reg51.h>
#define uchar unsigned char
void Delay(unsigned int 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;
EX1=1;
IT0=1;
IT1=1;
IP=0;
for(a=0;a<9;a++)
{
Delay(500);
P1=display[a];
}
}
}
void int0_isr(void) interrupt 0 using 1
{
uchar n;
for(n=0;n<10;n++)
{
P1=0x0f;
Delay(500);
P1=0xf0;
Delay(500);
}
}
void int1_isr (void) interrupt 2 using 2
{
uchar m;
for(m=0;m<10;m++)
{
P1=0xff;
Delay(500);
P1=0;
Delay(500);
}
}
实验效果:
2.5.3 中断嵌套
中断嵌套只发生正执行一个低优先级中断,此时又有一高优先级中断产生,就会去执行高优先级中断服务程序。高优先级中断服务程序完成后,再继续执行低优先级中断程序。
【3】电路见上图 b. 设计一中断嵌套程序:要求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为高优先级。
程序代码如下:
#include <reg51.h>
#define uchar unsigned char
void Delay(unsigned int i)
{
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;
EX1=1;
IT0=1;
IT1=1;
PX0=0;
PX1=1;
for(a=0;a<9;a++)
{
Delay(500);
P1=display[a];
}
}
}
void int0_isr(void) interrupt 0 using 0
{
for(;;)
{
P1=0x0f;
Delay(400);
P1=0xf0;
Delay(400);
}
}
void int1_isr (void) interrupt 2 using 1
{
uchar m;
for(m=0;m<5;m++)
{
P1=0;
Delay(500);
P1=0xff;
Delay(500);
}
}
实验效果:
三、定时器/计数器的工作原理及应用
3.1 定时器/计数器的工作原理
AT89S51定时器/计数器结构如下图,定时器/计数器T0由特殊功能寄存器TH0、TL0构成,T1由特殊功能寄存器TH1、TL1构成。
T0、T1都有定时器和计数器两种工作模式,两种模式实质都是对脉冲信号进行计数,只不过计数信号来源不同。
计数器模式是对加在T0(P3.4)和T1(P3.5)两个引脚上的外部脉冲进行计数;
定时器模式是对系统时钟信号经12分频后的内部脉冲信号(机器周期)计数。由于系统时钟频率是定值,可根据计数值计算出定时时间。两个定时器/计数器属于增1计数器,即每计一个脉冲,计数器增1。
T0、T1具有4种工作方式(方式0、1、2和3)。
【方式0】
当M1、M0=00,设置为方式0,定时器/计数器等效逻辑结构如下图(以T1为例,TMOD.5、TMOD.4 = 00)。
方式0为13位计数器,由TLx(x = 0,1)的低5位和THx的高8位构成。TLx低5位溢出则向THx进位,THx计数溢出则把TCON中的溢出标志位TFx置“1”。
上图中,C/T*位控制电子开关决定2种工作模式。
(1)C/T*=0,电子开关打在上面,T1(或T0)为定时器工作模式,系统时钟12分频后的脉冲作为计数信号。
(2)C/T*=1,电子开关打在下面,T1(或T0)为计数器工作模式,对P3.5(或P3.4)引脚上的外部输入脉冲计数,当引脚上发生负跳变时,计数器加1。
GATE位状态决定定时器/计数器运行控制取决于TRx一个条件,还是取决于TRx和INTx*引脚状态两个条件。
(1) GATE=0时,A点(见图7-4)电位恒为1,B点电位仅取决于TRx状态。TRx=1,B点为高电平,控制端控制电子开关闭合,允许T1(或T0)对脉冲计数。TRx=0,B点为低电平,电子开关断开,禁止T1(或T0)计数。 (2) GATE=1时,B点电位由INTx*(x = 0,1)的电平和TRx的状态两个条件来确定。当TRx=1,且INTx* =1时,B点才为1,电子开关闭合,允许T1(或T0)计数。故这种情况下计数器是否计数是由TRx和INTx*两个条件来共同控制的。
【方式1】
当M1、M0=01时,工作于方式1,等效电路逻辑结构如下图。
方式1和方式0差别仅仅在于计数器的位数不同,方式1为16位计数器,由THx高8位和TLx低8位构成(x = 0,1),方式0则为13位计数器,有关控制状态位含义(GATE、C/T* 、TFx、TRx)与方式0相同。
【方式2】
方式0和方式1最大特点是计数溢出后,计数器为全0。因此在循环定时或循环计数应用时就存在用指令反复装入计数初值的问题,这会影响定时精度,方式2就是为解决此问题而设置的。 当M1、M0=10时,工作方式2,等效逻辑结构见图7-6(以T1为例,x=1)。 工作方式2为自动恢复初值(初值自动装入)的8位定时器/计数器,TLx(x=0,1)作为常数缓冲器,当TLx计数溢出时,在溢出标志TFx置“1”的同时,还自动将THx中的初值送至TLx,使TLx从初值开始重新计数。定时器/计数器方式2工作过程如下图。
逻辑结构框图
工作过程
方式2可省去用户软件中重装初值的指令执行时间,简化定时初值的计算方法,可相当精确地定时。
【方式3】
方式3是为增加一个附加的8位定时器/计数器而设置的,从而使AT89S51具有3个定时器/计数器。方式3只适用于T0,T1不能工作在方式3。T1方式3时相当于TR1 = 0,停止计数(此时T1可作为串口波特率产生器)。
3.2 定时器/计数器的应用
3.2.1 P1口控制8只LED每0.5s闪亮一次
在AT89S51的P1口上接有8只LED,原理电路见图 c. 采用T0方式1的定时中断方式,使P1口外接的8只LED每0.5s闪亮一次。
程序代码如下:
#include<reg51.h>
char i=100;
void main ()
{
TMOD=0x01;
TH0=0xee;
TL0=0x00;
P1=0x00;
EA=1;
ET0=1;
TR0=1;
while(1);
{
;
}
}
void timer0() interrupt 1
{
TH0=0xee;
TL0=0x00;
i--;
if(i<=0)
{
P1=~P1;
i=100;
}
}
1)设置TMOD寄存器
T0工作在方式1,应使TMOD寄存器的M1、M0=01;应设置C/T*=0,为定时器模式;对T0的运行控制仅由TR0来控制,应使相应的GATE位为0。定时器T1不使用,各相关位均设为0。所以,TMOD寄存器应初始化为0x01。
(2)计算定时器T0的计数初值
设定时时间5ms(即5 000µs),设T0计数初值为X,假设晶振的频率为11.059 2MHz,则定时时间为:
定时时间=(216−X)×12/晶振频率 则 5 000=(216 −X) ×12/11.059 2 得 X = 60 928 转换成十六进制:0xee00,其中0xee装入TH0,0x00装入TL0。
(3)设置IE寄存器
本例由于采用定时器T0中断,因此需将IE寄存器中的EA、ET0位置1。
(4)启动和停止定时器T0
将定时器控制寄存器TCON中的TR0=1,则启动定时器T0;TR0=0,则停止定时器T0定时。
实验结果:
3.2.2 计数器的应用
如上图 c. T1采用计数模式,方式1中断,计数输入引脚T1(P3.5)上外接按钮开关,作为计数信号输入。按4次按钮开关后,P1口的8只LED闪烁不停。
程序代码如下:
#include <reg51.h>
void Delay(unsigned int i)
{
unsigned int j;
for(;i>0;i--)
for(j=0;j<125;j++)
{;}
}
void main( )
{
TMOD=0x50;
TH1=0xff;
TL1=0xfc;
EA=1;
ET1=1;
TR1=1;
while(1) ;
}
void T1_int(void) interrupt 3
{
for(;;)
{
P1=0xff;
Delay(500) ;
P1=0;
Delay(500);
}
}
(1)设置TMOD寄存器
T1工作在方式1,应使TMOD的M1、M0=01;设置C/T*=1,为计数器模式;对T0运行控制仅由TR0来控制,应使GATE0=0。定时器T0不使用,各相关位均设为0。所以,TMOD寄存器应初始化为0x50。
(2)计算定时器T1的计数初值
由于每按1次按钮开关,计数1次,按4次后,P1口的8只LED闪烁不停。因此计数器初值为65 536−4=65 532,将其转换成十六进制后为0xfffc,所以,TH0=0xff,TL0=0xfc。
(3)设置IE寄存器
本例由于采用T1中断,因此需将IE寄存器的EA、ET1位置1。
(4)启动和停止定时器T1
将寄存器TCON中TR1=1,则启动T1计数;TR1=0,则停止T1计数。
实验结果:
3.2.3 采用定时计数器控制LED灯每隔1s周期性亮灭,并在keil中对LED管脚进行波形观察,测量真实周期数
实验原理图同4.2.1,
设定时时间10ms(即10000µs),设T0计数初值为X,假设晶振的频率为11.059 2MHz,则定时时间为:
定时时间=(216−X)×12/晶振频率 则 10000=(216 −X) ×12/11.059 2 得 X =56320 转换成十六进制:0xdc00,其中0xdc装入TH0,0x00装入TL0。
程序代码如下:
#include<reg51.h>
char i=100;
void main ()
{
TMOD=0x01;
TH0=0xdc;
TL0=0x00;
P1=0x00;
EA=1;
ET0=1;
TR0=1;
while(1);
{
;
}
}
void timer0() interrupt 1
{
TH0=0xdc;
TL0=0x00;
i--;
if(i<=0)
{
P1=~P1;
i=100;
}
}
实验结果:
采用定时计数器循环,在keil中对LED管脚进行波形观察。
最终波形图如下:
四、中断优化
一般来说,中断函数中要尽量避免使用执行时间较长(耗时)的代码,以避免中断服务影响到主程序代码的执行效率。但是在上面外部中断的实验中,中断函数采用了软件延时函数去控制LED亮灭的间隔周期。这是一种不好的编程。请你思考,换一种更合理的方式,不在中断函数使用延时循环,实现同样的功能。
优化代码如下:
#include <reg51.h>
sbit LED = P1^0; // 定义LED端口
// 定义模式标志位
volatile unsigned char mode = 0;
void Delay(unsigned int i) {
unsigned int j;
for(;i>0;i--)
for(j=0;j<125;j++)
; // 空循环实现延时
}
void main() {
TMOD = 0x50; // 设置定时器1为模式2
TH1 = 0xff; // 设置定时器1的初值
TL1 = 0xfc;
EA = 1; // 允许全局中断
ET1 = 1; // 允许定时器1中断
TR1 = 1; // 启动定时器1
while(1) {
switch(mode) {
case 0: // 模式0:LED常灭
LED = 0;
break;
case 1: // 模式1:LED常亮
LED = 1;
break;
case 2: // 模式2:LED闪烁
LED = ~LED; // 取反LED状态
Delay(500); // 延时500毫秒
break;
}
}
}
void T1_int(void) interrupt 3 {
static unsigned int counter = 0;
counter++;
if(counter >= 1000) { // 假设每次中断是1ms,那么1000次中断就是1秒
counter = 0;
mode = (mode + 1) % 3;//切换模式
}
}
在修改后的代码中,我们使用了一个全局变量mode来表示当前的LED模式。主循环根据mode的值来控制LED的亮灭。定时器1的中断服务程序T1_int用于切换模式,每当达到一定的时间间隔(这里是1秒),mode的值就会增加,并且对3取模,从而循环切换模式。
这样,中断服务程序就非常快,不会占用太多时间,而LED的控制逻辑则交由主循环处理。这种设计更加合理,能够保证中断的快速响应,并且不会影响到主循环的执行。
五、总结
通过本次学习,我们了解了51单片机中断与定时计数的基本概念和用法。中断允许我们处理紧急任务,而定时计数则可以帮助我们实现精确的时间控制。掌握这些知识,对于51单片机的学习和应用都是非常重要的。文章多有不足,欢迎大家批评指正。