时间是我们设计功能模块时一个十分重要的输入或者衡量指标,而51单片机中就为我们提供了两个时域上的模块:定时器和计数器。它们可以帮助我们实现一些非实时性的功能:延迟、计时以及定时等。其中51单片机有两个16位的模块:定时器0(T0)、定时器1(T1),可以用来实现定时器或者计数器的功能,而它们工作在哪种情况下则是由我们自己来决定。
目录
🐱定时器原理
🐍定时器组成和结构
🐏定时器内部结构
🐏定时器寄存器详解
🐏定时器工作模式
🐐定时器编程
🐱定时器原理
如下图所示,我们有两个定时器T1和T0,而两个定时器分别由TH1、TL1和TH0、TL0两类特殊寄存器组成,每一个寄存器都是八位的,所以加起来整一个定时器就是16位寄存器。而除了组成每个定时器本身的两个特殊功能寄存器外,还会有两个控制寄存器来实现对两个定时器的控制,它们分别是TCON和TMOD寄存器,这两个寄存器都是8位寄存器。
每一个定时器都有两种的工作模式:定时器以及计数器,无论是哪一种工作模式,定时器的本质上都是一个加法计数器,只不过计数器是外部输入脉冲,而定时器是内部产生的脉冲。下面我们来具体看看这两种工作模式的具体细节:
①计数器工作模式:当选择计数器工作模式的时候,我们的输入是通过引脚来连接外部,通过外部输入随时间变化的波形。而每一个定时器都有一个对应的波形信号输入引脚,对于定时器T1来说它的输入引脚上P3.5,而对于定时器T0来说它的输入是P3.4。它计数的模式是当信号发生负跳变时就记录一次。其计数的频率最高为内部振荡频率的1/24。
⭐为什么计数频率最高为内部振荡频率的1/24?
我们知道一个机器周期 = 12个振荡周期 = 6个状态周期,每个机器周期的S5-P2时刻的信号驱使下系统会去检测引脚的输入电平,只有当前一个振荡周期时检测到的是1而下一个振荡周期时检测到的是0(负跳变)计数器才会加1,并且加一还是在下一次的S3-P1的时候才会加1。由上面的说法我们可以知道一次检测需要至少需要经历两个机器周期,也就是24个振荡周期,也就是计数周期最少为24个振荡周期,换算过来那就是计数频率最高为1/24个振荡频率。
②定时器工作模式:当我们选择定时器工作模式时,它的计数信号来自内部时钟信号,经历一次机器周期后定时器会计数一次,即定时时间 = 计数值 x 机器周期。
考虑到上面的想法,我们可以知道对于定时器或者计数器的一些工作限制:
首先是作计数器时外部计数的频率要低于1/24内部振荡频率;其次是作定时器时输入信号的高低电平转换间隔时间不能少于一个机器周期(即高电平、低电平最少保持一个机器周期)
🐍定时器组成和结构
🐏定时器内部结构
前面我们提到我们的每一个定时器都是由两个特殊功能寄存器THx和TLx以及两个控制功能寄存器TCON和TMOD寄存器组成。而下面我们来看看一个定时器的具体结构:
(选择定时模式)首先我们来看C/T控制信号,该控制信号将决定我们使用哪一种定时方式,当C/T = 0时我们使用的是定时器的工作方式,当C/T = 1时使用的是计数器的工作方式。其中从图中我们可以明显看出当C/T = 0时开关打到上面,而当C/T = 1时开关就打到下面。
(控制计数启动)然后我们来看控制端K,控制端也是一个开关,当开关打上去后就能让定时器开始工作了。而由下面的电路可以知道,即要想让控制端打到上面我们需要同时兼顾GATE、INTx和TRx三个变量。
而控制上面这两个开关的寄存器就是TMOD和TCON两个寄存器:
两个寄存器都是8位的寄存器,其中它们高4位都是用来存放控制定时器1的状态位,而低4位都是用来存放控制定时器0的状态位。
当单片机复位的时候,上面的两个寄存器所有的位都会被清0,两个特殊功能寄存器控制的目标都不相同。TMOD是工作方式寄存器,它用于设置定时器的工作模式和工作方式;而TCON寄存器用于控制定时器开始和停止以及设置状态。
所以我们编程定时器的时候就是通过控制这些寄存器:TMOD、TCON、TH0、TL0、TH1和TL1等来实现一个定时器的功能的。
并且在我们设置好了定时器后,该定时器就会独立于CPU以外工作,并不会去占用CPU的资源,只有当定时器的计数溢出后才会返回来向CPU发出申请。
🐏定时器寄存器详解
下面我们来看看控制定时器的这些寄存器的功能:
功能寄存器名称 | 功能详解 |
TMOD | 设置定时器工作模式和工作方式 |
TCON | 设置定时器开关 |
TH0和TL0 | 存放定时器T0的初值/计数结果 |
TH1和TL1 | 存放定时器T1的初值/计数结果 |
⭐初值?
初值就是开始的值,例如我们计数从0一直记到100和我们从50计算到100中间经历的时间是不同的,而我们不可能让每一次计数的时间都是从0-100,我们的时间是任意的,所以我们引入了初值的概念,初值是由我们自己设置的,通过设置初值获取我们希望的(计数周期)定时时间。
TMOD寄存器(89H)
TMOD寄存器的地址是89H,这也就决定了它不能够用于位寻址。我们以一个定时器为例子,TMOD管理一个定时器的是4个位,包括GATE、C/T、M1和M0。前面我们知道C/T位是控制工作方式是计数器还是定时器的0是定时器,1是计数器;而M1和M0是共用的,用来设置选择工作模式,从模式0到模式3。而GATE位我们通常将其设置为 0,因为我们只想通过TRx位来控制定时器开关,所以公式我们只需要保证GATE = 0,则GATE非就是1,那么K = TRx。
TCON寄存器(88H)
TCON寄存器是另外一个控制的寄存器,它也是有4位去控制一个寄存器,我们仍然以一个寄存器为例子(寄存器1):TF1表示计数溢出,也就是计数已经达到目标值了,数到100了!那么硬件就会自动将该位置1,不用人工操作;然后是TR1位,由上面知道我们将这位置为控制定时器的“按钮”,当TR1=1那么就开启定时器;低四位则用于以后的中断设置。
设置的时候可以直接通过位名称、特殊功能寄存器名称来设置。
THx / TLx寄存器
这两个寄存器和前面两个寄存器很不相同,这些寄存器是用来存放我们的初值的,两个8位寄存器,但是不一定完全全部用于存放初值,具体由我们将要介绍的工作模式确定。
🐏定时器工作模式
我们根据定时器的工作形式,将定时器划分为3-4种工作模式,分别为方式0-方式3,而定时器0有4种工作方式而定时器1只有3种工作方式。
其中方式0用到了定时器特殊寄存器(THx、TLx)的13位--8192(8.192ms)、方式1用到了16位--65536(65.536ms),而方式2和方式3都只用到了8位--256(0.256ms)。所以我们选择定时器工作模式的时候根据定时时间去选择也未尝不可。
而初值和我们需要的定时时间有如下的关系式:
其中M是最大的计数值,X是初值,TM = 12个时钟周期 = 12 / 晶振频率(fosc)
举个计算初值的例子:
我们采用定时器T0,选择工作方式1,定时40ms,晶振频率fosc = 12MHz,那么其初值x计算方式如下:
首先由于fosc = 12MHz,所以Tm = 12 * 1/fosc = 1us。在工作方式1下,M = 65536,而T = 40ms,全部带入后计算得:
(65536 - X)*1us = 40ms 解得X = 25536 = 63C0,所以TL0=C0,TH0=63
🌙方式0
首先是方式0,方式0设置的时候M1M0为00,其只会使用到TL1的低五位和TH1的高八位,所以一共只会使用到13位。该方式中如果低5位有溢出那么进位会直接给到高八位,也就是说TL1相当于高三位几乎没有作用。我们可以在TH1也计数满的时候直接设置触发一个中断。
🌙方式1
方式1和方式0几乎类似,不过它使用到了THx和TLx的全部八位,它的计算时间公式为:
(65536 - 初值)*12 / fosc
🌙方式2
方式2是一个特殊的工作方式,它只用TLx的原因是它想要设置一个自动初值的机理,我们之前设置完初值后,计数器会在已经有初值的基础上继续往上增加,那么这样势必会改变原来的内容,那么我们就需要每一次定时都要设置一次初值。为了解决这个问题,工作方式2出现了,它的工作方式是会将THx作为一个一直常数缓冲器,当计数溢出时就会自动将THx里面的初值填充到TLx中。它可以实现每次溢出自动填充,免去重新赋值的代码带来的冗余工作时间,但是同时也减少了定时上限。
🌙方式3
方式3只在定时器0里面有,当T1作为波特率发生器时才使用到,这个后面再学习;
🐐定时器编程
看完定时器原理之后,我们来看看定时器如何编程,我用两个具体的例子来说明,一个是在定时器范围内的定时,一个是定时器范围外的定时。
例子1:系统振荡频率为12Mhz,选择定时器1,工作方式0,定时4ms,产生一个周期为8ms的方波:
我们首先来计算初值,我们知道振荡频率为12MHz,所以TM=12 / 12MHz,T=4ms。则代入公式中:(8192-X)1us = 4ms解得X=4192。转换为二进制得X=1000001100000。
所以TL1=00000=00H,TH1=10000011 = 83H,而由于我们选择的是定时器1的工作方式0,所以TMOD = 00H设置工作模式GATE=0,C/T=0,M1=0,M0=0所以TMOD=00H。然后再设置TR1为1打开定时器。
然后跳进一个循环中,在这个循环里面我们不做事,只等待计数器溢出,一旦溢出就跳转到函数RE,然后重新装填初值,并且将引脚电平取反,最后再次跳入循环中。
ORG 00H
LJMP MAIN
ORG 30H
MAIN:
MOV TL1, #00H
MOV TH1, #83H
MOV TMOD, #00H
SETB TR1
SETB P3.0
LOOP:
JBC TF1, RE
AJMP LOOP
RE:
MOV TL1, #00H
MOV TH1, #83H
CPL P3.0
LJMP LOOP
END
测试电路:
最后的结果,我们可以看出一个周期占了8格,一格1ms也就是周期8ms,一次跳转4ms,满足我们的设置。
例子2:系统振荡频率为12Mhz,选择定时器0,工作方式1,定时1s,产生一个周期为2s的方波:
我们知道我们定时器三种模式8.192ms、65.536ms和0.256ms,它们的上限都是固定的,那如果我们想设置1s的定时呢?这里我们选增加一个工作寄存器的方式来实现超过上限的操作。
我们知道工作模式1最多65.536ms,那我们就取定时到50ms,然后每定时到50ms后记录一次,当记录到一定次数才跳转到某个特殊程序处理。
有了这个思路,我们考虑到2s可以由20个50ms组成,所以我们的工作寄存器就设置为20。然后计算初值:(65536-X)*1us = 50ms解得X=15536=3CB0(注意不是BCD码)所以TH0=3C;TL0=B0,然后又因为我们使用工作模式1,所以TMOD=01H,最后由于我们使用定时器0,所以应该打开TR0。
代码如下:
ORG 00H
AJMP MAIN
ORG 30H
MAIN:
MOV TL0, #0B0H
MOV TH0, #3CH
MOV TMOD, #01H
SETB TR0
MOV R2,#20
LOOP:
JBC TF0,CHECK
AJMP LOOP
CHECK:
DJNZ R2, CONTINUE
CPL P3.0
MOV R2, #20
CONTINUE:
MOV TL0, #0B0H
MOV TH0, #3CH
AJMP LOOP
END
测试电路:
最后的结果,我们可以看出半个周期占了10格,一格0.1s,所以总的就是1s,周期是2s,满足我们的设置(这里忘记调到直流了,图线很难看...) 。