单片机基础知识 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++;
}
注意:一般在中断服务程序中不要写过多的处理语句,因为如果语句过多,中断服务程序中的代码还未执行完毕,而下一次中断又来临,这样我们就会丢失这次中断。因此,能在主程序完成的功能就不要在中断函数中写。