定时器与计数器/Timer And Counter

定时器与计数器/Timer And Counter

文章内容有误可以直接在最下面评论

在描述问题的时候把标题加在前面,要求最好精确到问题出现位置的小标题。

比如:汇编-寻址方式-立即寻址:{问题描述}

错别字就没有什么必要了,我也懒得改(doge)


前言

中断的应用举例中 我们注意到里面有一个delay()函数。

1
2
3
4
5
6
while(1) {
led=1;
delay(65535);
led=0;
delay(65535);
}

上面的代码段就是delay()函数的调用。因为单片机执行每条指令速度很快,如果只是单纯的对led进行更改:

1
2
3
4
while(1) {
led=1;
led=0;
}

因为速度非常快,而又因为人类的视觉残留,就会导致看上去并没有什么变化,这时就需要一个delay()函数使CPU空转来达到延时的效果

但软件调用总归是有偏差的,能否使用硬件来执行延时呢?

定时器和计数器的概念

定时器和计数器的原理其实大差不差,都是对脉冲进行计数直至溢出,但有区别的是:

  1. 定时器是对等间隔脉冲进行计数,这个脉冲是来自于单片机内部,所以它的频率和幅值都是稳定的;

  2. 计数器是对普通脉冲进行计数,这个脉冲来自于单片机外部,它的频率和幅值都是随机变化的

在我们本学期学习的知识中,因为定时器对等间隔脉冲进行计数的特性,定时器的运用是比较多的,主要涉及到的有以下两点:

  1. 前言部分所说的延时,分别有查询法和中断法;

  2. 下一节串口中要涉及到的波特率发生器。

需要着重了解并掌握的就是定时器初值的计算以及在C语言代码中如何赋初值

相关特殊功能寄存器

首先是控制定时器如何工作的TMOD

定时器工作方式寄存器/TMOD

TMOD的字节地址是89H,根据 存储器 章节中所学知识,该字节地址无法被8整除,所以TMOD不可以位寻址

TMOD中高四位和低四位分别对应控制T1和T0的工作方式,每四位从高到低分别是:

  1. GATE: 门控位,为1时有外部中断源控制,一般直接为0;

    但是在第七、八、九章作业客观题中出现过一道题: 要想测试INT0引脚上的一个正脉冲宽度,那么特殊功能寄存器TMOD的内容应为 A. 09H;B. 87H;C. 00H;D. 80H。根据上面所述只要GATE位为1就可以了,且一般使用T0,也就是答案选A。

  2. C/T:功能选择位,为1时选择计数器,为0时选择定时器;

  3. M1、M0:工作方式选择位,有方式0-3,可定义四种 工作方式

因为TMOD不能位寻址只能字节寻址,所以变更数据时必须一次性更改所有位的数据;

例如需要T1工作在方式2,C语言代码就为

1
TMOD = 0x20;

有一个技巧就是,当使用定时器时(即门控和C/T位都为0),选T1就是修改高位数。选T0就是修改低位数变化,且变化的数是几,就使用方式几。

控制寄存器/TCON

这个我们在 中断 里已经学过低四位,而定时器就是用的高四位。

TCON每两位对应一个中断源,从低到高分别是:INT0、INT1、T0、T1;

而两位中,高位(IEx、TFx)都为中断标志,即在申请中断时会置1;

低位(ITx、TRx)对于外部中断,就是它的触发方式;对于定时器,就是启停标志,即当该位置1,定时器就开始对脉冲进行计时。

TRx的C语言代码举例:

1
2
//一系列的初始化语句(设置中断、定时器的初值)
TR0 = 0;//经过初始化后,定时器就可以可以开始工作了

中断相关

还记得“ 外内外内串 ”吗?

定时器T0、T1用到的就是这两个“内”,即ETx、PTx分别是对应了定时器的中断允许位和 中断优先级 位。

定时器的工作方式

M1 M0 方式 说明 最大计数值 最大定时时间(当Tcy为1us时)
00 0 13位定时器/计数器(THx的8位和TLx的低5位) 8192($2^{13}$) 8192us
01 1 Tx都为16位定时器/计数器 65536($2^{16}$) 65536us
10 2 Tx为自动重赋初值的8位定时器/计数器 256($2^{8}$) 256us
11 3 T0分成两个独立的8位定时器/计数器, T1在方式3时停止工作 256 256us

这里提到了一个THx和TLx,这两个就是对脉冲计数的寄存器,每个有8位,当使用方式0时,就是THx的8位和TLx的低5位在计数;当使用方式1时就是16位一起计数,所以方式1一共会计$2^{16}$=65536个数;而方式2和3的THx和TLx两个相等,即$2^{8}$=256个数

需要注意的是,四个方式里只有方式2会自动赋初值,即在定时器计数溢出后会自动回到赋的初值,而其他三个方式会直接清零,所以在溢出后需要重新赋初值。

这里其实可以和数电联动一波:数电里学习的计数器有两种重置方法——清零和置数,方式二其实就相当于接了置数端,并且将初值赋给了置数的几个输入口,而其他几个接的是清零端,溢出后会直接清零。

定时器赋初值

首先要知道两个概念,计数值和Tcy:

  1. 计数值就是每个方式对应的最大值减去需要设定的初值;

  2. Tcy,即机器周期,也就是晶振频率的倒数乘上12,即$T_{cy}=\frac{12}{f_{osc}}$。

由此我们可以得出以下公式:

$$
计数值=最大值-初值\
$$

$$
定时时间=计数值\times{T_{cy}}\
$$

$$
T_{cy}={12}/{f_{osc}}
$$

所以在已经得出Tcy的情况下,初值可以直接这么计算

$$
初值=最大值-定时时间/T_{cy}
$$

据此,就可以算出初值了。

例如选择方式1,记50ms:最大值就是65536,Tcy为1us,定时时间就是$50\times10^{3}us$,即计数值就为50000,那么初值就是最大值减初值即为15536。

在赋值的时候需要注意,方式1是分为高八位和低八位的

应用举例

查询法

这里引用的是定时器实验里的源码,我对一些细节进行了一些修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<reg52.h>

sbit led=P1^4;

void main(void) {
unsigned char temp = 0x00;
TMOD = 0x01;//使用T0,方式1
TH0 = (65536 - 50000 / 1) / 256;
TL0 = (65536 - 50000 / 1) % 256;
TR0 = 1;//启动T0,T0开始计时
while (1) {
while (TF0) {
TH0 = (65536 - 50000 / 1) / 256;//重新赋初值
TL0 = (65536 - 50000 / 1) % 256;
TF0 = 0;//将中断标志手动软件置0
temp++;
if (temp ==20) {//执行20次50ms的定时后,就执行if语句
led = ~led;
temp = 0;
}
}
}
}

我修改的部分里首先要指出的是,赋初值的部分我并没有像源码一样:

1
2
TH0 = 0x4C;
TL0 = 0x00;

他其实就是人工计算出来然后直接贴上去,但我们都在用单片机何必多次一举,所以直接将公式贴进去让单片机帮我们算就行,但是考试是不免会考到让你手算初值,所以计算初值的方法还是有必要学的

然后就是高八位和低八位的处理,他是直接赋值,所以直接把计算好的结果化成16进制在拆开赋值就行,我们这里是让单片机计算,那就可以用C语言整型变量除法和取余的特性,除以2的8次方即为取高八位,对值取2的8次方的余数就是取低8位。

查询法的原理其实就是运用了当定时器溢出时,会向CPU申请中断的特性,此时对应的中断标志会置1,当while循环检测到中断标志为1后,就会进入while循环,而while循环里就是我们想要在定时器定时结束后执行的逻辑代码。因为没有进入中断服务子程序,中断标志没有硬件自动置0,所以还需要手动软件置0.

需要注意的是,无论有没有在IE特殊功能寄存器内让定时器允许中断,当他计时溢出后中断标志依旧会硬件置1,只是CPU不会处理这个这个中断,即不会进入中断服务子程序。查询法本来就不需要进入中断服务子程序,所以也不需要对IE寄存器进行赋值。

中断法

中断法其实就是利用了中断服务子程序来执行逻辑代码,也就是相当于把查询法内while (TF0) {}里面的那段拿出来单独做成了中断服务子程序。

参考代码依旧是定时器实验里的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<reg52.h>

sbit led=P1^4;

unsigned int temp;

void main (void) {
TMOD = 0x02;//使用T0,方式2
TH0 = 0xA4;//赋初值,可以手算验证以下
TL0 = 0xA4;
TF0 = 0; //清除T0中断标志
ET0 = 1; //T0允许中断
EA = 1; //中断控制总开关
TR0 = 1; //T0开始定时
while(1);
}

void timer0_int() interrupt 1 {//中断服务子程序
//方式2不需要重新赋初值
TF0=0; //清除T0中断标志,可以不用,中断子程序会硬件清0,这里只是为了保险
ET0=0; //禁止T0中断,同为为了保险起见
temp++;
if (temp==10000) {
led=~led;
temp=0;
}
ET0=1; //重新允许T0中断
}

定时器与计数器/Timer And Counter

https://mere.pt/dpj-timer/

作者

MerePT

发布于

2022-05-21

许可协议