欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

51单片机开发入门(5)-定时器/计数器

发布时间:2023/12/20 编程问答 80 豆豆
生活随笔 收集整理的这篇文章主要介绍了 51单片机开发入门(5)-定时器/计数器 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

定时器/计数器阐述

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进行选择:

M1M0方式功 能
00013位定时器/计数器
01116位定时器/计数器
102自动重载的8位定时器/计数器
113仅适用于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;

工作方式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 = (6553650000) / 256;TL0 = (6553650000) % 256;gluiTemp ++} void main(void) { unsigned char i,j; gluiTemp = 0;TMOD = 0x01;TH0 = (6553650000) / 256;TL0 = (6553650000) % 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 = (6553650000) / 256;TL0 = (6553650000) % 256;glucCounter ++ ;if ( glucCounter == 20 ){gluiTemp ++ ;glucCounter = 0;} } void main(void) { unsigned char i,j; gluiTemp = 0;glucCounter = 0;TMOD = 0x01;TH = (6553650000) / 256;TL = (6553650000) % 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=65536500/ 256; //计数初值重载TL0=65536500)% 256; P2_0=!P2_0; //P2.0取反 } void main(void) { TMOD=0x01; //T0工作在定时器方式lTH0=65536500/256; //计数初值TL0=65536500)%256; ET0=1; EA=1; //中断使能TR0=1; //启动T0while(1); }

但是这里精度还有待考究

中断方式

#include <reg51.h> sbit P2_0=P2^0; void main(void) { TMOD=0x01; //T0工作在定时器方式lTH0=65536500/256; //计数初值TL0=65536500)%256; TR0=1; //启动T0while(1){TH0=65536500/256; //计数初值TL0=65536500)%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 = (6553650000) / 256;TL0 = (6553650000) % 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 = (6553650000) / 256;TL0 = (6553650000) % 256;TH1 = 2565 ;TL1 = 2565;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 = (6553650000) / 256;//设定初值,100ms定时TL0 = (6553650000) % 256;glucCounter ++ ;//定义一个全局变量,实现计数功能if ( glucCounter == 10 ){WAVE = ! WAVE;glucCounter = 0;} } void main( ) {WAVE = 0;TMOD = 0x01;TH0 = (6553650000) / 256;TL0 = (6553650000) % 256;EA = 1;ET0 = 1;TR0 = 1;glucCounter = 0;while(1) { ; } }

8051 定时计数器工程应用

设计要求

航标灯控制(工程设计)

2位拨码开关:灯质设定(频率与占空比)

光敏电阻:实现光照采集(光照强,电阻小;光照弱,电阻大)

灯驱动:发光二极管采用12V供电,要实验单片机驱动电路设计,三极管驱动

电路仿真图:

软件程序

这里把相关函数先封装起来了,具体程序代码如下所示:

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 方式1TR0=1; //开启定时器T0TH0=(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];//结构体变量赋值且运算赋值给sumCounter50ms.state[i]=sum; //确定定时时间}}pre_key=key;} } void T0_ISR(void) interrupt 1 //中断服务函数 {static uint i=0; //定义静态变量iuchar 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

总结

以上是生活随笔为你收集整理的51单片机开发入门(5)-定时器/计数器的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。