定时器介绍

在介绍定时器之前我们先科普下几个知识:

  1. CPU时序的有关知识

    1. 振荡周期:为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)。
    2. 状态周期:2个振荡周期为1个状态周期,用S表示。振荡周期又称S周期或时钟周期。
    3. 机器周期:1个机器周期含6个状态周期,12个振荡周期。
    4. 指令周期:完成1条指令所占用的全部时间,它以机器周期为单位。例如:外接晶振为12MHz时51单片机相关周期的具体值为:
    • 振荡周期=1/12us;
    • 状态周期=1/6us;
    • 机器周期=1us;
    • 指令周期=1~4us;
  2. 学习定时器前需要明白的几点

    1. 51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
    2. 定时器/计数器和单片机的CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要CPU的参与。
    3. 51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加1。
      有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。
  3. 定时器作用

    1. 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
    2. 替代长时间的Delay,提高CPU的运行效率和处理速度
      (…)

单片机定时器原理

STC89C5X 单片机内有两个可编程的定时/计数器 T0、 T1 和一个特殊功能定时器 T2。 定时/计数器的实质是加 1 计数器(16 位) , 由高 8 位和低 8 位两个寄存器 THxTLx 组成。 它随着计数器的输入脉冲进行自加 1, 也就是每来一个脉冲, 计数器就自动加 1, 当加到计数器为全 1 时, 再输入一个脉冲就使计数器回零, 且计数器的溢出使相应的中断标志位置 1, 向 CPU 发出中断请求(定时/计数器中断允许时) 。 如果定时/计数器工作于定时模式, 则表示定时时间已到;如果工作于计数模式, 则表示计数值已满。可见, 由溢出时计数器的值减去计数初值才是加 1 计数器的计数值。

单片机定时/计数器结构

51 单片机定时器/计数器内部结构图

  • 上图中的 T0 和 T1 引脚对应的是单片机 P3.4 和 P3.5 管脚。 51 单片机定时/计数器的工作由两个特殊功能寄存器控制。
  • TMOD 是定时/计数器的工作方式寄存器, 确定工作方式和功能。
  • TCON是控制寄存器, 控制 T0、 T1 的启动和停止及设置溢出标志。
  1. 工作方式寄存器 TMOD
    工作方式寄存器 TMOD 用于设置定时/计数器的工作方式, 低四位用于 T0, 高四位用于 T1。 其格式如下:

-

  • GATE 是门控位, GATE=0 时, 用于控制定时器的启动是否受外部中断源信号的影响。 只要用软件使 TCON 中的 TR0 或 TR1 为 1, 就可以启动定时/计数器工作;GATA=1 时, 要用软件使 TR0 或 TR1 为 1, 同时外部中断引脚 INT0/1 也为高电平时, 才能启动定时/计数器工作。 即此时定时器的启动条件, 加上INT0/1 引脚为高电平这一条件。
  • C/T :定时/计数模式选择位

    • C/T =0为定时模式; C/T =1 为计数模式
  • M1M0: 工作方式设置位。 定时/计数器有四种工作方式。

定时/计数器工作方式设置表

  1. 控制寄存器 TCON
    TCON 的低 4 位用于控制外部中断,已在前面介绍。 TCON的高 4 位用于控制定时/计数器的启动和中断申请。 其格式如下:

  • TF1(TCON.7) : T1 溢出中断请求标志位。 T1 计数溢出时由硬件自动置 TF1为 1。 CPU 响应中断后 TF1 由硬件自动清 0。 T1 工作时, CPU 可随时查询 TF1 的状态。 所以, TF1 可用作查询测试的标志。 TF1 也可以用软件置 1 或清 0, 同硬件置 1 或清 0 的效果一样。
  • TR1(TCON.6) : T1 运行控制位。 TR1 置 1 时, T1 开始工作; TR1 置 0 时,T1 停止工作。 TR1 由软件置 1 或清 0。 所以, 用软件可控制定时/计数器的启动与停止。
  • TF0(TCON.5) : T0 溢出中断请求标志位, 其功能与 TF1 类同。
  • TR0(TCON.4) : T0 运行控制位, 其功能与 TR1 类同。

定时/计数器的工作方式

  • 方式 0

    方式 0 为 13 位计数, 由 TL0 的低 5 位(高 3 位未用) 和 TH0 的 8 位组成。TL0 的低 5 位溢出时向 TH0 进位, TH0 溢出时, 置位 TCON 中的 TF0 标志, 向 CPU发出中断请求。 其结构图如下所示:

    门控位 GATE 具有特殊的作用。 当 GATE=0 时, 经反相后使或门输出为 1, 此时仅由 TR0 控制与门的开启, 与门输出 1 时, 控制开关接通, 计数开始; 当 GATE=1时, 由外中断引脚信号控制或门的输出, 此时控制与门的开启由外中断引脚信号和 TR0 共同控制。 当 TR0=1 时, 外中断引脚信号引脚的高电平启动计数, 外中断引脚信号引脚的低电平停止计数。 这种方式常用来测量外中断引脚上正脉冲的宽度。 计数模式时, 计数脉冲是 T0 引脚上的外部脉冲。计数初值与计数个数的关系为: X=2(13) -N。 其中 2(13) 表示 2 的 13 次方。

  • 方式 1

    方式 1 的计数位数是 16 位, 由 TL0 作为低 8 位, TH0 作为高 8 位, 组成了16 位加 1 计数器。 其结构图如下所示:

    计数初值与计数个数的关系为: X=2(16) -N。

  • 方式 2

    方式 2 为自动重装初值的 8 位计数方式。 工作方式 2 特别适合于用作较精确的脉冲信号发生器。 其结构图如下所示:

    计数初值与计数个数的关系为: X=2(8) -N。

  • 方式 3

    方式 3 只适用于定时/计数器 T0, 定时器 T1 处于方式 3 时相当于 TR1=0,停止计数。 工作方式 3 将 T0 分成为两个独立的 8 位计数器 TL0 和 TH0。 其结构如下所示:

    这几种工作方式中应用较多的是方式 1 和方式 2。 定时器中通常使用定时器方式 1, 串口通信中通常使用方式 2。

定时器配置

在使用定时器时,应该如何配置使其工作?其步骤如下(各步骤顺序可任意):

  1. 对TMOD赋值,以确定T0和T1的工作方式,如果使用定时器0即对T0配置,如果使用定时器1即对T1配置。
  2. 根据所要定时的时间计算初值,并将其写入TH0、TL0或TH1、TL1。
  3. 如果使用中断,则对EA赋值,开放定时器中断。
  4. 使TR0或TR1置位,启动定时/计数器定时或计数。

计算定时/计数器初值:

前面我们介绍过机器周期的概念,它是CPU完成一个基本操作所需要的时间。其计算公式是:机器周期=1/单片机的时钟频率。51单片机内部时钟频率是外部时钟的12分频,也就是说当外部晶振的频率输入到单片机里面的时候要进行12分频。比如说你用的是12MHZ晶振,那么单片机内部的时钟频率就是12/12MHZ,当你使用12MHZ的外部晶振的时候,机器周期=1/1M=1us。如果我们想定时1ms的初值是多少呢?1ms/1us=1000。也就是要计数1000个,初值=65535-1000+1 (因为实际上计数器计数到66636(2的16次方)才溢出,所以后面要加1)=64536=FC18H,所以初值即为THx=0XFC,TLx=0X18。

这里以定时器0为例介绍配置定时器工作方式1、设定1ms初值,开启定时器计数功能以及总中断,如下:

voidTimer0Init()
{
    TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
    TH0=0XFC;//给定时器赋初值,定时1ms
    TL0=0X18;
    ET0=1;//打开定时器0中断允许
    EA=1;//打开总中断
    TR0=1;//打开定时器
}

定时器模块化

Timer0.c文件

#include <REGX52.H>

/**
  * @brief 定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */

void Timer0Init(void)
{
    TMOD &= 0xF0;        //设置定时器模式,把TMOD的低四位清零,高四位保持不变
    TMOD |= 0x01;        //设置定时器模式,把TMOD的最低位置1,高四位保持不变
    TL0 = 0x18;        //设置定时初始值
    TH0 = 0xFC;        //设置定时初始值
    TF0 = 0;        //清除TF0标志
    TR0 = 1;        //定时器0开始计时

    ET0=1;
    EA=1;
    PT0=0;
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
    static unsigned int T0Count;//静态局部变量
    TL0 = 0x18;        //设置定时初始值
    TH0 = 0xFC;        //设置定时初始值
    T0Count++;
    if(T0Count>=1000)
    {
        T0Count=0;

    }

}
*/

"&"清零:1&上任何数=任何数,0&上任何数=0。1010 0011 & 1111 0000 = 1010 0000

“|”置1:0|上任何数=任何数。1010 0000 | 0000 0001 = 1010 0001

独立按键模块

#include <REGX52.H>
#include "Delay.h"

/**
  * @brief 获取独立按键的键码
  * @param  无
  * @retval 按下按键的键码,范围0~4,无按键按下时返回值为0
  */

unsigned char Key()
{
    unsigned char KeyNumber=0;

    if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
    if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
    if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
    if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
    return KeyNumber;
}

按键控制LED流水灯程序

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "INTRINS.H"

unsigned char KeyNum,LEDMode;
void main()
{
    P2=0xFE;
    Timer0Init();
    while(1)
    {
        KeyNum=Key();
        if(KeyNum)//如果KeyNum不等于零
        {
            if(KeyNum==1)
            {
                LEDMode++;
                if(LEDMode>=2)LEDMode=0;//每按下一次按键LEDMode都是0101变化
            }
        }

    }
}



void Timer0_Routine() interrupt 1
{
    static unsigned int T0Count;//静态局部变量
    TL0 = 0x18;        //设置定时初始值
    TH0 = 0xFC;        //设置定时初始值
    T0Count++;
    if(T0Count>=500)
    {
        T0Count=0;
        if(LEDMode==0)
            P2=_crol_(P2,1);//循环左移
        if(LEDMode)
            P2=_cror_(P2,1);//循环右移
    }

}

#include "INTRINS.H"函数库
iror(unsigned int, unsigned char):循环右移
irol (unsigned int, unsigned char):循环左移

定时器时钟程序

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec=55,Min=59,Hour=23;
void main()
{
    LCD_Init();
    Timer0Init();

    LCD_ShowString(1,1,"Clock:");
    LCD_ShowString(2,1,"  :  :");


    while(1)
    {
        LCD_ShowNum(2,1,Hour,2);
        LCD_ShowNum(2,4,Min,2);
        LCD_ShowNum(2,7,Sec,2);
    }
}

void Timer0_Routine() interrupt 1
{
    static unsigned int T0Count;//静态局部变量
    TL0 = 0x18;        //设置定时初始值
    TH0 = 0xFC;        //设置定时初始值
    T0Count++;
    if(T0Count>=1000)
    {
        T0Count=0;
        Sec++;
        if(Sec>=60)
        {
            Sec=0;
            Min++;
            if(Min>=60)
            {
                Min=0;
                Hour++;
                if(Hour>=24)
                {
                    Hour=0;
                }
            }
        }

    }

}

参考资料

51单片机入门教程-2020版 程序全程纯手打 从零开始入门\_哔哩哔哩\_bilibili

最后修改:2023 年 10 月 21 日
如果觉得我的文章对你有用,请随意赞赏