API串口通信
, ]% h1 \# k% T. ^3 A9 F串行端口是系統資源的一部分,其本質(zhì)是作為CPU和串行設備間的編碼轉換器。當數據從 CPU經(jīng)過(guò)串行端口發(fā)送出去時(shí),字節數據轉換為串行的位(Bit); 接收數據時(shí),串行的位被轉換為字節數據。應用程序要使用串口進(jìn)行通信,必須在使用之前向操作系統提出資源申請要求(即打開(kāi)串口),通信完成后再釋放資源(即關(guān)閉串口)。
M. T$ @* `: Q* q: D" d6 r串行通信一般可以分為同步和異步兩種操作方式。所謂同步方式是指在串口的接收緩沖區中讀取規定數目的數據,直到規定數目的數據全部被讀出或設定的超時(shí)時(shí)間已到才返回。如果規定的待讀取數據量大且設定的超時(shí)時(shí)間也較長(cháng),而接收緩沖區較小,則可能引起線(xiàn)程阻塞。而異步方式是利用Windows的多線(xiàn)程結構,讓串口的讀寫(xiě)操作在后臺進(jìn)行,而應用程序的其他部分在前臺執行。
: J9 B! s% w6 i" w如果按驅動(dòng)方式分,串口通信也可分為查詢(xún)和事件驅動(dòng)兩種操作類(lèi)型。所謂查詢(xún)方式是指一個(gè)進(jìn)程中的某一線(xiàn)程定時(shí)查詢(xún)串口的接收緩沖區,如果緩沖區中有數據,就讀取數據;若緩沖區中沒(méi)有數據,該線(xiàn)程將繼續執行。查詢(xún)方式會(huì )占用大量的CPU時(shí)間,它實(shí)際上是同步方式的一種派生。查詢(xún)方式是一種最直接的讀串口方式,但定時(shí)查詢(xún)可能發(fā)生得過(guò)早或過(guò)晚,在數據變化較快的情況下,特別是主控計算機的串口通過(guò)擴展板擴展至多個(gè)時(shí),容易發(fā)生數據的丟失。雖然指定時(shí)間隔越小,數據的實(shí)時(shí)性越高,但系統的資源也被占去越多。而事件驅動(dòng)方式則是一種高效的串口讀寫(xiě)方式,通過(guò)設置事件來(lái)通知系統工作,即當所希望的事件發(fā)生時(shí),Windows發(fā)出該事件已發(fā)生的通知,系統才進(jìn)行相應處理,避免了數據丟失,與DOS環(huán)境下的中斷方式很相似,實(shí)時(shí)性較高。Windows中提供文件讀寫(xiě)的異步方式,主要是針對文件I/O相對較慢的特點(diǎn)而進(jìn)行的改進(jìn),它利用了Windows的多線(xiàn)程結構。雖然在Windows中沒(méi)有實(shí)現任何對文件I/O的異步操作,但它卻能對串口進(jìn)行異步操作,因此可以提高系統的整體性能。
# h9 H4 _, i+ L8 u3 z, Q通過(guò)Visual C++的標準通信函數_inp和_outp可直接通過(guò)串口輸入和輸出數據。一般來(lái)說(shuō),在Visual C++中開(kāi)發(fā)串口通信程序主要有調用API函數和使用ActiveX控件技術(shù)兩種方式?;静襟E為:打開(kāi)串口設備,設置串口通信屬性,進(jìn)行串口讀寫(xiě)操作,關(guān)閉串口。下面將較為詳細地討論在VC中實(shí)現串口通信的上述兩種方法。
3 @5 G$ d$ o& {# k1 ^! A) V2 I使用Win32的API
+ x' s3 O! s* v W4 ~7 H( q9 s: dAPI是附帶在Windows內部的一個(gè)極其重要的組成部分。Windows的32位API主要是一系列復雜的函數和消息集合,可以看做是Windows系統為其下運行的各種開(kāi)發(fā)系統提供的開(kāi)放式通用功能增強接口。Windows環(huán)境下對串行端口進(jìn)行操作,是把它作為文件來(lái)處理的,其中涉及到大量API函數,操作起來(lái)比較復雜,可以概括為以下的幾個(gè)操作步驟:
9 W( a8 p1 I+ n7 T( y1. 打開(kāi)串行通信設備。在VC中使用CreateFile函數打開(kāi)串口,CreateFile將返回串口的句柄。該句柄將被用于后續的通信操作,并貫穿整個(gè)通信過(guò)程。當采用異步方式時(shí),CreateFile函數的參數fdwAttrsAndFlags必須設為FILE_FLAG_ OVERLAPPED,如:
! g6 P/ W( `- S" R! M: mm_hComFile =CreateFile(“COM1”,
4 X. h. E! \8 t1 c5 W//HANDLE m_hComFile,全局變量
4 o+ T. H% z) q; @GENERIC_READ | GENERIC_WRITE,
4 ?& V; J7 {6 E6 H$ y- H// 允許讀寫(xiě)操作
0, // 此項必須為0
. j' A' ~4 o6 {- S) F8 XNULL, // 安全設置
& U+ v& x9 t) N+ _+ `8 y7 e& a5 b% K. eOPEN_EXISTING, //設置打開(kāi)方式
& R, m4 ]6 S5 B. k2 s1 |% ~FILE_FLAG_OVERLAPPED,
//使用異步通信標志
& h3 C% F* Q8 UNULL );
2. 指定并初始化讀寫(xiě)緩沖區。程序通過(guò)調用SetupComm函數來(lái)指定讀寫(xiě)緩沖區的大小,并執行重新分配內部輸入和輸出緩沖的任務(wù),用PurgeComm函數對輸入和輸出緩沖進(jìn)行初始化,如:
SetCommMask(m_hComFile, EV_RXCHAR | EV_TXEMPTY ); //設置事件驅動(dòng)的類(lèi)型
) z+ f/ U) n. }" m$ uSetupComm(m_hComFile, 1024,1024) ;
//設置輸入、輸出緩沖區的大小
PurgeComm(m_hComFile,
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
//清空輸入、輸出緩沖區
3.設置串口屬性,配置DCB結構。當用CreateFile函數完成串口打開(kāi)操作時(shí),默認繼承設備控制塊(DCB結構)設置。通過(guò)調用GetCommState函數讀取當前串口設備控制塊DCB設置,修改后通過(guò)SetCommState函數將其寫(xiě)入。也可以使用GetCommProperties獲取COMMPROP結構,其中記載了系統支持的各項設置,包括當前所使用的串行設備、數據傳輸波特率、輸入輸出緩沖區大小等。例如:
DCB dcb ;
//定義設備控制塊結構
GetCommState(m_hComFile, &dcb ) ;
//讀取串口原來(lái)的參數設置
dcb.BaudRate =9600;
dcb.ByteSize =8;
dcb.Parity = NOPARITY;
0 R. z- n U2 r6 w- ~3 Q: ?dcb.StopBits = ONESTOPBIT ;
dcb.fBinary = TRUE ;
dcb.fParity = FALSE;
C' \" X9 N8 l/ s9 O. W1 ZSetCommState(m_hComFile, &dcb ) ;
//串口參數配置
4. 設置超時(shí)值。串口打開(kāi)后,I/O操作的超時(shí)值采用默認值。超時(shí)值的設置與結構COMMTIMEOUTS及函數GetCommTimeouts和SetCommTimeouts有關(guān)。用GetCommTimeouts函數可以獲得當前I/O操作的超時(shí)值配置,而調用SetCommTimeouts函數可以修改此配置,如:
COMMTIMEOUTS timeouts ;
' v( ?3 C" k8 c( D0 I" p: m* F//定義超時(shí)結構,并填寫(xiě)該結構
timeouts.ReadIntervalTimeout = 500;
- `$ H/ y' N9 R) Ktimeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.ReadTotalTimeoutConstant = 1000;
timeouts.WriteTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 1000; SetCommTimeouts(m_hComFile,&timeouts );
3 Q9 }" v% y+ K$ r, q7 n//設置讀寫(xiě)操作所允許的超時(shí)
0 e/ i9 G/ K$ c$ l; a% m, z3 @) }/ j其中,區間超時(shí)(ReadIntervalTimeout)指的是在讀取兩個(gè)字符之間的時(shí)間間隔,它僅對從端口中讀取數據有效;總超時(shí)指的是當讀或寫(xiě)特定的字節數需要的總時(shí)間超過(guò)某一閾值時(shí),超時(shí)觸發(fā)。超時(shí)的計算公式如下:
ReadTotalTimeout= (ReadTotalTimeoutMultiplier * bytes_to_read)+ ReadToTaltimeoutConstant
WriteTotalTimeout = (WriteTotalTimeoutMuliplier * bytes_to_write) + WritetoTotalTimeoutConstant
5. 進(jìn)行串行數據通信。調用函數ReadFile和WriteFile讀寫(xiě)串口。若采用異步通信方式,兩函數中最后一個(gè)參數為指向OVERLAPPED結構的非空指針,在讀寫(xiě)函數返回值為FALSE的情況下,調用GetLastError函數,返回值為ERROR_IO_PENDING,表明I/O操作懸掛,即操作轉入后臺繼續執行。此時(shí),可以用WaitForSingleObject函數來(lái)等待結束信號并設置最長(cháng)等待時(shí)間。下面的例子中,在主線(xiàn)程中發(fā)送命令,用一個(gè)輔助線(xiàn)程來(lái)監視串口,有數據到達時(shí)依靠事件驅動(dòng)讀入數據并向主線(xiàn)程報告。
下面的代碼實(shí)現在主線(xiàn)程中準備并發(fā)送數據:
BOOL fWriteStat ;
char sndBuffer[count];
i4 ^! J" x; ^...... // sndBuffer[]中存放待發(fā)送的數據
OVERLAPPED overwrite;
//設置用于異步操作的OVERLAPPED結構
overwrite. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
2 ^* y8 c" R1 Z4 ~2 V9 L% `fWriteStat = WriteFile(m_hComFile, sndBuffer, dwBytesToWrite, &dwBytesWritten, &overwrite); //寫(xiě)數據
if (!fWriteStat){
if (GetLastError() == ERROR_IO_PENDING) {……}
}
創(chuàng )建輔助線(xiàn)程:
hReadThread=CreateThread( (LPSECURITY_ATTRIBUTES) NULL,
. }- X( V3 K2 V J; I" D0 R# Y//安全屬性
0, //初始化線(xiàn)程棧的大小,缺省為與主線(xiàn)程大小相同
- Y& A I6 }9 v9 B7 U# H(LPTHREAD_START_ROUTINE) CommReadProc, //線(xiàn)程函數
( F" s9 ]% c8 r8 D* E; p6 A& ZGetSafeHwnd(), //此處傳入主框架的句柄
0, (LPDWORD)lpThreadID );
在輔助線(xiàn)程中監視串口并接收數據:
8 ~4 ~, h: L- x4 pUINT CommReadProc(HWND hSendWnd){
DWORD dwEvtMask=0 ;
! D6 ]& S8 W# W SetCommMask(m_hComFile, EV_RXCHAR|EV_TXEMPTY );
//設置串口事件驅動(dòng)
# j# @- |! J6 \! v; n% l: `4 u# Z WaitCommEvent(m_hComFile, &dwEvtMask, os ); //等待串口事件
% d" W0 D: s }* q if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ //緩沖區中有數據到達
DWORD dwLength = ComStat.cbInQue ;
//輸入緩沖區數據長(cháng)度
% r3 K" v3 G5 r, o( yCOMSTAT ComStat ;
ClearCommError(m_hComFile, &dwErrorFlags, &ComStat ) ;
+ A4 Y4 {1 i5 sOVERLAPPED overread;
" a( N0 A$ l8 z5 x7 ^9 goverread. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
if (dwLength > 0) {
' H3 w) P/ i4 @! j2 `BOOL fReadStat = ReadFile(m_hComFile, lpBuffer,dwLength, &dwBytesRead,&overread);
//讀數據
if (!fReadStat){
if (GetLastError() == ERROR_IO_PENDING){
……}
}
:
ostMessage((HWND)hSendWnd,
& M$ ~$ H, Y! U3 D6 x0 f: B7 cWM_NOTIFYPROCESS,0,0);
//通知主線(xiàn)程,串口收到數據
}
6. 關(guān)閉串行端口。調用函數CloseHandle即可。
[) l i' Y9 J9 \4 E$ T總體說(shuō)來(lái),調用API 函數實(shí)現串行通信,程序更為復雜,但應用更加靈活。在A(yíng)PI串口通信中可以將串口的屬性設置和操作封裝成一個(gè)專(zhuān)用的串口類(lèi),同時(shí)結合Windows非阻塞通信、多線(xiàn)程、動(dòng)態(tài)鏈接庫等手段,編寫(xiě)出高質(zhì)量的通信程序,特別是在CPU處理任務(wù)比較繁重、與外圍設備中有大量的通信數據時(shí),更具實(shí)際意義。