抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

定时计数器

51单片机有2个16位定时器/计数器:定时器T0(T0为P3.4)和定时器T1(T1为P3.5)

这里所说的16位是指定时/计数器内部分别有16位的计数寄存器。

当工作在定时模式时,每经过一个机器周期(约1.085us)内部的16位计数寄存器的值就会加1,当这个寄存器装满时溢出。
可以算出工作在定时模式时最高单次定时时间:65535*1.085us

当工作在计数器模式时,T0(P3.4引脚)或T1(P3.5引脚)每来一个脉冲计数寄存器加1

使用定时-计数器

(一)启动定时计数器

T0/T1的启动由控制寄存器是TCON(可位寻址)控制,如下图(IE1/IE0/IT1/IT0先不用看)

image-20210822095401344

总结:TRx用来启动定时计数器Tx,TFx用来判断什么时候溢出(Tx表示是T0还是T1)

(二)设定定时计数器的工作模式

这一步由寄存器TMOD(不可位寻址)控制

image-20210822100638172

第7位和第3位(GATE位):置1时只有外部引脚INT1/INT0为高电平、且开启了定时计数器(TRx=1)时,定时计数器才会开始计数

第6位和第2位(C/T位):为1时表示工作在计数模式(外部来一个脉冲就+1),为0表示工作在定时模式

M1/M0位:用于选择定时计数器是计数方式(前面说过,这是16位定时计数器,但不一定全部16位都用,可以只用其中几位),有4种方式:

M1 M0 工作方式及说明(TLx、THx分别表示Tx定时计数器【共16位】的低8位、高8位)
0 0 13位计数方式,TLx用低5位,THx的8位全用,总共13位,可以从0计数到:2131=81912^{13}-1=8191
0 1 16位计数方式,即TLx的8位,THx的8位全部使用,计数值0~65535
1 0 8位自动重装载方式,溢出时自动将THx的值装载到TLx里面
1 1 如果是T1此时该定时计数器失效;如果是T0,这时T0的高8位TH0作为T1使用(由TR1、TF1启动、判断溢出),但只能计数到(281=2552^8-1=255),TL0还是自己(T0)使用(由TR0、TF0启动、判断溢出)

(三)查询定时计数器是否溢出

上面(第一点):TFx溢出时会被硬件自动置为1, 但前提是使用了中断函数,否则可以用程序清0

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/******************************************************************
* 【程序功能】: 定时器0工作模式1 16位定时模式,数码管动态显示0-10,秒表。
*******************************************************************/
#include <reg52.h>
#include <intrins.h>

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选

//共阴数码管段选表0-9
uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};

void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}

void display(uchar i)
{
uchar bai, shi, ge;
bai = i / 100; //236 / 100 = 2
shi = i % 100 / 10; //236 % 100 / 10 = 3
ge = i % 10;//236 % 10 =6

//第一位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFE; //1111 1110
WE = 0;//锁存位选数据

DU = 1;//打开段选锁存器
P0 = tabel[bai];//
DU = 0;//锁存段选数据
delay(5);

//第二位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFD; //1111 1101
WE = 0;//锁存位选数据

DU = 1;//打开段选锁存器
P0 = tabel[shi];//
DU = 0;//锁存段选数据
delay(5);

//第三位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFB; //1111 1011
WE = 0;//锁存位选数据

DU = 1;//打开段选锁存器
P0 = tabel[ge];//
DU = 0;//锁存段选数据
delay(5);
}

//定时器T0初始化
void T0_init()
{
TR0 = 1; //启动定时器0
TMOD = 0X01; //定时器工作模式1,16位定时器计数模式
TH0 = 0x4b;
TL0 = 0xfd; //定时50ms
//0x4bfd即19453,共计数65535-19453+1 = 46083次,耗时46083*1.085us = 50 ms
}

void main()//main函数自身会循环
{
uchar mSec, Sec;//毫秒和秒储存变量
T0_init();//定时器T0初始化
while(1)
{
if(TF0 == 1)//判断是否溢出
{
TF0 = 0;//软件清零溢出标志位
TH0 = 0x4b;
TL0 = 0xfd; //定时50ms
mSec++;//50ms到
if(mSec == 20)
{
mSec = 0;
Sec++;//1秒时间到
}
}
display(Sec); //数码管显示函数
if(Sec > 10)
Sec = 0;//秒清零
}
}

方式1初值计算方法,假如定时t(单位:us),使用T0:

则:

1
2
3
TH0 = (65535 - t / 1.085) / 256
TL0 = (65535 - t / 1.085) % 256
四舍五入取整后转换为16进制

中断系统

主程序正在执行某一事件,突然发生一个紧急事件,需要中断当前正在执行的程序,去处理这一紧急事件,这就是中断

image-20210822084113315

引起CPU中断的根源,称为中断源。中断源向CPU提出的中断请求。CPU暂时中断原来的事务A,转去处理事件B。对事件B处理完毕后,再回到原来被中断的地方(即断点),称为中断返回。实现上述中断功能的部件称为中断系统(中断机构)。

51子系列存在5个中断源:
外部中断源2个

  1. INT0——由P3.2端口线引入,低电平或下降沿引起。

  2. INT1——由P3.3端口线引入,低电平或下降沿引起。

    这两个外部中断源标志和它们的触发方式控制位由特殊功能寄存器TCON的低4位控制

内部中断源3个

  1. T0——定时器/计数器0中断,由T0回零溢出引起。

  2. T1——定时器/计数器1中断,由T1回零溢出引起。

  3. TI/RI——串行I/O中断,串行端口完成一帧字符发送/接收后引起。

    这3个内部中断源的控制位分别锁存在特殊功能寄存器TCON和SCON中。

使用中断

如何使用中断,有3大步骤:

(一)允许产生中断

要允许产生中断首先要配置中断允许寄存器IE和XICON

(可位寻址表示每一位都已经用变量定义好了,比如第7位是EA)

image-20210822091845225

总结:要允许中断,必须先开启总中断EA=1,再开启对应模块的中断

(二)设置什么时候响应中断

什么时候响应中断由控制寄存器TCON控制

image-20210822092158946

TFx:当计数值溢出时,就会向cpu发出中断请求,然后自动进入对应的中断处理函数,然后TFx由硬件自动清0

其它IEx, ITx也差不多是这样

(三)产生中断后你要干什么——中断处理函数

中断处理函数的功能就是当产生中断后你要干什么,这个函数有格式要求,不需要主动调用,由cpu响应中断时自动调用:

格式如下:

1
2
3
4
5
void 函数名() interrupt 中断入口号  //中断处理函数,加关键字interrupt和入口号0/1/2/3/4
{
//中断处理语句
}

中断优先级:

如果有多个中断同时产生,该调用哪个中断处理函数呢,因此引出了中断优先级这个概念

中断优先级级别越高,就越先被执行

image-20210822120231820

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
定时器0, 工作模式1, 16位定时计数
数码管动态显示0-10,每隔1秒显示一次
*/

#include <reg52.h>
#include <intrins.h>

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
sbit key_s2 = P3^0;//独立按键S2
sbit key_s3 = P3^1;//独立按键S3
uchar num;//数码管显示的值
uchar mSec, Sec;//毫秒和秒储存变量

//共阴数码管段选表0-9
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};

void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}

void display(uchar i)
{
static uchar wei;
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = SMGwei[wei];
WE = 0;//锁存位选数据
switch(wei)
{
case 0: DU = 1; P0 = SMGduan[i / 100]; DU = 0; break;
case 1: DU = 1; P0 = SMGduan[i % 100 / 10]; DU = 0; break;
case 2: DU = 1; P0 = SMGduan[i % 10]; DU = 0; break;
}
wei++;
if(wei == 3)
wei = 0;
}
//定时器0初始化
void T0_init()
{
EA = 1; //打开总中断
ET0 = 1;//打开定时器0中断
TR0 = 1; //启动定时器0
TMOD = 0X01; //定时器工作模式1,16位定时模式
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
}

void main() //主程序执行按键扫描
{
T0_init();//定时器0初始化
while(1)
{
if(key_s2 == 0)//判断S2是否被按下
{
delay(20);//按键消抖
if(key_s2 == 0)
{
if(num != 120)
num++;
while(!key_s2);//松手检测
}
}
if(key_s3 == 0)//判断S3是否被按下
{
delay(20);//按键消抖
if(key_s3 == 0)
{
if(num > 0)
num--;
while(!key_s3);//松手检测
}
}
}
}

//定时器0中断处理函数
void timer0() interrupt 1 //T0中断入口为1
{
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
display(num); //数码管显示s
}

评论