飞书文档https://x509p6c8to.feishu.cn/wiki/IUiswrNZpij2jpkbU6UcmCBpn9b
一、简介
定时器,顾名思义,是用来控制时间的。它可以根据设定的时间间隔,准时产生触发信号,用于控制程序的执行流程。
我们为什么要使用定时器呢?
回想一下之前写的程序,在实现延时这一功能时,我们使用了delay() 函数,这个函数并没有采用任何外设,只是写了两个循环嵌套,让cpu计数,当计数完成也就代表延时结束,简单点说就是让cpu通过不停的计数来消耗时间,所以这种方式有个很大的弊端,就是当cpu "死跑" 延时的时候,是做不了其他事情的,如果这个时候需要一个工具来帮助cpu完成计时,这就是定时器的作用。
51单片机有两个定时器,T0和T1。这两个定时器可以通过寄存器进行配置,可实现定时和计数功能。
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 定时器的本质原理是,每经过一个机器周期,计数器的值就加1。当计数器的值达到设定的值时,就会产生一个中断信号,通知CPU进行相应的处理。 在定时器模式下,计数器的输入信号是机器周期。因此,当配置为定时器使用时,每经过1个机器周期,计数存储器的值就加1。这种方式可以用来产生定时信号,以控制时间间隔或者实现定时操作。比如在工厂自动化生产线、智能家居控制、网络通信等场景中,都需要定时器来进行精准的时间控制。 而在计数器模式下,计数器的输入信号是外部信号。当配置为计数器时,每来一个负跳变信号就加1,以此达到计数的目的。这种方式可以用来对外部事件进行计数或者脉冲测量。如统计按键按下的次数,统计电机编码器转的圈数。 |
我们可以根据具体的需求来选择使用定时器还是计数器。比如,在需要精确控制程序执行时间的情况下,我们可以选择使用定时器;而在需要对某些事件进行计数时,则可以选择使用计数器。
二、定时**/** 计数器相关的寄存器

模式选择
STC89C52RC 的 T0 和 T1 均有 4 种工作模式:
- 模式0:13位定时器/计数器
- 模式 1 : 16 位定时器 / 计数器 (常用) 13 位适用于短时间计时, 16 位适用于较长时间计时
- 模式2:8位自动重装模式时器/计数器 此模式下,计时到设定值后会自动重新开始计时,实现周期性计时功能
- 模式3:两个8位计数器(禁用定时器1,把定时器2设置为两个8位计数器)



|------------------------------------------------------------------------------------|
| TMOD这个寄存器是不可位寻址的, 如要想让M0这位置一的话,直接 M0=1;这个写法是错误的,因为它不能位寻址。 只能 TMOD = 0x01(00000001) |
|----------------------------------------------------------------------------------------------------------------------------|
| 例如初始化定时器0的模式为模式1, 设置TMOD寄存器的第0位M0为1,第1位M1为0 0b0000 0001 TMOD=0x01; //设置16bit定时器模式 如果是初始化定时器1的模式为模式1 0b0001 0000 TMOD=0X10; |
模式****1 :时钟源选择


0b 0000 0001
TMOD = 0x01
C/T 寄存器:时钟来源选择
来源一: SYSclk ,系统时钟,作为定时模式(常用)
来源二:外部脉冲输入引脚 T0 ,也就是 P3.4 引脚,作为计数模式

|------------------------------------------------------------------------------------|
| 两个定时器的C/T寄存器对应TMOD寄存器的第2位和第6位 这里设置定时器0时钟源为系统时钟,则设置TMOD第2位为0 0b0000 0001 TMOD=0x01; |
系统时钟: 12T/6T 分频模式选择

上面的开关是选择分频器,选择12分频,假设芯片时钟是12Mhz进行12分频就是1Mhz(1MHz=1000kHz,1kHz=1000Hz,每秒钟计数100万次),每个计数一次就是1us,这样配置通这条线路送到计数器,每隔1us就要记1次数。
在烧录时设置,默认为12T模式

开关控制


非门:输入 1 ,输出 0 ,输入 0 ,输出 1
或门:输入任意一个为 1 ,输出都为 1
与门:输入两个都为 1 ,输出才是 1 ,其它都是 0
GATE 、 INT0 、 TR0 一起作为定时器开关的控制寄存器
|------|------|-----|-------|
| GATE | INT0 | TR0 | 定时器状态 |
| 0 | / | 1 | 使能 |
| / | 1 | 1 | 使能 |
当GATE为0时,经过非门变为1,再与INT0通过或门,此时无论INT0为0/1,都会输出1,然后与TR0经过与门,TR0也要为1,最终才能输出1。
当INT0为1时,不管GATE是0/1,通过或门都输出1,然后与TR0经过与门,TR0也要为1,最终才能输出1。
GATE **:**门控位;GATE=0,设置软件启动;


INT0 **:**INT0=1 设置为硬件启动,INT0对应外部中断0,也就是可以通过外部中断0可以控制定时器开关。
|-------------------------------------------------------------------------------------------|
| 这里我们设置为软件启动 两个定时器的GATE寄存器对应TMOD寄存器的第3位和第7位 这里设置定时器0使能,则设置TMOD第3位为0 0b0000 0001 TMOD=0x01; |
TR0 **:**运行控制位


可位寻址
|-----------------------|
| TR0 = 1; //使能定时器0开始计时 |
最终
|-----------------------------------------------------------|
| TMOD=0x01; //设置定时器0为:模式1、系统时钟源、软件启动 TR0 = 1; //使能定时器0开始计时 |
预装载值

TL0 TH0 :定时器预装载值
16位=TL0寄存器的8bit+TH0寄存器的8bit

可以看到,计数器里有两个时基单元,TL0(低8位)和 TH0(高8位)总共16位,所以叫16位定时/计数器,它最多可以计数 2^16=65535次,也就是定时器增加到 65536 时,会触发一次溢出。
单片机系统时钟是11.0592MHz时,在12T模式,定时器频率为11.0592MHz除12为921600Hz,就是一秒计数921600次,10ms计数9216次。 20ms 9216*2
如果我们需要定时器10ms溢出一次,我们可以设置定时器的预装载值为65536-9216,这样,计算9216次后达到65536就会溢出一次。
|-----------------------------------------------------|
| //10ms定时器 TIMS=65536-9216; TL0=TIMS; TH0=TIMS>>8; |
溢出中断
TF0:溢出标志位



|---------------------------------|
| ET0=1; //使能定时器0中断 EA=1; //使能总中断 |
所以,下方是使能定时器0定时10ms的完整程序
#include <reg52.h>
#define TIMS (65536-9216)
void main()
{
TMOD = 0x01; //配置定时器0为16位定时器,TH0、TL0全用
TH0 = TIMS >> 8; //设置定时初值高8位
TL0 = TIMS; //设置定时初值低8位
ET0 = 1; //开启定时器0中断
EA = 1; //开启全局中断
TR0 = 1; //定时器0开始计数
while(1);
}
//10ms执行一次
void Timer0() interrupt 1
{
//每次产生中断后重新设置下次定时器初值
TH0 = TIMS >> 8;
TL0 = TIMS;
//这里可以写中断函数要执行的操作
}
如果我们想让LED间隔1s闪烁,我们可以添加指示灯操作,添加变量count,每次触发定时器中断时加1,加到1000ms,也就是1s时,改变LED的状态,就可以实现啦。
#include <reg52.h>
#define TIMS (65536-9216)
sbit led = P2^7;
unsigned int count = 0;
void main()
{
TMOD = 0x01; //配置定时器0为16位定时器,TH0、TL0全用
TH0 = TIMS >> 8; //设置定时初值高8位
TL0 = TIMS; //设置定时初值低8位
ET0 = 1; //开启定时器0中断
EA = 1; //开启全局中断
TR0 = 1; //定时器0开始计数
while(1);
}
//10ms执行一次
void Timer0() interrupt 1
{
//每次产生中断后重新设置下次定时器初值 - 10毫秒产生1次中断
TH0 = TIMS >> 8;
TL0 = TIMS;
//1000毫秒执行一次P1电平反转
count++;
if(count >= 100)
{
led = ~led;
count = 0;
}
}