介绍
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度
(…)
STC89C52定时器资源
定时器框图
定时器工作模式
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
![](https://img-blog.csdnimg.cn/323cb5e7b91e4118a7343a2a0dd9a13f.png)
计数器最大只能存到65535
定时器时钟
中断系统
中断程序流程
STC89C52中断资源
定时器和中断系统
定时器相关寄存器
单片机通过配置寄存器来控制内部线路的连接,通过内部线路不同的连接方式实现不同电路,不同电路完成不同功能。
更多知识点详情查看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++;
}
}