在工業(yè)生產(chǎn)控制系統(tǒng)中,有許多需要定時完成的操作,如定時顯示當(dāng)前時間,定時刷新屏幕上的進度條,上位 機定時向下位機發(fā)送命令和傳送數(shù)據(jù)等。特別是在對控制性能要求較高的實時控制系統(tǒng)和數(shù)據(jù)采集系統(tǒng)中,就更需要精確定時操作。
眾所周知,Windows 是基于消息機制的系統(tǒng),任何事件的執(zhí)行都是通過發(fā)送和接收消息來完成的。 這樣就帶來了一些問題,如一旦計算機的CPU被某個進程占用,或系統(tǒng)資源緊張時,發(fā)送到消息隊列 中的消息就暫時被掛起,得不到實時處理。因此,不能簡單地通過Windows消息引發(fā)一個對定時要求 嚴(yán)格的事件。另外,由于在Windows中已經(jīng)封裝了計算機底層硬件的訪問,所以,要想通過直接利用 訪問硬件來完成精確定時,也比較困難。所以在實際應(yīng)用時,應(yīng)針對具體定時精度的要求,采取相適 應(yīng)的定時方法。
VC中提供了很多關(guān)于時間操作的函數(shù),利用它們控制程序能夠精確地完成定時和計時操作。本文詳細介紹了 VC中基于Windows的精確定時的七種方式,如下圖所示:
方式一:VC中的WM_TIMER消息映射能進行簡單的時間控制。首先調(diào)用函數(shù)SetTimer()設(shè)置定時 間隔,如SetTimer(0,200,NULL)即為設(shè)置200ms的時間間隔。然后在應(yīng)用程序中增加定時響應(yīng)函數(shù) OnTimer(),并在該函數(shù)中添加響應(yīng)的處理語句,用來完成到達定時時間的操作。這種定時方法非常 簡單,可以實現(xiàn)一定的定時功能,但其定時功能如同Sleep()函數(shù)的延時功能一樣,精度非常低,最小 計時精度僅為30ms,CPU占用低,且定時器消息在多任務(wù)操作系統(tǒng)中的優(yōu)先級很低,不能得到及時響 應(yīng),往往不能滿足實時控制環(huán)境下的應(yīng)用。只可以用來實現(xiàn)諸如位圖的動態(tài)顯示等對定時精度要求不高的情況。如示例工程中的Timer1。
方式二:VC中使用sleep()函數(shù)實現(xiàn)延時,它的單位是ms,如延時2秒,用sleep(2000)。精度非常 低,最小計時精度僅為30ms,用sleep函數(shù)的不利處在于延時期間不能處理其他的消息,如果時間太 長,就好象死機一樣,CPU占用率非常高,只能用于要求不高的延時程序中。如示例工程中的Timer2。
方式三:利用COleDateTime類和COleDateTimeSpan類結(jié)合WINDOWS的消息處理過程來實現(xiàn)秒級延時。如示例工程中的Timer3和Timer3_1。以下是實現(xiàn)2秒的延時代碼: COleDateTime start_time = COleDateTime::GetCurrentTime();COleDateTimeSpan end_time= COleDateTime::GetCurrentTime()-start_time;while(end_time.GetTotalSeconds()< 2) //實現(xiàn)延時2秒{ MSG msg;GetMessage(&msg,NULL,0,0);TranslateMessage(&msg); DispatchMessage(&msg);//以上四行是實現(xiàn)在延時或定時期間能處理其他的消息, //雖然這樣可以降低CPU的占有率,//但降低了延時或定時精度,實際應(yīng)用中可以去掉。end_time = COleDateTime::GetCurrentTime()-start_time;}//這樣在延時的時候我們也能夠處理其他的消息。
方式四:在精度要求較高的情況下,VC中可以利用GetTickCount()函數(shù),該函數(shù)的返回值是 DWORD型,表示以ms為單位的計算機啟動后經(jīng)歷的時間間隔。精度比WM_TIMER消息映射高,在較 短的定時中其計時誤差為15ms,在較長的定時中其計時誤差較低,如果定時時間太長,就好象死機一樣,CPU占用率非常高,只能用于要求不高的延時程序中。如示例工程中的Timer4和Timer4_1。下列代碼可以實現(xiàn)50ms的精確定時: DWORD dwStart = GetTickCount();DWORD dwEnd = dwStart;do{ dwEnd = GetTickCount()-dwStart;}while(dwEnd <50);
為使GetTickCount()函數(shù)在延時或定時期間能處理其他的消息,可以把代碼改為: DWORD dwStart = GetTickCount();DWORD dwEnd = dwStart;do{ MSG msg; GetMessage(&msg,NULL,0,0); TranslateMessage(&msg); DispatchMessage(&msg); dwEnd = GetTickCount()-dwStart;}while(dwEnd <50);
雖然這樣可以降低CPU的占有率,并在延時或定時期間也能處理其他的消息,但降低了延時或定時精度。
方式五:與GetTickCount()函數(shù)類似的多媒體定時器函數(shù)DWORD timeGetTime(void),該函數(shù)定時精 度為ms級,返回從Windows啟動開始經(jīng)過的毫秒數(shù)。微軟公司在其多媒體Windows中提供了精確定時器的底 層API持,利用多媒體定時器可以很精確地讀出系統(tǒng)的當(dāng)前時間,并且能在非常精確的時間間隔內(nèi)完成一 個事件、函數(shù)或過程的調(diào)用。不同之處在于調(diào)用DWORD timeGetTime(void) 函數(shù)之前必須將 Winmm.lib 和 Mmsystem.h 添加到工程中,否則在編譯時提示DWORD timeGetTime(void)函數(shù)未定義。由于使用該 函數(shù)是通過查詢的方式進行定時控制的,所以,應(yīng)該建立定時循環(huán)來進行定時事件的控制。如示例工程中的Timer5和Timer5_1。
方式六:使用多媒體定時器timeSetEvent()函數(shù),該函數(shù)定時精度為ms級。利用該函數(shù)可以實現(xiàn)周期性的函數(shù)調(diào)用。如示例工程中的Timer6和Timer6_1。函數(shù)的原型如下: MMRESULT timeSetEvent( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, WORD dwUser, UINT fuEvent )
該函數(shù)設(shè)置一個定時回調(diào)事件,此事件可以是一個一次性事件或周期性事件。事件一旦被激活,便調(diào)用指定的回調(diào)函數(shù), 成功后返回事件的標(biāo)識符代碼,否則返回NULL。函數(shù)的參數(shù)說明如下:
uDelay:以毫秒指定事件的周期。 Uresolution:以毫秒指定延時的精度,數(shù)值越小定時器事件分辨率越高。缺省值為1ms。 LpTimeProc:指向一個回調(diào)函數(shù)。 DwUser:存放用戶提供的回調(diào)數(shù)據(jù)。 FuEvent:指定定時器事件類型: TIME_ONESHOT:uDelay毫秒后只產(chǎn)生一次事件 TIME_PERIODIC :每隔uDelay毫秒周期性地產(chǎn)生事件。
具體應(yīng)用時,可以通過調(diào)用timeSetEvent()函數(shù),將需要周期性執(zhí)行的任務(wù)定義在LpTimeProc回調(diào)函數(shù) 中(如:定時采樣、控制等),從而完成所需處理的事件。需要注意的是,任務(wù)處理的時間不能大于周期間隔時間。另外,在定時器使用完畢后, 應(yīng)及時調(diào)用timeKillEvent()將之釋放。
方式七:對于精確度要求更高的定時操作,則應(yīng)該使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函數(shù)。這兩個函數(shù)是VC提供的僅供Windows 95及其后續(xù)版本使用的精確時間函數(shù),并要求計算機從硬件上支持精確定時器。如示例工程中的Timer7、Timer7_1、Timer7_2、Timer7_3。
QueryPerformanceFrequency()函數(shù)和QueryPerformanceCounter()函數(shù)的原型如下: BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
數(shù)據(jù)類型ARGE_INTEGER既可以是一個8字節(jié)長的整型數(shù),也可以是兩個4字節(jié)長的整型數(shù)的聯(lián)合結(jié)構(gòu), 其具體用法根據(jù)編譯器是否支持64位而定。該類型的定義如下: typedef union _LARGE_INTEGER{ struct { DWORD LowPart ;// 4字節(jié)整型數(shù) LONG HighPart;// 4字節(jié)整型數(shù) }; LONGLONG QuadPart ;// 8字節(jié)整型數(shù)}LARGE_INTEGER ;
在進行定時之前,先調(diào)用QueryPerformanceFrequency()函數(shù)獲得機器內(nèi)部定時器的時鐘頻率, 然后在需要嚴(yán)格定時的事件發(fā)生之前和發(fā)生之后分別調(diào)用QueryPerformanceCounter()函數(shù),利用兩次獲得的計數(shù)之差及時鐘頻率,計算出事件經(jīng) 歷的精確時間。下列代碼實現(xiàn)1ms的精確定時: LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計數(shù)器的時鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值do{ QueryPerformanceCounter(&litmp); QPart2 = litmp.QuadPart;//獲得中止值 dfMinus = (double)(QPart2-QPart1); dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時間值,單位為秒}while(dfTim<0.001);
其定時誤差不超過1微秒,精度與CPU等機器配置有關(guān)。 下面的程序用來測試函數(shù)Sleep(100)的精確持續(xù)時間: LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計數(shù)器的時鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值Sleep(100);QueryPerformanceCounter(&litmp);QPart2 = litmp.QuadPart;//獲得中止值dfMinus = (double)(QPart2-QPart1);dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時間值,單位為秒
由于Sleep()函數(shù)自身的誤差,上述程序每次執(zhí)行的結(jié)果都會有微小誤差。下列代碼實現(xiàn)1微秒的精確定時: LARGE_INTEGER litmp; LONGLONG QPart1,QPart2;double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp);dfFreq = (double)litmp.QuadPart;// 獲得計數(shù)器的時鐘頻率QueryPerformanceCounter(&litmp);QPart1 = litmp.QuadPart;// 獲得初始值do{ QueryPerformanceCounter(&litmp); QPart2 = litmp.QuadPart;//獲得中止值 dfMinus = (double)(QPart2-QPart1); dfTim = dfMinus / dfFreq;// 獲得對應(yīng)的時間值,單位為秒}while(dfTim<0.000001);
其定時誤差一般不超過0.5微秒,精度與CPU等機器配置有關(guān)。 共2頁。 9 7 1 2
使用CPU時間戳進行高精度計時
從一論壇看到的轉(zhuǎn)貼,不知道作者名?! ﹃P(guān)注性能的程序開發(fā)人員而言,一個好的計時部件既是益友,也是良師。計時器既可以作為程序組件幫助程序員精確的控制程序進程,又是一件有力的調(diào)試武器,在有經(jīng)驗的程序員手里可以盡快的確定程序的性能瓶頸,或者對不同的算法作出有說服力的性能比較?! ≡赪indows平臺下,常用的計時器有兩種,一種是timeGetTime多媒體計時器,它可以提供毫秒級的計時。但這個精度對很多應(yīng)用場合而言還是太粗糙了。另一種是QueryPerformanceCount計數(shù)器,隨系統(tǒng)的不同可以提供微秒級的計數(shù)。對于實時圖形處理、多媒體數(shù)據(jù)流處理、或者實時系統(tǒng)構(gòu)造的程序員,善用QueryPerformanceCount/QueryPerformanceFrequency是一項基本功?! ”疚囊榻B的,是另一種直接利用Pentium CPU內(nèi)部時間戳進行計時的高精度計時手段。以下討論主要得益于《Windows圖形編程》一書,第15頁-17頁,有興趣的讀者可以直接參考該書。關(guān)于RDTSC指令的詳細討論,可以參考Intel產(chǎn)品手冊。本文僅僅作拋磚之用?! ≡贗ntel Pentium以上級別的CPU中,有一個稱為“時間戳(Time Stamp)”的部件,它以64位無符號整型數(shù)的格式,記錄了自CPU上電以來所經(jīng)過的時鐘周期數(shù)。由于目前的CPU主頻都非常高,因此這個部件可以達到納秒級的計時精度。這個精確性是上述兩種方法所無法比擬的。 在Pentium以上的CPU中,提供了一條機器指令RDTSC(Read Time Stamp Counter)來讀取這個時間戳的數(shù)字,并將其保存在EDX:EAX寄存器對中。由于EDX:EAX寄存器對恰好是Win32平臺下C++語言保存函數(shù)返回值的寄存器,所以我們可以把這條指令看成是一個普通的函數(shù)調(diào)用。像這樣:inline unsigned __int64 GetCycleCount(){__asm RDTSC}
但是不行,因為RDTSC不被C++的內(nèi)嵌匯編器直接支持,所以我們要用_emit偽指令直接嵌入該指令的機器碼形式0X0F、0X31,如下:
inline unsigned __int64 GetCycleCount() { __asm _emit 0x0F __asm _emit 0x31 }
以后在需要計數(shù)器的場合,可以像使用普通的Win32 API一樣,調(diào)用兩次GetCycleCount函數(shù),比較兩個返回值的差,像這樣:
unsigned long t; t = (unsigned long)GetCycleCount(); //Do Something time-intensive ... t -= (unsigned long)GetCycleCount();
《Windows圖形編程》第15頁編寫了一個類,把這個計數(shù)器封裝起來。有興趣的讀者可以去參考那個類的代碼。作者為了更精確的定時,做了一點小小的改進,把執(zhí)行RDTSC指令的時間,通過連續(xù)兩次調(diào)用GetCycleCount函數(shù)計算出來并保存了起來,以后每次計時結(jié)束后,都從實際得到的計數(shù)中減掉這一小段時間,以得到更準(zhǔn)確的計時數(shù)字。但我個人覺得這一點點改進意義不大。在我的機器上實測,這條指令大概花掉了幾十到100多個周期,在Celeron 800MHz的機器上,這不過是十分之一微秒的時間。對大多數(shù)應(yīng)用來說,這點時間完全可以忽略不計;而對那些確實要精確到納秒數(shù)量級的應(yīng)用來說,這個補償也過于粗糙了。 這個方法的優(yōu)點是: 1. 高精度。可以直接達到納秒級的計時精度(在1GHz的CPU上每個時鐘周期就是一納秒),這是其他計時方法所難以企及的。
2.成本低。timeGetTime 函數(shù)需要鏈接多媒體庫winmm.lib,QueryPerformance* 函數(shù)根據(jù)MSDN的說明,需要硬件的支持(雖然我還沒有見過不支持的機器)和KERNEL庫的支持,所以二者都只能在Windows平臺下使用(關(guān)于DOS平臺下的高精度計時問題,可以參考《圖形程序開發(fā)人員指南》,里面有關(guān)于控制定時器8253的詳細說明)。但RDTSC指令是一條CPU指令,凡是i386平臺下Pentium以上的機器均支持,甚至沒有平臺的限制(我相信i386版本UNIX和Linux下這個方法同樣適用,但沒有條件試驗),而且函數(shù)調(diào)用的開銷是最小的。
3.具有和CPU主頻直接對應(yīng)的速率關(guān)系。一個計數(shù)相當(dāng)于1/(CPU主頻Hz數(shù))秒,這樣只要知道了CPU的主頻,可以直接計算出時間。這和QueryPerformanceCount不同,后者需要通過QueryPerformanceFrequency獲取當(dāng)前計數(shù)器每秒的計數(shù)次數(shù)才能換算成時間。 這個方法的缺點是:
1.現(xiàn)有的C/C++編譯器多數(shù)不直接支持使用RDTSC指令,需要用直接嵌入機器碼的方式編程,比較麻煩。
2. 數(shù)據(jù)抖動比較厲害。其實對任何計量手段而言,精度和穩(wěn)定性永遠是一對矛盾。如果用低精度的timeGetTime來計時,基本上每次計時的結(jié)果都是相同的;而RDTSC指令每次結(jié)果都不一樣,經(jīng)常有幾百甚至上千的差距。這是這種方法高精度本身固有的矛盾。
關(guān)于這個方法計時的最大長度,我們可以簡單的用下列公式計算: 自CPU上電以來的秒數(shù) = RDTSC讀出的周期數(shù) / CPU主頻速率(Hz) 64位無符號整數(shù)所能表達的最大數(shù)字是1.8×10^19,在我的Celeron 800上可以計時大約700年(書中說可以在200MHz的Pentium上計時117年,這個數(shù)字不知道是怎么得出來的,與我的計算有出入)。無論如何,我們大可不必關(guān)心溢出的問題。 下面是幾個小例子,簡要比較了三種計時方法的用法與精度
//Timer1.cpp 使用了RDTSC指令的Timer類//KTimer類的定義可以參見《Windows圖形編程》P15 //編譯行:CL Timer1.cpp /link USER32.lib #include <stdio.h> #include "KTimer.h"
main() { unsigned t; KTimer timer; timer.Start(); Sleep(1000); t = timer.Stop(); printf("Lasting Time: %d\n",t); }
//Timer2.cpp 使用了timeGetTime函數(shù) //需包含<mmsys.h>,但由于Windows頭文件錯綜復(fù)雜的關(guān)系 //簡單包含<windows.h>比較偷懶:) //編譯行:CL timer2.cpp /link winmm.lib #include <windows.h> #include <stdio.h>
main() { DWORD t1, t2; t1 = timeGetTime(); Sleep(1000); t2 = timeGetTime(); printf("Begin Time: %u\n", t1); printf("End Time: %u\n", t2); printf("Lasting Time: %u\n",(t2-t1)); }
//Timer3.cpp 使用了QueryPerformanceCounter函數(shù) //編譯行:CL timer3.cpp /link KERNEl32.lib #include <windows.h> #include <stdio.h>
main() { LARGE_INTEGER t1, t2, tc; QueryPerformanceFrequency(&tc); printf("Frequency: %u\n", tc.QuadPart); QueryPerformanceCounter(&t1); Sleep(1000); QueryPerformanceCounter(&t2); printf("Begin Time: %u\n", t1.QuadPart); printf("End Time: %u\n", t2.QuadPart); printf("Lasting Time: %u\n",( t2.QuadPart- t1.QuadPart)); } //////////////////////////////////////////////// //以上三個示例程序都是測試1秒鐘休眠所耗費的時間 file://測/試環(huán)境:Celeron 800MHz / 256M SDRAM // Windows 2000 Professional SP2 // Microsoft Visual C++ 6.0 SP5 ////////////////////////////////////////////////
以下是Timer1的運行結(jié)果,使用的是高精度的RDTSC指令 Lasting Time: 804586872
以下是Timer2的運行結(jié)果,使用的是最粗糙的timeGetTime API Begin Time: 20254254 End Time: 20255255 Lasting Time: 1001
以下是Timer3的運行結(jié)果,使用的是QueryPerformanceCount API Frequency: 3579545 Begin Time: 3804729124 End Time: 3808298836 Lasting Time: 3569712
對于wince, 只有采用KTimer那種方式,因為QueryPerformanceFrequency,timeGetTime均不支持。
Visual C++實現(xiàn)微秒級精度定時器 所屬類別:VC++ 推薦指數(shù):★★★☆ 文檔人氣:1117 本周人氣:10 發(fā)布日期:2006-6-25 在工業(yè)生產(chǎn)控制系統(tǒng)中,有許多需要定時完成的操作,如:定時顯示當(dāng)前時間,定時刷新屏幕上的進度條,上位機定時向下位機發(fā)送命令和傳送數(shù)據(jù)等。特別是在對控制性能要求較高的控制系統(tǒng)和數(shù)據(jù)采集系統(tǒng)中,就更需要精確定時操作。眾所周知,Windows是基于消息機制的系統(tǒng),任何事件的執(zhí)行都是通過發(fā)送和接收消息來完成的。這樣就帶來了一些問題,如一旦計算機的CPU被某個進程占用,或系統(tǒng)資源緊張時,發(fā)送到消息隊列中的消息就暫時被掛起,得不到實時處理。因此,不能簡單地通過Windows消息引發(fā)一個對定時要求嚴(yán)格的事件。另外,由于在Windows中已經(jīng)封裝了計算機底層硬件的訪問,所以要想通過直接利用訪問硬件來完成精確定時,也比較困難。在實際應(yīng)用時,應(yīng)針對具體定時精度的要求,采取與之相適應(yīng)的定時方法。
本實例實現(xiàn)了一中微秒級的精確定時,程序的界面提供了兩個"Edit"編輯框,其中一個編輯框輸入用戶理想的定時長度,另外一個編輯框返回實際的時間長度,經(jīng)過大量的實驗測試,一般情況下誤差不超過5個微秒。程序的運行界面如圖一所示: 圖一、實現(xiàn)微秒級的精確定時器
一、實現(xiàn)方法
Visual C++中提供了很多關(guān)于時間操作的函數(shù),利用它們控制程序能夠精確地完成定時和計時操作。Visaul C++中的WM_TIMER消息映射能進行簡單的時間控制。首先調(diào)用函數(shù)SetTimer()設(shè)置定時間隔(退出程序時別忘了調(diào)用和SetTimer()配對使用的KillTimer()函數(shù)),如SetTimer(0,200,NULL)即為設(shè)置200ms的時間間隔。然后在應(yīng)用程序中增加定時響應(yīng)函數(shù)OnTimer(),并在該函數(shù)中添加響應(yīng)的處理語句,用來完成到達定時時間的操作。這種定時方法非常簡單,但其定時功能如同Sleep()函數(shù)的延時功能一樣,精度非常低,只可以用來實現(xiàn)諸如位圖的動態(tài)顯示等對定時精度要求不高的情況。
微軟公司在其多媒體Windows中提供了精確定時器的底層API支持。利用多媒體定時器可以很精確地讀出系統(tǒng)的當(dāng)前時間,并且能在非常精確的時間間隔內(nèi)完成一個事件、函數(shù)或過程的調(diào)用。利用多媒體定時器的基本功能,可以通過兩種方法實現(xiàn)精確定時。1)使用timeGetTime()函數(shù),該函數(shù)定時精度為ms級,返回從Windows啟動開始所經(jīng)過的時間。由于使用該函數(shù)是通過查詢的方式進行定時控制的,所以,應(yīng)該建立定時循環(huán)來進行定時事件的控制。2)使用timeSetEvent()函數(shù),該函數(shù)原型如下: MMRESULT timeSetEvent(UINT uDelay,UINT uResolution,LPTIMECALLBACK lpTimeProc,DWORD dwUser,UINT fuEvent);
該函數(shù)的參數(shù)說明如下:參數(shù)uDelay表示延遲時間;參數(shù)uResolution表示時間精度,在Windows中缺省值為1ms;lpTimeProc表示回調(diào)函數(shù),為用戶自定義函數(shù),定時調(diào)用; 參數(shù)dwUser表示用戶提供的回調(diào)數(shù)據(jù);參數(shù)fuEvent為定時器的事件類型,TIME_ONESHOT表示執(zhí)行一次;TIME_PERIODIC:周期性執(zhí)行。具體應(yīng)用時,可以通過調(diào)用timeSetEvent()函數(shù),將需要周期性執(zhí)行的任務(wù)定義在lpTimeProc回調(diào)函數(shù)中(如:定時采樣、控制等),從而完成所需處理的事件。需要注意的是:任務(wù)處理的時間不能大于周期間隔時間。另外,在定時器使用完畢后,應(yīng)及時調(diào)用timeKillEvent()將之釋放。下面這段代碼的主要功能是設(shè)置兩個時鐘定時器,一個間隔是1ms,一個間隔是2s。每執(zhí)行一次,把當(dāng)前系統(tǒng)時鐘值輸入文件"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; //利用函數(shù)timeGetDevCaps取出系統(tǒng)分辨率的取值范圍,如果無錯則繼續(xù); if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR) { wAccuracy=min(max(tc.wPeriodMin, //分辨率的值不能超出系統(tǒng)的取值范圍 TIMER_ACCURACY),tc.wPeriodMax); //調(diào)用timeBeginPeriod函數(shù)設(shè)置定時器的分辨率 timeBeginPeriod(wAccuracy); //設(shè)置定時器 InitializeTimer(); } } CCureApp:: ~CCureApp() { fout <<"結(jié)束時鐘"<< endl; //結(jié)束時鐘 timeKillEvent(TimerID_1ms); // 刪除兩個定時器 timeKillEvent(TimerID_2s); // 刪除設(shè)置的分辨率 timeEndPeriod(wAccuracy); } void CCureApp::InitializeTimer() { StartOneMilliSecondTimer(); StartTwoSecondTimer(); } //1ms定時器的回調(diào)函數(shù),類似于中斷處理程序,一定要聲明為全局PASCAL函數(shù),//否則編譯會有問題 void PASCAL OneMilliSecondProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2) { // 定義計數(shù)器 static int ms = 0; CCureApp *app = (CCureApp *)dwUser; // 取得系統(tǒng)時間,以ms為單位 DWORD osBinaryTime = GetTickCount(); //輸出計數(shù)器值和當(dāng)前系統(tǒng)時間 app->fout<<++ms<<":1ms:" } // 加裝1ms定時器 void CCureApp::StartOneMilliSecondTimer() { if((TimerID_1ms = timeSetEvent(wTimerRes_1ms, wAccuracy, (LPTIMECALBACK) OneMil liSecondProc, // 回調(diào)函數(shù); (DWORD)this, // 用戶傳送到回調(diào)函數(shù)的數(shù)據(jù); TIME_PERIODIC)) == 0)//周期調(diào)用定時處理函數(shù); { AfxMessageBox("不能進行定時!", MB_OK | MB_ICONASTERISK); } else fout << "16ms 計 時:" << endl; //不等于0表明加裝成功,返回此定時器的句柄; }
在精度要求較高的情況下,如要求定時誤差不大于1ms時,還可以利用GetTickCount()函數(shù)返回自計算機啟動后的時間,該函數(shù)的返回值是DWORD型,表示以ms為單位的計算機啟動后經(jīng)歷的時間間隔。通過兩次調(diào)用GetTickCount()函數(shù),然后控制它們的差值來取得定時效果.下列的代碼可以實現(xiàn)50ms的精確定時,其誤差是毫秒級的。 // 起始值和中止值DWORD dwStart, dwStop ; dwStop = GetTickCount(); while(TRUE) { // 上一次的中止值變成新的起始值 dwStart = dwStop ; // 此處添加相應(yīng)控制語句 do { dwStop = GetTickCount() ; }while(dwStop - 50 < dwStart) ; }
用上述兩種方式取得的定時效果雖然在許多場合已經(jīng)滿足實際的要求,但由于它們的精度只有毫秒級的,而且在要求定時時間間隔小時,實際定時誤差大。對于精確度要求更高的定時操作,則應(yīng)該使用QueryPerformanceFrequency()和QueryPerformanceCounter()函數(shù)。這兩個函數(shù)是Visual C++提供并且僅供Windows 95及其后續(xù)版本使用,其精度與CPU的時鐘頻率有關(guān),它們要求計算機從硬件上支持精確定時器。QueryPerformanceFrequency()函數(shù)和QueryPerformanceCounter()函數(shù)的原型如下: BOOL QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency);BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
上述兩個函數(shù)的參數(shù)的數(shù)據(jù)類型LARGE_INTEGER既可以是一個8字節(jié)長的整型數(shù),也可以是兩個4字節(jié)長的整型數(shù)的聯(lián)合結(jié)構(gòu),其具體用法根據(jù)編譯器是否支持64位而定。該類型的定義如下: typedef union _LARGE_INTEGER{ struct{ DWORD LowPart ; // 4字節(jié)整型數(shù) LONG HighPart ; // 4字節(jié)整型數(shù) }; LONG QuadPart ; // 8字節(jié)整型數(shù)} LARGE_INTEGER ;
使用QueryPerformanceFrequency()和QueryPerformanceCounter()函數(shù)進行精確定時的步驟如下:
1、首先調(diào)用QueryPerformanceFrequency()函數(shù)取得高精度運行計數(shù)器的頻率f,單位是每秒多少次(n/s),此數(shù)一般很大;
2、在需要定時的代碼的兩端分別調(diào)用QueryPerformanceCounter()以取得高精度運行計數(shù)器的數(shù)值n1、n2,兩次數(shù)值的差值通過f換算成時間間隔,t=(n2-n1)/f,當(dāng)t大于或等于定時時間長度時,啟動定時器;
二、編程步驟
1、啟動Visual C++6.0,生成一個基于對話框的應(yīng)用程序,將程序命名為"HightTimer";
2、在對話框面板中添加控件,布局如圖一所示,其中包含兩個靜態(tài)文本框,兩個編輯框和兩個按紐。上面和下面位置的編輯框的ID分別為IDC_TEST和IDC_ACTUAL,"EXIT"按紐的ID為IDOK,"TEST"按紐ID為ID_TEST;
3、通過Class Wizard添加成員變量,兩個編輯框控件分別對應(yīng)為DWORD m_dwTest和DWORD m_dwAct,另外添加"TEST"按紐的鼠標(biāo)單擊消息處理函數(shù);
4、添加代碼,編譯運行程序。
三、程序代碼 /////////////////////////////////////////////////////////////////////////LARGE_INTEGER MySleep(LARGE_INTEGER Interval) // 功能:執(zhí)行實際的延時功能,Interval 參數(shù)為需要執(zhí)行的延時與時間有關(guān)的數(shù)量,此函數(shù)返回執(zhí)//行后實際所用的時間有關(guān)的數(shù)量 ; { 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); //取輸入的測試時間值到與編輯框相關(guān)聯(lián)的成員變量m_dwTest中 ; LARGE_INTEGER frequence; //取高精度運行計數(shù)器的頻率,若硬件不支持則返回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; //通過頻率換算微秒數(shù)到對應(yīng)的數(shù)量(與CPU時鐘有關(guān)),1秒=1000000微秒; test.QuadPart = frequence.QuadPart * m_dwTest / 1000000; ret = MySleep( test ); //調(diào)用此函數(shù)開始延時,返回實際花銷的數(shù)量 ; m_dwAct = (DWORD)(1000000 * ret.QuadPart / frequence.QuadPart ); //換算到微秒數(shù); UpdateData(FALSE); //顯示到對話框面板 ;}
四、小結(jié)
本實例介紹了實現(xiàn)精確定時的不同方法,尤其是對于需要精確到微秒級別的定時處理,給出了實現(xiàn)的方法和代碼,細心的讀者朋友在運行程序的過程中可能會發(fā)現(xiàn)要求的定時長度和實際返回的時間長度還是有一些差異的,造成上述情況的原因是由于在進行定時處理時,還需要運行一些簡單的循環(huán)代碼,所以會產(chǎn)生微秒級的誤差
Windows下的高精度計時和高頻事件的產(chǎn)生
作者:戎亞新 南京航空航天大學(xué)仿真與控制實驗室 下載源代碼
在開發(fā) Windows 下的應(yīng)用程序時,經(jīng)常需要用的計時,尤其在一些對時間要求比較高的程序中,計時的精確性是很重要的,本文介紹了兩種精確計時的方法,計時的精度可以達到ms級,而且可以認為它是精確的,可以在大多數(shù)情況下作為時間的基準(zhǔn)。 1. 用API函數(shù)::timeGetTime()獲取從開機到現(xiàn)在經(jīng)過的ms數(shù),它的返回類型為DWORD類型,因此它的最大計時長度為2^32ms,約等于49天,::timeGetTime()是一個多媒體函數(shù),所以它的優(yōu)先級是很高的,一般可以將它看成是精確的。 2. 用查詢系統(tǒng)定時器的計數(shù)值的方法,用到的API函數(shù)是QueryPerformanceCounter、QueryPerformanceFrequency,方法是用當(dāng)前計數(shù)值減去開始計時時刻的計數(shù)值,得到計數(shù)差值,再除以系統(tǒng)定時器的頻率就是計的時間,通常系統(tǒng)定時器的頻率非常高,我在 intel845e 的主板上達到了3579545hz,當(dāng)然對于不同的主板,它的頻率是不同的。程序運行的結(jié)果 如圖一所示:
圖一 這種計時方法要用另外一個線程專門來查詢系統(tǒng)定時器的計數(shù)值,這就用到了多線程的知識。由于線程的調(diào)用是需要處理器時間的,所以在本中,多線程定時器的時間總要落后于多媒體定時器時間。但在中間的任何一個讀取時間的時刻都是非常精確的,只是從讀取到顯示有一個延遲過程。 下面講一下Windows高頻事件的產(chǎn)生,還是利用上面兩種方法,Windows下有一個多媒體定時器,用法為一組API函數(shù)的調(diào)用,它們是: MMRESULT timeBeginPeriod( UINT uPeriod ) ;
MMRESULT timeSetEvent( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD dwUser, UINT fuEvent );
void CALLBACK TimeProc( UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 );
MMRESULT timeKillEvent( UINT uTimerID ); MMRESULT timeEndPeriod( UINT uPeriod ); 其中timeBeginPeriod是用來設(shè)置最高定時精度的,最高精度為1ms,如果要產(chǎn)生間隔為1ms的中斷,必須調(diào)用timeBeginPeriod(1);當(dāng)定時器用完之后就要用timeEndPeriod(1);來恢復(fù)默認的精度。具體使用方法為在timeBeginPeriod(1)調(diào)用之后用timeSetEvent()注冊一個回調(diào)函數(shù),即一個中斷處理過程。它還可以向回調(diào)函數(shù)傳遞一個參數(shù),通??梢詡魉鸵粋€窗口句柄之類的東西。而回調(diào)函數(shù)TimeProc則從dwwUser參數(shù)中取出傳遞的參數(shù)使用。在Windows下,可以用這種方法進行1ms精度的定時數(shù)據(jù)采集,數(shù)據(jù)發(fā)送,但要保證1ms能完成所有的操作和運算。本人經(jīng)過實踐證明,用它來實現(xiàn)控制的精度是足夠的。 第二種方法還是使用多線程查詢系統(tǒng)定時器計數(shù)值,它與上面提到的方法相比有優(yōu)點也有缺點,缺點是精度不夠高,優(yōu)點是產(chǎn)生的間隔能突破1ms的限制,可以達到更小的間隔,理論上事件產(chǎn)生的頻率可以和系統(tǒng)定時器的頻率一樣。主要示例代碼如下: UINT Timer(LPVOID pParam) { QueryPerformanceCounter((LARGE_INTEGER *)& gl_BeginTime ); while(gl_bStart) { QueryPerformanceCounter((LARGE_INTEGER *)&gl_CurrentTime ); If(gl_CurrentTime - gl_BeginTime > 1.0/Interval ) { //定時的事件,比如發(fā)送數(shù)據(jù)到端口,采集數(shù)據(jù)等 gl_BeginTime = gl_CurrentTime; } } return 1; } 這是多線程中的一個線程函數(shù),Interval是產(chǎn)生事件的間隔,如果為0.001則為1ms產(chǎn)生一次,理論上如果Interval為1,則以最大的頻率產(chǎn)生事件。即可以用Windows產(chǎn)生很高頻率的事件,但是由于線程的調(diào)用是要有時間的,有的時候可能會造成這個線程一直沒有得到執(zhí)行,從而造成有一段時間沒有進行計數(shù),這段時間的定時事件就沒有產(chǎn)生了,如果定時的頻率越高,丟失的可能性就越大。但如果用它來產(chǎn)生高頻隨時間變化的隨機信號還是很有價值的。這在實時仿真中尤其如此。
具體的實現(xiàn)請參看詳細的例子代碼。
如有疑問,請與我聯(lián)系: qq:21881480 email:bsrong@elong.com 或 bsrong_nuaa@msn.com
1.project setting 的link設(shè)置winmm.lib 庫 2.#include <afxmt.h> #include "mmsystem.h" 3. DWORD dwOldTime=timeGetTime(); //... DWORD dwCurTime=timeGetTime(); DWORD dwLen = dwCurTime - dwOldTime;
使用多媒體定時器還需要加入 winmm.lib 庫文件! (project-setting里面)
主題 VC++的鏈接錯誤LNK2001(ZT) 學(xué)習(xí)VC++時經(jīng)常會遇到鏈接錯誤LNK2001,該錯誤非常討厭,因為對于 編程者來說,最好改的錯誤莫過于編譯錯誤,而一般說來發(fā)生連接錯誤時, 編譯都已通過。產(chǎn)生連接錯誤的原因非常多,尤其LNK2001錯誤,常常使人不 明其所以然。如果不深入地學(xué)習(xí)和理解VC++,要想改正連接錯誤LNK2001非 常困難。 初學(xué)者在學(xué)習(xí)VC++的過程中,遇到的LNK2001錯誤的錯誤消息主要為: unresolved external symbol “symbol”(不確定的外部“符號”)。 如果連接程序不能在所有的庫和目標(biāo)文件內(nèi)找到所引用的函數(shù)、變量或 標(biāo)簽,將產(chǎn)生此錯誤消息。一般來說,發(fā)生錯誤的原因有兩個:一是所引用 的函數(shù)、變量不存在、拼寫不正確或者使用錯誤;其次可能使用了不同版本 的連接庫。 以下是可能產(chǎn)生LNK2001錯誤的原因: 一.由于編碼錯誤導(dǎo)致的LNK2001。 1.不相匹配的程序代碼或模塊定義(.DEF)文件能導(dǎo)致LNK2001。例如, 如果在C++ 源文件內(nèi)聲明了一變量“var1”,卻試圖在另一文件內(nèi)以變量 “VAR1”訪問該變量,將發(fā)生該錯誤。 2.如果使用的內(nèi)聯(lián)函數(shù)是在.CPP文件內(nèi)定義的,而不是在頭文件內(nèi)定 義將導(dǎo)致LNK2001錯誤。 3.調(diào)用函數(shù)時如果所用的參數(shù)類型同函數(shù)聲明時的類型不符將會產(chǎn)生 LNK2001。 4.試圖從基類的構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛擬函數(shù)時將會導(dǎo)致LNK2001。 5.要注意函數(shù)和變量的可公用性,只有全局變量、函數(shù)是可公用的。 靜態(tài)函數(shù)和靜態(tài)變量具有相同的使用范圍限制。當(dāng)試圖從文件外部訪問 任何沒有在該文件內(nèi)聲明的靜態(tài)變量時將導(dǎo)致編譯錯誤或LNK2001。 函數(shù)內(nèi)聲明的變量(局部變量) 只能在該函數(shù)的范圍內(nèi)使用。 C++ 的全局常量只有靜態(tài)連接性能。這不同于C,如果試圖在C++的 多個文件內(nèi)使用全局變量也會產(chǎn)生LNK2001錯誤。一種解決的方法是需要時在 頭文件中加入該常量的初始化代碼,并在.CPP文件中包含該頭文件;另一種 方法是使用時給該變量賦以常數(shù)。 二.由于編譯和鏈接的設(shè)置而造成的LNK2001 1.如果編譯時使用的是/NOD(/NODEFAULTLIB)選項,程序所需要的運行 庫和MFC庫在連接時由編譯器寫入目標(biāo)文件模塊, 但除非在文件中明確包含 這些庫名,否則這些庫不會被鏈接進工程文件。在這種情況下使用/NOD將導(dǎo) 致錯誤LNK2001。 2.如果沒有為wWinMainCRTStartup設(shè)定程序入口,在使用Unicode和MFC 時將得到“unresolved external on _WinMain@16”的LNK2001錯誤信息。 3.使用/MD選項編譯時,既然所有的運行庫都被保留在動態(tài)鏈接庫之內(nèi), 源文件中對“func”的引用,在目標(biāo)文件里即對“__imp__func” 的引用。 如果試圖使用靜態(tài)庫LIBC.LIB或LIBCMT.LIB進行連接,將在__imp__func上發(fā) 生LNK2001;如果不使用/MD選項編譯,在使用MSVCxx.LIB連接時也會發(fā)生LNK2001。 4.使用/ML選項編譯時,如用LIBCMT.LIB鏈接會在_errno上發(fā)生LNK2001。 5.當(dāng)編譯調(diào)試版的應(yīng)用程序時,如果采用發(fā)行版模態(tài)庫進行連接也會產(chǎn) 生LNK2001;同樣,使用調(diào)試版模態(tài)庫連接發(fā)行版應(yīng)用程序時也會產(chǎn)生相同的 問題。 6.不同版本的庫和編譯器的混合使用也能產(chǎn)生問題,因為新版的庫里可 能包含早先的版本沒有的符號和說明。 7.在不同的模塊使用內(nèi)聯(lián)和非內(nèi)聯(lián)的編譯選項能夠?qū)е翷NK2001。如果 創(chuàng)建C++庫時打開了函數(shù)內(nèi)聯(lián)(/Ob1或/Ob2),但是在描述該函數(shù)的相應(yīng)頭 文件里卻關(guān)閉了函數(shù)內(nèi)聯(lián)(沒有inline關(guān)鍵字),這時將得到該錯誤信息。 為避免該問題的發(fā)生,應(yīng)該在相應(yīng)的頭文件中用inline關(guān)鍵字標(biāo)志內(nèi)聯(lián)函數(shù)。 8.不正確的/SUBSYSTEM或/ENTRY設(shè)置也能導(dǎo)致LNK2001。 其實,產(chǎn)生LNK2001的原因還有很多,以上的原因只是一部分而已,對初 學(xué)者來說這些就夠理解一陣子了。但是,分析錯誤原因的目的是為了避免錯 誤的發(fā)生。LNK2001錯誤雖然比較困難,但是只要注意到了上述問題,還是能 夠避免和予以解決的。
1. Windows子系統(tǒng)設(shè)置錯誤, 提示: libcmtd.lib(crt0.obj) : error LNK2001: unresolved external symbol _main Windows項目要使用Windows子系統(tǒng), 而不是Console, 可以這樣設(shè)置: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Project Options中將/subsystem:console改成/subsystem:windows
2. Console子系統(tǒng)設(shè)置錯誤, 提示: LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16 控制臺項目要使用Console子系統(tǒng), 而不是Windows, 設(shè)置: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Project Options中將/subsystem:windows改成/subsystem:console
3. 程序入口設(shè)置錯誤, 提示: msvcrtd.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16 通常, MFC項目的程序入口函數(shù)是WinMain, 如果編譯項目的Unicode版本, 程序入口必須改為wWinMainCRTStartup, 所以需要重新設(shè)置程序入口: [Project] --> [Settings] --> 選擇"Link"屬性頁, 在Category中選擇Output, 再在Entry-point symbol中填入wWinMainCRTStartup, 即可
(發(fā)表于2006-10-8 16:49:00)
adam830:4. 線程運行時庫設(shè)置錯誤, 提示: nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex 這是因為MFC要使用多線程時庫, 需要更改設(shè)置: [Project] --> [Settings] --> 選擇"C/C++"屬性頁, 在Category中選擇Code Generation, 再在Use run-time library中選擇Debug Multithreaded或者multithreaded 其中, Single-Threaded 單線程靜態(tài)鏈接庫(release版本) Multithreaded 多線程靜態(tài)鏈接庫(release版本) multithreaded DLL 多線程動態(tài)鏈接庫(release版本) Debug Single-Threaded 單線程靜態(tài)鏈接庫(debug版本) Debug Multithreaded 多線程靜態(tài)鏈接庫(debug版本) Debug Multithreaded DLL 多線程動態(tài)鏈接庫(debug版本) 單線程: 不需要多線程調(diào)用時, 多用在DOS環(huán)境下 多線程: 可以并發(fā)運行 靜態(tài)庫: 直接將庫與程序Link, 可以脫離MFC庫運行 動態(tài)庫: 需要相應(yīng)的DLL動態(tài)庫, 程序才能運行 release版本: 正式發(fā)布時使用 debug版本: 調(diào)試階段使用 (發(fā)表于2006-10-8 16:49:00)
多媒體定時器對實現(xiàn)高精度定時是很理想的工具,而且其精度是十分可靠的。但是,多媒體定時器也并不是完美的。因為它可靠的精度是建立在對系統(tǒng)資源的消耗之上的。因此,在利用多媒體定時器完成工作的同時,必須注意以下幾點: ---- 1. 多媒體定時器的設(shè)置分辨率不能超出系統(tǒng)許可范圍。
---- 2. 在使用完定時器以后,一定要及時刪除定時器及其分辨率,否則系統(tǒng)會越來越慢。
---- 3. 多媒體定時器在啟動時,將自動開辟一個獨立的線程。在定時器線程結(jié)束之前,注意一定不能再次啟動該定時器,不然將迅速造成死機。 |
|