1:51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
2:定时器/计数器和单片机的CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要CPU的参与。
3:51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加1。
4:有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。
5:定时器各工作方式如下表
打开单片机的定时器中断过程十分繁复,在一篇文章中难以完全讲解,故本文只简单的介绍必要步骤,对其中的个中原理略过。
#include
typedef unsigned int u16;
typedef unsigned char u8;
u16 interrupt_cnt,clock;
u8 i;
u8 str[8]={0,0,0,0,0,0,0,0};
u8 segSel[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; //数码管段选数据
sbit LSA=P2^2; //定义38译码器位选口
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit LED0=P2^0; //定义LED灯
sbit LED1=P2^1;
void bitSel(u8 x) //段选函数
{
switch(x)
{
case(0):
LSA=1;LSB=1;LSC=1; break;
case(1):
LSA=0;LSB=1;LSC=1; break;
case(2):
LSA=1;LSB=0;LSC=1; break;
case(3):
LSA=0;LSB=0;LSC=1; break;
case(4):
LSA=1;LSB=1;LSC=0; break;
case(5):
LSA=0;LSB=1;LSC=0; break;
case(6):
LSA=1;LSB=0;LSC=0; break;
case(7):
LSA=0;LSB=0;LSC=0; break;
}
}
void delay(int t) //简易延时函数
{
while(t) t--;
}
void display(u16 dig) //数码管动态扫描函数
{
u16 tmp=dig;
i=7;
while(tmp>0)
{
str[i]=tmp%10;
tmp/=10;
i--;
}
for(i=7;i>=1;i--)
{
bitSel(i);
P0=segSel[str[i]];
delay(100);
P0=0x00;
}
}
void interrupt_T0() //中断起始函数
{
EA=1;
ET0=1;
TR0=1;
TMOD|=0x01;
}
void timer_interrupt() interrupt 1 //定时器中断函数
{
TH0=0xFC;
TL0=0x18;
interrupt_cnt++;
if(interrupt_cnt==1000)
{
clock++;
interrupt_cnt=0;
TH0=0xFC;
TL0=0x18;
LED1=~LED1;
}
}
int main()
{
interrupt_T0();
TH0=0xD8;
TL0=0xF0;
while(1)
display(clock);
}
1:打开总中断(EA=1)
2:打开定时器中断(ET0=1)
3:启动定时器1(TR0=1)
4:设置定时器工作方式为方式1(TMOD=0x01)(M1=0,M0=1)
定时器中断有一个很重要的概念:溢出。最开始我也搞不懂,郭天祥书上也讲的不明不白。有一个概念叫“T0溢出率”,闻所未闻,他也不说明。。。最后查各种资料才发现,T0溢出率就是一秒内T0溢出的次数(T0由TH0和TL0组成,后文详讲),初值越大溢出率越高,溢出一次时间越短。通过设定T0溢出率,我们就可以精确的表示1秒的长短(就是T0溢出时间的叠加)
下面我们来看看TH1和TL1究竟应该怎么操作
定时器工作方式一是怎样运行的呢?
我们来看看课本上的定义:
定时/计数器实质上是一个加1计数器。它随着计数器的输入脉冲进行自加1,也就是每来一个脉冲,计数器就自动加1,当加到计数器为全1时,再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置1,向CPU发出中断请求(定时/计数器中断允许时)。如果定时/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。
上面提到了计数器回零,再前面我们提到了“溢出”这个概念,想必大家已经猜到了,计数器回零就是溢出。
那么TH0,TL0又是什么东西呢?
TH0,TL0可以被看作是T0计数器的高八位和低八位,我们在写代码的时候对其分别赋值,但在程序运行的时候他们是被当成一个数对待的。
如果我们要实现一秒的计时该怎么办?
当我们使用12MHZ的晶振时,单片机的机器周期是1us,对于机器周期,我们把它看成单片机里的最小时间单位。1ms/1us=1000,也就是要计数1000个数,故T0初值=65535-1000+1(因为计数器在65536时才算溢出)=64536,转换成16进制就是0xFC18,高八位0xFC,低八位0x18分别赋值。然后,1s=1000ms,只要加一个程序内部的自增变量(interrupt_cnt),每溢出一次自加1,当该变量加到了1000时,就代表经过了一秒钟了。最后我们让clock变量自加一,用数码管显示来表示时间的流逝。至于数码管的操作,改天有空再开一篇新的博文来写。
#include
typedef unsigned int u16;
typedef unsigned char u8;
u16 interrupt_cnt,clock;
u16 i,a;
u8 str[8]={0,0,0,0,0,0,0,0};
u8 segSel[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit LED0=P2^0;
sbit LED1=P2^1;
sbit KEY3=P3^2;
void bitSel(u8 x) //位选
{
switch(x)
{
case(0):
LSA=1;LSB=1;LSC=1; break;
case(1):
LSA=0;LSB=1;LSC=1; break;
case(2):
LSA=1;LSB=0;LSC=1; break;
case(3):
LSA=0;LSB=0;LSC=1; break;
case(4):
LSA=1;LSB=1;LSC=0; break;
case(5):
LSA=0;LSB=1;LSC=0; break;
case(6):
LSA=1;LSB=0;LSC=0; break;
case(7):
LSA=0;LSB=0;LSC=0; break;
}
}
void delay(int t) //延时函数
{
while(t) t--;
}
void display(u16 dig) //动态数码管显示函数
{
u16 tmp=dig;
i=7;
while(tmp>0)
{
str[i]=tmp%10;
tmp/=10;
i--;
}
for(i=7;i>=1;i--)
{
bitSel(i);
P0=segSel[str[i]];
delay(100);
P0=0x00;
}
}
void timer0_init() //定时器0初始化
{
EA=1;
ET0=1;
TR0=1;
TH0=6;
TL0=6;
TMOD=0x02;
}
void timer0_interrupt() interrupt 1 //设置定时器中断程序
{
if(a==4000)
{
a=0;
LED0=~LED0;
clock++;
}
else a++;
}
int main()
{
timer0_init();
while(1)
{
display(clock); //在大循环中运行显示函数部分
}
return 0;
}
与前文方式一步骤基本相同,唯一不同的是把TMOD=0x01改成了TMOD=0x02(0001 0010)。
前文已解释了定时器中断的基本原理,故不赘述。方式二与方式一最大的区别就是T0装载的初始值是一样的,而且该定时器单次只使用高八位。当高八位溢出后 ,低八位自动把其中的值装载入高八位,故不需要像方式一一样在代码里写装载定时器的函数。我们知道,八位数据最大数据范围是0~256,故它在257时便会溢出申请中断并自动重新装载。我们在程序里面唯一需要做的就是使clock变量自增一。
大家可能对初始TH0,TL0装载的值为6这一点有疑问。为什么呢?我们已经知道一个机器周期是1us,该方式下定时器在257时便会自动重新装载,所以只要我们把初始值设定为6:
256-6=250us,250*4000=10^6us=1s
故当interrupt_cnt=4000时,我们的clock变量便可以自加一,表示一秒已经过去了!
小tip:
1.对TMOD赋值,以确定T0和T1的工作方式。
2.计算初值,并将其写入TH0、TL0或TH1、TL1。
3.中断方式时,则对EA赋值,开放定时器中断。
4.使TR0或TR1置位,启动定时/计数器定时或计数。