【51单片机系列】C51中的定时计数器
STC89C5X系列含有3个定时器:定时器0、定时器1、定时器2。51系列单片机一定有基本的2个定时器(定时器0和定时器1),但不全有3个中断,需要查看芯片手册,通常使用的是基本的2个定时器:定时器0/1。
一、 定时器介绍
在介绍定时器之前,需要先了解以下内容。
1.1、CPU时序的有关知识
① 振荡周期:为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期);
② 状态周期:2个振荡周期为1个状态周期,用S表示。振荡周期又称为S周期或时钟周期;
③ 机器周期:1个机器周期包含6个状态周期,12个振荡周期;
④ 指令周期:完成1条指令所占用的全部事件,以机器周期为单位。
即\(T_{机器周期} = 6T_{状态周期} = 12T_{振荡周期}\)。
比如,外接晶振为12MHz时,51单片机相关周期的具体值为:
振荡周期=1/12us;
状态周期=2x振荡周期=1/6us;
机器周期=6x状态周期=12x振荡周期=1us;
指令周期=1 ~ 4us;
1.2、学习定时器前需要明确几点
① 51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时/计数器。
② 定时/计数器和单片机的CPU是相互独立的,定时/计数器工作的过程是自动完成的,不需要CPU参数。
③ 51单片机中的定时/计数器是根据机器内部的时钟或者外部的脉冲信号对寄存器中的数据加1.
有了定时/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时/计数器处理,CPU转去处理一些复杂的事情,同时可以实现精确定时作用。
1.3、单片机定时器原理
STC89C5X单片机内有两个可编程的定时/计数器T0、T1和一个特殊功能寄存器T2。
定时/计数器的实质是加1计数器(16位),由高8位和低8位两个寄存器THx和TLx组成。它随着计数器的输入脉冲进行自加1,也就是每来一个脉冲,计数器就自动加1,当加到计数器全为1时,再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置1,向CPU发出中断请求(定时/计数器中断允许时)。如果定时/计数器工作与定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。由溢出时计数器的值减去计数初值才是加1计数器的计数值。
1.4、51单片机定时/计数器结构
51单片机定时/计数器内部结构如下:
上图中的T0和T1引脚对应的是单片机P3.4和P3.5管脚。
51单片机定时/计数器的工作由两个特殊功能寄存器控制。TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。
- 工作方式寄存器TMOD
工作方式寄存器TMOD用于设置定时/计数器的工作方式,低四位用于T0,高四位用于T1。其格式如下:
- GATE是门控位,用于控制定时器的启动是否受外部中断源信号的影响。
- GATE=0时,只要用软件使TCON中的TR0或TR1为1,就可以启动定时/计数器工作;
- GATE=1时,要用软件使TR0或TR1为1,同时外部中断引脚INT0/1也为高电平时,才能启动定时/计数器工作。即此时定时器的启动条件,加上了INT0/1引脚为高电平这一条件。
- C/T:定时/计数模式选择位。C/T=0为定时模式;C/T=1为计数模式。
- M1M0:工作方式设置位。定时/计数器有四种工作方式:
定时/计数器工作方式设置表
M1M0 | 工作方式 | 说明 |
---|---|---|
00 | 方式0 | 13位定时/计数器 |
01 | 方式1 | 16位定时/计数器 |
10 | 方式2 | 8位自动重装定时/计数器 |
11 | 方式3 | T0分成两个独立的8位定时/计数器;T1此方式停止计数 |
- 控制寄存器TCON
TCON的低四位用于控制外部中断,高四位用于控制定时/计数器的启动和中断申请。格式如下:
- TF1:T1溢出中断请求标志位。T1计数溢出时由硬件自动置TF1为1。CPU响应中断后TF1由硬件自动清0。T1工作时,CPU可随时查询TF1的状态。所以,TF1可用作查询测试的标志。TF1也可以用软件置1或清0,同硬件置1或清0的效果一样。
- TR1:T1运行控制位。TR1置1时,T1开始工作;TR1置0时,T1停止工作。TR1由软件置1或清0.所以,用软件可控制定时/计数器的启动与停止。
- TF0:T0溢出中断请求标志位,其功能与TF1类似;
- TR0:T0运行控制位,其功能与TR1类似。
1.5、定时/计数器的工作方式
定时/计数器工作方式有四种:方式0(13位定时/计数器)、方式1(16位定时/计数器)、方式2(8位自动重装定时/计数器)、方式3(T0分成两个独立的8位定时/计数器,T1此方式停止计数)。
- 方式0
方式0是13位计数,由TL0的低5位和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\)。
- 方式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。
二、 定时器配置
使用定时器时,按照如下步骤配置使其工作,各步骤的顺序可任意。
① 对TMOD赋值,以确定T0和T1的工作方式,如果使用定时器0即对T0配置,如果使用定时器1即对T1配置。
② 根据所要定时的时间计算初值,并将其写入TH0、TL0或TH1、TL1。
③ 如果使用中断,则对EA赋值,开放定时器中断。
④ 使TR0或TR1置位,启动定时/计数器定时或计数。
上面的步骤中有一个定时/计数器初值的计算,接下来介绍下如何计算定时/计数器初值。
机器周期是CPU完成一个基本操作所需要的时间。其计算公式是:机器周期=1/单片机的时钟频率。
51单片机内部时钟频率是外部时钟的12分频,也就是说当外部晶振的频率输入到单片机里面的时候要进行12分频。
比如使用的是12MH晶振,那么单片机内部的时钟频率是12/12MHz。当使用12MHz的外部晶振的时候,机器周期=1/1MHz=1us。
那么要定时1ms的初值为多少?首先计数个数为1ms/1us=1000,即要计数1000个;计数初值=65535-1000+1=64536=FC18H(因为实际上计数器计数要达到65536才溢出,所以后面加1),初值即为THx=0xFC,TLx=0x18。
直到了如何计算定时/计数器初值,那么想定时多长时间都可以计算出来。由于定时计数器位数有限,不可能直接通过初值定时很长时间,如果要实现很长时间的定时,比如定时1秒,可以通过初值设置定时1ms,每当定时1ms结束后又重新赋初值,并且设定一个全局变量累计定时1ms的次数,当累计到1000次表示已经定时1秒了。需要其它定时时间类似操作,这样就可以使用定时器来实现精确延时代替之前的delay函数。
示例,以定时器0为例配置定时器工作方式1、设定1ms初值,开启定时器计数功能及总中断,如下:
void Timer0Init()
{
TMOD |= 0x01; // 选择为定时器0模式,工作方式1,仅用TR0打开启动
TH0 = 0xFC; // 给定时器赋初值,定时1ms
TL0 = 0x18;
ET0 = 1; // 打开T0中断允许位
EA = 1; // 打开总中断
TR0 = 1; // 打开定时器
}
三、定时器使用示例
本示例实现的功能:通过定时器0中断控制LED间隔1秒闪烁。在proteus中仿真,使用到的资源有:LED模块和定时器0。硬件电路很简单,只使用到LED模块,定时器0属于51单片机内部资源,通过软件配置即可使用。proteus中设计LED模块如下:
软件实现:
/*
实现功能:使用定时器0控制LED模块间隔1秒亮灭
寄存器介绍:
TMOD 高四位用于T1,低四位用于T0;
GATE=0表示仅TRx启动定时/计数器,GATE=1表示外部中断+TRx启动定时/计数器
C/T=0表示定时模式,C/T=1表示计数模式
M1M0表示工作方式
[2023-12-19] zoya
*/
#include "reg52.h"
#include "typedef.h"
#define GPIO_LED P2
/*************************************************************************
* 函数名: Timer0Init
* 函数功能: 定时器0初始化,选择工作方式1,仅TR0打开启动,定时1ms
* 输入: void
* 输出: void
**************************************************************************/
void Timer0Init()
{
// 1. 对TMOD赋值,确定T0为工作方式1,仅TR0打开启动
TMOD |= 0x01; // 低四位为 0001,GATE=0仅TR0启动,C/T=0定时模式,M1M0=01工作方式1
// 2. 根据定时时间计算初值,赋初值
TH0 = 0xFC;
TL0 = 0x18;
// 3. 使用中断,打开总中断,打开定时器中断
EA=1;
ET0=1;
// 4. 置位TR0,启动定时
TR0=1;
}
void main()
{
Timer0Init(); // 外部中断0初始化
while(1);
}
/*************************************************************************
* 函数名: Timer0
* 函数功能: 定时器0中断服务函数,
* 当进入该函数时表示定时时间已到,需要重新赋初值
* 输入: void
* 输出: void
**************************************************************************/
void Timer0() interrupt 1
{
static u16 i; // 统计定时1ms的次数
TH0 = 0xFC;
TL0 = 0x18;
i++;
if(1000 == i) // 如果定时1ms的次数达到了1000次表示过了1s
{
i=0;
GPIO_LED = ~GPIO_LED;
}
}
仿真结果: