Win32下串口通信與16位串口通信有很大的區(qū)別。在Win32下,,可以使用兩種編程方式實(shí)現(xiàn)串口通信,,其一是調(diào)用的Windows的API函數(shù),其二是使用ActiveX控件,。使用API 調(diào)用,,可以清楚地掌握串口通信的機(jī)制,熟悉各種配置和自由靈活采用不同的流控進(jìn)行串口通信,。下面介紹串口操作的基本知識,。
打開串口:使用CreateFile()函數(shù),可以打開串口,。有兩種方法可以打開串口,,一種是同步方式(NonOverlapped),另外一種異步方式(Overlapped),。使用Overlapped打開時,,適當(dāng)?shù)姆椒ㄊ牵?/p>
HANDLE hComm;
hComm = CreateFile( gszPort,GENERIC_READ | GENERIC_WRITE,,0,,0,,OPEN_EXISTING,F(xiàn)ILE_FLAG_OVERLAPPED,,0);
if (hComm == INVALID_HANDLE_VALUE)
// error opening port; abort
配置串口:
1.DCB配置
DCB(Device Control Block)結(jié)構(gòu)定義了串口通信設(shè)備的控制設(shè)置,。許多重要設(shè)置都是在DCB結(jié)構(gòu)中設(shè)置的,有三種方式可以初始化DCB,。
?。?)通過GetCommState()函數(shù)得DCB的初始值,其使用方式為:
DCB dcb = {0};
if (,!GetCommState(hComm,, &dcb))
// Error getting current DCB settings
else
// DCB is ready for use.
(2)用BuildCommDCB()函數(shù)初始化DCB結(jié)構(gòu),,該函數(shù)填充 DCB的波特率,、奇偶校驗(yàn)類型、數(shù)據(jù)位,、停止位,。對于流控成員函數(shù)設(shè)置了缺省值。其用法是:
DCB dcb;
FillMemory(&dcb,, sizeof(dcb),, 0);
dcb.DCBlength = sizeof(dcb);
if (!BuildCommDCB(“9600,,n,,8,1“,, &dcb)) {
// Couldn‘t build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.
?。?)用SetCommState()函數(shù)手動設(shè)置DCB初值。用法如下:
DCB dcb;
FillMemory(&dcb,, sizeof(dcb),, 0);
if (!GetCommState(hComm,, &dcb)) // get current DCB
// Error in GetCommState
return FALSE;
// Update DCB rate.
dcb.BaudRate = CBR_9600 ;
// Set new state.
if (,!SetCommState(hComm, &dcb))
// Error in SetCommState.
Possibly a problem with the communications
// port handle or a problem with the DCB structure itself.
手動設(shè)置DCB值時,,DCB的結(jié)構(gòu)的各成員的含義,,可以參看MSDN幫助。
Win32串口編程
Win32下串口通信與16位串口通信有很大的區(qū)別,。在Win32下,,可以使用兩種編程方式實(shí)現(xiàn)串口通信,其一是調(diào)用的Windows的API函數(shù),其二是使用ActiveX控件,。使用API 調(diào)用,,可以清楚地掌握串口通信的機(jī)制,熟悉各種配置和自由靈活采用不同的流控進(jìn)行串口通信,。下面介紹串口操作的基本知識,。
打開串口:使用CreateFile()函數(shù),可以打開串口,。有兩種方法可以打開串口,,一種是同步方式(NonOverlapped),,另外一種異步方式(Overlapped),。使用Overlapped打開時,適當(dāng)?shù)姆椒ㄊ牵?/p>
HANDLE hComm;
hComm = CreateFile( gszPort,,GENERIC_READ | GENERIC_WRITE,,0,0,,OPEN_EXISTING,,F(xiàn)ILE_FLAG_OVERLAPPED,0);
if (hComm == INVALID_HANDLE_VALUE)
// error opening port; abort
配置串口:
1.DCB配置
DCB(Device Control Block)結(jié)構(gòu)定義了串口通信設(shè)備的控制設(shè)置,。許多重要設(shè)置都是在DCB結(jié)構(gòu)中設(shè)置的,,有三種方式可以初始化DCB。
?。?)通過GetCommState()函數(shù)得DCB的初始值,,其使用方式為:
DCB dcb = {0};
if (!GetCommState(hComm,, &dcb))
// Error getting current DCB settings
else
// DCB is ready for use.
?。?)用BuildCommDCB()函數(shù)初始化DCB結(jié)構(gòu),該函數(shù)填充 DCB的波特率,、奇偶校驗(yàn)類型,、數(shù)據(jù)位、停止位,。對于流控成員函數(shù)設(shè)置了缺省值,。其用法是:
DCB dcb;
FillMemory(&dcb, sizeof(dcb),, 0);
dcb.DCBlength = sizeof(dcb);
if (,!BuildCommDCB(“9600,n,,8,,1“, &dcb)) {
// Couldn‘t build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.
(3)用SetCommState()函數(shù)手動設(shè)置DCB初值,。用法如下:
DCB dcb;
FillMemory(&dcb,, sizeof(dcb), 0);
if (,!GetCommState(hComm,, &dcb)) // get current DCB
// Error in GetCommState
return FALSE;
// Update DCB rate.
dcb.BaudRate = CBR_9600 ;
// Set new state.
if (!SetCommState(hComm,, &dcb))
// Error in SetCommState.
Possibly a problem with the communications
// port handle or a problem with the DCB structure itself.
手動設(shè)置DCB值時,,DCB的結(jié)構(gòu)的各成員的含義,可以參看MSDN幫助,。
2.流控設(shè)置
硬件流控:串口通信中的硬件流控有兩種,,DTE/DSR方式和RTS/CTS方式,這與DCB結(jié)構(gòu)的初始化有關(guān)系,,DCB結(jié)構(gòu)中的OutxCtsFlow,、 fOutxDsrFlow、fDsrSensitivity,、fRtsControl,、fDtrControl幾個成員的初始值很關(guān)鍵,不同的值代表不同流控,,也可以自己設(shè)置流控,,但建議采用標(biāo)準(zhǔn)流行的流控方式。采用硬件流控時,,DTE,、DSR、RTS,、CTS的邏輯位直接影響到數(shù)據(jù)的讀寫及收發(fā)數(shù)據(jù)的緩沖區(qū)控制,。
軟件流控:串口通信中采用特殊字符XON和XOFF作為控制串口數(shù)據(jù)的收發(fā)。與此相關(guān)的DCB成員是:fOut,、fInX,、XoffChar、XonChar,、 XoffLim和XonLim,。具體含義參見MSDN幫助。
串口讀寫操作:串口讀寫有兩種方式:同步方式(NonOverlapped)和異步方式(Overlapped),。同步方式是指必須完成了讀寫操作,,函數(shù)才返回,這可能造成程序死掉,,因?yàn)槿绻谧x寫時發(fā)生了錯誤,,永遠(yuǎn)不返回就會出錯,可能線程將永遠(yuǎn)等待在那兒。而異步方式則靈活得多,,一旦讀寫不成功,,就將讀寫掛起,函數(shù)直接返回,,可以通過GetLastError函數(shù)得知讀寫未成功的原因,,所以常常采用異步方式操作。
讀操作:ReadFile()函數(shù)用于完成讀操作,。異步方式的讀操作為:
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent
?。∟ULL, TRUE,, FALSE,, NULL);
if (osReader.hEvent == NULL)
// Error creating overlapped event; abort.
if (!fWaitingOnRead) {
// Issue read operation.
if (,!ReadFile(hComm,, lpBuf, READ_BUF_SIZE,,
&dwRead,, &osReader)) {
if (GetLastError() ,!= ERROR_IO_PENDING)
// read not delayed?
// Error in communications; report it.
else
fWaitingOnRead = TRUE;
}
else {
// read completed immediately
HandleASuccessfulRead(lpBuf,, dwRead);
}
}
如果讀操作被掛起,,可以調(diào)用WaitForSingleObject()函數(shù)或WaitForMuntilpleObjects()函數(shù)等待讀操作完成或者超時發(fā)生,再調(diào)用 GetOverlappedResult()得到想要的信息,。
寫操作:與讀操作相似,,故不詳述,調(diào)用的API函數(shù)是: WriteFile函數(shù),。
串口狀態(tài):
?。?)通信事件:用SetCommMask()函數(shù)設(shè)置想要得到的通信事件的掩碼,再調(diào)用WaitCommEvent()函數(shù)檢測通信事件的發(fā)生,??稍O(shè)置的通信事件標(biāo)志(即SetCommMask()函數(shù)所設(shè)置的掩碼)可以有EV_BREAK、EV_CTS,、EV_DSR,、 EV_ERR、EV_RING,、EV_RLSD,、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY,。
注意:1對于EV_RING標(biāo)志的設(shè)置,,WIN95是不會返回EV_RING事件的,因?yàn)閃IN95不檢測該事件,。2設(shè)置EV_RXCHAR,,可以檢測到字符到達(dá),但是在綁定此事件和ReadFile()函數(shù)一起讀取串口接收數(shù)據(jù)時,,可能會出現(xiàn)錯誤,,造成少讀字節(jié)數(shù),具體原因查看MSDN幫助,??梢圆捎醚h(huán)讀的辦法,另外一個比較好的解決辦法是調(diào)用ClearCommError()函數(shù),,確定在一次讀操作中在緩沖區(qū)中等待被讀的字節(jié)數(shù),。
(2)錯誤處理和通信狀態(tài):在串口通信中,,可能會產(chǎn)生很多的錯誤,,使用ClearCommError()函數(shù)可以檢測錯誤并且清除錯誤條件。
?。?)Modem狀態(tài):用SetcommMask()可以包含很多事件標(biāo)志,,但是這些事件標(biāo)志只指示在串口線路上的電壓變化情況。而調(diào)用 GetCommModemStatus()函數(shù)可以獲得線路上真正的電壓狀態(tài),。
擴(kuò)展函數(shù):如果應(yīng)用程序想用自己的流控,,可以使用 EscapeCommFunction()函數(shù)設(shè)置DTR和RTS線路的電平。
通信超時:在通信中,,超時是個很重要的考慮因素,,因?yàn)槿绻跀?shù)據(jù)接收過程中由于某種原因突然中斷或停止,如果不采取超時控制機(jī)制,,將會使得I/O線程被掛起或無限阻塞,。串口通信中的超時設(shè)置分為兩步,首先設(shè)置 COMMTIMEOUTS結(jié)構(gòu)的五個變量,,然后調(diào)用SetcommTimeouts()設(shè)置超時值,。對于使用異步方式讀寫的操作,如果操作掛起后,,異步成功完成了讀寫,,WaitForSingleObject()或 WaitForMultipleObjects()函數(shù)將返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE,。其實(shí)還可以用GetCommTimeouts()得到系統(tǒng)初始值,。
關(guān)閉串口:程序結(jié)束或需要釋放串口資源時,,應(yīng)該正確關(guān)閉串口,關(guān)閉串口比較簡單,,使用API調(diào)用CloseHandle()關(guān)閉串口的句柄就可以了,。
調(diào)用方法為:CloseHandle(hComm);
但是值得注意的是在關(guān)閉串口之前必須保證讀寫串口線程已經(jīng)退出,否則會引起誤操作,,一般采用的辦法是使用事件驅(qū)動機(jī)制,,啟動一事件,通知串口讀寫線程強(qiáng)制退出,,在線程退出之前,,通知主線程可以關(guān)閉串口。
二,、實(shí)現(xiàn)
1.程序設(shè)計(jì)思路
對于不同的應(yīng)用程序,,雖然界面不同,但是如果采用串口與主機(jī)之間的通信,,對串口的處理方式大致相似,,無非就是通過串口收發(fā)數(shù)據(jù),對于通過串口接收到的數(shù)據(jù),,交給上層軟件處理顯示,,對于上層要發(fā)給串口的數(shù)據(jù),進(jìn)行轉(zhuǎn)發(fā),。但在實(shí)際編程中,,由于采用的通信方式和流控不同,串口設(shè)置也不同,,這就涉及到 DCB的初始化問題和讀寫串口等細(xì)節(jié)問題。串口通信應(yīng)用程序設(shè)計(jì)的總體思路(即操作過程)是:首先,,確定要打開的串口名,、波特率、奇偶校驗(yàn)方式,、數(shù)據(jù)位,、停止位,傳遞給CreateFile()函數(shù)打開特定串口,;其次,,為了保護(hù)系統(tǒng)對串口的初始設(shè)置,調(diào)用 GetCommTimeouts()得到串口的原始超時設(shè)置,;然后,,初始化DCB對象,調(diào)用SetCommState() 設(shè)置DCB,,調(diào)用SetCommTimeouts()設(shè)置串口超時控制,;再次,,調(diào)用SetupComm()設(shè)置串口接收發(fā)送數(shù)據(jù)的緩沖區(qū)大小,串口的設(shè)置就基本完成,,之后就可以啟動讀寫線程了,。
一般來說,串口的讀寫由串口讀寫線程完成,,這樣可以避免讀寫阻塞時主程序死鎖,。對于全雙工的串口讀寫,應(yīng)該分別開啟讀線程和寫線程,;對于半雙工和單工的,,建議只需開啟一個線程即可。在線程中,,按照預(yù)定好的通信握手方式,,正確檢測串口狀態(tài),讀取發(fā)送串口數(shù)據(jù),。
2.實(shí)現(xiàn)細(xì)節(jié)
在半雙工的情況下,,首先完成必要的串口配置,成功打開串口,、DCB設(shè)置,、超時設(shè)置;然后開啟線程,,如: CwinThread hSerialThread = (CWinThread*) AfxBeginThread(SerialOperation,,hWnd,THREAD_PRIORITY_NORMAL); 其中開啟之線程為SerialOperation,,優(yōu)先級為普通,。
全雙工情況下的串口編程,與單工差不多,,區(qū)別僅僅在于啟動雙線程,,分別為讀線程和寫線程,讀線程根據(jù)不同的事件或消息,,通過不斷查詢串口所收到的有效數(shù)據(jù),,完成讀操作;寫線程通過接收主線程的發(fā)送數(shù)據(jù)事件和要發(fā)送的數(shù)據(jù),,向串口發(fā)送,。