延时函数的精确计算
利用for语句的延时特性,编写一个让一个发光二级管以间隔1S亮灭闪动的程序。
#include<reg52.h>
#define uint unsigned int
sbit led1=P1^0;
uint i,j;
void main()
{
while(1)
{
led1=0;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
led1=1;
for(i=1000;i>0;i--)
for(j=110;j>0;j--);
}
}
简单的分析一下整个程序:点亮灯→延时一会儿→关闭灯→延时一会儿→点亮灯→延时一会儿→关闭灯→延时一会儿……
下面来介绍如何用keil4来模拟出这个延时语句究竟是多长时间。打开keil4编辑界面,单击→【Target】→【Xtal(MHz)】
将24.0改成12.0(我的开发板是12.000MHZ),如图1所示。
图1
Keil编译器在编译程序时,计算代码执行时间与晶振频率值有关,既然要模拟真实时间,那么软件模拟速度要与硬件一一对应。根据自己的实际情况来调整晶振值。
单击【OK】后,再单击窗口上的调试快捷图标,进入软件模拟调试模式,如图2所示。
图2
在软件调试模式下,可以设置断点 、单步、全速、进入某个函数内部运行程序,同时还可以查看变换过程、模拟硬件I/O口电平变化、查看代码执行时间等。调整状态下多了图3所示的几个调试按钮。
图3
1.(从左向右第1个按钮,下同)将程序复位到主函数的最开始处,准备重新运行程序。
2.全速运行,运行程序时中间不停止。
3.停止全速运行,全速运行时激活该按钮,用来停止正全速运行的程序。
4.进入子函数内部。
5.单步执行代码,它不会进入子函数内部,可直接跳过函数。
6.跳出当前进入的函数,只有进入子函数内部该按钮才被激活。
7.程序直接运行至当前所在行。
在单步执行代码时,可以查看I/O口电平变化和变量值的变化。现将。先将硬件I/O口模拟器打开,在图4单击【Port 1】项,弹出图5所示对话框。
图4
图5
图5显示的是软件模拟出的单片机P1口8位口线的状态,单片机上电后I/O口全为1,即十六进制的0xFF.
单击图2中右下角变量观察窗口【Watch#1】标签,窗口如图6所示,可以看到上面显示“<double-click or F2 to add>”的字样,然后分别按两次F2,输入程序中的两个变量i和j。在右面显示出变量的值0X0000,如图6所示。因为i和j在最开始定义的时候并没有给它们赋初值,编译器默认给它们的初值为0,而当进入for语句后,才给i和j分别赋了1000和110.
图6
在图7中我们最关心的数据只有一个,也是核心部分“sec”,它后面显示的数据就是程序执行所用的时间,单位是秒,可以看到
图7
上面显示的是389us,这是程序启动执行到穆青停止位置所花的时间(注意:这个时间是累计时间。)
回到代码编辑框,在图2中可以看到主函数“led1=0;”,前面有一个黄色的小箭头。这个小箭头指向的代码是下一部将要执行的代码,单击单步运行图标,这时看到黄色小箭头向下移动了一行,在P1口软件模拟窗口中,P1的最低位的对号没有了,这说明“led1=0;”这条语句执行完了,在实际硬件中也就点亮了P1^0对应的二极管。同时sec后面变为390uS,因此可以算出执行这条指令实际花了390-389=1uS的时间,这个时间恰好就是51单片机在12.000M晶振下一个机器周期所花费的时间。
接着再单击单步运行按钮,这时右下角变量查看窗口中的i值变成0x3E8,在这个值上右击选择【Number Base→Decimal】项,将数值显示方式改成十进制显示,可以看到i的值为1000,实际上就是刚才上一步运行第一个for语句时给i赋的值。继续单步运行可以看到i的值从1000开始往下递减,同时左侧的sec在一次次增加,但j的值始终为0,这是因为每执行一条外层for语句,内层for语句执行110次,即j已经从110递减到0了,所以看上去j始终为0。那如果要看这个for嵌套语句到底执行了多长时间的话是不是就要单击1000次呢?其实不用那么麻烦,可以通过设置断点来解决这个问题。
设置断点有很多好处,在软件模拟调试状态下,当程序全速运行时,每遇到断点,程序会自动停止在断点处,即下一步将要执行断点处所在的这条指令。这样,在延时语句的两端各设置一个断点,然后通过全速运行,便可以计算出所求严时代码所执行的时间。
设置方法如下:单击复位钮,然后在第一个for所在行前面空白处双击鼠标,前面出现一个红色方框,表示本行设置了一个断点,然后在下面“led1=1;”所在的行以同样的方式插入另一个断点,这两个断点之间的断码就是这个两级for嵌套语句,如图8所示。
图8
单击全速运行按钮,程序会自动停止在第一个for语句所在行,查看时间为390uS,在单击一次全速按钮,程序停止在第二个for语句下面一行处,查看时间显示为892.397mS。两者相减为892.007mS。误差太大进行调整。当:
for(i=1004;i>0;i--)
for(j=123;j>0;j--);显示时间为1.000381s。两者相减为0.999991S,误差为9s,如果忽略uS级误差,此时间为1S。到此延时函数就计算出来了。
补充:
1. 可以改变for语句中两个变量来重新测试时间,for语句中两个变量类型都为unsigned int型时(注意,若变量为其他类型则时间不遵循以下规律,因为变量类型不同,单片机运行所需的时间就不同),内层for语句变量恒为123时,内层for语句变量为x*1004,这个for嵌套语句就延时xS。大家可以自己验证,测试出更精确的延时语句。
2.单片机的几个周期介绍
<1.>时钟周期。也称震荡周期,定义为时钟频率的倒数,他是单片机中最基本,最小的时间的单位。
<2.>状态周期。它是时钟周期的两倍。
<3.>机器周期。单片机的基本操作周期,在一个机器周期内,单片机完成一项基本操作,如取指令、存储器读/写等。它由12个时钟周期(6个状态周期)组成。
<4.>指令周期。它是指CPU执行一条指令所需要的时间。一般一个指令周期含有1~4个机器周期。