郭 宇,陳年生,方曉平
(湖北師范學(xué)院 計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院, 湖北 黃石 435002)
自動(dòng)化技術(shù)的進(jìn)步使得實(shí)時(shí)控制系統(tǒng)得到越來越廣泛的應(yīng)用,實(shí)時(shí)采集數(shù)據(jù)是系統(tǒng)中重要的部分,這個(gè)過程直接影響控制質(zhì)量和生產(chǎn)效率,因此對(duì)定時(shí)器精度和實(shí)時(shí)性要求相當(dāng)高,工業(yè)控制軟件定時(shí)精度需達(dá)到毫秒級(jí)。
在以往DOS操作系統(tǒng)中,用戶可以容易且精確地控制采樣精度,這得益于對(duì)硬件的直接操作?,F(xiàn)在主流的Windows操作系統(tǒng),提供了豐富的圖形界面,人機(jī)交互友好,使用方便,用戶卻難以對(duì)底層硬件進(jìn)行直接操作。另外,Windows是一個(gè)基于消息機(jī)制的操作系統(tǒng),各個(gè)應(yīng)用程序都有自己的消息隊(duì)列[1],這樣可能會(huì)帶來諸如低優(yōu)先級(jí)消息不能得到及時(shí)響應(yīng)等問題。PLC廣泛應(yīng)用于工業(yè)自動(dòng)化控制,現(xiàn)擬對(duì)某PLC中D寄存器數(shù)據(jù)進(jìn)行實(shí)時(shí)采樣。在Windows環(huán)境使用Visual Studio開發(fā)工具編寫PLC數(shù)據(jù)采集的上位機(jī)軟件,通過視窗和操作系統(tǒng)來完成定時(shí)任務(wù),這樣對(duì)實(shí)時(shí)性就提出了挑戰(zhàn),以下將著重介紹軟件中使用的兩種定時(shí)器。
WM_TIMER定時(shí)器是Windows操作系統(tǒng)自帶的一種定時(shí)器,使用起來方便簡(jiǎn)單,因此也經(jīng)常被使用。
其實(shí)現(xiàn)原理是先由SetTimer()函數(shù)在內(nèi)存中創(chuàng)建一個(gè)定時(shí)器,并在參數(shù)中設(shè)置好定時(shí)間隔,每當(dāng)達(dá)到設(shè)定時(shí)間,系統(tǒng)就會(huì)發(fā)出一個(gè)WM_TIMER消息,并由相應(yīng)的響應(yīng)函數(shù)(回調(diào)函數(shù))去做處理。定時(shí)器使用完后由KillTimer()函數(shù)釋放。
相關(guān)函數(shù)如下:
1)SetTimer() 創(chuàng)建定時(shí)器;
2)OnTimer() 消息響應(yīng)函數(shù),由用戶編寫處理事件的代碼;
3)KillTimer() 中止定時(shí)器,釋放定時(shí)器資源。
對(duì)SetTimer()函數(shù)作如下說明:
UINT_PTR SetTimer(
UINT_PTR nIDEvent.
//nIDEvent是定時(shí)器編號(hào)ID,倘若有多個(gè)定時(shí)器,可通過ID識(shí)別
UINT nElapse,
//用戶設(shè)定的時(shí)間間隔,以ms為單位
TIMERPROC lpTimerFunc
//回調(diào)函數(shù)
);
其中最后一個(gè)參數(shù)lpTimerFunc是處理定時(shí)器消息的回調(diào)函數(shù),在這個(gè)函數(shù)里面由程序設(shè)計(jì)人員添加處理事件的代碼。這個(gè)參數(shù)也可以設(shè)為NULL,此時(shí)將使用系統(tǒng)默認(rèn)的響應(yīng)函數(shù)OnTimer()。
在Visual Studio 2010環(huán)境中,使用類向?qū)蒓nTimer()函數(shù):在類視圖中右擊需要定時(shí)器的類,選擇“類向?qū)А?,在彈出來的?duì)話框中的“消息”選項(xiàng)卡下選擇“WM_TIMER”,點(diǎn)擊“添加處理程序”,這樣類中就會(huì)自動(dòng)生成OnTimer()函數(shù)。程序人員在該函數(shù)中添加實(shí)現(xiàn)功能的代碼,每隔設(shè)定的時(shí)間都會(huì)執(zhí)行一次該函數(shù),本例就是在OnTimer()函數(shù)中添加采集PLC內(nèi)D寄存器數(shù)據(jù)的代碼,其流程如圖1所示。
圖1 WM_TIMER定時(shí)器采集數(shù)據(jù)的程序框圖
VS2010作為開發(fā)工具,在對(duì)話框類中實(shí)現(xiàn)定時(shí)器功能:
1)在啟動(dòng)按鈕中創(chuàng)建定時(shí)器
void CDDataDlg::OnBnClickedStart()
{
// TODO: 在此添加控件通知處理程序代碼
...
SetTimer(1,200,NULL);
}
2)數(shù)據(jù)采集,使用類向?qū)ё詣?dòng)生成
void CDDataDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息處理程序代碼/或調(diào)用默認(rèn)值
...
實(shí)現(xiàn)數(shù)據(jù)采集的代碼
...
}
3)在結(jié)束按鈕中關(guān)閉定時(shí)器,釋放資源
void CDDataDlg::OnBnClickedEnd()
{
// TODO: 在此添加控件通知處理程序代碼
KillTimer(1);
}
微軟公司在其多媒體Windows中提供了精確定時(shí)器的底層API(應(yīng)用程序編程接口)支持,相對(duì)于WM_TIMER定時(shí)器,多媒體定時(shí)器的設(shè)計(jì)要稍顯復(fù)雜,但是精度更高。
使用多媒體定時(shí)器需要包含頭文件Mmsystem.h,其接口函數(shù)位于mmsystem.dll的動(dòng)態(tài)鏈接庫中,多媒體計(jì)時(shí)器的設(shè)計(jì)包括以下函數(shù):
1)timeGetDevCaps() 獲取定時(shí)器所支持的最大和最小精度,不同的PC機(jī)由于硬件配置的不同獲取的精度范圍可能有所差異;
2)timeBeginPeriod() 設(shè)置定時(shí)器的定時(shí)精度,其參數(shù)可由timeGetDevCaps()函數(shù)獲?。?/p>
3)timeEndPeriod() 清除定時(shí)器精度,釋放定時(shí)器資源;
4)timeKillEvent() 刪除定時(shí)器事件。
5)MMRESULT timeSetEvent(
UINT uDelay,
//以毫秒設(shè)定定時(shí)周期
UINT wAccuracy,
//以毫秒指定延時(shí)的精度,默認(rèn)值為1ms
LPTIMECALLBACK lpTimeProc,
//回調(diào)函數(shù),用戶自定義,定時(shí)調(diào)用
DWORD dwUser,
//存放回調(diào)數(shù)據(jù)
UINT fuEvent
//指定定時(shí)器事件類型:
//TIME_ONESHOT:uDelay毫秒后只產(chǎn)生一次事件
//TIME_PERIODIC:每隔uDelay毫秒周期性地產(chǎn)生事件
);
該函數(shù)設(shè)置一個(gè)定時(shí)回調(diào)事件,此事件可以是一個(gè)一次性事件或者是周期性事件。事件一旦被激活,即調(diào)用指定的回調(diào)函數(shù),成功則返回事件的標(biāo)識(shí)符代碼,否則返回NULL.
6)static VOID CALLBACK TimeProc() 回調(diào)函數(shù)是由開發(fā)者自己編寫的周期性執(zhí)行的函數(shù)?;卣{(diào)函數(shù)可以定義為相應(yīng)的類成員函數(shù)或者全局函數(shù)。C++的類成員都有一個(gè)自動(dòng)隱藏的私有成員this指針,如果回調(diào)函數(shù)定義為類的普通成員函數(shù),函數(shù)類型與CALLBACK函數(shù)預(yù)設(shè)類型不相同。這時(shí)要定義為靜態(tài)類型,屏蔽this指針,然而靜態(tài)成員函數(shù)只能訪問靜態(tài)成員,不便操作,所以,回調(diào)函數(shù)一般定義為全局函數(shù)[2]。
在使用多媒體定時(shí)器時(shí),將需要周期性循環(huán)執(zhí)行的任務(wù)定義在 lpTimeProc回調(diào)函數(shù)中(定時(shí)采樣、控制等),通過調(diào)用timeSetEvent()函數(shù)觸發(fā)回調(diào)函數(shù),完成所需處理的任務(wù)。定時(shí)器使用完畢后,應(yīng)及時(shí)調(diào)用timeKillEvent()釋放。如圖2所示,簡(jiǎn)要概括了使用多媒體定時(shí)器的基本流程。
圖2 多媒體定時(shí)器采集數(shù)據(jù)的程序框圖
Visual Studio 2010為開發(fā)工具,使用多媒體定時(shí)器對(duì)PLC內(nèi)D寄存器數(shù)據(jù)進(jìn)行實(shí)時(shí)采集,主要步驟和代碼如下:
1)包含頭文件,定義全局變量
#include 'Mmsystem"
#pragma comment(lib, "Winmm.lib")
HWND hWnd;//窗口句柄
UINT uTimerID;//定時(shí)器句柄
UINT uResolution;//定時(shí)器分辨率
在BOOL CDDataAquisitionDlg::OnInitDialog()中添加代碼:
hWnd=this->m_hWnd;
2)定義回調(diào)函數(shù),用于處理需要周期性執(zhí)行的任務(wù),即采集數(shù)據(jù)
static VOID CALLBACK ProFunc(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
...
采集數(shù)據(jù)
...
}
3)啟動(dòng)定時(shí)器,觸發(fā)2)中回調(diào)函數(shù)
BOOL TimerStart(UINT &wAccuracy,UINT uDelay,UINT &TimerID,LPVOID fptc,DWORD dwUser)
{
TIMECAPS tc;
if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR)
{
//分辨率的值不能超出系統(tǒng)的取值范圍
wAccuracy = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
//設(shè)置定時(shí)器的分辨率
timeBeginPeriod(wAccuracy);
}
else
return FALSE;
//啟動(dòng)定時(shí)器
TimerID=timeSetEvent(uDelay,wAccuracy,(LPTIMECALLBACK)fptc,dwUser,TIME_PERIODIC);
if(TimerID==0) return FALSE;
return TRUE;
}
4)關(guān)閉定時(shí)器,釋放定時(shí)器資源
void TimerEnd(UINT uTimerID,UINT &uResolution)
{
//刪除定時(shí)事件
timeKillEvent(uTimerID);
if(lpuPeriod != NULL)
//清除定時(shí)器分辨率
timeEndPeriod(uResolution);
}
支持WM_TIMER定時(shí)器的底層硬件中斷頻率是18.2Hz,即每隔至少54.945ms中斷一次。我們知道,Windows是一個(gè)基于消息機(jī)制的搶占式操作系統(tǒng),操作系統(tǒng)給每個(gè)應(yīng)用程序都建立一個(gè)消息隊(duì)列,而且消息隊(duì)列中常有大量的消息等待處理[3],一旦系統(tǒng)資源緊張或者CPU被占用,隊(duì)列中的消息將被掛起,不能得到實(shí)時(shí)響應(yīng)。另外,WM_TIMER消息的優(yōu)先級(jí)很低,排在其他消息(WM_PAINT除外)之后響應(yīng),一個(gè)消息隊(duì)列中只允許有一個(gè)WM_TIMER消息,倘若遇到系統(tǒng)繁忙處理一個(gè)消息超時(shí),后面產(chǎn)生的多個(gè)相同ID的消息會(huì)合并成一個(gè),所以該定時(shí)器對(duì)消息的處理在數(shù)目上可能出現(xiàn)錯(cuò)誤,時(shí)間上甚至出現(xiàn)“丟秒”的現(xiàn)象。顯然WM_TIMER定時(shí)器不適用于精度要求較高的控制系統(tǒng)中,但是WM_TIMER定時(shí)器使用起來很方便,而且相當(dāng)安全,可以在定時(shí)函數(shù)中進(jìn)行幾乎任何操作也不用擔(dān)心死機(jī)的問題。即使出現(xiàn)處理超時(shí)的情況,也不會(huì)引起系統(tǒng)崩潰。
多媒體定時(shí)器的工作原理和WM_TIMER定時(shí)器不同,多媒體定時(shí)器并不依賴消息機(jī)制,而是提供硬件中斷服務(wù),并且Windows提供了高精度定時(shí)器的底層API支持。timeSetEvent()函數(shù)產(chǎn)生獨(dú)立的線程,直接調(diào)用回調(diào)函數(shù),不存在消息隊(duì)列和優(yōu)先級(jí)的問題,是一種理想的高精度定時(shí)器,最小精度可以達(dá)到1ms。在使用多媒體定時(shí)器的過程中要注意:定時(shí)器線程結(jié)束前不可再次啟動(dòng)定時(shí)器,這樣容易迅速出現(xiàn)死機(jī)現(xiàn)象[4]。任務(wù)處理的時(shí)間不能大于周期間隔時(shí)間,不然會(huì)引起程序崩潰。定時(shí)器是系統(tǒng)資源,使用完畢之后應(yīng)該釋放,分辨率也要清除,以免降低系統(tǒng)響應(yīng)的速度。
最后,通過在Visual Studio中設(shè)計(jì)實(shí)驗(yàn)做形象對(duì)比,建立單文檔的MFC工程,在View類中作圖,使條帶長度從0開始隨時(shí)間而動(dòng)態(tài)增加,可在任意時(shí)刻執(zhí)行暫停,同時(shí)生成條帶圖形并讀取長度。在程序中添加多個(gè)WM_TIMER定時(shí)器和多媒體定時(shí)器。實(shí)驗(yàn)中,每個(gè)定時(shí)器都設(shè)置一個(gè)時(shí)間間隔(可更改),擬使每個(gè)時(shí)間間隔內(nèi)條帶的長度都增加1個(gè)單位長度。當(dāng)時(shí)間間隔設(shè)置的很小,只有十幾毫秒甚至一毫秒時(shí),從圖3可以看出,WM_TIMER定時(shí)器對(duì)時(shí)間間隔的差異并不敏感,可見這些時(shí)間間隔不在其定時(shí)精度范圍,而多媒體定時(shí)器卻十分精準(zhǔn),精確到1ms.當(dāng)時(shí)間間隔達(dá)到幾百毫秒或者秒級(jí)時(shí),如圖4所示,兩種定時(shí)器幾乎沒有差異。此實(shí)驗(yàn)在不同的機(jī)型上測(cè)試過,如Intel奔騰雙核,主頻2.5GHz的長城電腦和Intel酷睿i5,主頻為3.1GHz的啟天M4350等,都有相同的實(shí)驗(yàn)結(jié)果。
綜上所述,WM_TIMER定時(shí)器實(shí)現(xiàn)過程較為簡(jiǎn)單、安全,主要用于對(duì)定時(shí)要求不高,采樣周期較長的系統(tǒng)中;而多媒體定時(shí)器可以用于精度要求較高,采樣周期較短的控制系統(tǒng)中。
圖3 定時(shí)間隔只有幾毫秒時(shí)兩種定時(shí)器對(duì)比
圖4 定時(shí)間隔幾百毫秒時(shí)兩種定時(shí)器對(duì)比
編寫此上位機(jī)軟件的目的是對(duì)PLC內(nèi)D寄存器的數(shù)據(jù)進(jìn)行實(shí)時(shí)采集,在開發(fā)過程中,先是選擇了WM_TIMER定時(shí)器,使用很方便,用于采樣周期較長(幾百毫秒)的任務(wù)時(shí),還是有著良好的效果。若是遇到精度要求較高,采樣周期只有幾毫秒的任務(wù),就必須使用多媒體定時(shí)器完成任務(wù)。本文兩種定時(shí)器的設(shè)計(jì)方法均在當(dāng)前流行的Visual Studio 2010中調(diào)試通過,并且在具體應(yīng)用中切實(shí)可行。
參考文獻(xiàn):
[1]王光輝. 深入分析Windows消息機(jī)制[J]. 電腦與電信,2011,02:69~70+73.
[2]王 偉,徐國華. 多媒體定時(shí)器在工業(yè)控制中的應(yīng)用[J]. 微型機(jī)與應(yīng)用,2001,12:8~10.
[3]卓紅艷,趙 平. 基于VC++的實(shí)時(shí)數(shù)據(jù)采集系統(tǒng)中定時(shí)器的使用與比較[J]. 現(xiàn)代電子技術(shù),2007,18:80~82.
[4]黃兆祥,郭麥成,沈利香. Win9x下實(shí)時(shí)數(shù)據(jù)采集的實(shí)現(xiàn)[J]. 微計(jì)算機(jī)信息,2003,11:34~35.
湖北師范大學(xué)學(xué)報(bào)(自然科學(xué)版)2013年4期