概述
在程序设计中我们经常会用到延时,对于精度要求不高的应用来说我们一般采用插入语句等待的方式来实现,对于精度高的应用来说我们一般采用定时器中断来实现。本章主要内容包括:
- 延时时间的调试
- 定时器功能介绍
- 定时器应用实例介绍
6.1延时调试
前面程序中用到的延时通过执行多条语句来实现的,那么我们会问执行一条语句需要多长时间,延时1ms又需要多少条语句,延时的精度高吗?下面讲解的内容将包含以上内容。众所周知,晶振为单片机提供了时间基准,在晶振的节拍下程序按时间顺序往下执行,在单片机应用中有三个重要的时间概念,分别为时钟周期、机器周期和指令周期。
时钟周期: 单片机晶振频率的倒数,例如开发板的板载晶振为11.0592MHz,时钟周期为1/11.0592us,是51单片机的最基本,最小的时间单位。
机器周期: 单片机完成一条最基本指令操作所需要的时间,51单片机的一个机器周期等于12个时钟周期,RY-51开发板机器周期:12/11.0592us=1.09us。
指令周期: 执行一条指令所需的时间,一般由若干个机器周期组成。当然根据指令的不同所需的机器周期也不同,只需一个机器周期的简单指令,称之为单周期指令,包含两个机器周期的指令称之为双周期指令。
以RY-51单片机开发板为例,执行一条最简单的语句需要至少1.09us,复杂的语句需要多个1.09us来实现。因此,我们常常在程序中通过添加数目不等的简单语句来实现延时,但是延时的精度往往不会太高,我们可以通过keil软件的仿真功能来确定延时的大概数值。接下来讲解延时函数仿真功能,我们以延时10ms为例进行介绍。
#include< reg52.h >
sbit led0 = P1^0;//LED小灯管脚定义
sbit FM = P2^4;//蜂鸣器管脚位定义
sbit Key17 = P3^0;//独立按键管脚定义
void delayms(unsigned int z)//延时函数
{
unsigned int x,y;
for(x=z;x >0;x--)
for(y=70;y >0;y--);
}
void main()
{
while(1)
{
delayms(10);//延时10ms
delayms(10);//延时10ms
delayms(10);//延时10ms
}
}
首先,根据前面介绍的步骤建立一个工程,并编辑好如上所示的代码。这个代码和前面讲解的代码有点不同,我们先把延时的语句写成一个函数delayms(),将其放在主程序之外。在主程序中调用函数来实现延时。改变延时函数的输入参数便可改变延时语句的条数,因此延时变得非常的灵活。主程序中我们对延时函数进行了三次调用,接下来我们看看执行一次函数调用需要多长时间。
在keil软件快捷按钮菜单栏中找到配置,点击进入如下图所示的子菜单:
如上图所示,在”Target”目录先将仿真晶振设置为“11.0592”。
如图所示,在“C51”目录下将代码优化等级设置为“0:Constant folding”。
设置好如上规则后,点击菜单栏的debug快捷键如下图所示,进入调试模式,可以看到程序进程已经执行到了第17条代码。
接下来给17,18,19三条语句设置断点,将鼠标移到语句处点击右键选择“insert/Remove Breakpoint”,设置好之后语句左边会出现红色的小方块。当程序执行到断点时,程序便会停止下来。
如图6-5所示,当程序顺序执行的黄色箭头停留在第17行时,所使用的时间为:0.00042209s,如图左侧所示。
点击如图6-6左上角的程序执行快捷键,程序进程黄色箭头停留在第18行,即执行完了一条延时函数语句delayms(10),观察左下角的进程时间为:0.01057943s将执行语句前后时间相减,结果约等于10.1ms,因此与我们延迟10ms的要求相符。
通过调用函数实现延时的方法在单片机编程中是非常常见的,经过上面的延时调试可见这种方法的精度并不是很高,对应特殊应用场合我们一般采用定时器的方式实现。
6.2定时器介绍
定时器的功能很容易理解,就是到了某个指定的时间会提示设定者,我们平常使用的闹钟实际上可看作是一个定时器。定时器是单片机的重要资源,那么我们什么时候会使用到单片机的定时器功能呢?正常情况下我们一直在运行单片机的主程序,在主程序中假设我们需要1s之后去执行某个操作,这时我们只好在主程序中进行延时,直到1s时间到了再去执行相应的操作,那么在这个延时的过程中主程序别的事情干不了了,这样就很浪费系统的资源。
如果我们这个时候使用定时器功能就可以很好的解决这个问题。首先,我们在主程序中设定定时器定时1s,并启动定时器开始计时,因为定时器的运行和单片机的主程序执行是分开的,不会相互影响,因此主程序继续往下执行。当1s的时间到了,定时器告诉主程序,这个时候主程序停下当前正在干的活而去响应定时器。
STC89C52系列单片机内置2个16位的定时器,同时也可以当作计数器来用,分别为定时器0(T0),定时器1(T1),每个定时器有4种工作方式。那么定时器是如何计时的呢?我们以定时器T0工作在16位模式下进行介绍。16位的二进制的最大值为:0b1111,1111,1111,1111=65535。首先,我们需要给这个16位的寄存器赋初始值,假设为1000,当我们在主程序中启动定时器T0,此时这个寄存器会每12个时钟周期从1000开始往上加1,直到加到65535,当再加1后,定时器T0就溢出了,寄存器值从65535变成0,当溢出后定时器会告诉主程序,定时时间到了。所以我们只要改变这个初始值1000就可以得到不同的定时时间了。细心的同学可能会发现,当初始值为0的时候可以定时的时间最长。那么我们来看看定时器T0工作在16位模式下最长能定时多久。我们开发采用的时钟频率为11.0592MHz,因此,每12个时钟周期时间为:12/11.0592us=1.09us。启动定时器后,从初始值0累加到65535再到溢出为0,总共累加了65536次。因此,最大定时长度为:1.09us*65536=71.4ms。
6.3定时器使用
前面介绍了51系列单片机中定时器的工作原理,这节将重介绍定时器的使用。前面介绍过单片机的某个功能的实现都有特殊功能寄存器SFR有关,当然定时器的使用也不例外,特殊功能寄存器列表如下表所示:
表6-1 定时器/计数器特殊功能寄存器列表
TCON是一个8位定时器/计数器中断控制寄存器,可位寻址,即每一位可单独赋值。B7、B6为定时器T1控制位,B5、B4为定时器T0控制位,如下表所示:
表6-2 TCON寄存器
TF0:定时器/计数器0中断溢出标志位。T1被允许计数后,从初始值开始加1计数。当最高位产生溢出时由硬件置“1”TF0,向CPU请求中断,一直保持到CPU响应中断时,才由硬件清零“0”TF0,另外,TF0也可由程序查询清零“0”。
TF1:定时器/计数器1中断溢出标志位。功能与TF0类似。
TR0:定时器T0的运行控制位。该位由软件置位和清零。当TR0=1时就允许T0开始计数。
TR1:定时器T1的运行控制位。该位由软件置位和清零。当TR1=1时就允许T1开始计数。
其它位为外部中断相关内容,与定时器功能无关,这里暂时不做介绍。
前面讲的这几个位是定时器中断控制位,TR0赋值为1后,定时器T0开始运行,当定时器T0溢出时,单片机硬件会将TF0位置1。我们可以在程序中通过查询这个位是否为1来确定定时是否到达。另外,我们如果设置了定时器中断函数,当定时到达后,单片机程序会跳转到定时器中断函数,并且由硬件将TF0清零。
TCON是一个可位寻址的寄存器,在单片机程序中可以直接对TCON赋值,或者对其中的位进行直接赋值。
图6-7 定时器模式寄存器
TMOD为定时器模式寄存器,寄存器的高4位为定时器1模式位,低四位为定时器0模式位,高低位的功能类似,下面以定时器0为例:
C/T:定时器、计数器功能选择位。清零作为定时器,置1作为计数器。
表6-3 模式选择
定时器/计数器功能原理图如下图所示:
图6-8 定时器/计数器功能原理图
如上图所示,当作为定时器或计数器时唯一的区别为输入的时钟不一样。当作为定时器时,输入的时钟为系统时钟,而当作定时器时,系统输入时钟为外部引脚。
6.4定时器应用实例
本节我们介绍定时器的两种应用实例进行介绍,第一种为程序查询方式响应定时器的溢出,第二种为中断函数处理的方式响应定时器溢出。应用定时器实现的功能为每隔50ms使led0小灯闪烁一次,并使用定时器T0,工作在模式1,即16位定时的模式下。
6.4.1程序查询方式实例
定时器使用步骤:
- 配置定时器模式控制寄存器TMOD;
- 装载定时器初始值TH0,T0;
- 置位TR0,启动定时器开始计时;
- 主程序查询定时器中断标志位TF0。
假设设置定时器0为每1ms溢出1次,因此在主程序中累计查询到50次便使led小灯闪烁一次。根据前面介绍的方法计算出定时器0初始值65536-FOSC/12 * 1 * 10-3,程序设计如下图所示:
#include< reg52.h >
#define FOSC 11059200 //单片机晶振频率
#define T_1ms (65536 - FOSC/12/1000) //定时器初始值计算
sbit led0 = P1^0;
unsigned char count = 0;
void main()
{
TMOD = 0x01; //定时器工作模式配置
TL0 = T_1ms; //装载初始值
TH0 = T_1ms >>8;
TR0 = 1; //启动定时器
while(1)
{
if(TF0==1)
{
TF0 = 0; //软件清零
TL0 = T_1ms;//重装初始值
TH0 = T_1ms >>8;
count++;
if(count >=50)// 每一毫秒进入一次中断,达到50次则为50ms,翻转小灯。
{
count = 0;
led0 = ~led0;
}
}
}
}
如上图所示,在主程序开始阶段,对TMOD进行赋值来配置定时器T0为工作模式1,然后对TL0,TH0寄存器进行初始化赋值,紧接着启动定时器T0开始计数。完成上述步骤后,进入主程序循环,在循环中不断的检测TF0,当检测到定时器溢出后将TF0清零,重新转载定时器初始值,当溢出达到50次后翻转led小灯的值。这里需要注意的地方是,当判断到溢出后需要通过软件对TF0进行软件清零。结合我们前面学习的知识,大家可以根据自己的需求来改变程序的功能,加深对定时器功能的理解。
6.4.2中断响应方式实例
中断响应方式与程序查询方式略有不同,在程序初始化处需要打开定时器中断,当定时器溢出后程序跳转到中断入口程序,并由硬件自动清理TF0,可在中断程序中实现led小灯闪烁的功能。
定时器使用步骤:
- 配置定时器模式控制寄存器TMOD;
- 装载定时器初始值TH0,T0;
- 置位TR0,启动定时器开始计时;
- 允许定时器中断,并开启总中断;
- 进入中断程序。
- 在中断程序中重载定时器初始值,并闪烁led小灯。
程序设计如下图所示:
#include< reg52.h >
#define FOSC 11059200 //单片机晶振频率
#define T_1ms (65536 - FOSC/12/1000) //定时器初始值计算
sbit led1 = P1^1;
unsigned char count = 0;
void main()
{
TMOD = 0x01; //定时器工作模式配置
TL0 = T_1ms; //装载初始值
TH0 = T_1ms >>8;
TR0 = 1; //启动定时器
ET0 = 1; //允许定时器中断
EA = 1; //开总中断
while(1); //循环
}
void timer0() interrupt 1
{
TL0 = T_1ms;//重装初始值
TH0 = T_1ms >>8;
count++;
if(count >=50)// 每一毫秒进入一次中断,达到50次则为50ms,翻转小灯。
{
count = 0;
led1 = ~led1;
}
}
6.4本章小结
本章介绍了延时函数的调试,定时器基础知识的介绍以及定时器功能的应用实例。结合我们我们程序的介绍,多多练习下载试验逐步的熟练掌握延时函数、定时器功能的应用。
-
单片机
+关注
关注
6036文章
44555浏览量
635058 -
晶振
+关注
关注
34文章
2865浏览量
68024 -
定时器
+关注
关注
23文章
3247浏览量
114769 -
开发板
+关注
关注
25文章
5046浏览量
97428
发布评论请先 登录
相关推荐
评论