10.1 方式0应用
通过设置TMOD寄存器中的M1M0位为00选择定时器方式0,方式0的计数位数是13位,对TO来说,由TL0寄存器的低5位(高3位未用)和TH0的8位组成。TL0的低5位溢出时向 TH0 进位,TH0 溢出时,置位 TCON 中的 TF0 标志,向 CPU 发出中断请求。其逻辑结构框图如下图所示。
由于定时器方式0为13位计数器,即最多能装载的数为213=8192个,当TL0和TH0的初始值为0时,最多经过8192个机器周期该计数器就会溢出一次,向CPU申请中断。
总结如下:当用定时器的方式0时,设机器周期为Tcy,定时器产生一次中断的时间为t,那么需要计数的个数N=t/Tcy,装入 THX和 TLX中的数分别为:
THX=(8192-N)/32,TLX=(8192-N)%32
先计算机器周期 Tcy,TX-1C 实验板上时钟频率为11.0592MHz,那么机器周期为12*(1/11059200)≈1.0851μs,若t=5ms,那么N=5000/1.0851≈4607,这是晶振在 11.0592MHz下定时 5ms 时初值的计算方法。当晶振为12MHz时,计算起来就比较方便了,用同样方法可算得N=5000。
编写单片机定时器程序的步骤在前面已经讲过,这里再重复一遍:
① 对 TMOD 赋值,以确定 T0 和 T1 的工作方式;
② 计算初值,并将初值写入 THO,TLO 或 TH1,TL1;
③ 中断方式时,对IE赋值,开放中断;
④ 使TRO或 TR1置位,启动定时器/计数器定时或计数。
示例:
//定时器0工作方式0 使第一个发光二极管以1s闪烁
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit led1=P1^0; //声明第1号发光二极管(0亮1灭)
sbit beep=P2^3; //声明蜂鸣器引脚(0响1灭)
uint num;
void main()
{
TMOD=0x00;//设置定时器0的工作方式为0(M1M0=00)
TH0=(8192-4607)/32; //装初值,5ms中断一次
TL0=(8192-4607)%32; //装初值,5ms中断一次
EA=1;//打开总中断
ET0=1;//打开定时器0中断
TR0=1;//启动定时器0
num=0;
led1=1;
beep=1;
while(1)//程序停止在这里等待中断发生
{
if(num==200) //加了200次说明1s时间到
{
num=0;//num清零之后可以重新计数
led1=~led1;//让发光二极管状态取反!
beep=~beep;
}
}
}
void T0_time() interrupt 1 //中断程序
{
TH0=(8192-4607)/32; //重装初值,5ms中断一次
TL0=(8192-4607)%32; //重装初值,5ms中断一次
num++;//num每加一次直至num==200
}
10.2 方式2应用
通过设置TMOD寄存器中的M1M0位为10选择定时器方式2,方式2被称为8位初值自动重装的8位定时器/计数器,THX被作为常数缓冲器,当TLX计数溢出时,在溢出标志TFX 置1的同时,还自动地将 THX 中的常数重新装入 TLX中,使 TLX从初值开始重新计数,这样就避免了人为软件重装初值所带来的时间误差,从而提高了定时的精度。定时器方式2的逻辑结构框图如下图所示。
方式2特别适合用做较精确的脉冲信号发生器,因为其只有8位计数器,当定较长时间时同样会给编写程序带来麻烦,同时也可能影响到精度,当我们使用一般的较精确定时时,用方式0或方式1就完全足够了,若要做精确的频率较高的信号发生器时可以选用方式2,但要注意,此时的晶振频率务必要选择精准,一定要是12的整数倍,因为这样计算机器周期时才不会产生误差。
由于定时器方式0为8位计数器,即最多能装载的数为28=256个,当TL0和TH0的初始值为0时,最多经过 256个机器周期该计数器就会溢出,若使用12MHz晶振,也只有 256μs的时间,若使用 11.0592MHz晶振,那计算机器周期时,品振自身产生的误差也已经不少了再加上累积过程,误差便会更大。
总结如下:当用定时器方式2时,设机器周期为Tcy,定时器产生一次中断的时间为t,那么需要计数的个数N=t/Tcy,装入 THX和TLX中的数分别为
THX=256-N,TLX=256-N
先计算机器周期Tcy,TX-1C 实验板上时钟频率为11.0592MHz,那么机器周期为12*(1/11059200)≈1.0851μs,以计时1s为例,当计250个数时,需耗时 1.0851*250=271.275us。再来计算定时 1s计数器需要溢出多少次,即1000000/271.27≈3686,这是晶振在 11.0592MHz下定时1s 时计数器溢出次数的计算方法。当晶振为12MHz时,计算起来就非常方便了(也更加精准),用同样方法可算得计数器溢出次数为4000次。
示例:
//定时器0工作方式2 使第一个发光二极管以1s闪烁
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit led1=P1^0; //声明第1号发光二极管(0亮1灭)
sbit beep=P2^3; //声明蜂鸣器引脚(0响1灭)
uint num; //注意3686超过uchar的上限了
void main()
{
TMOD=0x02;//设置定时器0的工作方式为2(M1M0=10)
TH0=6; //装初值
TL0=6; //装初值
EA=1;//打开总中断
ET0=1;//打开定时器0中断
TR0=1;//启动定时器0
num=0;
led1=1;
beep=1;
while(1)//程序停止在这里等待中断发生
{
if(num==3686) //加了3686次说明1s时间到
{
num=0;//num清零之后可以重新计数
led1=~led1;//让发光二极管状态取反!
beep=~beep;
}
}
}
void T0_time() interrupt 1 //中断程序
{
num++;//num每加一次直至num==3686,自动重装,无需再赋值
}
10.3 方式3应用
方式3只适用于定时器/计数器 T0,当设定定时器T1处于方式3时,定时器T1不计数方式3将T0分成两个独立的8位计数器TL0和TH0,定时器方式3的逻辑结构框图如下图所示。
通过设置TMOD寄存器中的M1M0位为11选择定时器方式3,分析上图可知,方式3时定时器 T0被分成两个独立的计数器。其中TL0为正常的8位计数器,计数溢出后置位 TF0,并向CPU申请中断,之后再重装初值。TH0也被固定为一个8位计数器,不过由于TL0 已经占用了 TF0 和 TR0,因此这里的 TH0 将占用定时器 T1的中断请求标志 TF1和定时器启动控制位 TR1。
这里需要强调,因为定时器T0在方式3时会占用定时器T1的中断标志位,为了避免中断冲突,我们在设计程序时一定要注意,当T0工作在方式3时,T1一定不要用在有中断的场合,当然,T1照样可以正常工作在方式0、方式1、方式2下,但无论哪种工作方式都不可以使用它的中断,因为定时器 T0要使用T1的中断。通常在这种情况下,T1 都被用来当做串行口的波特率发生器。
示例:
//定时器0工作方式3,使第一个发光二极管以1s闪烁,使第二个发光二极管以0.5s闪烁
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit led1=P1^0; //声明第1号发光二极管(0亮1灭)
sbit led2=P1^1; //声明第2号发光二极管(0亮1灭)
uint num1,num2; //注意3686超过uchar的上限了
void main()
{
TMOD=0x03;//设置定时器0的工作方式为3(M1M0=11)
TH0=6; //装初值
TL0=6; //装初值
EA=1; //打开总中断
ET0=1; //打开定时器0中断
ET1=1; //打开定时器1中断
TR0=1;//启动定时器0
TR1=1;//启动定时器1
num1=0;
num2=0;
led1=1;
led2=1;
while(1)//程序停止在这里等待中断发生
{
if(num1>=3686) //加了(超过)3686次说明1s时间到
{
num1=0;//num清零之后可以重新计数
led1=~led1;//让发光二极管状态取反!
}
if(num2>=1843) //加了(超过)1843次说明0.5s时间到
{
num2=0;//num清零之后可以重新计数
led2=~led2;//让发光二极管状态取反!
}
}
}
void TL0_time() interrupt 1 //中断程序
{
TL0=6; //重装初值
num1++;//num1每加一次直至num==3686
}
void TH0_time() interrupt 3 //中断程序
{
TH0=6; //重装初值
num2++;//num2每加一次直至num==1843
}
10.4 52单片机定时器2介绍
52单片机与51单片机相比,除了其内部程序存储容量增大外,还多了一个T2定时器计数器。定时器2是一个16位定时器/计数器,通过设置特殊功能寄存器 T2CON中的C/位可将其设置为定时器或计数器;通过设置 T2CON 中的工作模式选择位可将定时器2设置为三种工作模式,分别为捕获、自动重新装载(递增或递减计数)和波特率发生器。
什么是捕获?——
通俗地讲,捕获就是捕捉某一瞬间的值,通常用它来测量外部某个脉冲的宽度或周期。使用捕获功能可以非常准确地测量出脉冲宽度或周期,它的工作原理是:单片机内部有两组寄存器,其中一组的内部数值是按固定机器周期递增或递减,通常这组寄存器就是定时器的计数器寄存器(TLX,THX),当与捕获功能相关的外部某引脚有一个负跳变时,捕获便会立即将此时第一组寄存器中的数值准确地获取,并且存入另一组寄存器中,这组寄存器通常被称为“陷阱寄存器”(RCAPXL,RCAPXH),同时向CPU申请中断,以方便软件记录。当该引脚的下一次负跳变来临时,便会产生另一个捕获,再次向 CPU申请中断,软件记录两次捕获之间数据后,便可以准确地计算出该脉冲的周期。
定时器2控制寄存器T2CON
T2CON寄存器用来设定与定时器2有关的一些操作,字节地址为 C8H,该寄存器可进行位寻址,即可对该寄存器的每一位进行单独操作。单片机复位时 T2CON全部被清 0,其各位定义如下表所示。
TF2—定时器2溢出标志位。
定时器2溢出时置位,必须由软件清0。当RCLK=1或TCLK=1时,TF2将不会置位。
EXF2—定时器2外部标志
当EXEN2=1且T2EX(单片机的P1.1口)的负跳变产生捕获或重装时,EXF2置位。定时器2中断使能时,EXF2=1将使CPU进入定时器2的中断服务程序。EXF2位必须用软件清0。在递增/递减计数器模式(DCEN=1)中,EXF2不会引起中断。
RCLK—接收时钟标志,
RCLK=1时,定时器2的溢出脉冲作为串行口模式1或模式3的接收时钟;RCLK=0时将定时器1的溢出脉冲作为接收时钟,
TCLK—发送时钟标志。
TCLK=1时,定时器2的溢出脉冲作为串行口模式1和模式3的发送时钟;TCLK=0时将定时器1的溢出脉冲作为发送时钟。
EXEN2—定时器2外部使能标志
当 EXEN2=1且定时器2未作为串行口时钟时,允许T2EX的负跳变产生捕获或重装;当EXEN2=0时,T2EX的跳变对定时器2无效。
TR2—定时器2启动/停止控制位。置1启动定时器2,清0停止定时器2。
C/—T2 的定时器/计数器选择位。C/=1 外部事件计数器(下降沿触发);C/=0内部定时器。
CP/RL2—捕获/重装标志
CP/RL2=1且EXEN2=1时,T2EX的负跳变产生捕获;
CP/RL2=0且EXEN2=0时,定时器2溢出或T2EX的负跳变都可使定时器自动重装。当RCLK=1或TCLK=1时,该位无效且定时器强制为溢出时自动重装。
下表为定时器/计数器2的三种工作模式。
1. 捕获模式
在捕获模式中,通过T2CON中的EXEN2设置两个选项.
当EXEN2=0时,定时器2作为一个16位定时器或计数器(由T2CON中C/T2位选择),溢出时置位 TF2(定时器2溢出标志位)。该位可用于产生中断(通过使能正寄存器中的定时器2中断使能位)。
当 EXEN2=1时,与以上描述相同,但增加了一个特性,即外部输入T2EX由1变0时,将定时器2中TL2和 TH2的当前值各自捕获到RCAP2L和RCAP2H。另外,T2EX的负跳变使 T2CON 中的 EXF2置位,EXF2也像TF2一样能够产生中断(其中断向量与定时器2溢出中断地址相同,在定时器2中断服务程序中可通过査询TF2和EXF2来确定引起中断的事件),捕获模式逻辑结构图如下图所示。在该模式中,TL2和TH2无重新装载值,甚至当 T2EX引脚产生捕获事件时,计数器仍以T2脚的负跳变或振荡频率的1/12计数。
2. 自动重装模式(递增/递减计数器)
16 位自动重装模式中,定时器2可通过 C/ 配置为定时器或计数器,并且可编程控制递增/递减计数。计数的方向由DCEN(递减计数使能位)确定,它位于T2MOD寄存器中。当 DCEN=0 时,定时器2默认为向上计数:当 DCEN=1 时,定时器2可通过 T2EX确定递增或递减计数。下图显示了当DCEN=0 时定时器2自动递增计数的过程。在该模式中,通过设置EXEN2位进行选择。
当 EXEN2=0时,定时器2递增计数到0FFFFH,并在溢出后将TF2置位,然后将 RCAP2L和 RCAP2H 中的16 位值作为重新装载值装入定时器2。RCAP2L和 RCAP2H 的值是通过软件预设的,其逻辑结构图如下图所示。
当EXEN2=1时,16位重新装载可通过溢出或T2EX从1到0的负跳变实现。此负跳变同时将 EXF2置位。如果定时器2中断被使能,则当TF2或EXF2置1时产生中断。在下下图中,DCEN=1时,定时器2可递增或递减计数。此模式允许T2EX 控制计数的方向。当T2EX置1时,定时器2递增计数,计数到0FFFFH后溢出并置位TF2,还将产生中断(如果中断被使能)。定时器2的溢出将使RCAP2L和RCAP2H中的16位值作为重新装载值放入TL2和TH2。当T2EX清0时,将使定时器2递减计数。当TL2和TH2计数到等于RCAP2L和RCAP2H时,定时器产生中断,其逻辑结构图如下下图所示。
定时器2模式控制寄存器T2MOD
用来设定定时器2自动重装模式递增或递减模式,字节地址为C9H,该寄存器不可位寻址。单片机复位时 T2MOD 全部被清 0,其各位定义如下表所示。
--—保留未使用;
T20E—定时器2输出使能位;
DCEN—向下计数使能位
3.波特率发生器模式
寄存器 T2CON的TCLK和RCLK 位允许从定时器1或定时器2获得串行口发送和接收的波特率。当TCLK=0时,定时器1作为串行口发送波特率发生器;当TCLK=1时,定时器2作为串行口发送波特率发生器。RCLK对串行口接收波特率有同样的作用。通过这两位,串行口能得到不同的接收和发送波特率,一个通过定时器1产生,另一个通过定时器2产生。
如下图所示为定时器2工作在波特率发生器模式时的逻辑结构图。与自动重装模式相似,当 TH2溢出时,波特率发生器模式使定时器2寄存器重新装载来自寄存器RCAP2H 和RCAP2L的16位的值,寄存器RCAP2H和RCAP2L的值由软件预置。
当定时器2配置为计数方式时,外部时钟信号由T2引脚进入,当工作于模式1和模式3波特率由下面给出的公式所决定:时,
模式1和模式3的波特率=定时器2的溢出率/16
定时器/计数器可配置成“定时”或“计数”方式,在许多应用上,定时器被设置在“定时”方式( C/=0)。当定时器2作为定时器时,它的操作不同于波特率发生器。通常定时器2作为定时器时,它会在每个机器周期递增(1/12振荡频率):当定时器2作为波特率发生器时,它以1/2振荡器频率递增,这时计算波特率的公式如下:
模式1和模式3的波特率=振荡器频率/32X[65536-(RCAP2H,RCAP2L)]
式中,(RCAP2H,RCAP2L)是RCAP2H和RCAP2L的内容,为16位无符号整数。
如上图所示,定时器2是作为波特率发生器,仅当寄存器T2CON中的RCLK或TCLK=1时,定时器2作为波特率发生器才有效。注意,TH2溢出并不置位TE2,也不产生中断。这样当定时器2作为波特率发生器时,定时器2中断不必被禁止。如果EXEN2(T2外部使能标志)被置位,在T2EX中由1到0的跳变时会置位EXF2(T2外部标志位),但并不导致(TH2,TL2)重新装载(RCAP2H,RCAP2L)。当定时器2用做波特率发生器时,如果需要,T2EX可用做附加的外部中断。
当定时器工作在波特率发生器模式时,不要对TH2和TL2进行读或写,每隔一个状态时间(fosc/2)或由 T2进入的异步信号,定时器2的计数器都将加1。在此情况下,对 TH2和 TH进行读或写是不准确的:可对RCAP2寄存器进行读,但不要进行写,否则将导致自动重装错误。当对定时器2的寄存器 RCAP2进行访问时,应关闭定时器(TR2清0)。
4.定时器/计数器 2 的设置
除了波特率发生器模式,T2CON 不包括 TR2位的设置,TR2位需单独设置来启动定时器。下两表分别列出了 T2为定时器和计数器的具体设置方法。
内部控制:仅当定时器溢出时进行捕获和重装;
外部控制:当定时器/计数器溢出并且T2EX(P1.1)发生电平负跳变时产生捕获和重装(定时器2用于波特率发生器模式时除外)。
5.可编程时钟输出
对52系列单片机,可设定定时器/计数器2通过P10引脚输出时钟。P10引脚除用做通用IO口外,还有两个功能可供选用:用于定时器/计数器2的外部计数输入和定时器/计数器2时钟信号输出。下图为时钟输出和外部事件计数方式示意图。
通过软件对T2CON.1位C/T2设置为0,对T2MOD.1位T2OE设置为1就可将定时器计数器2选定为时钟信号发生器,而T2CON.2位TR2控制时钟信号输出开始或结束(TR2为启/停控制位),由主振荡器频率和定时器/计数器2定时、自动再装入方式的计数初值决定时钟信号的输出频率。其设置公式如下:
时钟信号输出频率=振荡器频率/4X[65536-(RCAP2H,RCAP2L)]
由公式知,在主振荡器频率设定后,时钟信号输出频率就取决于定时计数初值的设定在时钟输出模式下,计数器回0溢出不会产生中断请求。这种功能相当于定时器/计数器2用做波特率发生器,同时又可以作为时钟发生器。但必须注意,无论如何,波特率发生器和时钟发生器不能单独确定各自不同的频率,原因是两者都用同一个陷阱寄存器RCAP2H和RCAP2L,不可能出现两个计数初值。
10.5 计数器应用
计数器功能是对外来脉冲信号计数,52单片机有TO(P3.4)、T1(P3.5)和T2(P1.0)三个输入引脚,分别是这三个计数器的计数脉冲输入端,在设置计数器工作状态时,每当外部输入的脉冲发生负跳变时,计数器加1,直到加满溢出,向CPU申请中断,依次重复。
虽然单片机具有对外来脉冲计数的功能,但并不是说任意频率的脉冲都可直接计数,单片机的晶振频率限制了所测计数脉冲的最高频率。在讲解计数原理之前先来了解两个概念
(1)机器周期。一个机器周期包含6个状态周期,用S1,S2,…,S6表示:共12个节拍,依次可表示为S1P1,S1P2,S2P1,S2P2,…,S6P1,S6P2,如下图所示。
(2)指令周期。执行一条指令所占用的全部时间,它以机器周期为单位。MCS-51系列单片机除乘法、除法指令是4周期指令外,其余都是单周期指令和双周期指令。若用12MHz晶振,则单周期指令和双周期指令的指令周期时间分别为1μs和2μs,乘法和除法指令为4μs。
当定时器/计数器设定为计数器时,计数脉冲来自相应的外部输入引脚TO(P3.4),T1(P3.5)或T2(P1.0)。当输入信号产生由1到0的负跳变时,计数器的值加1。每个机器周期的S5P2期间,对外部输入引脚进行采样。如在第一个机器周期中采得的值为1,而在下一个周期中采得的值为0,则在紧跟着的再下一个机器周期S3P1期间,计数器加1。由于确认一次负跳变需要花两个机器周期,即24个振荡周期,因此外部输入计数脉冲的最高频率为振荡器频率的1/24。例如,选用6MHz频率的晶振,允许输入的外部脉冲的最高频率为250kHZ,如果选用12MHZ 频率晶振,则最高可输入500kHz的外部脉冲。对于外部输入脉冲的占空比也有一定的限制,为了确保某一给定的电平在变化之前能被采样一次,则这一电平至少要保持一个机器周期。
示例:
//利用计数器0工作方式1,在 TX-1C 实验板上实现如下描述
//用一根导线-端连接 GND 引脚,另一端去接触 TO(P3.4)引脚
//每接触一下,计数器计一次数,将所计的数值实时显示在数码管的前两位
//计满100 时清0,再从头计起
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2^6; //声明U1锁存器的锁存端
sbit wela=P2^7; //声明U2锁存器的锁存端
uchar code dula_table[]={
0x3f,0x06,0x5b,0x4f, //0,1,2,3
0x66,0x6d,0x7d,0x07, //4,5,6,7
0x7f,0x6f,0x77,0x7c, //8,9,10,11
0x39,0x5e,0x79,0x71 //12,13,14,15
};
uchar code wela_table[]=
{
0xdf,0xef,0xf7,0xfb,0xfd,0xfe //从右向左数第一到六位
};
uchar a,b;
uint wei,num;
void main()
{
void delayxms(uint xms);
uint read();
void init(); //计数器初始化函数
void display(); //读取函数
init();
while(1)
{
num=read();
if(num>=100)
{
num=0;
TH0=0; //将计数器值清0
TL0=0;
}
a=num/10;
b=num%10;
display();
}
}
void delayxms(uint xms)
{
uint x,y;
for(x=xms;x>0;x--)
for(y=124;y>0;y--);
}
void init() //计数器初始化函数
{
TMOD=0x05; // 设置计数器0为工作方式1(0000 0101)
TH0=0;
TL0=0;
TR0=1;
a=0;
b=0;
num=0;
}
uint read() //读取函数
{
uchar t1,th1,th2;
uint val;
while(1)
{
th1=TH0;
t1=TL0;
th2=TH0;
if(th1==th2) break; //只读一次可能会错误,连续读两次防止读取时发生进位
}
val = th1*256+t1;
return val;
}
void display()
{
void wedu(uchar dula_num,uchar wela_num);
for(wei=0;wei<2;wei++)
{
switch(wei)
{
case 0: wedu(b,wei);break;
case 1: wedu(a,wei);break;
}
}
}
void wedu(uchar dula_num,uchar wela_num)
{
wela=1; //打开U2锁存端
P0=wela_table[wela_num]; //送入U2锁存端
wela=0; //关闭U2锁存端
P0=0xc0; //消影,防止P0残留电位信号干扰段选
dula=1; //打开U1锁存端
P0=dula_table[dula_num]; //送入段选信号
dula=0; //关闭U1锁存端
P0=0xff; //消影,防止P0残留电位信号干扰片选
delayxms(1);
}
注意:由于抖动,每次数字变化不可能稳定增长1
参考资料:
[1] 郭天祥. 新概念51单片机C语言教程:入门、提高、开发、拓展全攻略[M]. 北京: 电子工业出版社, 2009.