51单片机定时器应用实例(方式0、1、2、3)

under 单片机  嵌入式开发  tag     Published on December 29th , 2020 at 11:17 am

定时器介绍

定时器:一般用于软件计时,给定时器设置一个时间,时间到了系统停止当前的工作跳转到事先定义好的定时器中断函数里,函数里可以做一些周期性的事情。

计数器:一般用于检测外来脉冲信号,给计数器设置一个次数,次数到了系统停止当前的工作跳转到事先定义好的计数器中断函数里,函数里做相应的事情。

寄存器描述

https://blog.csdn.net/weixin_42653531/article/details/82530685?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-6&spm=1001.2101.3001.4242

方式0:

#include<reg52.h>
 
#define uchar unsigned char
#define uint  unsigned int
 
sbit led1=P1^0;
uchar num;
 
void TIM0init(void)
{
     TMOD=0x00;            //设置定时器0为工作方式0
     TH0=(8192-5000)/32;   //装入初值,怎么计算,下面分析
     TL0=(8192-5000)%32;    
     EA=1;    //开总中断
     ET0=1;   //开定时器中断
     TR0=1;   //启动定时器0
}
/*
interrupt 0  指明是外部中断0;
interrupt 1  指明是定时器中断0; 
interrupt 2  指明是外部中断1;
interrupt 3  指明是定时器中断1;
interrupt 4  指明是串行口中断;
函数名字可以随便起,但定时器0的中断号是固定为1的
*/
void T0_time()  interrupt 1      
{
     TH0=(8192-5000)/32; //重装初值,如果不重装,中断只触发一次
     TL0=(8192-5000)%32;
     num++;
}
                          
void main()
{
    TIM0init(); 
    while(1)
    {
      if(num==200)     //如果到了200,说明一秒时间到
          {
             num=0;
         led1=~led1;   //让发光管状态取反
      }
    }
}

假设单片机用的晶振是12MHz,上面的中断函数每过5ms会被调用一次,也就是发光管每一秒状态取反一次。那么怎么计算初值以确定TL0和TH0的值呢?

定时器方式0是指13位定时器,=8192;也就是说,当设置好初值后,系统会在这个初值的隔一个机器周期就会自增1,当累加到8192的时候溢出,然后触发中断。所以(8192-初值)*机器周期=定时器产生一次中断的时间。

如果我们要设定的定时器产生一次中断的时间为5ms,那么:

                                             机器周期=12*(1/12MHz)=1μs

                                             初值=(8192-5ms/1μs)=3192

13位定时器中,TH0整个 8 位全用,TL0只用低 5 位参与分频。

方式2

在定时器的方式0和方式1中,当计数溢出后,计数器变为0,因此在循环定时或循环计数时必须用软件反复设置计数初值,这必然会影响到定时的精度,同时也给程序设计带来很多麻烦。

方式2被称为8位初值自动重装的8位定时器/计数器,TL(0/1)从初值开始计数,当溢出时,在溢出标志TF(0/1)置1的同时,自动将TH(0/1)中的常数重新装入TL(0/1)中,使TL(0/1)从初值开始重新计数,这样避免了认为软件重新装初值所带来的时间误差,从而提高了定时的精度。

#include<reg52.h>
 
#define uchar unsigned char
#define uint  unsigned int
 
sbit led1=P1^0;
uint num;
 
void TIM0init(void)
{
     TMOD=0x02;    //设置定时器0为工作方式2
     TH0=6;   //装入初值
     TL0=6;    
     EA=1;    //开总中断
     ET0=1;   //开定时器中断
     TR0=1;   //启动定时器0
}
 
void T0_time()  interrupt 1      
{
     //相比上面的方式0,这里不需要认为加入重装初值的代码
     num++;
}
                          
void main()
{
    TIM0init(); 
    while(1)
    {
      if(num==4000)     //如果到了4000,说明1秒时间到
          {
             num=0;
         led1=~led1;   //让发光管状态取反
      }
    }
}

这个也是基于12MHz的振荡频率,TL0跟TL1必然是相同的,计算初值的方法跟上面一样。方式2为8位定时器/计数器,最多能装载=256个,相对方式0的13位和方式1的16位的少。方式2经历256个机器周期该计数器就会溢出。

还有一个值得注意的是num变量的类型变了,因为4000已经超出了uchar的方位,所以改为uint。

方式3

当选择方式3时,定时器T0就会被分成两个独立的计数器或者定时器。此时,TL0为8位计数器,计数溢出好置位TF0,并向CPU申请中断,之后需要软件重装初值; TH0也被固定为8位计数器,不过TL0已经占用了TF0和TR0,因此TH0将占T1的中断请求标志TF1和定时器启动控制为TR1。

为了防止中断冲突,定时器T0在方式3时,T1不能产生中断,但可以正常工作在方式0、1、2下。通常这种情况下T1将用作串行口的波特率发生器。

下面的例子是利用定时器方式3,TL0计数器对应的8位定时器实现第一个发光管以1s亮灭闪烁,用TH0计数器对应的8位定时器实现第二个发光管以0.5s亮灭闪烁。

#include<reg52.h>
 
#define uchar unsigned char
#define uint  unsigned int
 
sbit led1=P1^0;
sbit led2=P1^1;
uint num1,num2;
 
void TIMEinit(void)
{
     TMOD=0x03;  //设置定时器0为工作方式3    
     TH0=6;      //装初值
     TL0=6;
     EA=1;     //开总中断
     ET0=1;      //开定时器0中断
     ET1=1;      //开定时器1中断
     TR0=1;     //启动定时器0
     TR1=1;     //启动定时器0的高8位计数器
}
 
void TL0_time()  interrupt 1
{
     TL0=6;  //重装初值
     num1++;
}
 
void TH0_time()  interrupt 3  //占用T1定时器的中断号
{
     TH0=6;  //重装初值
     num2++;
}
                          
void main()
{
     TIMEinit();
     while(1)
     {
     if(num1>=4000)  //12*(1/12MHz)*(256-6)*4000=1s
         {                          
         num1=0;
         led1=~led1;
     }
         if(num2>=2000)  //12*(1/12MHz)*(256-6)*2000=0.5s
         {
         num2=0;
         led2=~led2 ;
         } 
    }
}

这里的num1>=4000而不是num1==4000,是为了稳妥起见,万一定时器计数超过了4000,而主循环还没来得及判断,则会错过4000.那led1就不能实现取反了。


本文由simyng创作, 采用知识共享署名4.0 国际许可协议进行许可,转载前请务必署名
  文章最后更新时间为:December 28th , 2020 at 07:33 pm