首先新建工程
配置定时器有关的寄存器(定时器初始化)
我们打开‘REGX52.H’,可以看到里面已经声明过一些寄存器的地址了。sfr是特殊寄存器的声明符号。
TMOD
按照我们之前所讲的内容,我们使用定时器T0。M1和M0配成01,对应的是模式1,即16位定时器/计数器。GATE选择0,C/~T选择0,这样的话我们就打开了定时器,而不是计数器。
那么代码就是这样写的
TMOD = 0x01; //0000 0001
TCON
先来说一下可位寻址和不可位寻址。
不可位寻址的寄存器只能整体赋值,不能一位一位的赋值,可位寻址的寄存器是可以一位一位单独赋值。上面的TMOD就是不可位寻址,所以我们直接给TMOD整体赋值。
其他的寄存器我们不用管,有些是计数器用的,有些需要连接外设。那么代码就是这样写:
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取余可以得到它低八位的值。
怎么解释呢,假如我们有一个数123,我们想把它分离到两个寄存器里,那么对123除以100可以得到最高位的1,对123用100取余,可以得到后两位的23,这样它的高低位就分离了。同理,我们的寄存器保存的是二进制数,8位二进制数的值就是256,除以256就可以得到高八位,对256取余可以得到低八位。
中断寄存器
配置好定时器后,我们从图中可以看出,还需要连接好中断这条通路,这样我们需要给 ET0配置1,EA配置1,PT0配置1。相关寄存器可以在手册中查到。
代码就是这样写的
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;
}
中断函数内容
刚才配置好定时器,就相当于我们定了个闹钟,那么闹钟响了之后我们该干什么呢?我们做的事情其实就是关于中断函数的内容。
中断函数是有语法要求的,我们需要知道中断函数的中断号,按照中断号来定义中断函数。这样在定时器计数溢出,需要进入中断的时候,主函数会暂停进程进入中断函数。如果不按照要求定义中断函数,那么定时器计数到目标值时,中断并不会发生。
首先查阅手册可以看到中断号。
后面这个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这个软件可以帮助我们生成我们需要的初始化函数。只需要在软件中对相关参数进行调配。
复制代码,加入工程,就可以使用啦!
是不是很方便,前面复杂的东西一大堆,到这里只需要一个软件就好。
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模式的时钟。
不过软件生成的代码少了中断的配置,我们还是得加上关于中断的三行代码(最后一个可以不需要)
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代表左移。第一个参数是需要移位的数值,第二个参数是移位的位数。
完整工程
实现的现象就是,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
所有模块
(别忘记添加延时模块和定时器模块哦!)
绝大多数模块我们以前写过,翻一翻之前的工程就能找到代码。
定时器实现一个计时钟
完整工程
实现的效果就是,定时器从零开始计数,时分秒进位,显示在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;
}
}
}
}