1 引言
单片机的定时器是单片机里最“活跃”的部件之一,很多程序、应用系统都离不开定时器。由于定时器的应用与单片机的其他硬件相关,存在着一定的复杂性。而定时器也是单片机应用中解决某类复杂问题的最为有效的方法,应用非常广泛[1]。定时器的应用,可以说即简单又复杂。对于简单应用场合,时间要求较长,不算很精确的场合,用起来就简单,对于复杂应用场合,时间要求即短又精确的场合,用起来就要复杂。本文以STC90C51RC/RD+系列单片机定时器为研究对象,并给出几个示例程序。 STC90C51RC/RD+系列单片机是宏晶科技推出的新一代超强抗干扰/高速/低功耗的单片机,指令代码完全兼容传统8051单片机,12时钟/机器周期和6时钟/机器周期可以任意选择。
2 定时器/计数器应用
2.1 STC90C51RC/RD+系列单片机定时/计数器
STC90C51RC/RD+系列单片机共3个16位定时器/计数器,其中定时器0还可以当成2个8位定时器使用。定时器0和定时器1,与传统8051的定时器完全兼容,当在定时器1做波特率发生器时,定时器0可以当两个8位定时器用。
STC90C51RC/RD+系列单片机内部设置的两个16位定时器/计数器T0和T1都具有计数方式和定时方式两种工作方式。对每个定时器/计数器(T0和T1),在特殊功能寄存器TMOD中都有一控制位C/T来选择T0或T1为定时器还是计数器。定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同:如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或者每6个时钟得到一个计数脉冲,计数值加1;如果计数脉冲来自单片机外部引脚(T0为P3.4,T1为P3.5),则为计数方式,每来一个脉冲加1。
当定时器/计数器工作在定时模式时,可在烧录用户程序时在STC-ISP编程器中设置是系统时钟/12还是系统时钟/6后让T0和T1进行计数。当定时器/计数器工作在计数模式时,对外部脉冲计数不分频。
定时器/计数器0有4种工作模式:模式0(13位定时器/计数器),模式1(16位定时器/计数器模式),模式2(8位自动重装模式),模式3(两个8位定时器/计数器)。定时器/计数器1除模式3外,其他工作模式与定时器/计数器0相同,T1在模式3时无效,停止计数。
定时器2是一个16位定时/计数器。通过设置特殊功能寄存器T2CON中的C/T2位,可将其作为定时器或计数器。
定时器2有3种操作模式:捕获、自动重新装载(递增或递减计数)和波特率发生器。这3种模式由T2CON中的位进行选择。
2.2 定时器/计数器应用
与传统8051单片机相比,STC90C51RC/RD+系列单片机性能更优越,就以定时/计数器为例,在笔者应用单片机定时器定时,输出脉冲过程中发现,如果需要用两个以上定时器同时工作,在定时时间较长情况下,前者的定时器能够胜任;但是,当两个同时工作的定时器至少有一个定时时间较短时,前者的定时器就会“罢工”。,鉴于此笔者选用STC90C51RC/RD+系列单片机作为研究对象。
如下几个示例程序,从不同方面应用STC90C51RC/RD+系列单片机的定时器,也充分展示了该类单片机的优越性。所有程序都经过keil软件调试通过,并生成*.hex文件烧入单片机实测。每一个程序都可作为一完整程序应用。
应用定时器进行定时或计数,不可避免要存在误差,而每个程序存在误差之处都以注释的形式标明。由于以下程序皆为C语言编写,需要经过编译器编译为汇编语言,所以,误差的精确值不易算出,仅仅标出产生误差的语句。而如果采用汇编语言编写,情况会好些。
1) 5秒内计数脉冲个数
单片机外接的振荡源频率为11.0592MHz,利用定时器0定时,每次定时50ms,循环100次,达到5s时间,利用定时器1计数5s内外部脉冲个数,并由串口传输给上位机。
本程序旨在说明定时器0与定时器1同时工作的编程方法与思路。
#include<reg51.h>
#include<intrins.h>
/* 新增特殊功能寄存器定义 */
sfr IAP_DATA = 0xe2;
sfr IAP_ADDRH = 0xe3; //地址高8位
sfr IAP_ADDRL = 0xe4; //地址低8位
sfr IAP_CMD = 0xe5;
sfr IAP_TRIG = 0xe6;
sfr IAP_CONTR = 0xe7;
sfr RCAP2H = 0xcb;
sfr RCAP2L = 0xca;
sfr T2CON = 0xc8;
sfr T2MOD = 0xc9;
sbit TR2 = 0xca ;
typedef unsignedcharuchar;
typedef unsigned int uint;
sbit LED=P1^0;
sbit LLED=P1^1;
uchar flag_2s,flag_50ms;
uchar number_2s,number_50ms;
uint a,b;
void stc_init(void) //初始化
{
SCON = 0x50; //工作方式1
RCAP2H=0xff;
RCAP2L=0xda;
T2CON=0x34;
T2MOD = 0x00;
TMOD=0x51; //定时器1工作于模式1计数
TH1=0x00;
TL1=0x00;
TH0=0x4C;
TL0=0x01;
TR1=1;
TR2=1;
TR0=1;
EA=1;
ET0=1;
}
void delay(uchar xms)
{
uchar i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
void stc_timer (void) interrupt 1
{
TH0=0x4c;重新赋值,赋值之后才能继续计数,故而,会产生误差。
TL0=0x01;
LLED=!LLED;
flag_50ms =1;
number_50ms++;
if(number_50ms==100)
{
number_50ms=0;
a=TL1; 此处读出计数器值,由于计数器正在计数,故而存在误差。
b=TH1;
TR1=0; 执行到此,定时器才停止计数。
TH1=0x00;
TL1=0x00;
TR1=1;
}
}
void main(void) 主函数,将脉冲个数发送给上位机。
{
stc_init();
do
{
delay(200);
SBUF=a;
while(TI==0);
TI=0;
delay(1000);
SBUF=b;
while(TI==0);
TI=0;
delay(1000);
}
while(1);
}
2) 定时器T0计数50个脉冲所需时间
/*查50个脉冲所需时间,脉冲由定时器T1发出,每50msP1.1取反,形成脉冲,将P1.1连接到T0(14号)引脚,即可计数,定时器T0计数输入脉冲个数,50(0x32)先显示,之后,显示50ms的个数,再次显示TL1的值,最后显示TH1的值,此两个值是时间零头*/
……
……省略号部分与第一个程序相同
uchar flag_2s,flag_50ms;
uchar number_2s,number_50ms;
uchar number_pulse;
uint a,b,c;//计数用,计数50ms之外的零头
uint time;//time为50个脉冲总时间(ms)
uint pulse;//查脉冲数
void stc_init(void) //初始化
{
SCON = 0x50; //工作方式1
RCAP2H=0xff;
RCAP2L=0xda;
T2CON=0x34;
T2MOD = 0x00;
TMOD=0x15; //定时器0工作于模式1计数
TH0=0x00;
TL0=0x00;
TH1=0x4C;
TL1=0x01;
TR1=1;
TR2=1;
TR0=1;
EA=1;
ET0=0;
ET1=1;
}
void stc_timer (void) interrupt 3
{
TH1=0x4c; 重新赋值,赋值之后才能继续计数,故而,会产生误差。
TL1=0x01;
LLED=!LLED;
number_50ms++;
}
void main(void)
{
number_50ms=0;
LLED=0;
stc_init();
pulse=TL0;
do
{pulse=TL0;
}
while(pulse!=50);
//以下6条在循环体外
TR1=0;
a=TL1; 此处读出计数器值,由于计数器正在计数,故而存在误差。
b=TH1;
TR1=1;
TH0=0x00;
TL0=0x00;
do
{
delay(200); 延时子程序与第一个程序相同
SBUF=pulse;
while(TI==0);
TI=0;
delay(1000);
SBUF=number_50ms;
while(TI==0);
TI=0;
delay(1000);
SBUF=a;
while(TI==0);
TI=0;
delay(1000);
SBUF=b;
while(TI==0);
TI=0;
delay(1000);
delay(1000);
}
while(number_50ms<103);//按照此条件,最后显示出的number_50ms为
//0x66(102)因为延迟所致,103是根据实际显示数值所定
}
3) 定时器T2计数T1波特率发生器
/*脉冲由定时器T0发出,一定时间后p1.2取反,形成脉冲, T1波特率发生器,发出38k方波,T2计数,T2计数脉冲输入端为p1.0
……省略号部分与第一个程序相同
……
uchar flag_2s,flag_50ms;
uchar number_2s,number_50ms;
uchar number_pulse;
uint a,b,c,PPP;//计数用,计数50ms之外的零头
uint time;//time为50个脉冲总时间(ms)
uint pulse;//查脉冲数
void stc_init(void) //初始化
{
SCON = 0x50; //工作方式1
RCAP2H=0X00; //定时器t2初始化
RCAP2L=0x00;
TH2=RCAP2H; //定时器2赋初值
TL2=RCAP2L;
T2CON=0X02;
TMOD=0x21; //定时器1工作于模式2
TH1=0xfd;
TL1=0xfd;
TR1=1;
TH0=0x40;
TL0=0x00;
TR0=1;
TR2=1;
EA=1;
ET0=1;
ET1=0;
}
void stc_timer (void) interrupt 1
{
TH0=0x40;
TL0=0x00;
LLED=!LLED;
}
void main(void)
{
LED=0;
number_50ms=0;
LLED=0;
pulse=0;
stc_init();
while(pulse!=20)
{
TR2=0;
pulse=TL2;
TR2=1;
EA=0;
SBUF=pulse;
while(TI==0);
TI=0;
EA=1;
delay(1000);
}
}
3 定时/计数器误差分析
由于单片机的机器周期为1μs~2μs,定时误差一般应在5μs~25μs之内,对于一般应用,此误差可以忽略,但是对于精确度要求比较高的应用场合,此误差必须进行校正[2]。定时误差是定时溢出后转入执行定时处理语句段之间所耗费的时间,此时间主要由定时溢出转入定时处理语句段所必须执行的指令或硬件过程产生.而且,在转入、转出定时器中断服务子程序过程都有延迟,即产生误差[1]。再有,如果像第一个程序那样,利用循环定时以产生更长定时,执行循环处理及控制指令时亦会产生误差,影响时间的精确性。
综上,虽然利用硬件定时会节省CPU的时间,并且,能产生较为“准确”的定时,但“准确”是相对的,误差会由于指令的执行而产生。为了得到更加精确的定时,这些误差是不容忽视的。
4 结束语
定时器是单片机里最“活跃”的部件之一,很多单片机的应用系统都是需要应用定时器。本文以STC90C51RC/RD+系列单片机作为研究对象,避开传统8051单片机的弊端,通过几个示例程序,展示了STC90C51RC/RD+系列单片机定时器的应用。程序并不复杂,也存在一定误差,如果想得到非常精确的定时,对于定时器的误差是必须考虑的,尤其是反复应用定时器的程序中,更要详尽考虑误差的产生原因及指令,必要时要做出修正。
参考文献:
[1] 王暄.单片机定时器的应用与误差纠正[J].电子元器件应用,2002,(5):15.
[2] 沈威羽.单片机定时器的应用与误差纠正[J].经营管理者,2009,(1):20.
作者简介:朱旭光(1980-),男,硕士,讲师,研究方向:计算机网络技术,微机控制等。