51 定时器
文章目录
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。定时器可用于代替Delay,用于长时间计时或是每隔特定时间执行特定操作
定时器的工作不需要单独执行,只需启动即可,相较于Delay,定时器在工作时不会影响其它操作(如:按键读取)
在STC89C53RC中有3个定时器(T0、T1、T2),T0和T1与传统的51单片机(Intel生产的旧版单片机)兼容,T2是此型号单片机增加的资源(定时器的数量、操作方式等和单片机型号有关,一般来说,T0和T1是所有51单片机共有的)
定时器分为三个部分:时钟、计数单元、中断系统
定时器的构成
STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器
模式2:8位自动重装模式
模式3:两个8位计数器
在一般情况下,我们均使用模式1
工作模式1框图*(图源:普中科技开发手册)*:
时钟
SYSclk:系统时钟,即晶振周期,本开发板上的晶振为11.0592MHz
(晶振在通电后内部的元件(压电陶瓷)会产生固定频率的震动)
T0 Pin为单片机的外部接口(即时钟可以时系统时钟,也可以是外部时钟,此时单片机相当于一个计数器)
12T代表12分频(11.0592*1 000 000 / 12 = 921,600)即输出频率为921600Hz
关于分频和时间周期
时间周期:通常指一个时钟周期
机器周期:机器周期(Machine Cycle)是计算机中的一个基本时间单位,它指的是执行一条机器指令所需的时间。
(即机器周期≠时间周期,一个机器周期可以包含多个时间周期)
- 分频:在嵌入式系统设计中,我们有时需要将一个高频率的时钟信号分频得到一个较低的频率用于其他模块的工作。这种操作称为分频,常见的分频方式包括2分频、4分频、8分频、12分频等。分频的目的可以是为了减小系统的功耗、降低干扰、适配不同的模块工作频率等。
(分频的作用实际上是增长时钟周期(也可以降低功耗),绝大多数51单片机默认12分频,原因:老版51单片机使用复杂操作集,导致1个时钟周期内CPU根本无法完成一条指令,因此默认需要12个时钟周期)
C/T:如果是高电平(1),那么就是计数器的功能(counter),如果是低电平(0)那就是计数器的功能(Timer)
计数单元
TL0和TH0为一个16位的计数器,有两个字节,高字节为TH,低字节为TL(即最多计65535)
每接收到一个脉冲,计数器加一
(当达到65535时,会溢出,变成0,当溢出时,会置标志位,即TF0,有标志位就会向中断系统申请中断)
中断系统
中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的,许多设备都需要用到中断系统
当CPU收到紧急事件请求时,会暂停当前工作,转而去处理紧急事件,在紧急事件处理完后再进行原来的操作。当存在多个中断源时,CPU会根据优先级进行处理,首先响应高优先级的中断请求。即中断就是对紧急事件的处理(相当于同时进行两项任务),高优先级可以打断低优先级中断
计数器的溢出就是一种中断源
对于STC89C52RC
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号*(图源:普中科技开发手册)*:
在单片机系统中,中断号是用来唯一标识不同中断源的编号。每个中断源都会被分配一个特定的中断号,用来识别它们所触发的中断
通过中断号,单片机可以准确地找到对应的中断服务程序进行处理。
(注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等)
中断结构*(图源:普中科技开发手册)*:
定时器的工作流程
流程:时钟->12分频->选择定时器模式->控制启动->计数器增加->计数器溢出,产生中断->通过TF0、两个开关->通过一个选择位,决定优先级
定时器可以单独工作,不一定要中断(此时主程序不会响应定时器,需要自行查询)
定时器相关寄存器
单片机通过配置寄存器来控制内部线路的连接,通过内部线路的不同连接来实现不同功能,即上面提到的各种开关就是通过寄存器控制的
寄存器是连接软硬件的媒介(寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个(位)寄存器背后都连接了一根导线,控制着电路的连接方式)
定时器0/1相关寄存器*(图源:普中科技开发手册)*
在对应位置赋值即可控制对应开关
C/T:分别控制定时器0和1
M1/M0:控制模式
GATE:门控端
M1/0:控制模式
TR0:是否启动定时器
TMOD(Timer Mode)
(控制工作模式)
不可位寻址:不能对寄存器中的每一位单独赋值,只能对寄存器整体赋值
GATE 门控端:计时器的启动有两种控制方式:
1、有TR0直接控制(1启动,0暂停)
2、有TR0和外部引脚共同控制(GATE置1时,只有INT0为高电平,且TR0为1才启动,当GATE至0时,为TR0单独控制)
【GATE右边的三角带个圈元件叫非门(又叫反相器),当输入0时;输出1,当输入为1时,输出0,即将数据反向】
【右边与INT0相连的,有点像三角形的元件叫或门,或门有多个输入端(比如此处有2个输入),一个输出端,只要输入中有一个为高电平时(逻辑“1”),输出就为高电平(逻辑“1”);只有当所有的输入全为低电平(逻辑“0”)时,输出才为低电平(逻辑“0”)】
【再右边与TR0相连的叫与门 ,有多个输入端,一个输出端。当所有的输入同时为高电平(逻辑1)时,输出才为高电平,否则输出为低电平(逻辑0)】
模式1:GATE置0->非门输出1->无论INT0置0还是1,或门均输出1->此时与门的输出取决于TR0
模式2:GATE置1->非门输出0->或门的输出取决于INT0
TCON(Timer Control)
(控制计数器)
(图源:普中科技开发手册)
即查询中断即查询TF0
可位寻址:可以对寄存器中的每一位单独赋值
中断寄存器
(图源:普中科技开发手册)
EA即为总开关
定时器的设置
计数范围为0~65535
每约1.085us计数加一(11.0592Mhz下),共及时约71105us,远小于1s,因此我们需要通过多次中断来获得1s的计时
手工计算
#include <STC89C5xRC.H>
void Timer0_init()
//配置寄存器
{
//TMOD = 0x01;
//0000 0001
//前四位为定时器1,不管;第五位为GATE,第六位为C/T,七、八位为M1、M0
//此处不可位寻址,只能整体赋值
//这样写有问题:在配置Timer1的时候会把Timer2的设置抹去
TMOD = TMOD & 0xF0;
//把TMOD的第四位清零而高四位不变(按位与:有0为0)
TMOD = TMOD | 0x01;
//TMOD的最低位置一(按位或:有1为1)
//TCON可为寻址
TF0 = 0;
//清零
TR0 = 1;
TH0 = 64613 / 256;
TL0 = 64613 % 256;
//约921.65次为1ms,即初始化为65535 - 922 = 64613
//分别取高位和低位
//配置中断
ET0 = 1;
EA = 1;
PT0 = 0;
}
void main()
{
Timer0_init();
//初始化定时器,此时定时器开始运行
//定时器的工作不需要单独执行,只需启动即可
while(1)
{
}
}
//当产生中断时,会跳转到此函数执行,执行完后再回到原函数
unsigned int T0_count = 0;
//计算中断次数
void Timer0_Routime() interrupt 1//根据中断号写
{
TH0 = 64613 / 256;
TL0 = 64613 % 256;
//严格来讲这里需要再+1,否则会多算1ms
//重置计数器
T0_count++;
if(T0_count >= 1000)
//1000次中断为1s
{
T0_count = 0;
P20 = ~P20;
}
}
通过软件获取
我们可以通过stc-isp的内置功能直接获得配置完成的定时器
void Timer0_Init(void) //1毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
EA = 1;//需要自己加
PT0 = 0;//同上
}
void main()
{
Timer0_init();
//初始化定时器,此时定时器开始运行
//定时器的工作不需要单独执行,只需启动即可
while(1)
{
}
}
unsigned int T0_count = 0;
void Timer0_Isr(void) interrupt 1
{
TL0 = 0x66; //设置定时初始值
//这里比自己设置的多一位,因为自己算出来的64613 + 922 = 65535 不会溢出,要再加1才溢出
TH0 = 0xFC; //设置定时初始值
T0_count++;
if(T0_count >= 1000)
{
T0_count = 0;
P20 = ~P20;
}
}
对定时器进行模块化
#include<STC89C5xRC.H>
/**
* @brief 定时器0初始化,1ms @11.0592MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void) //1毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
EA = 1;//需要自己加
PT0 = 0;//同上
}
/*
void Timer0_Isr(void) interrupt 1
{
static unsigned int T0_count = 0;
TL0 = 0x66;
TH0 = 0xFC;
T0_count++;
if(T0_count >= 1000)
{
T0_count = 0;
P20 = ~P20;
}
}
*/
//不容易模块化的部分,放在此处作为模板
定时器的运用
独立按键控制LED流水灯模式
#include <STC89C5xRC.H>
#include <INTRINS.H>
#include "Timer0.h"
#include "Inde_Key.h"
unsigned char KeyNum = 0;
unsigned char LEDMode = 0;
void main()
{
P2 = 0xFE;//点亮最低位LED
Timer0_init();
//初始化定时器,此时定时器开始运行
//定时器的工作不需要单独执行,只需启动即可
while(1)
{
KeyNum = Inde_Key();
if(KeyNum)
{
if(KeyNum == 1)
{
LEDMode++;
if(LEDMode == 2)
{
LEDMode = 0;
}
}
}
}
}
void Timer0_Isr(void) interrupt 1
{
static unsigned int T0_count = 0;
TL0 = 0x66;
TH0 = 0xFC;
T0_count++;
if(T0_count >= 500)//间隔500ms
{
T0_count = 0;
if(LEDMode == 0)
{
P2 = _crol_(P2, 1);
//左移,当移到最高位时,回到第一位,是一个循环过程
//属于INTRINS.H
}
if(LEDMode == 1)
{
P2 = _cror_(P2, 1);
//右移
}
}
}
定时器时钟
#include <STC89C5xRC.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char second = 0;
unsigned char minute = 0;
unsigned char hour = 0;
void main()
{
Timer0_Init();
LCD_Init();
LCD_ShowString(1, 1, "Clock:");
while(1)
{
LCD_ShowNum(2, 1, hour, 2);
LCD_ShowString(2, 3, ":");
LCD_ShowNum(2, 4, minute, 2);
LCD_ShowString(2, 6, ":");
LCD_ShowNum(2, 7, second, 2);
}
}
void Timer0_Isr(void) interrupt 1
{
static unsigned int T0_count = 0;
TL0 = 0x66;
TH0 = 0xFC;
T0_count++;
if(T0_count >= 1000)
{
T0_count = 0;
second++;
//LCD_ShowNum不宜放在这里,该函数运行时间较长
//中断函数中不应执行时间较长的操作
if(second == 60)
{
second = 0;
minute++;
if(minute == 60)
{
minute = 0;
hour++;
if(hour == 24)
{
hour = 0;
}
}
}
}
}