1. 程式人生 > >Visual C++實現微秒級精度定時器

Visual C++實現微秒級精度定時器

在工業生產控制系統中,有許多需要定時完成的操作,如:定時顯示當前時間,定時重新整理螢幕上的進度條,上位機定時向下位機發送命令和傳送資料等。特別是在對控制性能要求較高的控制系統和資料採集系統中,就更需要精確定時操作。眾所周知,Windows是基於訊息機制的系統,任何事件的執行都是通過傳送和接收訊息來完成的。這樣就帶來了一些問題,如一旦計算機的CPU被某個程序佔用,或系統資源緊張時,傳送到訊息佇列中的訊息就暫時被掛起,得不到實時處理。因此,不能簡單地通過Windows訊息引發一個對定時要求嚴格的事件。另外,由於在Windows中已經封裝了計算機底層硬體的訪問,所以要想通過直接利用訪問硬體來完成精確定時,也比較困難。在實際應用時,應針對具體定時精度的要求,採取與之相適應的定時方法。

本例項實現了一中微秒級的精確定時,程式的介面提供了兩個"Edit"編輯框,其中一個編輯框輸入使用者理想的定時長度,另外一個編輯框返回實際的時間長度,經過大量的實驗測試,一般情況下誤差不超過5個微秒。程式的執行介面如圖一所示:

 1實現微秒級的精確定時器

1.實現方法

Visual C++中提供了很多關於時間操作的函式,利用它們控制程式能夠精確地完成定時和計時操作。Visual C++中的WM_TIMER訊息對映能進行簡單的時間控制。首先呼叫函式SetTimer()設定定時間隔(退出程式時別忘了呼叫和SetTimer()配對使用的KillTimer()函式),如SetTimer(0,200,NULL)

即為設定200ms的時間間隔。然後在應用程式中增加定時響應函式OnTimer(),並在該函式中新增響應的處理語句,用來完成到達定時時間的操作。這種定時方法非常簡單,但其定時功能如同Sleep()函式的延時功能一樣,精度非常低,只可以用來實現諸如點陣圖的動態顯示等對定時精度要求不高的情況。

微軟公司在其多媒體Windows中提供了精確定時器的底層API支援。利用多媒體定時器可以很精確地讀出系統的當前時間,並且能在非常精確的時間間隔內完成一個事件、函式或過程的呼叫。利用多媒體定時器的基本功能,可以通過兩種方法實現精確定時。

1.使用timeGetTime()函式,該函式定時精度為ms級,返回從Windows

啟動開始所經過的時間。由於使用該函式是通過查詢的方式進行定時控制的,所以,應該建立定時迴圈來進行定時事件的控制。

2.使用timeSetEvent()函式,該函式原型如下:

MMRESULTtimeSetEvent(UINTuDelay,

UINTuResolution,

LPTIMECALLBACKlpTimeProc,

DWORDdwUser,

UINTfuEvent);

該函式的引數說明如下:引數uDelay表示延遲時間;引數uResolution表示時間精度,在Windows中預設值為1ms;lpTimeProc表示回撥函式,為使用者自定義函式,定時呼叫引數dwUser表示使用者提供的回撥資料;引數fuEvent為定時器的事件型別,TIME_ONESHOT表示執行一次;TIME_PERIODIC:週期性執行。具體應用時,可以通過呼叫timeSetEvent()函式,將需要週期性執行的任務定義在lpTimeProc回撥函式中(如:定時取樣、控制等),從而完成所需處理的事件。需要注意的是:任務處理的時間不能大於週期間隔時間。另外,在定時器使用完畢後,應及時呼叫timeKillEvent()將之釋放。下面這段程式碼的主要功能是設定兩個時鐘定時器,一個間隔是1ms,一個間隔是2s。每執行一次,把當前系統時鐘值輸入檔案"cure.out"中,以比較該定時器的精確度。

#define ONE_MILLI_SECOND1// 定義1ms2s時鐘間隔ms為單位 ;

#define TWO_SECOND2000

#define TIMER_ACCURACY1// 定義時鐘解析度ms為單位

UINTwTimerRes_1mswTimerRes_2s// 定義時間間隔

UINTwAccuracy// 定義解析度

UINTTimerID_1msTimerID_2s// 定義定時器控制代碼

////////////////////////////////////////////////////////////////////////////

CCureApp::CCureApp() : fout("cure.out"ios::out// 開啟輸出檔案"cure.out";

{

// 給時間間隔變數賦值

wTimerRes_1ms = ONE_MILLI_SECOND;

wTimerRes_2s = TWO_SECOND;

TIMECAPStc;

// 利用函式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);

}

voidCCureApp::InitializeTimer()

{

StartOneMilliSecondTimer();

StartTwoSecondTimer();

}

// 1ms定時器的回撥函式類似於中斷處理程式一定要宣告為全域性PASCAL函式,

// 否則編譯會有問題

voidPASCALOneMilliSecondProc(UINTwTimerIDUINTmsgDWORDdwUser,

DWORDdwlDWORDdw2)

{

// 定義計數器

staticintms = 0;

CCureApp *app = (CCureApp *)dwUser;

// 取得系統時間,ms為單位

DWORDosBinaryTime = GetTickCount();

// 輸出計數器值和當前系統時間

app->fout << ++ms << ":1ms:";

}

// 加裝1ms定時器

voidCCureApp::StartOneMilliSecondTimer()

{

if((TimerID_1ms = timeSetEvent(wTimerRes_1mswAccuracy,

        (LPTIMECALBACKOneMilliSecondProc// 回撥函式;

        (DWORD)this,  // 使用者傳送到回撥函式的資料;

TIME_PERIODIC)) == 0)// 週期呼叫定時處理函式;

    {

AfxMessageBox("不能進行定時!"MB_OK | MB_ICONASTERISK);

    }

else

fout << "16ms :" << endl// 不等於0表明加裝成功

}

在精度要求較高的情況下,如要求定時誤差不大於1ms時,還可以利用GetTickCount()函式返回自計算機啟動後的時間,該函式的返回值是DWORD型,表示以ms為單位的計算機啟動後經歷的時間間隔。通過兩次呼叫GetTickCount()函式,然後控制它們的差值來取得定時效果.下列的程式碼可以實現50ms的精確定時,其誤差是毫秒級的。

// 起始值和中止值

DWORDdwStartdwStop;

dwStop = GetTickCount();

while(TRUE)

{

// 上一次的中止值變成新的起始值

dwStart = dwStop// 此處新增相應控制語句

do

    {

dwStop = GetTickCount();

    }

while(dwStop - 50 < dwStart);

}

用上述兩種方式取得的定時效果雖然在許多場合已經滿足實際的要求,但由於它們的精度只有毫秒級的,而且在要求定時時間間隔小時,實際定時誤差大。對於精確度要求更高的定時操作,則應該使用QueryPerformanceFrequency()QueryPerformanceCounter()函式。這兩個函式是Visual C++提供並且僅供Windows 95及其後續版本使用,其精度與CPU的時鐘頻率有關,它們要求計算機從硬體上支援精確定時器。QueryPerformanceFrequency()函式和QueryPerformanceCounter()函式的原型如下:

BOOLQueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

BOOLQueryPerformanceCounter(LARGE_INTEGER *lpCount);

上述兩個函式的引數的資料型別LARGE_INTEGER既可以是一個8位元組長的整型數,也可以是兩個4位元組長的整型數的聯合結構,其具體用法根據編譯器是否支援64位而定。該型別的定義如下:

typedefunion_LARGE_INTEGER

{

struct

    {

DWORDLowPart// 4位元組整型數

LONGHighPart// 4位元組整型數

    };

LONGQuadPart// 8位元組整型數

}LARGE_INTEGER;

使用QueryPerformanceFrequency()QueryPerformanceCounter()函式進行精確定時的步驟如下:

1.首先呼叫QueryPerformanceFrequency()函式取得高精度執行計數器的頻率f,單位是每秒多少次(n/s),此數一般很大;

2.在需要定時的程式碼的兩端分別呼叫QueryPerformanceCounter()以取得高精度執行計數器的數值n1n2,兩次數值的差值通過f換算成時間間隔,t=(n2-n1)/f,當t大於或等於定時時間長度時,啟動定時器;

2.程式設計步驟

1.啟動Visual C++6.0,生成一個基於對話方塊的應用程式,將程式命名為"HightTimer";

2.在對話方塊面板中新增控制元件,佈局如圖一所示,其中包含兩個靜態文字框,兩個編輯框和兩個按紐。上面和下面位置的編輯框的ID分別為IDC_TESTIDC_ACTUAL"EXIT"按紐的IDIDOK"TEST"按紐IDID_TEST;

3.通過Class Wizard新增成員變數,兩個編輯框控制元件分別對應為DWORD m_dwTestDWORD m_dwAct,另外新增"TEST"按紐的滑鼠單擊訊息處理函式;

4.新增程式碼,編譯執行程式

3.程式程式碼

/////////////////////////////////////////////////////////////////////////

// 功能:執行實際的延時功能, Interval 引數為需要執行的延時與時間有關的數量,

// 此函式返回執行後實際所用的時間有關的數量 ;

LARGE_INTEGERMySleep(LARGE_INTEGERInterval)

{

LARGE_INTEGERpriviouscurrentElapse;

QueryPerformanceCounter(&privious);

current = privious;

while (current.QuadPart - privious.QuadPart < Interval.QuadPart)

QueryPerformanceCounter(&current);

Elapse.QuadPart = current.QuadPart - privious.QuadPart;

returnElapse;

}

voidCHightTimerDlg::OnTest()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE); // 取輸入的測試時間值到與編輯框相關聯的成員變數m_dwTest;

LARGE_INTEGERfrequence;

// 取高精度執行計數器的頻率若硬體不支援則返回FALSE

if(!QueryPerformanceFrequency(&frequence))

MessageBox("Your computer hardware doesn't support"

" the high-resolution performance counter",

"Not Support"MB_ICONEXCLAMATION | MB_OK);

LARGE_INTEGERtestret;

// 通過頻率換算微秒數到對應的數量(與CPU時鐘有關), 1 = 1000000微秒;

test.QuadPart = frequence.QuadPart * m_dwTest / 1000000;

ret = MySleep(test); // 呼叫此函式開始延時返回實際花銷的數量 ;

m_dwAct = (DWORD)(1000000 * ret.QuadPart / frequence.QuadPart); // 換算到微秒數;

UpdateData(FALSE); // 顯示到對話方塊面板 ;

}

4.小結

本例項介紹了實現精確定時的不同方法,尤其是對於需要精確到微秒級別的定時處理,給出了實現的方法和程式碼,細心的讀者朋友在執行程式的過程中可能會發現要求的定時長度和實際返回的時間長度還是有一些差異的,造成上述情況的原因是由於在進行定時處理時,還需要執行一些簡單的迴圈程式碼,所以會產生微秒級的誤差

轉自:http://blog.csdn.net/jiangxinyu/article/details/6213040