---- 在windows编程中有时要用到定时器的功能,大家常用的是通过SetTimer函数设置一个定时器,在事件WM_TIMER响应函数中 处理,然后用KillTimer函数取消此定时器。但此种方式的定时器只能精确到大约55毫秒,对于55毫秒以下的时间精度便无能为力。
---- 在这里向大家提供一个可以精确到1毫秒的定时器(分辨率可以达到1毫秒)— —多媒体定时器。它主要通过以下函数来实现:
---- timeBeginPeriod —— 建立应用程序使用的定时器分辨率;
---- timeEndPeriod —— 清除前面用timeBeginPeriod函数建立的最小定时器分辨率;
---- timeSetEvent —— 产生一个在指定的时间或时间周期间隔内执行的定时器事件;
---- timeKillEvent —— 删除前面用timeSetEvent产生的定时器事件;
---- timeGetDevCaps —— 返回关于定时器服务能力的信息。
使用准备:
首先要在相应的文件前加入:#include “mmsystem.h”
在Visual C++集成开发环境的Project | Setting | Link | Input | library modules下加入winmm.lib
这样就做好了多媒体定时器相关函数的链接。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在工业生产控制系统中,有许多需要定时完成的操作,如:定时显示当前时间,定时刷新屏幕上的进度条,上位机定时向下位机发送命令和传送数据等。特别是在对控制性能要求较高的控制系统和数据采集系统中,就更需要精确定时操作。
众所周知,Windows是基于消息机制的系统,任何事件的执行都是通过发送和接收消息来完成的。这样就带来了一些问题,如一旦计算机的CPU被某个进程占用,或系统资源紧张时,发送到消息队列中的消息就暂时被挂起,得不到实时处理。因此,不能简单地通过Windows消息引发一个对定时要求严格的事件。另外,由于在Windows中已经封装了计算机底层硬件的访问,所以要想通过直接利用访问硬件来完成精确定时,也比较困难。在实际应用时,应针对具体定时精度的要求,采取与之相适应的定时方法。
多媒体定时器不依赖于消息机制,而是用函数timeSetEvent()产生了一个独立的线程,直接调用预先设置好的回调函数进行处理,从而切实保障了定时中断得到实时响应,其定时精度可达1ms。
本实例实现了一中微秒级的精确定时,程序的界面提供了两个"Edit"编辑框,其中一个编辑框输入用户理想的定时长度,另外一个编辑框返回实际的时间长度,经过大量的实验测试,一般情况下误差不超过5个微秒。程序的运行界面如图一所示:
图一、实现微秒级的精确定时器 |
MMRESULT timeSetEvent(UINT uDelay,UINT uResolution,LPTIMECALLBACK lpTimeProc, DWORD dwUser,UINT fuEvent); |
参数uDelay表示延迟时间;
参数uResolution表示时间精度,在Windows中缺省值为1ms;
lpTimeProc表示回调函数,为用户自定义函数,定时调用;
参数dwUser表示用户提供的回调数据;
参数fuEvent为定时器的事件类型,TIME_ONESHOT表示执行一次;
TIME_PERIODIC:周期性执行。
具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在lpTimeProc回调函数中(如:定时采样、控制等),从而完成所需处理的事件。
需要注意的是:任务处理的时间不能大于周期间隔时间。
在回调函数中调用事件触发消息且在回调函数中语句尽量简单,不要在回调函数内做一些耗时的操作。
另外,在定时器使用完毕后,应及时调用timeKillEvent()将之释放。
下面这段代码的主要功能是设置两个时钟定时器,一个间隔是1ms,一个间隔是2s。每执行一次,把当前系统时钟值输入文件"cure.out"中,以比较该定时器的精确度。
# define ONE_MILLI_SECOND 1 //定义1ms和2s时钟间隔,以ms为单位 ; # define TWO_SECOND 2000 # define TIMER_ACCURACY 1 //定义时钟分辨率,以ms为单位 UINT wTimerRes_1ms,wTimerRes_2s; //定义时间间隔 UINT wAccuracy; //定义分辨率 UINT TimerID_1ms,TimerID_2s; //定义定时器句柄 /////////////////////////////// CCureApp::CCureApp():fout("cure.out", ios::out) //打开输出文件"cure.out"; { // 给时间间隔变量赋值 wTimerRes_1ms = ONE_MILLI_SECOND; wTimerRes_2s = TWO_SECOND; TIMECAPS tc; //利用函数timeGetDevCaps取出系统分辨率的取值范围,如果无错则继续; if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR) { wAccuracy=min(max(tc.wPeriodMin, //分辨率的值不能超出系统的取值范围 TIMER_ACCURACY),tc.wPeriodMax); //调用timeBeginPeriod函数设置定时器的分辨率 timeBeginPeriod(wAccuracy); //设置定时器 InitializeTimer(); } } CCureApp:: ~CCureApp() { fout <<"结束时钟"<< endl; //结束时钟 timeKillEvent(TimerID_1ms); // 删除两个定时器 timeKillEvent(TimerID_2s); // 删除设置的分辨率 timeEndPeriod(wAccuracy); } void CCureApp::InitializeTimer() { StartOneMilliSecondTimer(); StartTwoSecondTimer(); } //1ms定时器的回调函数,类似于中断处理程序,一定要声明为全局PASCAL函数, //否则编译会有问题 void PASCAL OneMilliSecondProc(UINT wTimerID, UINT msg,DWORD dwUser, DWORD dwl,DWORD dw2) { // 定义计数器 static int ms = 0; CCureApp *app = (CCureApp *)dwUser; // 取得系统时间,以ms为单位 DWORD osBinaryTime = GetTickCount(); //输出计数器值和当前系统时间 app->fout<<++ms<<":1ms:" } // 加装1ms定时器 void CCureApp::StartOneMilliSecondTimer() { if((TimerID_1ms = timeSetEvent(wTimerRes_1ms, wAccuracy, (LPTIMECALBACK) OneMil liSecondProc, // 回调函数; (DWORD)this, // 用户传送到回调函数的数据; TIME_PERIODIC)) == 0)//周期调用定时处理函数; { AfxMessageBox("不能进行定时!", MB_OK | MB_ICONASTERISK); } else fout << "16ms 计 时:" << endl; //不等于0表明加装成功,返回此定时器的句柄; } |
// 起始值和中止值 DWORD dwStart, dwStop ; dwStop = GetTickCount(); while(TRUE) { // 上一次的中止值变成新的起始值 dwStart = dwStop ; // 此处添加相应控制语句 do { dwStop = GetTickCount() ; }while(dwStop - 50 < dwStart) ; } |
BOOL QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency); BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount); |
typedef union _LARGE_INTEGER { struct{ DWORD LowPart ; // 4字节整型数 LONG HighPart ; // 4字节整型数 }; LONG QuadPart ; // 8字节整型数 } LARGE_INTEGER ; |
二、编程步骤
1、启动Visual C++6.0,生成一个基于对话框的应用程序,将程序命名为"HightTimer";
2、在对话框面板中添加控件,布局如图一所示,其中包含两个静态文本框,两个编辑框和两个按纽。上面和下面位置的编辑框的ID分别为IDC_TEST和IDC_ACTUAL,"EXIT"按纽的ID为IDOK,"TEST"按纽ID为ID_TEST;
3、通过Class Wizard添加成员变量,两个编辑框控件分别对应为DWORD m_dwTest和DWORD m_dwAct,另外添加"TEST"按纽的鼠标单击消息处理函数;
4、添加代码,编译运行程序。
三、程序代码
///////////////////////////////////////////////////////////////////////// LARGE_INTEGER MySleep(LARGE_INTEGER Interval) // 功能:执行实际的延时功能,Interval 参数为需要执行的延时与时间有关的数量,此函数返回执//行后实际所用的时间有关的数量 ; { LARGE_INTEGER privious, current, Elapse; QueryPerformanceCounter( &privious ); current = privious; while( current.QuadPart - privious.QuadPart < Interval.QuadPart ) QueryPerformanceCounter( ¤t ); Elapse.QuadPart = current.QuadPart - privious.QuadPart; return Elapse; } void CHightTimerDlg::OnTest() { // TODO: Add your control notification handler code here UpdateData(TRUE); //取输入的测试时间值到与编辑框相关联的成员变量m_dwTest中 ; LARGE_INTEGER frequence; //取高精度运行计数器的频率,若硬件不支持则返回FALSE if(!QueryPerformanceFrequency( &frequence)) MessageBox("Your computer hardware doesn't support the high-resolution performance counter", "Not Support", MB_ICONEXCLAMATION | MB_OK); LARGE_INTEGER test, ret; //通过频率换算微秒数到对应的数量(与CPU时钟有关),1秒=1000000微秒; test.QuadPart = frequence.QuadPart * m_dwTest / 1000000; ret = MySleep( test ); //调用此函数开始延时,返回实际花销的数量 ; m_dwAct = (DWORD)(1000000 * ret.QuadPart / frequence.QuadPart ); //换算到微秒数; UpdateData(FALSE); //显示到对话框面板 ; } |
四、小结
本实例介绍了实现精确定时的不同方法,尤其是对于需要精确到微秒级别的定时处理,给出了实现的方法和代码,细心的读者朋友在运行程序的过程中可能会发现要求的定时长度和实际返回的时间长度还是有一些差异的,造成上述情况的原因是由于在进行定时处理时,还需要运行一些简单的循环代码,所以会产生微秒级的误差。
联系客服