定时器是生活中常见的一种定时装置,常见的定时器有机械定时器和电子定时器
使用的时候我们给他设置时间,当我们指定的时间到了以后定时器会给我们提醒
其实,每天都跟我们打交道的单片机里面也有一个简单的定时器,他一直站在单片机的角落,为机器精确的定时默默的奉献出自己的一份力量
(1)单片机的心脏与机器周期
总所周知,单片机的心脏——晶振是一颗很重要的原件,晶振负责给单片机提供稳定的周期频率,保证单片机的稳定运行
晶振是一颗能产生稳定脉冲频率的电子期间,通电的时候,晶振每一秒发出脉冲频率的次数分之一成为晶振频率
晶振频率 = 1秒/1秒内晶振发出脉冲的次数
我们把晶振频率记作f,晶振周期记作T
晶振周期T= 1/晶振频率
在单片机里面,单片机完成一次操作所用的时间叫做机器时间。这里不得不说明,在汇编里面,单片机的机器时间是12个晶振周期,但是在C语言里面,这个周期是不固定的,这也就是为什么用空循环来延迟,得不到精确的时间
void delay(unsigned int xms) //调用时需提供参数xms的实际值。
{
unsigned int i,j; //定义无符号整型变量i、j
for(i=0; i< xms; i++) //后面没带";",下一条语句是循环体。参数xms延时x毫秒。
for(j=0; j< 110; j++); //后面带";",表示这个for语句的循环体是空语句。
}//这个值得学习,这里的代码是延迟代码
//复制请注明原作者 谢谢
//复制请注明原作者 谢谢
这也就是为什么我们不太建议用这个延迟精确时间的缘由
因为C51在汇编状态的机器周期是12,所以可以认为单片机的机器周期值就是12
本小节讲到这里就结束啦,这一节大家就要记住
·单片机的机器周期值是12,机器频率 = 12f(晶振周期)
·f(晶振频率) = 1/晶振1秒脉冲次数
·T(晶振周期)=1/f
(2)认识定时器的结构
单片机里面的定时器结构图示如下(这个是我自己画的,有点丑)
如图所示,C51定时器原理如图,定时器里面有一个寄存器,单片机每经过一次机器周期就会自动给这个寄存器+1,一直加,直到寄存器满溢出,溢出后寄存器自动恢复0,并且给出一个信号告诉我们寄存器满了.这就是定时器一次定时的工作流程
(3)定时器结构
C51中,一共有两组定时器,记作T0,T1定时器不仅能定时,还可以当计数器使用。我们本节主要讨论定时器。
定时器由三个寄存器组成
C51里面的定时器有两组(TH0,TL0)(TH0,TL1),每组由高八位和低八位组成的十六位寄存器
TH1,TL1属于定时器1,TH0,TL0属于定时器0
这里有个特别重要的概念!!,定时器的溢出
定时器的溢出不是满了就算溢出,而是,满了再加一才算溢出,拿T0举例,现在T0的寄存器的数值是0xFFFF(65535,1111 1111 1111 1111),这时候再加一,才会溢出(也就是65535+1 = 65536才溢出),这就意味着我们计算的时候,溢出值要用65536来计算
为了方便使用定时器,我们需要给定时器的寄存器设置初始值,下面先介绍如何计算定时器初始值
假设单片机使用11.0592MHz的晶振,那么……
机器的频率f = 1/11059200
前面我们知道,定时器会在每一次机器周期自动+1,也就是说定时器每加一一次的时间是机器周期t ,t = 12/晶振周期T
定时器计数计到65536才算溢出
假设延迟S(注意单位是秒),1000毫秒 = 1秒
计算的时候要使用Hz作为频率单位,1MHz = 1 000 000Hz
假设定时器计数N次,溢出
那么步长N = (晶振频率f * 要延迟的秒数S)/ 12(机器周期)
初始值X = 65536 - 步长N
/*
小练笔,假设晶振频率是11.0592MHz
单片机周期是12
现在要延迟20ms
计算步长和初始值
20ms = 0.02s
步长N = 11059200 * 0.02/12 = 18432
初始值X=65536-N = 47104
*/
细心的朋友可能发现了,如果从0开始计时,C51定时器定时的时长最大值是0.07111
那我要如何延迟超过71ms的时间呢,这里有个方法:重复,让定时器重复延迟多次,达到我们想要的时间,不过这就算是后话了,后面实例的时候再说吧
2.TCON寄存器
TCON寄存器在机器里的地址:0x88,可位寻址,复位值(单片机复位的时候寄存器的值) = 0x00(0000 0000)
在定时器里面,TCON寄存器主要有两个作用,开关定时器,复位和检测定时器是否溢出
TF :
检测定时器是否溢出,没有溢出的时候是0,溢出的时候是1。
当定时器溢出的时候要我们手动给TF寄存器写0复位
TR:定时器开关,写0的时候关闭定时器自动计数定时,写1的时候启用定时器计数定时。
TF1,TR1属于定时器1,TF0,TR0属于定时器0
注意这个寄存器是可位寻址的(可以直接访问寄存器中的某个地址),举个栗子哈,如果我们要哦将TR1写0,我们不用操作整个寄存器,只需要
TR1 = 0;
即可
以下是TCON寄存器在定时器下的使用方法合集(这些关键字在reg52.h里面已经被定义,使用的时候像下面一样用直接用)
TR0 = 0;//关闭Timer0
TR0 = 1;//打开Timer0
TR1 = 0;//关闭Timer1
TR1 = 1;//打开Timer1
//----------------------------------------
if (TF0 == 1){TF0 = 0;}//判断Timer0是否溢出,如果溢出,重置溢出判断位
if(TF1 ==1){TF1 = 0;}//判断Timer1是否溢出,如果溢出,重置溢出判断位
//特别提醒,溢出后一定要记得重置TF
特别提醒,溢出一定要记得重置TF位
3.TMOD寄存器
TMOD寄存器在机器里的地址:0x89,不可位寻址,复位值(单片机复位的时候寄存器的值) = 0x00(0000 0000)
TMOD寄存器设置寄存器的模式,我们先来看看TMOD寄存器的结构
高位对应Timer1,低位对应Timer0
名称 | 功能 |
---|---|
GATE | 置1的时候为门控位,正常使用定时器置0 |
C/T | 切换定时,计数器。置1使用计数器,置0使用定时器 |
M1 | 定时器模式设置,请看下表 |
M0 |
我们发现,定时器有两组模式设置位,这两组模式设置为能组合出4种模式
M1 | M0 | 模式功能 |
---|---|---|
0 | 0 | 兼容8048单片机13位定时/计数器 |
0 | 1 | 16位定时/计数器 |
1 | 0 | 把一个定时器的寄存器拆开成两个八位,第一个八位满了就把这个值给到另一个8位,继续计时 |
1 | 1 | 禁用Timer1,这时候Timer0相当于两个8位Timer |
最常用的是M1 = 0,M0 = 1的16位定时器
总结,TMOD定时器的设置方法:设置一个16位定时器
功能 | 设置值 | 说明 |
---|---|---|
GATE | 0 | 不使用门控位 |
C/T | 0 | 设置值位0的时候使用定时器 |
M1 | 0 | 使用16位定时器 |
M0 | 1 |
这里必须要说明,TMOD寄存器是不可位寻址的,我们不能直接访问这个寄存器里面的某一个地址。这就是说我们不能用GATE1 = 0;之类的方法来设置TMOD寄存器,修改的时候一次性修改整个TMOD寄存器
//按照上面的式设置寄存器
TMOD = 0x01;//TMOD寄存器修改值位0x01---》 0000 0001 16位定时器,Timer0 模式1
TMOD = 0x10;//TMOD寄存器修改值位0x01---》 0001 0000 16位定时器,Timer1 模式1
这样就完成了最基本的16位定时器设置
定时器结构
这里大家做个简单的了解就行
我们可以看到,OSC是晶振,/d得到机器周期,这就是给寄存器+1的信号
这个信号经过C/T(定时/计数器选择)当C/T = 0的时候选择定时器,当C/T = 1的时候选择计数器
GATE是一个门控位置,GATE的信号经过一个非门取反,也就是说GATE = 0的时候非门的一端输出1,GATE = 0的时候非门的一端输出0GATE配合Intr(这是一个外部中断输入口,没有信号的时候置0),这个电路后面经过一个或门,只要有一路或者两路为高电平就输出高电平,两路都输出低电平的时候输出低电平
TR是使用定时/计数器的总开关,这里有个与门,只有两路都输入高电平的时候才会输出高电平,输出高电平的时候控制后面的开关,把信号输入到计数器的寄存器
当计数器的寄存器溢出的时候就出发TF,TF = 1;
到了这,就不难理解这些寄存器怎么设置了
首先,我们把GATE设置成0,因为我们用不上Intr外部中断输入,Intr = 0,GATE设置成0以后机器经过或门的时候输出1。
或门输出1的时候,我们置TR = 1,这样与门输出1,信号就能通到计数寄存器上了
这时置C/T = 0,让脉冲源来自机器周期的脉冲信号
修改M0=1,M1=0。使用16位寄存器
一个标准16位定时器设置值
GATE | C/T | M0 | M1 | TF | TR | TH | TL |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 1 | 根据需要设置值 |
以下是定时器操作的全部代码,我们以Timer0为例子
#include< reg52.h >
void main()
{//注意设置定时器初始值要在main函数里面设置
TMOD = 0x01;
/*设置定时器模式寄存器TMOD为 0000 0001
GATE = 0
C/T = 0
M1 =0
M0 = 1
标准16位定时器
*/
//先设置定时器初始值
TL0 = 0x3A;//设置定时器初始值低位
TH0 = 0x8E;//设置定时器初始值高位
TR0 = 0;//记得一定要设置定时器开启
while(1)
{
if (TF0 == 1)
{//这里可以先放下你的定时器计时满了以后要执行的代码
//溢出后你需要先给你的定时器再次设置初始值
TL0 = 0x3A;//设置定时器初始值低位
TH0 = 0x8E;//设置定时器初始值高位
TF0 = 0;//设置好初始值后再复位定时器
}
}
}
以上就是最基本的操作方法咯,前面我们说过,如果只使用定时器,单片机最长延迟71ms,如果需要延迟更长的时间,可以利用单片机重复延迟
使用11.0592MHz的晶振,在这里,我们计算出延迟1ms的初始值是921.6
这里,如果初始值设置为921,时间会略微段短一点,设置为922,又会略微长一点,这就是为什么我们要精确定时的时候,使用11.0592MHz的晶振会不准,使用12MHz的晶振是不错的解决办法,可惜使用了12MHz的晶振,又无法进行正常的串口通讯(这个后面再提吧)
这里我取的数值是922,因为偏差比921小
以下是延迟函数delay()的参考代码
#include< reg52.h >
void main()
{//注意设置定时器初始值要在main函数里面设置
TMOD = 0x01;
/*设置定时器模式寄存器TMOD为 0000 0001
GATE = 0
C/T = 0
M1 =0
M0 = 1
标准16位定时器
*/
TL0 = 0x03;//设置定时器初始值低位
TH0 = 0x9A;//设置定时器初始值高位
void delay(unsigned int Ms)//延迟函数,输入的单位是毫秒
{
unsigned int counter = 0;
TR0 =1;//启用定时器
for(counter = 0;counter< Ms;counter++)
{
while(TF0 !=0){}//循环一直进行,直到TF0不再是0
TL0 = 0x03;//设置定时器初始值低位
TH0 = 0x9A;//设置定时器初始值高位
TF0 = 0;//复位溢出位,复位之前一定要先设置好初始值
}
TR0 = 0;//关闭定时器
}
while(1)
{
delay(1);//测试定时器
delay(10);
delay(100);
delay(1000);
}
}
这个参考代码不算特别完整,仅供多次定时器延迟达到一个长于71ms的一种方法
全部0条评论
快来发表一下你的评论吧 !