![cover](https://blog-images-1304155699.cos.ap-nanjing.myqcloud.com/mmexport1623569708228.jpg)
51单片机开发入门(5)-定时器/计数器
本文最后更新于 2024-05-23,文章内容可能已经过时。
定时器/计数器阐述
AT89S51单片机有**两个16位**内部定时/计数器,记作T0、T1。
(AT89S52有3个定时/计数器,比AT89S51多了个T2。)
本质上,定时/计数器就是一个可以通过编程控制计数脉冲源、计数初值,并具有溢出标志和中断响应机制的**硬件加法计数器**。
定时/计数器在计数值**溢出时置位一个标志位TF**,可以供中断或者查询使用。
定时/计数器的计数脉冲源可以通过编程进行设置,从而起到**定时或者计数**的作用。
定时/计数器的结构
定时/计数器为16位硬件加法计数器,由高8位TH和低8位TL两个计数寄存器组成。
定时/计数器具有可选的两个计数脉冲源:
1、外部输入脉冲
2、内部机器周期脉冲
- 定时器模式
加法计数器对**内部机器周期**进行计数。
机器周期**T**已知
通过设置计数值**N确定定时时间t**。
t=NxT
- 计数器模式
加法计数器对**外部输入脉冲**进行计数,实现计数或频率测量功能。
外部计数脉冲由T0(P3.4)或T1(P3.5)引脚输入到计数器。计数器在检测到**外部输入脉冲下降沿时计数值加1,由于检测一个从1到0的下降沿需要2个机器周期,因此可以计数的外部脉冲最高频率为fosc/24**。
例:晶振频率12MHz时,最高外部计数频率500KHz。
12MHz / 24 = 500KHz
- 定时/计数器具有两个设置寄存器:
TMOD**,确定工作方式和功能;
**TCON,控制T0、T1的启停,且包含溢出标志。
定时/计数器有关的SFR
定时/计数器0计数值寄存器(16位):
共16位,分为高8位和低8位进行存储;
根据工作方式的不同可以使用其中的13位、8位、16位;
计数值范围根据使用位数而发生变化;
只能加法计数;
用户在编程时可以向TH和TL写入定时/计数器的计数初始值,在启动定时/计数器后,定时/计数器将从设置的初始值开始加法计数。
📌当计数到最大值时,产生溢出标志。
- TMOD(T/C方式控制寄存器) SFR地址:0x89
GATE:门控信号
1 -- T/C启动要求TR和INT同时为‘1’;
0 -- T/C启动仅受TR控制
C/T:计数器/定时器选择位
0 -- 定时器模式;内部时钟
1 -- 计数器模式;外部脉冲
M1和M0:T/C工作方式选择
可以选择工作方式0、1、2、3。
- TCON(T/C控制寄存器) SFR地址:0x88
TR0:T/C0启动控制位;
TF0:T/C0溢出标志位,用于中断响应;
TR1:T/C1启动控制位;
TF1:T/C1溢出标志位,用于中断响应;
TR:1--启动计数;0--停止计数;
TF:计数值溢出时由硬件置1;CPU转入中断响应程
序时由硬件自动清零,也可以软件清零。
定时/计数器的工作方式
8051单片机的定时/计数器具有**四种**工作方式,可以使用TMOD中的M1和M0进行选择:
M1 | M0 | 方式 | 功 能 |
---|---|---|---|
0 | 0 | 0 | 13位定时器/计数器 |
0 | 1 | 1 | 16位定时器/计数器 |
1 | 0 | 2 | 自动重载的8位定时器/计数器 |
1 | 1 | 3 | 仅适用于T0,两个8位定时器/计数器 |
工作方式0(定时/计数器0和1均可)
方式0为**13位**计数,由TL0的低5位(高3位未用)和TH0的8位组成。TL0的低5位溢出时向TH0进位,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。
工作方式1 (定时/计数器0和1均可)
方式1的计数位数是**16位**,由TL0作为低8位、TH0作为高8位,组成了16位加1计数器 。
工作方式2 (定时/计数器0和1均可)
方式2为自动重装初值的8位计数方式。
🎯**工作方式2特别适合于用作较精确的脉冲信号发生器。 **
定时器时间的设置(定时期初值的计算)
在内部定时方式下, T0、T1对内部机器周期计数,若fosc=6MHz,一个机器周期为12/fosc=2us,所以
✔ 方式0 13位定时器最大定时间隔=2^13×2us=16.384ms
✔ 方式1 16位定时器最大定时间隔=2^16×2us=131.072ms
✔ 方式2 8位定时器最大定时间隔 =2^8 ×2us=512us
例:若使T0工作在方式0,要求定时1ms,求计数初值。设计数初值为x,则有:
(2^13-x)×2us= 1000us
x = 2^13-500 = 0x1E0C
因此,TH,TL可置 8192-500
TH0 = 0xF0;TL0 = 0x0C;
![image-20210613171201627](https://blog-images-1304155699.cos.ap-nanjing.myqcloud.com/image-20210613171201627.png)
![image-20210613171208244](https://blog-images-1304155699.cos.ap-nanjing.myqcloud.com/image-20210613171208244.png)
工作方式3(仅适用于定时/计数器0)
方式3只适用于T/C0。当方式3时,TH0和TL0成为两个独立的计数器。 此时,TL0可用作定时/计数器,占用TR0和TF0;TH0只能用作定时器,占用TR1和TF1;T/C1仍可用于方式0、1、2,但不能使用中断。
📡只有在T/C1被串口占用时,T/C0才使用方式3
定时/计数器的使用
定时器/计数器的初始化及其步骤
使用8051的T0、T1前,应对它进行编程初始化,主要是对TCON和TMOD编程;计算和装载计数初值(TH、TL)。
一般完成以下几个步骤:
○确定定时/计数器的工作方式(编程TMOD寄存器);
○计算计数初始值,并赋予TH、TL;
○如果在中断方式工作,需使能中断;
○启动定时器/计数器(编程TR0或TR1位)。
数码管显示实验
- “实现数值0~65535的变化显示”
问题:如何使数值变化的速度减慢?(如每隔1秒数值加1)
一般方法(延时函数)
void main(void)
{
unsigned char i,j;
unsigned int uiTemp = 0;
while(1)
{
convert(uiTemp);
for(i=0;i<6;i++)
{
P2=LED_seg[i]; //送段码
P0=LED_bit[i]; //送位码
delay1ms(5); //5ms延迟
}
j ++;
if ( j == 33 ) //约1秒
{
uiTemp++;
j = 0;
}
}
}
- 定时/计数器方法
实现数值0~65535的变化显示,每隔50ms数值加1。( 设晶振频率为12MHz)
#include < reg51.h>
unsigned int gluiTemp ;
void Timer0( ) interrupt 1 using 1
{
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
gluiTemp ++ ;
}
void main(void)
{
unsigned char i,j;
gluiTemp = 0;
TMOD = 0x01;
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
ET0 = 1;
EA = 1;
TR0 = 1;
while(1)
{
convert(gluiTemp);
for(i=0;i<6;i++)
{
P2=LED_seg[i]; //送段码
P0=LED_bit[i]; //送位码
delay1ms(5); //5ms延迟
P0=0;
}
}
}
实现数值0~65535的变化显示,每隔1s数值加1。( 设晶振频率为12MHz)
#include < reg51.h>
unsigned char glucCounter ;//定义一个全局变量,实现计数功能
unsigned int gluiTemp ;
void Timer0( ) interrupt 1 using 1
{
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
glucCounter ++ ;
if ( glucCounter == 20 )
{
gluiTemp ++ ;
glucCounter = 0;
}
}
void main(void)
{
unsigned char i,j;
gluiTemp = 0;
glucCounter = 0;
TMOD = 0x01;
TH = (65536 – 50000) / 256;
TL = (65536 – 50000) % 256;
ET0 = 1;
EA = 1;
TR0 = 1;
while(1)
{
convert(gluiTemp);
for(i=0;i<6;i++)
{
P2=LED_seg[i]; //送段码
P0=LED_bit[i]; //送位码
delay1ms(5); //5ms延迟
P0=0;
}
}
}
单片机输出(1)
例1:设晶振频率fosc=6MHz,要求在P2.0脚上输出周期为2ms的方波。
解:采用定时器T0的方式1进行编程
思路:采用定时间隔1ms,每次时间到P2.0取反并且启动下一次定时,从而实现2ms周期的方波。
定时所需计数次数n=1000us/2us=500
由于计数器递增计数,为得到500个计数之后的定时器溢出,必须给定时器置初值65536-500。
中断方式
#include <reg51.h>
sbit P2_0=P2^0;
void T0_ISR() interrupt 1 using 1 //T0中断服务程序入口
{
TH0= (65536-500)/ 256; //计数初值重载
TL0=(65536-500)% 256;
P2_0=!P2_0; //P2.0取反
}
void main(void)
{
TMOD=0x01; //T0工作在定时器方式l
TH0=(65536-500)/256; //计数初值
TL0=(65536-500)%256;
ET0=1; EA=1; //中断使能
TR0=1; //启动T0
while(1);
}
但是这里精度还有待考究
中断方式
#include <reg51.h>
sbit P2_0=P2^0;
void main(void)
{
TMOD=0x01; //T0工作在定时器方式l
TH0= (65536-500)/256; //计数初值
TL0=(65536-500)%256;
TR0=1; //启动T0
while(1)
{
TH0= (65536-500)/256; //计数初值
TL0=(65536-500)%256;
/*查询TF0是否为‘1’,如果为‘1’则说明溢出(定时时间到)*/
while (!TF0);
P2_0 = ! P2_0;
TF0 = 0;
}
}
单片机输出(2)
例2:设单片机fosc=6MHz,要求在P2.0引脚上输出周期为2ms,占空比为75%的矩形波。在例2基础上应作何修改?
解:采用定时器T0的方式1进行编程
思路:采用定时间隔0.5ms,并设置一个全局变量i对定时器的中断次数进行计数,小于等于3时输出高电平,等于4时输出低电平,以此循环。
#include "reg51.h"
sbit P1_0=P1^0;
unsigned char i;
void TIMER0_ISR (void) interrupt 1
{
TH0=(65536-250)/256;
TL0=(65536- 250)%256;
i++;
if(i = = 3) { P1_0=0; }
if(I = = 4) { P1_0=1; i=0; }
}
void main (void)
{
TMOD=0x01;
TH0=(65536- 250)/256;
TL0=(65536- 250)%256;
ET0=1; EA=1;
TR0=1;
i = 0;
P1_0 = 1;
while (1) { ; }
}
小思考
如果周期2ms占空比要求是35%,可以采用采用定时间隔0.5ms吗?
思路:高电平持续时间35%,低电平持续时间65%,应考虑取其公约数。一般取最大公约数。
所以可取时间间隔为0.1ms。
高电平持续7个定时间隔,低电平持续13个时间间隔。
单片机输出(3)
例3:设单片机fosc=6MHz,要求在P2.0引脚上输出周期为2ms,占空比为65%的矩形波。在例2基础上应作何修改?
#include "reg51.h"
sbit P1_0=P1^0;
unsigned char i;
void TIMER0_ISR (void) interrupt 1
{
TH0=(65536-50)/256;
TL0=(65536- 50)%256;
i++;
if(i = = 7) { P1_0=0; }
if(I = = 20) { P1_0=1; i=0; }
}
void main (void)
{
TMOD=0x01;
TH0=(65536- 50)/256;
TL0=(65536- 50)%256;
ET0=1; EA=1;
TR0=1;
i = 0;
P1_0 = 1;
while (1) { ; }
}
单片机输出(4)
例4:设晶振频率fosc=6MHz,要求在P2.0脚上输出周期为2s的方波。
长定时的实现
思路:定时间隔为1秒,使用T0无法直接得到1秒的定时。因此,需要使用多次定时复合的方法来得到较长时间的定时。
方法一 使用两个定时/计数器实现1秒定时:
T0工作在定时方式1,定时100ms,从而控制P1.0输出周期为200ms的方波;将这个方波输入到T1,T1工作在计数方式2,计数5次后溢出,控制P2.0反向,从而实现周期为2秒的方波输出。
#include <reg51.h>
sbit P1_0 = P1^0;
sbit P1_7 = P1^7;
void Timer0( ) interrupt 1 using 1
{
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
P1_0 = ! P1_0;
}
void Timer1( ) interrupt 3 using 2
{
P1_7 = ! P1_7;
}
void main( )
{
P1_7 = 0;
P1_0 = 1;
TMOD = 0x61;
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
TH1 = 256 – 5 ;
TL1 = 256 – 5;
IP = 0x08;
EA = 1;
ET0 = 1;
ET1 = 1;
TR0 = 1;
TR1 = 1;
while(1) { ; }
}
方法二 仅使用一个定时/计数器实现
使用方法一时需要使用两个定时器和两个IO引脚,资源消耗比较多。如何使用较少的资源实现同样的定时功能
#include < reg51.h>
sbit WAVE = P1^7;
unsigned char glucCounter;
void Timer0( ) interrupt 1 using 1
{
TH0 = (65536 – 50000) / 256;//设定初值,100ms定时
TL0 = (65536 – 50000) % 256;
glucCounter ++ ;//定义一个全局变量,实现计数功能
if ( glucCounter == 10 )
{
WAVE = ! WAVE;
glucCounter = 0;
}
}
void main( )
{
WAVE = 0;
TMOD = 0x01;
TH0 = (65536 – 50000) / 256;
TL0 = (65536 – 50000) % 256;
EA = 1;
ET0 = 1;
TR0 = 1;
glucCounter = 0;
while(1) { ; }
}
8051 定时计数器工程应用
设计要求
航标灯控制(工程设计)
2位拨码开关:灯质设定(频率与占空比)
光敏电阻:实现光照采集(光照强,电阻小;光照弱,电阻大)
灯驱动:发光二极管采用12V供电,要实验单片机驱动电路设计,三极管驱动
![image-20210613174204721](https://blog-images-1304155699.cos.ap-nanjing.myqcloud.com/image-20210613174204721.png)
电路仿真图:
软件程序
这里把相关函数先封装起来了,具体程序代码如下所示:
main.c
#include <reg51.h>
#include "timer.h"
/*******变量类型宏定义*******/
#define uchar unsigned char
#define uint unsigned int
/*******引用结构变量*******/
extern struct IALA IALA4[4];
extern struct Turn Counter50ms;
/*******IO口定义*******/
sbit D=P0^0; //灯输出控制,高电平有效
sbit OPCON=P1^2; //光照检测,高电平(亮)
/*******定时器0初始化*******/
void Timer0Init()
{
TMOD=(TMOD&0xf0)|0x01;//定时器0 方式1
TR0=1; //开启定时器T0
TH0=(65535-COUNT_50MS)/256;//设定定时器初始值(高8位)
TL0=(65535-COUNT_50MS)%256;//设定定时器初始值(低8位)
TF0=0; //中断标志位清零
ET0=1; //允许定时器0中断
EA=1; //打开总中断
PT0=0; //中断优先级设置
}
/*******主函数*******/
void main(void)
{
uchar key,pre_key=0,i;
uint sum;
Timer0Init(); //定时器0初始化
D=0; //LED控制端初始化
while(1)
{
key=key_scan(); //获取拨码开关对应状态
if(key!=pre_key)//拨码开关状态改变
{
sum=0; //变量初始化
for(i=0;i<6;i++)
{
sum=sum+IALA4[key].gcd*IALA4[key].state[i];//结构体变量赋值且运算赋值给sum
Counter50ms.state[i]=sum; //确定定时时间
}
}
pre_key=key;
}
}
void T0_ISR(void) interrupt 1 //中断服务函数
{
static uint i=0; //定义静态变量i
uchar j;
TH0=(65535-COUNT_50MS)/256; //重新赋初值
TL0=(65535-COUNT_50MS)%256;
if(OPCON==0) //如果是黑夜,对应灯质状态
{
for(j=0; j<2; j++) //循环闪烁
{
if(i==Counter50ms.state[2*j])
{
D=D_CLOSE;
}
if(i==Counter50ms.state[2*j+1])
{
D=D_OPEN;
}
}
if(i==Counter50ms.state[4])
{
D=D_CLOSE;
}
if(i>=Counter50ms.state[5])
{
D=D_OPEN;
i=0;
}
i++;
}
else
{
//白天熄灯
D=D_CLOSE;
i=0;
}
}
timer.c
#include <reg51.h>
#include "timer.h"
sbit D=P0^0; //灯输出控制,高电平有效
sbit OPCON=P1^2; //光照检测,高电平(亮)
//结构体全局变量定义,const:常数
const struct IALA IALA4[4]={
//NUM gcd ON1 OFF1 ON2 OFF2 ON3 OFF3
{0,0,0,0,0,0,0,0},//全亮
{1,10,1,3,0,0,0,0},
{2,10,1,1,1,7,0,0},
{3,10,1,2,1,2,1,5}
};
//结构体全局变量定义
struct Turn Counter50ms;
unsigned char key_scan(void) //拨码开关状态检测
{
unsigned char key;
key=0;
if(P1&0x01)
key=key|0x01;
if(P1&0x02)
key=key|0x02;
return(key);
}
timer.h
#ifndef _TIMER_H_
#define _TIMER_H_
#define COUNT_50MS 500 //50ms gcd:50ms的倍数
#define D_OPEN 1 //开灯
#define D_CLOSE 0 //关灯
//结构体类型声明1
struct IALA
{
unsigned char num; //拨码开关状态
unsigned char gcd; //各时间间隔最大公约数
unsigned char state[6];//无符号字符型时间常数
};
//结构体类型声明2
struct Turn
{
unsigned int state[6];
};
unsigned char key_scan(void); //函数声明
#endif
- 感谢你赐予我前进的力量