51单片机学习之定时器


概念基础

单片机延时基础

之前的延时我们都使用的是利用C语言的多次的空循环进行延迟操作,这样的缺点是:CPU时间被占用无法进行其他任务,导致系统效率降低。而且延时时间越长,该缺点越明显,所以,这种延时操作只适用于短暂延时,或简单的项目。

而定时器/计数器的使用,实现了单片机对时间的有效控制。单片机中有多个定时/计数器,都可以实现定时和计数的功能。

定时/计数器结构

定时/计数器结构

16位寄存器T0、T1分别由TH0、TL0和TH1、TL1四个8位计数器组成 ,其中两个定时器的区别为:

  • T0(定时器0)可分成2个独立的8位定时器,而定时器1则不能;
  • T1(定时器1)可作为串口的波特率发生器,而定时器0则不能。

还有两个特殊功能寄存器用于控制定时/计数器:

  • TMOD,工作方式控制寄存器,确定工作方式和功能;
  • TCON,工作状态控制寄存器,控制定时/计数器的启动、停止及溢出标志。

定时/计数器控制

工作方式寄存器TMOD

低四位(0,1,2,3)用于T0,高四位用于T1:

7 6 5 4 3 2 1 0
功能 GATE $C/\overline{T}$ M1 M0 GATE $C/\overline{T}$ M1 M0
  • GATE:门控位,用于控制定时器的启动是否受外部中断源的影响。当GATE = 0时,只要使用软件将TCON中的TR0或TR1置为1,就可以启动定时/计数器工作;当GATE = 0时,要用软件将TCON中的TR0或TR1为1,同时外部中断的引脚INT0/1也为高电平,才可以启动定时/计数器。即此时多了一个启动条件:INT0/1引脚需为高电平。

  • $C/\overline{T}$:定时/计数器模式选择位,当$C/\overline{T}=0$ 为定时模式;$C/\overline{T}= 1$为计数模式。

  • M1/M0:工作方式设置位,定时/计数器有四种工作方式:

    M1M0 工作方式 说明
    00 方式0 13位定时/计数器
    01 方式1 16位定时/计数器
    10 方式2 8位自动重装定时/计数器
    11 方式3 T0分成两个独立的定位/计数器,T1此方式停止计数
定时/计数器工作方式
  • 方式0:为13位定时计数器:由TL0的低5位(高3位未用)和TH0的8位组成。TL0的低5位溢出时向TH0进位,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。

    方式0

    计数个数与计数初值的关系:$X = 2^8-N$

  • 方式1:为16位定时计数器:由TL0作为低8位,TH0 作为高8位,组成了16位加1计数器 。

    方式1

    计数个数与计数初值的关系为:$X=2^{16}-N$

  • 方式2:8位自动重装初值定时/计数器

    方式2

    计数个数与计数初值的关系为:$X=2^8-N$

  • 方式3:只适用于定时/计数器T0,定时器T1处于方式3时相当于TR1=0,停止计数。

    方式3

    工作方式3将T0分成为两个独立的8位计数器TL0和TH0 。

工作状态寄存器TCON

位序 D7 D6 D5 D4 D3 D2 D1 D0
位名称 TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
  • TF1:T1溢出标志位

    当T1计数满溢出时,硬件将TF1置1,并申请中断。接入服务程序之后,其将自动清零;T1工作时,CPU可随时查询TF1的状态。所以,TF1可用作查询测试的标志。TF1也可以用软件置1或清0,同硬件的效果一样。

  • TR1:T1运行控制位

    TR1 = 1:启动定时器;TR1 = 0:关闭计时器,由软件控制。

  • TF0:T0溢出标志位

    功能同TF1,工作对象是T0。

  • TR0:T0运行控制位

    功能同TF1,工作对象是T0。

  • IE1:外部中断1请求标志位

  • IT1:外部中断1触发方式选择位
  • IE0:外部中断0请求标志位
  • IT1:外部中断0触发方式选择位

定时/计数器原理

定时/计数器的实质是加1计数器,由高8位和低8位2个寄存器组成。加1计数器有两个计数脉冲来源:一是由系统的时钟振荡输出脉冲经12分频后送来;二是由T0或T1引脚输入的外部脉冲源。

每来一个脉冲,计数器加1,当加到计数器全为1的时候,再输入一个脉冲使计数器归零,且计数器的溢出时TCON中的TF0或TF1置1,向CPU发出中断请求(定时/计数器中断允许时)。

如果定时/计数器处于定时模式,则表示定时时间结束;计数模式即计数值已满。所以,溢出时计数器的值减去计数器的初值即为加1计数器的计数值。

当设置为定时器模式时,加1计数器是对内部机器周期计数(1个机器周期相当于12个震荡周期,即计数频率为晶振频率的$1/12$)。计数个数乘以机器周期$T_{cy}$就是定时时间t。

设置为计数器模式时,外部事件计数脉冲由T0或T1引脚输入到计数器。在每个机器周期的S5P2期间采样T0,T1引脚电平。当某周期采样到一高电平输入,而下一周期又采样到一低电平时,则计数器加1,更新的计数值,在下一个周期的S3P1期间装入计数器。由于检测一个从1到0的下降沿需要两个机器周期,因此要求被采样的电平至少要维持一个机器周期。当晶振频率为12MHz时,最高计数频率不超过1/2MHz,即计数脉冲的周期要大于$2\mu{s}$。

CPU 时序的有关知识

  • S5P2: 代表第5状态中的第二节拍

机器周期:一般也叫CPU周期。 表示从内存读取一条指令字的最短时间,它是指令周期的最小单位。它等于单指令的周期长度 。 在51单片机中 一个机器周期=6状态周期=12时钟周期。

指令周期:一条指令包括1个或多个机器周期。所有的C语言代码最后都会编译成汇编代码来执行,而执行一条汇编指令需要的机器周期就叫做指令周期

状态周期:在8051单片机中把一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示)。一个状态周期等于两个时钟周期。

时钟周期:又叫振荡周期, 是指为单片机提供时钟信号的振荡源的周期( 晶振频率的倒数 )。

定时/计数器操作

  • 步骤:

    1. 选择工作方式(设置M1,M0)
    2. 选择控制方式(设置GATE)
    3. 选择模式:定时器还是计数器(设置C/T)
    4. 给定时/计数器赋初值(设置$TH_X$和$TL_x$)
    5. 开启定时器中断(设置ET0和ET1)
    6. 开启总中断(设置EA)
    7. 打开计数器(设置TR1和TR0)
  • 配置计数器:

    void TimerConfiguration(){
        TMOD = 0X01;    //定时器T0工作方式1
        TH0 = 0X3c;        //设置初始值
        TL0 = 0X0B0;    
        EA = 1;            //打开总中断
        ET0 = 1;        //打开定时器T0中断
        TR0 = 1;        //启动定时器T0
    }
  • 定时器T0初值计算(晶振为12MHz)

    51单片机内部时钟频率是外部时钟的12分频, 也就是说当外部晶振的频率输入到单片机里面的时候要进行12分频。比如说你用的是12MHz的晶振,那么单片机内部的时钟频率就是$12MHz\over12$ = 1MHz,当你使用12MHz的外部晶振的时候,机器周期=$1 \over1MHz$=$1\mu{s}$。

    当我们需要定时50ms时,则计数值为${50ms}\over{1{\mu}s}$=50000,所以初值 = 最大值计数值 - 50000 = 15536 (3cb0),即$TH_x$ =0x3c,$TL_x$= 0xb0。

程序代码

  • LED按定时器T0设定的时间周期闪烁

    #include<reg52.h>
    sbit led = P1 ^ 0;
    
    void t0_init(){
        TMOD = 0X01;    //设置定时器T0工作方式1    
        TH0 = 0X3c;        //设置初始值
        TH0 = 0Xb0;
        EA = 1;            //打开总中断
        ET0 = 1;        //打开定时器T0中断
        TR0 = 1;        //启动定时器T0
    }
    
    void main(){
        led = 1;
        t0_init();
        while(1);
    }
    
    void t0() interrupt 1{
        TH0 = 0X3c;        //设置初始值
        TL0 = 0Xb0;
        led = ~led;        
    }
  • LED按500ms/次的精确频率闪动

    #include<reg52.h>
    sbit led = P1 ^ 0;
    int i = 0;
    
    void t1_init(){
        TMOD = 0X10;    //设置定时器T1工作方式1    
        TH0 = 0X3c;        //设置初始值
        TH0 = 0Xb0;
        EA = 1;            //打开总中断
        ET1 = 1;        //打开定时器T1中断
        TR1 = 1;        //启动定时器T1
    }
    
    void main(){
        led = 1;
        t1_init();
        while(1){
            if(i == 10){
                led = ~led;
                i = 0;
            }
        }
    }
    
    void t1() interrupt 3{
        TH0 = 0X3c;        //设置初始值
        TL0 = 0Xb0;
           i++;
    }

文章作者: Mahoo Huang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mahoo Huang !
评论
  目录