目录
(3)数码管显示0~999,间隔1s。(对数码管进行消除残影)
STC89C52系列单片机内部设置的两个16位定时器/计数器T0和T1都具有计数方式和定时方式两种工作方式。对每个定时器/计数器(T0和T1),在特殊功能寄存器TMOD中都有一控制位C/T来选择T0或T1为定时器还是计数器。定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同: 如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或者每6个时钟得到一个计数脉冲(可以在烧录时设置),计数值加1;如果计数脉冲来自单片机外部引脚(TO为P3.4,T1为P3.5),则为计数方式,每来一个脉冲加1。
一、 定时器/计数器寄存器的功能
定时器/计数器0和1的相关寄存器标题
定时器/计数器中断控制寄存器TCON(可位寻址)
TF0/1 | 定时器/计数器T0/1溢出标志。T0/1被允许计数以后,从初值开始加1计数。当最高位产生溢出时由硬件将TF0/1置“1”,并向CPU请求中断,一直保持到CPU响应中断时,才由硬件将TF0/1清“0”(TF1也可由程序查询清“0”)。 |
TR0/1 | 定时器/计数器T0/1运行控制位。该位由软件置位和清零。当GATE=0,TR0/1=1时就允许T0/1开始计数,TR0/1=0时禁止T0/1计数。当GATE=1,TR0/1=1且INT0/1输入高电平时,才允许T0/1计数。 |
IE0/1 | 外部中断0/1请求源。IE0/1=1,外部中断向CPU请求中断,当CPU响应该中断时由硬件将IE0/1清“0”。 |
IT0/1 | 外部中断T0/1触发方式控制位。IT0/1=0时,外部中断0为低电平触发方式,当INT0/1(P3.2/P3.3)输入低电平时,置位IE0/1。采用低电平触发方式时,外部中断源(输入到INT0/1)必须保持低电平有效,直到该中断被CPU响应,同时在该中断服务程序执行完之前,外部中断源必须被清除(即P3.2/P3.3输入高电平),否则将产生另一次中断。当IT0/1=1时,则外部中断0/1(INT0/1)端口由“1”→“0”下降沿跳变,激活中断请求标志位IE0/1,向主机请求中断处理。 |
不可位寻址:只能对整体赋值
可位寻址:可对每一位单独赋值也可以整体赋值
定时器/计数器工作模式寄存器TMOD(不可位寻址)
GATE | 门控信号。置1时只有在INT0/1脚为高和TR0/1=1时才能打开定时器/计数器0/1 |
C/T | 计数器/定时器选择位。置0则用作定时器(从内部系统时钟输入);置1则用作计数器(从P3.4/3.5脚输入) |
M0、M1 | 定时器/计数器工作模式选择 |
M1 | M0 | C/T工作模式选择 |
0 | 0 | 13位定时器/计数器,兼容8048定时模式,TL0/1只用低5位参与分频,TH0整个8位全用。 |
0 | 1 | 16位定时器/计数器,TL0/1、TH0/1全用 |
1 | 0 | 8位自动重装载定时器,当溢出时将TH0存放的值自动重装入TL0 |
1 | 1 | 定时器0此时作为双8位定时器/计数器。TL0作为一个8位定时器/计数器,通过标准定时器0的控制位控制。TH0仅作为一个8位定时器,由定时器1的控制位控制。 注意该工作模式下定时器1此时无效(停止计数)。 |
ET0/1/2 | 定时/计数器T0/1/2的溢出中断允许位 |
EX0/1 | 外部中断0/1中断允许位 |
ES | 串行口1中断允许控制位 |
EA | CPU的总中断允许控制位,EA=1,CPU开放中断;EA=0,CPU屏蔽所有的中断申请 |
定时器/计数器工作流程
二、定时
1、定时器初值计算
若晶振震荡频率(fosc)为12MHz,则机械周期 = 12 / fosc
12/(12*10^6)=1us
//12为分频系数,绝大多数51单片机默认12分频,分频的作用是增长时钟周期
假设要定时的时间为1000us即1ms,计时初值为x(从x开始计时到65536溢出)则有
(2^16-x)*1us=1000
//16为定时器位数,假设为16位定时器
解得定时初值为
x=65536-1000/1=64536
将定时器初值分为高8位和低8位存储
TH0 = (65536-1000/1)/256; //设置定时初值,取高8位
TL0 = (65536-1000/1)%256; //设置定时初值,取低8位
对于低位和高位的提取,我们先以10进制数1234为例:
取高2位:1234/10^2=12,取低2位:1234%10^2=34
同理,对2进制低位和高位的提取:
取高8位:TH0 = 64536/2^8=64536/256=252;
取低8位:TL0 = 64536%2^8=64536%256=24;
为简化,可以写成16进制形式
TH0 = 0xFC;
TL0 = 0x18;
若你的晶振震荡频率为11.0596MHz,同理得
TH0 = 0xFC;
TL0 = 0x66;
如果你懒得计算,可以用51单片机的烧录器STC-ISP中的定时器计算器求得
(其生成代码中的寄存器AUXR可以不配置)
那么问题来了,我们可以无限地设置定时器的时间吗?这当然是不行的,从上面的计算公式我们就可以知道,晶振震荡频率为12MHz的单片机,其最大定时间隔为=2^16*1us=65536us
2、定时器/计数器初始化配置
对于不可寻址的TMOD,设置为定时器模式,选择工作模式2即16位定时器/计数器
以开启定时器0为例:
TMOD=0x01; //设置定时器模式
若你还使用了其他定时器,为避免值覆盖问题,也可以这样设置:
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
完整配置为
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01;
TH0 = 0xFC; //设置定时初值
TL0 = 0x18;
TF0 = 0; //清除TF0标志
TR0 = 1; //使能定时器,设置运行控制位
ET0=1; //使能中断
PT0=0; //设置中断优先级
EA=1; //启动定时器
}
由上面的工作流程图可知PT0和TF0默认为0,所以也可不配置。而可位寻址IE、TCON也可以进行整体赋值,如下:
void Timer0_Init(void)
{
TMOD = 0x01; //设置定时器模式
TH0 = 0xFC; //设置定时初值
TL0 = 0x18; //设置定时初值
IE = 0x82; //ET0=1,EA=1;
TCON = 0x10; //TR0 = 1;
}
3、定时功能的实例
(1)定时1ms实现流水灯
#include <REGX52.H>
sbit led=P1^7;
int count,k;
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
void Timer0_Routine() interrupt 1 //定时器0
{
TL0 = 0x18; //重新设置定时初值
TH0 = 0xFC; //重新设置定时初值
P2 = ~(0x01<<k);
k++;
if(k == 8)k=0;
}
void main(void)
{
Timer0Init();
while(1)
{
}
}
(2)数码管间隔1s进行移位
#include <REGX52.H>
#define uchar unsigned char
#define uint unsigned int
uchar count;
uchar code NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};//共阴0--9
uchar code Location[]={0x1C, 0x18, 0x14, 0x10, 0x0C, 0x08, 0x04, 0x00};
void Timer0Init(void)
{
TMOD=0x01; //选择定时器器模式,选择工作模式2
TH0=(65536-1000)/256;
TL0=(65536-1000)%256;
ET0=1;
TR0=1;
EA=1;
}
void Timer0_Routine() interrupt 1
{
int T0count;
TH0=(65536-1000)/256;
TL0=(65536-1000)%256;
T0count++;
if(T0count>=1000)
{
T0count=0;
count++;
if(count==8)
{
count=0;
}
}
}
void main(void)
{
Timer0Init();
while(1)
{
P0=NixieTable[8];
P2=Location[count];
}
}
(以上两种定时器初始化方式效果一致)
(3)数码管显示0~999,间隔1s。(对数码管进行消除残影)
1、main.c
#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
#include "Timer0.h"
unsigned char Number = 0;
void show(unsigned char n)
{
Nixie(1, n / 100 % 10); //显示百位
Nixie(2, n / 10 % 10); //显示十位
Nixie(3, n % 10); //显示个位
}
void main(void)
{
Timer0_Init(); //中断初始化
while(1)
{
show(Number);
}
}
void Timer0_Routine() interrupt 1 //定时器中断0
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000) //一秒执行一次中断
{
T0Count=0;
Number++;
if(Number >= 1000)
{
Number = 0;
}
}
}
2、Timer0.c
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
3、Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
4、Nixie.c
#include <REGX52.H>
#include "Delay.h"
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};//阴极数码管段码:0--9
void Nixie(unsigned int Location, Number)//位码
{
switch(Location)
{
case 1: P2_4 = 1; P2_3 = 1; P2_2 = 1; break;
case 2: P2_4 = 1; P2_3 = 1; P2_2 = 0; break;
case 3: P2_4 = 1; P2_3 = 0; P2_2 = 1; break;
case 4: P2_4 = 1; P2_3 = 0; P2_2 = 0; break;
case 5: P2_4 = 0; P2_3 = 1; P2_2 = 1; break;
case 6: P2_4 = 0; P2_3 = 1; P2_2 = 0; break;
case 7: P2_4 = 0; P2_3 = 0; P2_2 = 1; break;
case 8: P2_4 = 0; P2_3 = 0; P2_2 = 0; break;
}
P0 = NixieTable[Number];
Delay(1); //稳定清零
P0 = 0x00; //消影:将上一位数据清零
}
5、Nixie.h
#ifndef __NIXIE_H__
#define __NIXIE_H__
void Nixie(unsigned int Location, Number);
#endif
6、Delay.c
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
7、Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
三、计数
1、计数器初始化配置
将C/T设置为计数器模式,M0/1选择模式3即8位自动重装载定时器。
为什么不用16位(模式2),16位也能实现起到相同的作用,但为节省资源,选择8位
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x06; //设置定时器模式
TH0 = 0xFF; //设置定时初值
TL0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
PT0=0;
EA=1;
}
若没有特殊要求,将定时器初值定义为65535即
TH0 = 0xFF; //设置定时初值
TL0 = 0xFF; //设置定时初值
每按下一次按键,计数器加1,65535加1达到65536溢出,执行中断程序,中断期间将定时初值重新设置。
2、案例应用
(1)每按下一次按键数码管数值加一(0~9)
#include <REGX52.H>
#define uchar unsigned char
#define uint unsigned int
uchar count;
uchar code NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};//共阴0--9
uchar code Location[]={0x1C, 0x18, 0x14, 0x10, 0x0C, 0x08, 0x04, 0x00};
void Timer0Init(void)
{
TMOD=0x06; //选择计数器模式(外部脉冲),选择工作模式2
TH0=0xFF;
TL0=0xFF;
ET0=1;
TR0=1;
EA=1;
}
void Timer0_Routine() interrupt 1
{
TH0=0xFF;
TL0=0xFF;
count++;
if(count==10)
{
count=0;
}
}
void main(void)
{
Timer0Init();
while(1)
{
P0=NixieTable[count];
P2=Location[0];
}
}
(2) 每当开关按下3次,灯闪烁5次
#include <REGX52.H>
int count,i;
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x06; //设置定时器模式
TL0 = 0xff; //设置定时初值
TH0 = 0xff; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
void Timer0_Routine() interrupt 1 //定时器0
{
TL0 = 0xff; //重新设置定时初值
TH0 = 0xff; //重新设置定时初值
count++;
}
void main(void)
{
int i=0;
Timer0Init();
while(1)
{
if(count==3)
{
for(i=0;i<5;i++)
{
P2=0xfe;
Delay(1000);
P2=0xff;
Delay(1000);
}
count=0;
}
}
}
如果想要更为详细地了解定时器/计数器,可以去查阅 STC89C52的数据手册。