C51单片机的定时器介绍

描述

定时器是生活中常见的一种定时装置,常见的定时器有机械定时器和电子定时器

使用的时候我们给他设置时间,当我们指定的时间到了以后定时器会给我们提醒

其实,每天都跟我们打交道的单片机里面也有一个简单的定时器,他一直站在单片机的角落,为机器精确的定时默默的奉献出自己的一份力量

(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定时器不仅能定时,还可以当计数器使用。我们本节主要讨论定时器。

定时器由三个寄存器组成

  1. 计数寄存器

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种模式

M1M0模式功能
00兼容8048单片机13位定时/计数器
0116位定时/计数器
10把一个定时器的寄存器拆开成两个八位,第一个八位满了就把这个值给到另一个8位,继续计时
11禁用Timer1,这时候Timer0相当于两个8位Timer

最常用的是M1 = 0,M0 = 1的16位定时器

总结,TMOD定时器的设置方法:设置一个16位定时器

功能设置值说明
GATE0不使用门控位
C/T0设置值位0的时候使用定时器
M10使用16位定时器
M01

这里必须要说明,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位定时器设置值

GATEC/TM0M1TFTRTHTL
001001根据需要设置值

以下是定时器操作的全部代码,我们以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的一种方法

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分