单片机定时器

介绍

定时器介绍: 51 单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机   内部完成
定时器作用:

1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作

2)替代长时间的Delay,提高CPU的运行效率和处理速度

STC89C52定时器资源

定时器个数: 3 个( T0 T1 T2 ), T0 T1 与传统的 51 单片机兼容, T2 是此型号单片机增加的资源
注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说, T0 T1 的操作方式是所有 51 单片机所共有的

定时器框图

定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行

定时器工作模式

STC89C52 T0 T1 均有四种工作模式:

  模式013位定时器/计数器

  模式116位定时器/计数器(常用)

  模式28位自动重装模式

  模式3:两个8位计数器

工作模式 1 框图:

计数器最大只能存到65535

定时器时钟

SYSclk :系统时钟,即晶振周期,本开发板上的晶振为 11.0592MHz

中断系统

中断程序流程

STC89C52中断资源

中断源个数: 8 个(外部中断 0 、定时器 0 中断、外部中断 1 、定时器 1 中断、串口中断、定时器 2 中断、外部中断 2 、外部中断 3
中断优先级个数: 4
中断号:

注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等

定时器和中断系统

定时器相关寄存器

单片机通过配置寄存器来控制内部线路的连接,通过内部线路不同的连接方式实现不同电路,不同电路完成不同功能。

 更多知识点详情查看STC89C52手册——难!

 按键控制流水灯模式

1、先写个子函数来配置寄存器

为了初始化Timer0(计时器0)

首先配置TMOD(定时器/计数器工作模式寄存器)

这里我们使用定时器0,定时器1不管,全部置0;

使用16位定时器(模式一),则分别给M1,M0置0,1;要用作定时器(从内部系统时钟输入)则个C/T置0;GATE置0。

可得

TMOD = 0x01;//0000 0001

************************************************************************************************************** 

再配置TCON(定时器/计数器控制寄存器)注意:TCON是可位寻址,可以单个寄存器单独赋值,但是TMOD是不可位寻址,只能整体赋值。

 

 TF0先置0,当TF0=1时,会向CPU申请中断,产生中断;TR0置1,让定时器开始工作;

IE0和IT0是用于配置以下这部分的

但是GATE已经置0了,不用管这一部分。

TF0 = 0;
TR0 = 1;

计数器T0计数范围是0~65535,每隔1us计数加一,总共定时时间65535us。

当计数器计数到65536时,发生溢出,会发出中断请求,使TF0自动置1。

当计数器为64535时,距离溢出值还有1000us,即1ms,则可记为时间1ms。

则直接赋予初值为64535

因为它是两个八位寄存器拼接在一起的,八位寄存器只能计数范围为0~255,所以把64535分成高低位,分别储存在TH0和TL0上。

设置TH0为64535的高位,TL0为64535的低位

TH0 = 64535 / 256;
TL0 = 64535 % 256;
//类似于十进制的取高位和低位
//high = 123 / 100 = 1;
//low = 123 % 100 = 23;
//8位寄存器,2^8=256
//TH0是高8位,TL0就是低8位,乘起来就是2的16次方

我们需要把下面这条红线的路打通(T0即是计时器0的线路),则需要配置ET0=1,EA=1(全局中断),PT0=0(中断优先级设置,1为高级,0为低级,默认为0);

可得子函数:

void Timer0_Init()
{
	TMOD = 0x01;//0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0 = 64535 / 256;
	TL0 = 64535 % 256;
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

在STC软件中有快捷方式生成此代码:

11.0592MHz,1毫秒,定时器0,16位,12T

 

但是它没有设置中断系统的代码,需要手动加上

    ET0 = 1;
	EA = 1;
	PT0 = 0;

 整理之后可得:(第一句AUXR需要删掉)

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
    ET0 = 1;
	EA = 1;
	PT0 = 0;
}

一般来说,我们对TH0和TL0赋值时采用计算的方法,相对软件给出的直接赋值,不够准确,建议直接使用软件给出的赋值。 

2、再根据下图写出子函数:

void Timer0_Rountine(void) interrupt 1
{
	
	
}

意思即是当计时器T0申请发生中断时,调用这个函数(即中断后需要执行的内容)

可以加以验证:

void Timer0_Rountine(void) interrupt 1
{
	P2_0 = 0;
}

类似的,我们可以尝试使用这段函数实现计时一秒:

unsigned int T0Count = 0;//用于记录中断次数
void Timer0_Rountine(void) interrupt 1
{
	T0Count++;
	TH0 = 64535 / 256;
	TL0 = 64535 % 256;
//重新初始化TH0和TL0,使其每次都是差1ms执行中断,则可灵活运用为每次中断间隔1ms
	if (T0Count >= 1000)//当中断次数为1000ms时,即1s时,执行以下内容
	{
		T0Count = 0;//把中断次数归零,重新开始计数
		P2_0 = ~P2_0;//LED灯取反,具象化表示计时1s
	}
}

技巧:在赋值TMOD时,假使已经使用了定时器T1(高四位),正要使用定时器T0(第四位),

下面这种写法会影响到T1的使用,那么我们可以用一种新的方法来改进代码

void Timer0_Init()
{
	TMOD = 0x01;//0000 0001
}

 改进:

void Timer0_Init()
{
	TMOD = TMOD & 0xF0;
//假设T1已经被赋值,假设为0001 0000,则0001 0000 & 0xF0 = 0001 0000;即不影响高四位,把低四位置0
    TMOD = TMOD | 0x01;
//在第一步的基础上,再0001 0000 | 0x01 = 0001 0001;即不影响高四位,把最后一位置1,即我们的目的
}

 简写:

void Timer0_Init()
{
	TMOD &= 0xF0;
    TMOD |= 0x01;
}

3、再将过程模块化,方便以后使用

4、验证成功后,开始实现目标代码

a、添加独立按键所要用到的Delay文件

b、编写识别独立按键键码的程序

Key.c

#include <REGX52.H>
#include "Delay.h"

unsigned char Key()
{
	unsigned char KeyNum = 0;
	
	if(P3_1==0){Delay(20); while(P3_1==0); Delay(20); KeyNum=1;}
	else if(P3_0==0){Delay(20); while(P3_0==0); Delay(20); KeyNum=2;}
	else if(P3_2==0){Delay(20); while(P3_2==0); Delay(20); KeyNum=3;}
	else if(P3_3==0){Delay(20); while(P3_3==0); Delay(20); KeyNum=4;}	
	
	return KeyNum;
}

并写好相对应的头文件,原理和矩阵键盘的类似。

并进行验证:

main.c(仅用作验证)

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"

unsigned char KEYNUM;
void main(void)
{
	//Timer0_Init();
	while(1)
	{
		KEYNUM = Key();
		if(KEYNUM)
		{
			if(KEYNUM==1) P2_0=~P2_0;
			else if(KEYNUM==2) P2_1=~P2_1;
			else if(KEYNUM==3) P2_2=~P2_2;
			else if(KEYNUM==4) P2_3=~P2_3;
		}
	}
}

 实现独立按键控制流水灯方向

这里需要引入新的函数,因此也要引入新的头文件

#include <INTRINS.H>

其中的_cror_()和_crol_()函数实现了移位的效果

unsigned char a = 0xFE;//1111 1110
a = _crol_(a,1);//a = 1111 1101
//与按位左移<<符号不同的是,当1000 0000按位左移后,会出现溢出,变成0000 0000
//但是使用_crol_(a,1)函数后,它则会自动跳到最低位,不会出现溢出情况0000 0001
//这就省去了写判断是否溢出的函数的步骤

联合中断函数可实现每0.5s跳转的流水灯,但是可以用独立按键来控制流水灯的方向。

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char KEYNUM;
unsigned char KeyMode;

void main(void)
{
	P2=0xFE;
	Timer0_Init();
	while(1)
	{
		KEYNUM = Key();
		if(KEYNUM)
		{
			if(KEYNUM==1) KeyMode = 1;
			else if(KEYNUM==2) KeyMode = 2;
		}
	}
}

unsigned int T0Count = 0;
void Timer0_Rountine(void) interrupt 1
{
	T0Count++;
	TL0 = 0x66;		
	TH0 = 0xFC;
	if (T0Count >= 500)
	{
		T0Count = 0;
		if(KeyMode == 1) P2 = _cror_(P2,1);
		else if(KeyMode == 2) P2 = _crol_(P2,1);
	}
}

最终形态(忽略.h文件)

Timer0.c

#include <REGX52.H>


  
void Timer0_Init(void)		
{
	TMOD &= 0xF0;		
	TMOD |= 0x01;		
	TL0 = 0x66;		
	TH0 = 0xFC;		
	TF0 = 0;		
	TR0 = 1;		
    ET0 = 1;
	EA = 1;
	PT0 = 0;
}

//模板
/*unsigned int T0Count = 0;
void Timer0_Rountine(void) interrupt 1
{
	T0Count++;
	TL0 = 0x66;		
	TH0 = 0xFC;
	if (T0Count >= 1000)
	{
		T0Count = 0;
		...........
	}
}*/

Key.c

#include <REGX52.H>
#include "Delay.h"


unsigned char Key()
{
	unsigned char KeyNum = 0;
	
	if(P3_1==0){Delay(20); while(P3_1==0); Delay(20); KeyNum=1;}
	else if(P3_0==0){Delay(20); while(P3_0==0); Delay(20); KeyNum=2;}
	else if(P3_2==0){Delay(20); while(P3_2==0); Delay(20); KeyNum=3;}
	else if(P3_3==0){Delay(20); while(P3_3==0); Delay(20); KeyNum=4;}	
	
	return KeyNum;
}

main.c

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char KEYNUM;
unsigned char KeyMode;

void main(void)
{
	P2=0xFE;
	Timer0_Init();
	while(1)
	{
		KEYNUM = Key();
		if(KEYNUM)
		{
			if(KEYNUM==1) KeyMode = 1;
			else if(KEYNUM==2) KeyMode = 2;
		}
	}
}

unsigned int T0Count = 0;
void Timer0_Rountine(void) interrupt 1
{
	T0Count++;
	TL0 = 0x66;		
	TH0 = 0xFC;
	if (T0Count >= 500)
	{
		T0Count = 0;
		if(KeyMode == 1) P2 = _cror_(P2,1);
		else if(KeyMode == 2) P2 = _crol_(P2,1);
	}
}

定时器时钟

运用了LCD1602.c和LCD1602.h

还有上面的Timer0.c和Timer0.h

主函数为:

#include <REGX52.H>
#include "Timer0.h"
#include "LCD1602.h"

unsigned char H,M,S;

void main(void)
{
	Timer0_Init();
	LCD_Init();
	while(1)
	{
		LCD_ShowString(1,1,"Time:");
		LCD_ShowNum(2,1,H,2);
		LCD_ShowString(2,3,":");
		LCD_ShowNum(2,4,M,2);
		LCD_ShowString(2,6,":");
		LCD_ShowNum(2,7,S,2);
		if(S>=60) {S=0;M++;}
		if(M>=60) {M=0;H++;}
		if(H>23) {H=0;}
	}
}


unsigned int T0Count = 0;
void Timer0_Rountine(void) interrupt 1
{
	T0Count++;
	TL0 = 0x66;		
	TH0 = 0xFC;
	if (T0Count >= 1000)
	{
		T0Count = 0;
		S++;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单片机定时器是一种常用的计时器,它可以在单片机内部产生脉冲信号,用于计时或者延时。单片机定时器的原理是基于一个内部的计数器,当计数器的值达到设定的阈值时,就产生一个中断信号,从而触发相应的事件。单片机定时器通常有两种工作模式:定时器模式和计数器模式。 在定时器模式下,单片机定时器以一定的时间间隔产生一个脉冲信号,用于计时。当计数器的值达到设定的阈值时,就产生一个中断信号,从而触发相应的事件。在计数器模式下,单片机定时器接收外部的脉冲信号,用于计数。当计数器的值达到设定的阈值时,同样产生一个中断信号,从而触发相应的事件。 下面是两个具体的例子来说明单片机定时器的编程方法: 1. 在定时器范围内的定时 ```c #include <reg52.h> void main() { TMOD = 0x01; // 设置定时器0为模式1 TH0 = 0x3C; // 设置定时器0的初始值为0x3C TL0 = 0xAF; // 设置定时器0的初始值为0xAF TR0 = 1; // 启动定时器0 while(1) { if(TF0 == 1) // 判断定时器0是否溢出 { TF0 = 0; // 清除定时器0的溢出标志 TH0 = 0x3C; // 重新设置定时器0的初始值为0x3C TL0 = 0xAF; // 重新设置定时器0的初始值为0xAF // 这里可以添加需要执行的代码 } } } ``` 2. 在定时器范围外的定时 ```c #include <reg52.h> void main() { TMOD = 0x06; // 设置定时器1为模式2 TH1 = 0x00; // 设置定时器1的初始值为0x0000 TL1 = 0x00; // 设置定时器1的初始值为0x0000 TR1 = 1; // 启动定时器1 while(1) { if(TF1 == 1) // 判断定时器1是否溢出 { TF1 = 0; // 清除定时器1的溢出标志 TH1 = 0x00; // 重新设置定时器1的初始值为0x0000 TL1 = 0x00; // 重新设置定时器1的初始值为0x0000 // 这里可以添加需要执行的代码 } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值