首先新建工程

配置定时器有关的寄存器(定时器初始化)

我们打开‘REGX52.H’,可以看到里面已经声明过一些寄存器的地址了。sfr是特殊寄存器的声明符号

TMOD

image.png

按照我们之前所讲的内容,我们使用定时器T0。M1和M0配成01,对应的是模式1,即16位定时器/计数器。GATE选择0,C/~T选择0,这样的话我们就打开了定时器,而不是计数器。

那么代码就是这样写的

TMOD = 0x01; //0000 0001

TCON

先来说一下可位寻址和不可位寻址。

不可位寻址的寄存器只能整体赋值,不能一位一位的赋值,可位寻址的寄存器是可以一位一位单独赋值。上面的TMOD就是不可位寻址,所以我们直接给TMOD整体赋值。

image.png

其他的寄存器我们不用管,有些是计数器用的,有些需要连接外设。那么代码就是这样写:

TF0 = 0;
TR0 = 1;

赋初值

我们来计算一下我们从开始计数到产生中断的时间

定时器每隔1us产生一次脉,计一次数。计数上限是65535。那么总共定时时间就是65536us,即65ms。如果我们需要让它每隔1ms来记一次数,我们可以给计数器一个初值.

我们设置定时器初始值为64535,离溢出差值1000us,所以计数时间为1ms。

代码是这样写的:

TH0 = 64535/256;//252,即二进制的1111 1100
TL0 = 64535%256;//23,即0001 01111

TH0和TL0上一讲提到过,它们存储着定时器内的值。这里解释一下,一个十进制数除以256可以得到它二进制高八位的值,一个数对256取余可以得到它低八位的值

image.png

怎么解释呢,假如我们有一个数123,我们想把它分离到两个寄存器里,那么对123除以100可以得到最高位的1,对123用100取余,可以得到后两位的23,这样它的高低位就分离了。同理,我们的寄存器保存的是二进制数,8位二进制数的值就是256,除以256就可以得到高八位,对256取余可以得到低八位。

中断寄存器

image.png

配置好定时器后,我们从图中可以看出,还需要连接好中断这条通路,这样我们需要给 ET0配置1,EA配置1,PT0配置1。相关寄存器可以在手册中查到。

image.png

代码就是这样写的

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

整个初始化函数

所以整个初始化函数就是这样

void Timer0_Init()
{
	TMOD = 0x01; //0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0 = 64535/256;//252,即二进制的1111 1100
	TL0 = 64535%256;//23,即0001 01111
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

中断函数内容

刚才配置好定时器,就相当于我们定了个闹钟,那么闹钟响了之后我们该干什么呢?我们做的事情其实就是关于中断函数的内容。

image.png

中断函数是有语法要求的,我们需要知道中断函数的中断号,按照中断号来定义中断函数。这样在定时器计数溢出,需要进入中断的时候,主函数会暂停进程进入中断函数。如果不按照要求定义中断函数,那么定时器计数到目标值时,中断并不会发生。

首先查阅手册可以看到中断号。

image.png

后面这个interrupt是中断号,它跟在函数定义后面,让这个函数从一个普通函数变成一个具有中断处理能力的特殊函数。前面的名称是可以改的,但是中断号不能改。

void Timer0_Routine() interrupt 1
{
	 //这里写中断函数的内容
}

举个例子

#include <REGX52.H>
void Timer0_Init()
{
	TMOD = 0x01; //0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0 = 64535/256;//252,即二进制的1111 1100
	TL0 = 64535%256;//23,即0001 01111
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}
void main()
{
	Timer0_Init();
	while(1)
	{
		
	}
}	
void Timer0_Routine() interrupt 1
{
	 P2_0 = 0;
}

我们可以写这样一个代码,主程序只有定时器初始化, 烧录程序会发现,LED0的确会亮,虽然我们主程序并没有点灯的语句。

现在升级一下代码,试试看效果。我们在中断函数里面加入T0Count变量,让他自增到1000来延长时间。同时在中断执行后,将定时器的初始值设置为64535,不然定时器执行中断后会置零,那我们之前计算的定时周期执行一次就无效了。

#include <REGX52.H>

void Timer0_Init()
{
	TMOD = 0x01; //0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0 = 64535/256;//252,即二进制的1111 1100
	TL0 = 64535%256;//23,即0001 01111
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

void main()
{
	Timer0_Init();
	while(1)
	{
		
	}
}	
unsigned int T0Count;
void Timer0_Routine() interrupt 1
{
	TH0 = 64535/256;//252,即二进制的1111 1100
	TL0 = 64535%256;//23,即0001 01111
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count = 0;
		P2_0 = ~P2_0;
	}
	
}

用逻辑语言升级以下代码

我们之前配置寄存器时的操作方式,是直接用等号赋值。比如配置TMOD寄存器,它是不可位寻址的,所以我们直接给TMOD一个0x01,让它的寄存器值为0000 0001,来配置T0。但是这样的操作有个非常明显的缺点,假如我们增加配置定时器T1,就需要对高四位进行操作,赋值0x10,即0001 0000。这样的配置会把低四位之前配置好的定时器T0给覆盖掉。

为了解决这样的缺点,我们提出一种新的配置寄存器方法。姑且叫做“与或赋值法”,“与”用来清零,“或”用来赋值。

学习过数电大家应该知道,&(与)符号和|(或)符号的含义。没学过也没关系,我来解释一下,两个数相与,全1结果就是1,否则结果就是0;两个数相或,只要有1结果就是1,否则结果就是0。

观察以下代码

TMOD = TMOD & 0xF0;//与操作用来清零。和1111相与的结果都是它本身,任何数和0000相与结果都是0.这样低四位就被清零,而高四位不会变化。
TMOD = TMOD | 0x01;//或操作用来赋值。任何数和1相或结果都是1,和0相或结果都是它本身。所以或操作可以用来赋值。高四位保持不变,低四位赋值为0001.

上述代码可以缩写一下

TMOD &= 0xF0;//低四位清零,高四位不变
TMOD |= 0x01;//低四位赋值0001,高四位不变

如果大家看过一些高级工程师的代码,就会发现这种方法使用率特别高,因为在工程量比较大的时候,我们会记不住之前寄存器的配置的方式。但是采用这样的方法,不会对以前寄存器的状态进行修改,还能对我们想要的位进行赋值,效果非常好。

用STC-ISP来生成初始化代码

刚才我们配置定时器的方法是查手册,配寄存器。实际上STC-ISP这个软件可以帮助我们生成我们需要的初始化函数。只需要在软件中对相关参数进行调配。

image.png

复制代码,加入工程,就可以使用啦!

是不是很方便,前面复杂的东西一大堆,到这里只需要一个软件就好。

void Timer0Init(void)		//1毫秒@12.000MHz
{
	//AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

上面注释掉的一行代码需要删掉,因为我们单片机只有12T模式,所以不需要选择12T模式的时钟。

image.png

不过软件生成的代码少了中断的配置,我们还是得加上关于中断的三行代码(最后一个可以不需要)

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

定时器模块化

//Timer0.h
#ifndef ___TIMER0_H__
#define ___TIMER0_H__

void Timer0Init(void);

#endif
//Timer0.c
#include <REGX52.H>
#include "Timer0.h"

/**
  * @brief	定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
*/
void Timer0Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
    ET0 = 1;
	EA = 1;
	PT0 = 0;
}

/**
  * @brief	定时器中断函数模板
  * @param  无
  * @retval 无
*/
/*
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count; ;//这里将它设置为静态变量。否则函数在主程序中引用完该变量会被销毁。不设置成全局变量的目的是为了节省内存
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count = 0;
		P2_0 = ~P2_0;
	}
	
}
*/

模块化函数之前讲过,这里不详细说了。建立好定时器模块后,别忘了在主函数头文件#include一下哦。

定时器和独立按键实现流水灯

这里介绍一个函数 _crol_,_cror_。它们在头文件 "INTRNINS.H"里。功能是实现数值移位,cror代表右移,crol代表左移。第一个参数是需要移位的数值,第二个参数是移位的位数。

image.png

完整工程

实现的现象就是,LED灯从左到右依次闪烁,按下独立按键1后,LED改变闪烁方向,从右向左依次闪烁。代码含义我就不一一分析,有问题的朋友们可以在评论区提出讨论。

//main.c
#include <REGX52.H>
#include <INTRINS.H>
#include "Key.H"
#include "Delay.H"
#include "Timer0.H"

unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;//1111 1110
	Timer0Init();
	while(1)
	{
		KeyNum = Key();
		if(KeyNum)
		{
			if(KeyNum==1) 
			{
				LEDMode++;
				if(LEDMode>=2) LEDMode = 0;
			}
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count; 
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)
	{
		T0Count = 0;
		if(LEDMode == 0)
			P2 = _crol_(P2,1);
		if(LEDMode == 1)
			P2 = _cror_(P2,1);
	}
}
//key.c
#include <REGX52.H>
#include "Delay.h"

/**
  * @brief	识别独立按键按下的位置
  * @param  无
  * @retval 返回按下独立按键的位置
*/
unsigned char Key()
{
	unsigned int KeyNumber=0;
	
	if(P3_1 == 0) {Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
	if(P3_0 == 0) {Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
	if(P3_2 == 0) {Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
	if(P3_3 == 0) {Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
	
	return KeyNumber;
}
//Key.h 
#ifndef __Key_H__
#define __Key_H__

unsigned char Key();

#endif

所有模块

(别忘记添加延时模块和定时器模块哦!)

绝大多数模块我们以前写过,翻一翻之前的工程就能找到代码。

image.png

定时器实现一个计时钟

完整工程

实现的效果就是,定时器从零开始计数,时分秒进位,显示在LCD1602显示屏上。完整工程代码如下所示。

//main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hou;

void main()
{
	Timer0Init();
	LCD_Init();
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowString(2,1,"  :  :  ");
	while(1)
	{
		LCD_ShowNum(2,1,Hou,2);
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowNum(2,7,Sec,2);
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count; 
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count = 0;
		Sec++;
		if(Sec>=60)
		{
			Sec = 0;
			Min++;
			if(Min>=60)
			{
				Min = 0;
				Hou++;
				if(Hou>=24)
					Hou = 0;
			}
		}
	}
	
}

所有模块

image.png

转自:https://blog.csdn.net/Destiny_Di/article/details/127121966