1、定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
2、定时器的作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
(2)替代长时间的Delay,提高CPU的运行效率和处理速度。
3、定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源(STC89C52定时器资源),定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的。
4、定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行。
5、STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
(1)SYSclk:系统时钟,即晶振周期,晶振见开发板(我的晶振频率是11.0592MHz)
(2)control的逻辑表达式:
(3)见上图,如果红色箭头指向的开关选择了含SYSclk的一条线路,就是选择了定时器功能,定时器会产生一个计数脉冲,计数脉冲驱动技术单元进行计数,计数单元共16位,计数最大值为65535,在计数脉冲的作用下,计数单元每隔1us计数加1(不一定是每隔1us加1,这与晶振的频率以及分频选择有关,每隔1us加1是12MHz晶振频率经过12分频的结果,12MHz经过12分频就是1MHz),当计数单元产生溢出,也就是计数计到最大值时,TF会被置为1,向CPU发送中断请求,待CPU响应中断后该位重新被置为0,计数单元重新开始计数。程序可以在每次中断结束前给计数单元赋初值以改变发出中断信号的时间间隔(比如将计数单元的值置为64535,那么计数单元过了1000微秒后就会发生溢出,也就是说定时器每隔1ms就能产生一个中断信号)。
6、中断系统:
中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。
当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断,实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。
当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。
STC89C52中断资源:
(1)中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)。
(2)中断优先级个数:4个。
(3)中断号:中断资源多种多样,为了让每个中断能准确匹配对应的中断函数,便有了中断号,一个中断号最多对应一个中断函数,比如定时器0对应的中断号为1,那么当定时器0产生中断时,程序会转去执行带有中断号1的函数,待函数执行完成程序继续向下执行。
(4)注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等。
7、定时器相关寄存器:
(1)寄存器是连接软硬件的媒介,在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式。
(2)定时器/计数器0和1的相关寄存器:
(3)中断寄存器:
8、借助STC-ISP可以快速生成配置定时器的子函数(中断系统需要自己配置):
注:AUXR并无定义,不需要配置;关于定时初值这里,一定要看系统频率和开发板上的晶振是否对应,不同系统频率初值是不一样的;上图所示初始化函数中TMOD只做了低4位清零,没有给低4位配置,具体见下面的代码(借助STC-ISP主要是计算定时初值)。
9、按键控制LED流水灯的正序or逆序:
(1)项目包含的文件:其中需要重写的都会在下面给出,未给出的沿用旧例出现过的即可。
(2)编写代码文件,然后编译:
①key.h文件:
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif
②key.c文件:
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0-4,无按键按下时返回值为0
*/
unsigned char Key()
{
unsigned char 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;
}
③Timer0.h文件:
#ifndef __Timer0_H__
#define __Timer0_H__
void Timer0_Init();
#endif
④Timer0.c文件:
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@11.0592MHz
* @param 无
* @retval 无
*/
void Timer0_Init()
{
TMOD = 0x01; //0000 0001
//仅关注0-3位,0位和1位决定配置定时器0为模式1(16位定时器/计数器)
//2位决定选择定时器功能(与之相对的是计数器功能)
//3位决定打开定时器(具体见原理图以及手册)
//TMOD“不可位寻址”,也就是说对其进行配置的话不能按位进行配置,要“一气呵成”
TF0 = 0; //中断标志位初始化为0,计数产生溢出时该位被置为1,向CPU发出中断请求,CPU响应中断后该位被置为0
TR0 = 1; //允许定时器T0计数
//由于TMOD的3位,也就是GATE被置为了0,那么无论IE0、IT0如何配置,结果都是一样的
//将定时器0的计数单元初始化为64535(这个不一定,跟晶振有关,0xFC66就不是64535),这样每隔1ms就能产生一个中断信号
TH0 = 0xFC; //定时器0的计数单元高8位
TL0 = 0x66; //定时器0的计数单元低8位
ET0 = 1;
EA = 1;
PT0 = 0;
//结合原理图理解,这是在配置中断系统,创造产生中断的环境
}
/* 定时器0函数中断模板
void Timer0_Routine() interrupt 1 //CPU响应中断后执行的函数(放在main.c中)
{
static unsigned int T0Count = 0; //定义计数器
T0Count++;
if(T0Count >= 1000) //每1000个中断信号(1秒)执行一次下面的代码段
{
T0Count = 0;
}
//每次中断结束都要重置计数单元
TH0 = 0xFC; //定时器0的计数单元高8位
TL0 = 0x66; //定时器0的计数单元低8位
}
*/
⑤main.c文件:
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"
#include "key.h"
#include <INTRINS.H> //使用循环移位函数需要包含这个头文件
unsigned char KeyNum = 0, LEDMode = 0;
void main()
{
Timer0_Init(); //首先配置好定时器
P2 = 0xFE; //初始状态是第一盏LED灯被点亮
while(1)
{
KeyNum = Key();
if(KeyNum == 1) //按下按键1,改变流水灯模式
{
LEDMode++;
if(LEDMode == 2) //LEDMode只会有0和1两个取值
LEDMode = 0;
}
}
}
void Timer0_Routine() interrupt 1
//CPU响应定时器0中断后执行的函数(函数名不重要,关键是interrupt这个中断号,1是定时器0的中断号)
{
static unsigned int T0Count = 0; //定义计数器
T0Count++;
if(T0Count >= 1000) //每1000个中断信号(本例中是1秒)执行一次下面的代码段
{
if(LEDMode == 0)
P2 = _crol_(P2,1); //P2左移1位(循环移位,1移到最左边后会被移回最右边)
if(LEDMode == 1)
P2 = _cror_(P2,1); //P2右移1位(循环移位,1移到最右边后会被移回最左边)
T0Count = 0;
}
//每次中断结束都要重置计数单元,否则中断发生的频率会比预期高很多
TH0 = 0xFC; //定时器0的计数单元高8位
TL0 = 0x66; //定时器0的计数单元低8位
}
(3)将生成的.hex文件下载到开发板中,按动独立按键K1,观察8个LED的现象。
10、借助定时器在液晶屏上显示计时器:
(1)项目包含的文件:其中需要重写的都会在下面给出,未给出的沿用旧例出现过的即可。
(2)在main.c文件中写入如下代码,然后进行编译。
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char sec = 0; //秒
unsigned char min = 0; //分
unsigned char hour = 0; //时
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,3,":"); //显示时间戳的冒号
LCD_ShowString(2,6,":");
Timer0_Init();
while(1)
{
LCD_ShowNum(2,7,sec,2); //用两位数字显示秒
LCD_ShowNum(2,4,min,2); //用两位数字显示秒
LCD_ShowNum(2,1,hour,2); //用两位数字显示秒
}
}
void Timer0_Routine() interrupt 1 //CPU响应中断后执行的函数
{
static unsigned int T0Count = 0; //定义计数器
T0Count++;
if(T0Count >= 1000) //每1000个中断信号(1秒)执行一次下面的代码段
{
T0Count = 0;
sec++; //秒计时
if(sec >= 60) //60秒进1分钟
{
sec = 0;
min++; //分计时
if(min >= 60) //60分钟进1小时
{
min = 0;
hour++; //时计时
}
}
}
//每次中断结束都要重置计数单元
TH0 = 0xFC; //定时器0的计数单元高8位
TL0 = 0x66; //定时器0的计数单元低8位
}
(3)将生成的.hex文件下载到开发板上,可以看到液晶屏开始计时。
10、借助定时器在液晶屏上显示计时器:
(1)项目包含的文件:其中需要重写的都会在下面给出,未给出的沿用旧例出现过的即可。
(2)在main.c文件中写入如下代码,然后进行编译。
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char sec = 0; //秒
unsigned char min = 0; //分
unsigned char hour = 0; //时
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,3,":"); //显示时间戳的冒号
LCD_ShowString(2,6,":");
Timer0_Init();
while(1)
{
LCD_ShowNum(2,7,sec,2); //用两位数字显示秒
LCD_ShowNum(2,4,min,2); //用两位数字显示秒
LCD_ShowNum(2,1,hour,2); //用两位数字显示秒
}
}
void Timer0_Routine() interrupt 1 //CPU响应中断后执行的函数
{
static unsigned int T0Count = 0; //定义计数器
T0Count++;
if(T0Count >= 1000) //每1000个中断信号(1秒)执行一次下面的代码段
{
T0Count = 0;
sec++; //秒计时
if(sec >= 60) //60秒进1分钟
{
sec = 0;
min++; //分计时
if(min >= 60) //60分钟进1小时
{
min = 0;
hour++; //时计时
}
}
}
//每次中断结束都要重置计数单元
TH0 = 0xFC; //定时器0的计数单元高8位
TL0 = 0x66; //定时器0的计数单元低8位
}
(3)将生成的.hex文件下载到开发板上,可以看到液晶屏开始计时。