圖一、串口通信示例程序 |
一、實現方法
在Win16中,可以利用OpenComm()、CloseComm()和WriteComm()等函數打開、關閉和讀寫串口。但在Win32中,串口和其他通信設備均被作爲文件處理,串口的打開、關閉和讀寫等操作所用的API函數與操作文件的函數相同。可通過CreateFile()函數打開串口;通過CloseFile()函數關閉串口;通過DCB結構、CommProp()、GetCommProperties()、SetCommProperties()、GetCommState()及SetCommState()等函數設置串口狀態,通過函數ReadFile()和WritFile()等函數讀寫串口。下面來詳細介紹其實現原理。
對於串行通信設備,Win32 API支持同步和異步兩種I/O操作。同步操作方式的程序設計相對比較簡單,但I/O操作函數在I/O操作結束前不能返回,這將掛起調用線程,直到I/O操作結束。異步操作方式相對要複雜一些,但它可讓耗時的I/O操作在後臺進行,不會掛起調用線程,這在大數據量通信的情況下對改善調用線程的響應速度是相當有效的。異步操作方式特別適合同時對多個串行設備進行I/O操作和同時對一個串行設備進行讀/寫操作。
串行設備的初始化
串行設備的初始化是利用CreateFile()函數實現的。該函數獲得串行設備句柄並對其進行通信參數設置,包括設置輸出/接收緩衝區大小、超時控制和事件監視等。 例如下面的代碼實現了串口的初始化:
//串行設備句柄; HANDLE hComDev=0; //串口打開標誌; BOOL bOpen=FALSE; //線程同步事件句柄; HANDLE hEvent=0; DCB dcb; COMMTIMEOUTS timeouts; //設備已打開 if(bOpen) return FALSE; //打開COM1 if((hComDev=CreateFile(“COM1”,GENERICREAD|GENERICWRITE,0,NULL,OPENEXISTING,FILEATTRIBUTENORMAL,NULL))==INVALIDHANDLEVALUE) return FALSE; //設置超時控制 SetCommTimeouts(hComDev,&timeouts); //設置接收緩衝區和輸出緩衝區的大小 SetupComm(hComDev,1024,512); //獲取缺省的DCB結構的值 GetCommState(hComDev,&dcb); //設定波特率爲9600 bps dcb.BaudRate=CBR9600; //設定無奇偶校驗 dcb.fParity=NOPARITY; //設定數據位爲8 dcb.ByteSize=8; //設定一個停止位 dcb.StopBits=ONESTOPBIT; //監視串口的錯誤和接收到字符兩種事件 SetCommMask(hComDev,EVERR|EVRXCHAR); //設置串行設備控制參數 SetCommState(hComDev,&dcb); //設備已打開 bOpen=TRUE; //創建人工重設、未發信號的事件 hEvent=CreateEvent(NULL,FALSE,FALSE, “WatchEvent”); //創建一個事件監視線程來監視串口事件 AfxBeginThread(CommWatchProc,pParam); } |
在設置串口DCB結構的參數時,不必設置每一個值。首先讀出DCB缺省的參數設置,然後只修改必要的參數,其他參數都取缺省值。由於對串口進行的是同步I/O操作,所以除非指定進行監測的事件發生,否則WaitCommEvent()函數不會返回。在串行設備初始化的最後要建立一個單獨的監視線程來監視串口事件,以免掛起當前調用線程,其中pParam可以是一個對事件進行處理的窗口類指針。
如果要進行異步I/O操作,打開設備句柄時,CreateFile的第6個參數應增加FILEFLAGOVERLAPPED 標誌。
數據發送
數據發送利用WriteFile()函數實現。對於同步I/O操作,它的最後一個參數可爲NULL;而對異步I/O操作,它的最後一個參數必需是一個指向OVERLAPPED結構的指針,通過OVERLAPPED結構來獲得當前的操作狀態。
BOOL WriteComm(LPCVOID lpSndBuffer,DWORD dwBytesToWrite) { //lpSndBuffer爲發送數據緩衝區指針, dwBytesToWrite爲將要發送的字節長度 //設備已打開 BOOL bWriteState; //實際發送的字節數 DWORD dwBytesWritten; //設備未打開 if(!bOpen) return FALSE; bWriteState=WriteFile(hComDev,lpSndBuffer,dwBytesToWrite,&dwBytesWritten,NULL); if(!bWriteState || dwBytesToWrite!=dwBytesWritten) //發送失敗 return FALSE; else //發送成功 return TRUE; } |
數據接收
接收數據的任務由ReadFile函數完成。該函數從串口接收緩衝區中讀取數據,讀取數據前,先用ClearCommError函數獲得接收緩衝區中的字節數。接收數據時,同步和異步讀取的差別同發送數據是一樣的。
DWORD ReadComm(LPVOID lpInBuffer,DWORD dwBytesToRead) { //lpInBuffer爲接收數據的緩衝區指針, dwBytesToRead爲準備讀取的數據長度(字節數) //串行設備狀態結構 COMSTAT ComStat; DWORD dwBytesRead,dwErrorFlags; //設備未打開 if(!bOpen) return 0; //讀取串行設備的當前狀態 ClearCommError(hComDev,&dwErrorFlags,&ComStat); //應該讀取的數據長度 dwBytesRead=min(dwBytesToRead,ComStat.cbInQue); if(dwBytesRead>0) //讀取數據 if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL)) dwBytesRead=0; return dwBytesRead; } |
事件監視線程
事件監視線程對串口事件進行監視,當監視的事件發生時,監視線程可將這個事件發送(SendMessage)或登記(PostMessage)到對事件進行處理的窗口類(由pParam指定)中。
UINT CommWatchProc(LPVOID pParam) { DWORD dwEventMask=0; //發生的事件; while(bOpen) { //等待監視的事件發生 WaitCommEvent(hComDev, &dwEventMask,NULL); if ((dwEventMask & EVRXCHAR)==EVRXCHAR) ……//接收到字符事件後,可以將此消息登記到由pParam有指定的窗口類中進行處理 if(dwEventMask & EVERR)==EVERROR) ……//發生錯誤時的處理 } SetEvent(hEvent); //發信號,指示監視線程結束 return 0; } |
關閉串行設備
在整個應用程序結束或不再使用串行設備時,應將串行設備關閉,包括取消事件監視,將設備打開標誌bOpen置爲FALSE以使事件監視線程結束,清除發送/接收緩衝區和關閉設備句柄。
void CloseSynComm() { if(!bOpen) return; //結束事件監視線程 bOpen=FALSE; SetCommMask(hComDev,0); //取消事件監視,此時監視線程中的WaitCommEvent將返回 WaitForSingleObject(hEvent,INFINITE); //等待監視線程結束 CloseHandle(hEvent); //關閉事件句柄 //停止發送和接收數據,並清除發送和接收緩衝區 PurgeComm(hComDev,PURGETXABORT| PURGERXABORT|PURGETXCLEAR|PURGERXCLEAR); //關閉設備句柄 CloseHandle(hComDev); } |
二、編程步驟
1、 啓動Visual C++6.0,生成一個基於對話框的的應用程序,將該程序命名爲“SerealCom”;
2、 按照圖一的界面設計對話框,具體設置參見代碼部分;
3、 使用Class Wizard爲對話框的按鈕添加鼠標單擊消息響應函數;
4、 添加代碼,編譯運行程序。
三、程序代碼
////////////////////////////////////////////////////////////// #if !defined(_COMM_ACCESS_FUNCTIONS_AND_DATA) #define _COMM_ACCESS_FUNCTIONS_AND_DATA #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define EVENTCHAR 0x0d #define MAXBLOCKLENGTH 59 extern BYTE XwCom; extern BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12]; extern sCom3[MAXBLOCKLENGTH+12]; extern BYTE opation; extern short ComNum; #define FC_DTRDSR 0x01 #define FC_RTSCTS 0x02 #define FC_XONXOFF 0x04 #define ASCII_BEL 0x07 #define ASCII_BS 0x08 #define ASCII_LF 0x0A #define ASCII_CR 0x0D #define ASCII_XON 0x11 #define ASCII_XOFF 0x13 class CComStatus { public: HANDLE m_hCom; BYTE m_bComId; BYTE m_bByteSize; BYTE m_bStopBits; BYTE m_bParity; DWORD m_dwBaudRate; //WORD m_fChEvt; char m_bEvtChar; DWORD m_fBinary; BOOL m_bConnected; BOOL m_fXonXoff; BOOL m_bFlowCtrl; OVERLAPPED m_rdos; OVERLAPPED m_wtos; //functions CComStatus(); CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity, DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary); BOOL OpenConnection(); BOOL CloseConnection(); BOOL SetupConnection(); BOOL IsConnected(); }; UINT CommWatchProc( LPVOID lpData ); BOOL WriteCommBlock( CComStatus& comDev, LPSTR lpByte , DWORD dwBytesToWrite); int ReadCommBlock(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength ); int ReadCommBlockEx(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength,DWORD dwTimeOut); #endif /////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "com232.h" BYTE XwCom=0x40; BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12],sCom3[MAXBLOCKLENGTH+12]; BYTE opation; short ComNum; CComStatus::CComStatus() { m_hCom = NULL; m_bComId = (char)ComNum;//COM1 m_bByteSize=8; m_bStopBits=ONESTOPBIT; m_bParity=NOPARITY; m_dwBaudRate=9600; m_bEvtChar=EVENTCHAR; m_fBinary=1; m_bConnected = FALSE; m_bFlowCtrl = FC_XONXOFF ; m_fXonXoff = FALSE; } CComStatus::CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity,DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary) { m_hCom = NULL; m_bComId = bComId; m_bByteSize=bByteSize; m_bStopBits=bStopBits; m_bParity=bParity; m_dwBaudRate=dwBaudRate; m_bEvtChar=bEvtChar; m_fBinary=fBinary; m_bConnected = FALSE; m_bFlowCtrl = FC_XONXOFF ; m_fXonXoff = FALSE; } BOOL CComStatus::OpenConnection() { char csCom[10]; COMMTIMEOUTS CommTimeOuts ; if((m_bComId < 0) || (m_bComId > 4)) return FALSE;//從COM1到COM4 if(m_hCom)//if already open return FALSE; //OVERLAPPED包含異步I/O信息 m_rdos.Offset = 0; m_rdos.OffsetHigh = 0; m_rdos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); if(m_rdos.hEvent == NULL) return FALSE; m_wtos.Offset = 0; m_wtos.OffsetHigh = 0; m_wtos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); if(m_wtos.hEvent == NULL) { CloseHandle(m_rdos.hEvent); return FALSE; } wsprintf(csCom,"COM%d",m_bComId); m_hCom = CreateFile(csCom,GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING,ILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL); if(m_hCom == INVALID_HANDLE_VALUE) { //dwError = GetLastError(); // handle error return FALSE; } else { SetCommMask( m_hCom, EV_RXCHAR ) ; // get any early notifications SetupComm( m_hCom, 4096, 4096 ) ; // setup device buffers // purge any information in the buffer PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT |PURGE_TXCLEAR | PURGE_RXCLEAR ) ; // set up for overlapped I/O DWORD dwTemp = 1000 / (this->m_dwBaudRate / 8); CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ; CommTimeOuts.ReadTotalTimeoutMultiplier = 0;//((dwTemp > 0) ? dwTemp : 1); CommTimeOuts.ReadTotalTimeoutConstant = 1000 ; // CBR_9600 is approximately 1byte/ms. For our purposes, allow // double the expected time per character for a fudge factor. CommTimeOuts.WriteTotalTimeoutMultiplier =2*CBR_9600/this->m_dwBaudRate;//( npTTYInfo ) ; CommTimeOuts.WriteTotalTimeoutConstant = 0;//1000 ; SetCommTimeouts( m_hCom, &CommTimeOuts ) ; } if(!SetupConnection()) { CloseConnection(); return FALSE; } EscapeCommFunction( m_hCom, SETDTR ); m_bConnected = TRUE; return TRUE; } BOOL CComStatus::CloseConnection() { if (NULL == m_hCom) return ( TRUE ) ; // set connected flag to FALSE m_bConnected = FALSE; // disable event notification and wait for thread // to halt SetCommMask( m_hCom, 0 ) ; EscapeCommFunction( m_hCom, CLRDTR ) ; // purge any outstanding reads/writes and close device handle PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ; CloseHandle( m_hCom ) ; m_hCom = NULL; // change the selectable items in the menu CloseHandle(m_rdos.hEvent); CloseHandle(m_wtos.hEvent); return ( TRUE ) ; } BOOL CComStatus::SetupConnection() { BOOL fRetVal ; BYTE bSet ; DCB dcb ; if(m_hCom == NULL) return FALSE; dcb.DCBlength = sizeof( DCB ) ; GetCommState( m_hCom, &dcb ) ; dcb.BaudRate = this->m_dwBaudRate; dcb.ByteSize = this->m_bByteSize; dcb.Parity = this->m_bParity; dcb.StopBits = this->m_bStopBits ; dcb.EvtChar = this->m_bEvtChar ; // setup hardware flow control bSet = (BYTE) ((m_bFlowCtrl & FC_DTRDSR) != 0) ; dcb.fOutxDsrFlow = bSet ; if (bSet) dcb.fDtrControl = DTR_CONTROL_HANDSHAKE ; else dcb.fDtrControl = DTR_CONTROL_ENABLE ; bSet = (BYTE) ((m_bFlowCtrl & FC_RTSCTS) != 0) ; dcb.fOutxCtsFlow = bSet ; if (bSet) dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ; else dcb.fRtsControl = RTS_CONTROL_ENABLE ; // setup software flow control bSet = (BYTE) ((m_bFlowCtrl & FC_XONXOFF) != 0) ; dcb.fInX = dcb.fOutX = bSet ; dcb.XonChar = ASCII_XON ; char xon = ASCII_XON ; dcb.XoffChar = ASCII_XOFF ; char xoff = ASCII_XOFF ; dcb.XonLim = 100 ; dcb.XoffLim = 100 ; // other various settings dcb.fBinary = TRUE ; dcb.fParity = TRUE ; fRetVal = SetCommState( m_hCom, &dcb ) ; return ( fRetVal ) ; } // end of SetupConnection() BOOL CComStatus::IsConnected() { return m_bConnected; } UINT CommWatchProc( LPVOID lpData ) { DWORD dwEvtMask ; //NPTTYINFO npTTYInfo = (NPTTYINFO) lpData ; OVERLAPPED os ; int nLength ; //BYTE abIn[ MAXBLOCK + 1] ; CComStatus * pCom = (CComStatus *)lpData; memset( &os, 0, sizeof( OVERLAPPED ) ) ; // create I/O event used for overlapped read os.hEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ) ; // no name if (os.hEvent == NULL) { MessageBox( NULL, "Failed to create event for thread!", "TTY Error!",MB_ICONEXCLAMATION | MB_OK ) ; return ( FALSE ) ; } if (!SetCommMask( pCom->m_hCom, EV_RXCHAR )) return ( FALSE ) ; char buf[256]; while ( pCom->m_bConnected ) { dwEvtMask = 0 ; WaitCommEvent( pCom->m_hCom, &dwEvtMask, NULL ); if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) { if ((nLength = ReadCommBlock( *pCom, (LPSTR) buf, 255 ))) { //WriteTTYBlock( hTTYWnd, (LPSTR) abIn, nLength ) ; buf[nLength]='/0'; AfxMessageBox(buf); } } } CloseHandle( os.hEvent ) ; return( TRUE ) ; } // end of CommWatchProc() int ReadCommBlock(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength ) { BOOL fReadStat ; COMSTAT ComStat ; DWORD dwErrorFlags; DWORD dwLength; DWORD dwError; char szError[ 10 ] ; // only try to read number of bytes in queue ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ; dwLength = min( (DWORD) nMaxLength, ComStat.cbInQue ) ; if (dwLength > 0) { fReadStat = ReadFile( comDev.m_hCom, lpszBlock,dwLength, &dwLength, &(comDev.m_rdos) ) ; if (!fReadStat) { if (GetLastError() == ERROR_IO_PENDING) { OutputDebugString("/n/rIO Pending"); while(!GetOverlappedResult( comDev.m_hCom ,&(comDev.m_rdos), &dwLength, TRUE )) { dwError = GetLastError(); if(dwError == ERROR_IO_INCOMPLETE) // normal result if not finished continue; else { // an error occurred, try to recover wsprintf( szError, "<CE-%u>", dwError ) ; ClearCommError( comDev.m_hCom , &dwErrorFlags, &ComStat ) ; break; } } } else { // some other error occurred dwLength = 0 ; ClearCommError( comDev.m_hCom , &dwErrorFlags, &ComStat ) ; } } } return ( dwLength ) ; } // end of ReadCommBlock() int ReadCommBlockEx(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength,DWORD dwTimeOut) { LPSTR lpOffset=lpszBlock; int nReadCount = 0; char chBuf; //time_t beginTime,endTime; if(!comDev.m_hCom) return 0; if(dwTimeOut <= 0) return 0; MSG msg; //time(&beginTime); DWORD dwLastTick,dwNowTick,dwGoneTime; dwGoneTime = 0; dwLastTick = GetTickCount(); dwNowTick = dwLastTick; // double diftime; do { if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } if(ReadCommBlock(comDev,&chBuf,1) > 0) { //TRACE("----get a char----/n"); *lpOffset = chBuf; lpOffset ++; nReadCount ++; } dwNowTick = GetTickCount(); if(dwNowTick < dwLastTick) { dwLastTick = dwNowTick; } dwGoneTime = dwNowTick - dwLastTick; //TRACE("gon time = %lu/n",dwGoneTime); }while((nReadCount < nMaxLength) && (dwGoneTime < dwTimeOut)); return (nReadCount); }//end ReadCommBlockEx BOOL WriteCommBlock( CComStatus& comDev, LPSTR lpByte , DWORD dwBytesToWrite) { BOOL fWriteStat ; DWORD dwBytesWritten ; DWORD dwErrorFlags; DWORD dwError; DWORD dwBytesSent=0; COMSTAT ComStat; char szError[ 128 ] ; fWriteStat = WriteFile( comDev.m_hCom , lpByte, dwBytesToWrite,&dwBytesWritten, &( comDev.m_wtos) ) ; if (!fWriteStat) { if(GetLastError() == ERROR_IO_PENDING) { while(!GetOverlappedResult( comDev.m_hCom,&(comDev.m_wtos), &dwBytesWritten, TRUE )) { dwError = GetLastError(); if(dwError == ERROR_IO_INCOMPLETE) { // normal result if not finished dwBytesSent += dwBytesWritten; continue; } else { // an error occurred, try to recover wsprintf( szError, "<CE-%u>", dwError ) ; ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ; break; } } dwBytesSent += dwBytesWritten; if( dwBytesSent != dwBytesToWrite ) wsprintf(szError,"/nProbable Write Timeout: Total of %ld bytes sent", dwBytesSent); else wsprintf(szError,"/n%ld bytes written", dwBytesSent); OutputDebugString(szError); } else { // some other error occurred ClearCommError( comDev.m_hCom, &dwErrorFlags, &ComStat ) ; return ( FALSE ); } } return ( TRUE ) ; } // end of WriteCommBlock() |
四、小結
以上給出了用Win32 API設計串行通信的基本思路,在實際應用中,我們可以利用Win32 API設計出滿足各種需要的串行通信程序。