单片机基础知识 06 (中断-2)

一. 定时器中断概念

51单片机的内部有两个16位可编程的定时器/计数器,即定时器T0和定时器T1。
52单片机内部多一个T2定时器/计数器。

定时器/计数器的实质是加1计数器(16位),由高8位和低8位两个寄存器组成。
TMOD是定时器/计数器的工作方式寄存器,确定工作方式和功能;
TCON控制寄存器,控制T0、T1的启动和停止及设置溢出标志。

加1计数器输入的计数脉冲有两个来源,一个由系统的时钟振荡器输出脉冲经12分频后送来;另一个是T0或T1引脚输入的外部脉冲源,每来一个脉冲计数器加1,当加到计数器全为1时,再输入一个脉冲就使计数器回零,且计数器的溢出使TCON寄存器中TF0或TF1置1,向CPU发出中断请求(定时器/计数器中断允许时)。

因此,溢出时计数器的值减去计数初值才是加1计数器的计数值。

设置为定时器模式时,加1计数器是对内部机器周期计数(1个机器周期等于12个振荡周期,即计数频率为晶振频率的1/12)。计数值N乘以机器周期Tcy就是定时时间t。

设置为计数器模式时,外部事件计数脉冲由T0或T1引脚输入到计数器。在每个机器周期的S5P2期间采样T0,T1引脚电平。当某周期采样到一高电平输入,而下一周期又采样到一低电平时,则计数器加1,更新的计数值在下一机器周期的S3P1期间装入计数器。由于检测一个从1到0的下降沿需要2个机器周期,因此要求被采样的电平至少要维持一个周期。当晶振频率为12MHz时,最高计数频率不超过1/2MHz,即计数脉冲的周期要大于2us。

二. 定时器/计数器工作方式寄存器TMOD

TMOD在特殊功能寄存器中,字节地址为89H,不能位寻址。
TMOD用来确定定时器的工作方式及功能选择,单片机复位时TMOD全部被清0。
在这里插入图片描述
在这里插入图片描述

三. 定时器/计数器控制寄存器TCON

TCON在在特殊功能寄存器中,字节地址为88H,可位寻址。
TCON用来控制定时器的启,停,标志定时器溢出和中断情况。单片机复位时TMOD全部被清0。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由上可知,每个定时器都有4种工作方式,可通过设置TMOD寄存器中的M1M0位来进行工作方式选择。

四. 计算定时器的初值

定时器一旦启动,它便在原来的数值上开始加1计数,若在程序开始时,我们没有设置TH0和TL0,它们的默认值都是0,假设时钟频率为12MHz,12个时钟周期为1个机器周期,那么此时机器周期就是1us,计满TH0和TL0就需要2^16-1个数,再来一个脉冲计数器溢出,随机向CPU申请中断。因此溢出一次共需65536us,约等于65.5ms。

如果我们要定时50ms的话,就需要给TH0和TL0装一个初值,在这个初值的基础上计50000个数后,定时器中断,此时刚好就是50ms中断一次,当需要定时1s时,我们写程序时当产生20次50ms中断后便认为是1s。

要计50000个数时,TH0和TL0中应该装入的总数是65536-50000=15536,把15536对256求模:
15536/256=60装入TH0中,把15536对256取余:15536%256=176装入TL0中。

总结如下:
当用定时器的方式1时,设机器周期为Tcy,定时器产生一次中断的时间为t,那么需要计数的个数N=t/Tcy,装入THX和TLX中的数分别为:
THX=(65536-N)/256,TLX=(65536-N)%256

五. 中断服务程序的写法

C51的中断服务函数格式如下:
void 函数名()interrupt 中断号 using 工作组
{
中断服务程序内容
}

中断函数不能返回任何值,所以最前面用void;后面紧跟函数名,名字可以随便起,但是不要与C语言中的关键字相同;中断函数不带任何参数,所以函数名后的小括号内为空;中断号是指单片机中几个中断源的序号,这个序号是编译器识别不同中断的唯一符号;最后的“using工作组”是指这个中断函数使用单片机内存中4组工作寄存器中的哪一组,C51编译器在编译程序时会自动分配工作组,因此此句通常省略。

举例:利用定时器0工作方式1,实现一个发光管以1s亮灭闪烁

#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit led = P1^0;
uchar num;

void main()
{
	TMOD=0x01;   //设置定时器0为工作方式1(M1M0为01)
	TH0=(65536-45872)/256; //装初值11.0592M晶振定时50ms数为45872
	TL0=(65536-45872)%256;
	EA=1; //开总中断
	ET0=1;//开定时器0中断
	TR0=1; //启动定时器0
	while(1) 
	{
		if(20 == num)
		{
			num=0;
			led1=~led1; 
		}
	}
}

void T0_time()interrupt 1
{
	TH0=(65536-45872)/256; //重装初值
	TL0=(65536-45872)%256;
	num++;
}

注意:一般在中断服务程序中不要写过多的处理语句,因为如果语句过多,中断服务程序中的代码还未执行完毕,而下一次中断又来临,这样我们就会丢失这次中断。因此,能在主程序完成的功能就不要在中断函数中写。