单片机-89C51部分:8、定时器
飞书文档https://x509p6c8to.feishu.cn/wiki/IUiswrNZpij2jpkbU6UcmCBpn9b
一、简介
定时器,顾名思义,是用来控制时间的。它可以根据设定的时间间隔,准时产生触发信号,用于控制程序的执行流程。
我们为什么要使用定时器呢?
回想一下之前写的程序,在实现延时这一功能时,我们使用了delay() 函数,这个函数并没有采用任何外设,只是写了两个循环嵌套,让cpu计数,当计数完成也就代表延时结束,简单点说就是让cpu通过不停的计数来消耗时间,所以这种方式有个很大的弊端,就是当cpu “死跑” 延时的时候,是做不了其他事情的,如果这个时候需要一个工具来帮助cpu完成计时,这就是定时器的作用。
51单片机有两个定时器,T0和T1。这两个定时器可以通过寄存器进行配置,可实现定时和计数功能。
定时器的本质原理是,每经过一个机器周期,计数器的值就加1。当计数器的值达到设定的值时,就会产生一个中断信号,通知CPU进行相应的处理。 |
我们可以根据具体的需求来选择使用定时器还是计数器。比如,在需要精确控制程序执行时间的情况下,我们可以选择使用定时器;而在需要对某些事件进行计数时,则可以选择使用计数器。
二、定时/计数器相关的寄存器
模式选择
STC89C52RC的T0和T1均有4种工作模式:
- 模式0:13位定时器/计数器
- 模式1:16位定时器/计数器 (常用)13位适用于短时间计时,16位适用于较长时间计时
- 模式2:8位自动重装模式时器/计数器 此模式下,计时到设定值后会自动重新开始计时,实现周期性计时功能
- 模式3:两个8位计数器(禁用定时器1,把定时器2设置为两个8位计数器)
TMOD这个寄存器是不可位寻址的, |
例如初始化定时器0的模式为模式1, |
模式1:时钟源选择
0b 0000 0001
TMOD = 0x01
C/T寄存器:时钟来源选择
来源一:SYSclk,系统时钟,作为定时模式(常用)
来源二:外部脉冲输入引脚T0,也就是P3.4引脚,作为计数模式
两个定时器的C/T寄存器对应TMOD寄存器的第2位和第6位 |
系统时钟: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可以控制定时器开关。
这里我们设置为软件启动 |
TR0 :运行控制位
可位寻址
TR0 = 1; //使能定时器0开始计时 |
最终
TMOD=0x01; //设置定时器0为:模式1、系统时钟源、软件启动 |
预装载值
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定时器 |
溢出中断
TF0:溢出标志位
ET0=1; //使能定时器0中断 |
所以,下方是使能定时器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;}
}