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()函數,該函數原型如下:

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 // 定義1ms2s時鐘間隔ms爲單位 ;

#define TWO_SECOND 2000

#define TIMER_ACCURACY 1 // 定義時鐘分辨率ms爲單位

UINT wTimerRes_1mswTimerRes_2s// 定義時間間隔

UINT wAccuracy// 定義分辨率

UINT TimerID_1msTimerID_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 wTimerIDUINT msgDWORD dwUser,

                               DWORD dwlDWORD 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_1mswAccuracy,

        (LPTIMECALBACKOneMilliSecondProc// 回調函數;

        (DWORD)this,  // 用戶傳送到回調函數的數據;

        TIME_PERIODIC)) == 0)// 週期調用定時處理函數;

    {

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

    }

    else

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

}

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

// 起始值和中止值

DWORD dwStartdwStop;

dwStop = GetTickCount();

while(TRUE)

{

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

    dwStart = dwStop// 此處添加相應控制語句

    do

    {

        dwStop = GetTickCount();

    }

    while(dwStop - 50 < dwStart);

}

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

BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);

上述兩個函數的參數的數據類型LARGE_INTEGER既可以是一個8字節長的整型數,也可以是兩個4字節長的整型數的聯合結構,其具體用法根據編譯器是否支持64位而定。該類型的定義如下:

typedef union _LARGE_INTEGER

{

    struct

    {

        DWORD LowPart// 4字節整型數

        LONG HighPart// 4字節整型數

    };

    LONG QuadPart// 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_INTEGER MySleep(LARGE_INTEGER Interval)

{

    LARGE_INTEGER priviouscurrentElapse;

    QueryPerformanceCounter(&privious);

    current = privious;

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

        QueryPerformanceCounter(&current);

    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 testret;

    // 通過頻率換算微秒數到對應的數量(與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

發佈了7 篇原創文章 · 獲贊 30 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章