使用Windows API進行串口編程


 

串口通信一般分為四大步:打開串口->配置串口->讀寫串口->關閉串口,還可以在串口上監聽讀寫等事件。 

1、打開和關閉串口

Windows中串口是作為文件來處理的,調用CreateFile()函數可以打開串口函數執行成功返回串口句柄,出錯返回INVALID_HANDLE_VALUE。 

HANDLE WINAPI CreateFile(
  _In_      LPCTSTR lpFileName,//要打開或創建的文件名
  _In_      DWORD dwDesiredAccess,//訪問類型
  _In_      DWORD dwShareMode,//共享方式
  _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全屬性
  _In_      DWORD dwCreationDisposition,//指定要打開的文件已存在或不存在的動作
  _In_      DWORD dwFlagsAndAttributes,//文件屬性和標志
  _In_opt_  HANDLE hTemplateFile//一個指向模板文件的句柄
);

lpFileName:要打開或創建的文件名。

 dwDesiredAccess:訪問方式。0為設備查詢訪問方式;GENERIC_READ為讀訪問;GENERIC_WRITE為寫訪問;  

 dwShareMode:共享方式。0表示文件不能被共享,其它打開文件的操作都會失敗;FILE_SHARE_READ表示允許其它讀操作;FILE_SHARE_WRITE表示允許其它寫操作;FILE_SHARE_DELETE表示允許其它刪除操作。  

 lpSecurityAttributes:安全屬性。一個指向SECURITY_ATTRIBUTES結構的指針。  

 dwCreationDisposition:創建或打開文件時的動作。 OPEN_ALWAYS:打開文件,如果文件不存在則創建它;TRUNCATE_EXISTING 打開文件,且將文件清空(故需要GENERIC_WRITE權限),如果文件不存在則會失敗;OPEN_EXISTING打開  文件,文件若不存在則會失敗;CREATE_ALWAYS創建文件,如果文件已存在則清空;CREATE_NEW創建文件,如文件存在則會失敗;

 dwFlagsAndAttributes:文件標志屬性。FILE_ATTRIBUTE_NORMAL常規屬性; FILE_FLAG_OVERLAPPED異步I/O標志,如果不指定此標志則默認為同步IO;FILE_ATTRIBUTE_READONLY文件為只讀; FILE_ATTRIBUTE_HIDDEN文件為隱藏。

                                   FILE_FLAG_DELETE_ON_CLOSE所有文件句柄關閉后文件被刪除;其它標志和屬性參考MSDN。

 hTemplateFile:一個文件的句柄,且該文件必須是以GENERIC_READ訪問方式打開的。如果此參數不是NULL,則會使用hTemplateFile關聯的文件的屬性和標志來創建文件。如果是打開一個現有文件,則該參數被忽略。

 使用CreateFile()打開串口時需要注意的是:lpFileName文件名直接寫串口號名,如“COM1”,COM10及以上的串口名格式應為:"\\\\.\\COM10";dwShareMode共享方式應為0,即串口應為獨占方式;dwCreationDisposition打開時的動作應為OPEN_EXISTING,即串口必須存在。

 調用CloseHandle()函數來關閉串口,函數參數為串口句柄。

 

HANDLE m_hSwitCom2
m_hSwitCom2=CreateFile(strCom,
            GENERIC_READ|GENERIC_WRITE,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
            NULL);
if(m_hSwitCom2==INVALID_HANDLE_VALUE)
            return FALSE;
        
SetCommMask(m_hSwitCom2,EV_RXCHAR);  //設置串口通信事件
SetupComm(m_hSwitCom2,30000,512);  //用來設置串口的發送/接受緩沖區的大小,如果通信的速率較高,則應該設置較大的緩沖區
        PurgeComm(m_hSwitCom2,PURGE_TXABORT|PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXCLEAR);  //停止讀寫操作、清空讀寫緩沖區,//第一次讀取串口數據、寫串口數據之前、串口長時間未使用、串口出現錯誤等情況下,應先清空讀或寫緩沖區。
        

2、配置串口

①設置超時

 在調用ReadFile()和WriteFile()讀寫串口的時候,如果沒有指定異步操作的話,讀寫都會一直等待指定大小的數據,這時候我們可能想要設置一個讀寫的超時時間。調用SetCommTimeouts()可以設置串口讀寫超時時間GetCommTimeouts()可以獲得當前的超時設置,一般先利用GetCommTimeouts獲得當前超時信息到一個COMMTIMEOUTS結構,然后對這個結構自定義,再調用SetCommTimeouts()進行設置。

BOOL GetCommTimeouts(
  _In_ HANDLE hFile,
  _Out_ LPCOMMTIMEOUTS lpCommTimeouts
);
BOOL SetCommTimeouts(
  _In_ HANDLE hFile,
  _In_ LPCOMMTIMEOUTS lpCommTimeouts
);

顯示代碼

COMMTIMEOUTS結構如下:

typedef struct _COMMTIMEOUTS {
    DWORD ReadIntervalTimeout;          /* Maximum time between read chars. */
    DWORD ReadTotalTimeoutMultiplier;   /* Multiplier of characters.        */
    DWORD ReadTotalTimeoutConstant;     /* Constant in milliseconds.        */
    DWORD WriteTotalTimeoutMultiplier;  /* Multiplier of characters.        */
    DWORD WriteTotalTimeoutConstant;    /* Constant in milliseconds.        */
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

ReadIntervalTimeout為讀操作時兩個字符間的間隔超時,如果兩個字符之間的間隔超過本限制則讀操作立即返回。

ReadTotalTimeoutMultiplier為讀操作在讀取每個字符時的超時。

ReadTotalTimeoutConstant為讀操作的固定超時。

WriteTotalTimeoutMultiplier為寫操作在寫每個字符時的超時。

WriteTotalTimeoutConstant為寫操作的固定超時。

以上各個成員設為0表示未設置對應超時。

超時設置有兩種:間隔超時和總超時,間隔超時就是ReadIntervalTimeout,總超時= ReadTotalTimeoutConstant + ReadTotalTimeoutMultiplier*要讀寫的字符數。

可以看出:間隔超時和總超時的設置是不相關的,寫操作只支持總超時,而讀操作兩種超時均支持。

比如:ReadTotalTimeoutMultiplier設為1000,其余成員為0,如果ReadFile()想要讀取5個字符,則總的超時時間為1*5=5秒;

         ReadTotalTimeoutConstant設為5000,其余為0,則總的超時時間為5秒;

         ReadTotalTimeoutMultiplier設為1000並且ReadTotalTimeoutConstant設為5000,其余為0,如果ReadFile()想要讀取5個字符,則總的超時間為1*5+5 =10秒。 

         如果將ReadIntervalTimeout設為MAXDWORD,ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則讀操作會一次讀入緩沖區的內容后立即返回,不管是否讀入了指定字符。

需要注意的是,用重疊方式讀寫串口時,SetCommTimeouts()仍然是起作用的,在這種情況下,超時規定的是I/O操作的完成時間,而不是ReadFile和WriteFile的返回時間。

 

②設置發送和接收緩沖區大小

SetupComm()函數用來設置串口的發送/接受緩沖區的大小如果通信的速率較高,則應該設置較大的緩沖區

 BOOL WINAPI SetupComm(
    __in HANDLE hFile,//串口句柄
    __in DWORD dwInQueue,//輸入緩沖區大小
    __in DWORD dwOutQueue//輸出緩沖區大小
    );

③設置串口的配置信息

函數GetCommState()和SetCommState()分別用來獲得和設置串口的配置信息,如波特率、校驗方式、數據位個數、停止位個數等。一般也是先調用GetCommState()獲得串口配置信息到一個DCB結構中去,在對這個結構自定義后調用SetCommState()進行設置。

BOOL WINAPI GetCommState(
    __in  HANDLE hFile,//串口句柄
    __out LPDCB lpDCB//保存的串口配置信息
    );

BOOL WINAPI SetCommState(
    __in HANDLE hFile,//串口句柄
    __in LPDCB lpDCB//設置的串口配置信息
    );
  typedef struct _DCB { // dcb 
        DWORD DCBlength;           // sizeof(DCB) 
        DWORD BaudRate;            // current baud rate 指定當前的波特率
        DWORD fBinary: 1;          // binary mode, no EOF check 指定是否允許二進制模式,WINDOWS 95中必須為TRUE
        DWORD fParity: 1;          // enable parity checking 指定奇偶校驗是否允許
        DWORD fOutxCtsFlow:1;      // CTS output flow control 指定CTS是否用於檢測發送控制.當為TRUE是CTS為OFF,發送將被掛起
        DWORD fOutxDsrFlow:1;      // DSR output flow control 指定DSR是否用於檢測發送控制.當為TRUE是DSR為OFF,發送將被掛起
        DWORD fDtrControl:2;       // DTR flow control type DTR_CONTROL_DISABLE值將DTR置為OFF, DTR_CONTROL_ENABLE值將DTR置為ON, DTR_CONTROL_HANDSHAKE允許DTR"握手",
        DWORD fDsrSensitivity:1;   // DSR sensitivity 當該值為TRUE時DSR為OFF時接收的字節被忽略
        DWORD fTXContinueOnXoff:1; // XOFF continues Tx 指定當接收緩沖區已滿,並且驅動程序已經發送出XoffChar字符時發送是否停止.TRUE時,在接收緩沖區接收到緩沖區已滿的字節XoffLim且驅動程序已經發送出XoffChar字符中止接收字節之后,發送繼續進行。FALSE時,在接收緩沖區接收到代表緩沖區已空的字節XonChar且驅動程序已經發送出恢復發送的XonChar之后,發送繼續進行。
        DWORD fOutX: 1;            // XON/XOFF out flow control TRUE時,接收到XoffChar之后便停止發送.接收到XonChar之后將重新開始
        DWORD fInX: 1;             // XON/XOFF in flow control TRUE時,接收緩沖區接收到代表緩沖區滿的XoffLim之后,XoffChar發送出去.接收緩沖區接收到代表緩沖區空的XonLim之后,XonChar發送出去
        DWORD fErrorChar: 1;       // enable error replacement 該值為TRUE且fParity為TRUE時,用ErrorChar 成員指定的字符代替奇偶校驗錯誤的接收字符
        DWORD fNull: 1;            // enable null stripping TRUE時,接收時去掉空(0值)字節
        DWORD fRtsControl:2;       // RTS flow control RTS_CONTROL_DISABLE時,RTS置為OFF RTS_CONTROL_ENABLE時, RTS置為ON RTS_CONTROL_HANDSHAKE時,當接收緩沖區小於半滿時RTS為ON 當接收緩沖區超過四分之三滿時RTS為OFF RTS_CONTROL_TOGGLE時,當接收緩沖區仍有剩余字節時RTS為ON ,否則缺省為OFF
        DWORD fAbortOnError:1;     // abort reads/writes on error TRUE時,有錯誤發生時中止讀和寫操作
        DWORD fDummy2:17;          // reserved 未使用
        WORD wReserved;            // not currently used 未使用,必須為0
        WORD XonLim;               // transmit XON threshold 指定在XON字符發送這前接收緩沖區中可允許的最小字節數
        WORD XoffLim;              // transmit XOFF threshold 指定在XOFF字符發送這前接收緩沖區中可允許的最小字節數
        BYTE ByteSize;             // number of bits/byte, 4-8 指定端口當前使用的數據位
        BYTE Parity;               // 0-4=no,odd,even,mark,space 指定端口當前使用的奇偶校驗方法,可能為:EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY
        BYTE StopBits;             // 0,1,2 = 1, 1.5, 2 指定端口當前使用的停止位數,可能為:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS
        char XonChar;              // Tx and Rx XON character 指定用於發送和接收字符XON的值
        char XoffChar;             // Tx and Rx XOFF character 指定用於發送和接收字符XOFF值
        char ErrorChar;            // error replacement character 本字符用來代替接收到的奇偶校驗發生錯誤時的值
        char EofChar;              // end of input character 當沒有使用二進制模式時,本字符可用來指示數據的結束
        char EvtChar;              // received event character 當接收到此字符時,會產生一個事件
        WORD wReserved1;           // reserved; do not use 未使用
    } DCB; 

 

DCB結構中幾個比較重要的成員有BaudRate(波特率)、fParity(指定奇偶校驗使能)、Parity(校驗方式)、ByteSize(數據位個數)、StopBits(停止位個數)。

BaudRate波特率常用的有CBR_9600、CBR_14400、CBR_19200、CBR_38400、CBR_56000、CBR_57600、CBR_115200、 CBR_128000、 CBR_256000。

fParity指定奇偶校驗使能,若此成員為1,允許奇偶校驗。

Parity校驗方式可以為0~4,對應宏為NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY,分別表示無校驗、奇校驗、偶校驗、校驗置位(標記校驗)、校驗清零。

ByteSize數據位個數可以為5~8位。

StopBits停止位可以為0~2,對應宏為ONESTOPBIT、ONE5STOPBITS、TWOSTOPBITS,分別表示1位停止位、1.5位停止位、2位停止位。

 

3、讀寫串口

①清空緩沖

PurgeComm()函數用來停止讀寫操作、清空讀寫緩沖區,第一次讀取串口數據、寫串口數據之前、串口長時間未使用、串口出現錯誤等情況下,應先清空讀或寫緩沖區。

//BOOL PurgeComm(HANDLE hFile,  DWORD dwFlags ); 

第二個參數dwFlags指定串口執行的動作,可以是以下值的組合:

  -PURGE_TXABORT:停止目前所有的傳輸工作立即返回不管是否完成傳輸動作。 
  -PURGE_RXABORT:停止目前所有的讀取工作立即返回不管是否完成讀取動作。 
  -PURGE_TXCLEAR:清除發送緩沖區的所有數據。 
  -PURGE_RXCLEAR:清除接收緩沖區的所有數據。

如清除串口的所有操作和緩沖:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT); 

②清除錯誤

ClearCommError()用來清除通信中的錯誤及獲得當前通信狀態。在讀寫操作之前,可以調用ClearCommError來清除錯誤和獲得緩沖區內數據大小

BOOL WINAPI ClearCommError(
  _In_       HANDLE hFile,//串口句柄
  _Out_opt_  LPDWORD lpErrors,//返回的錯誤碼
  _Out_opt_  LPCOMSTAT lpStat//返回的通訊狀態
);

lpErrors用來保存錯誤碼,具體對應的什么錯誤為:

   1-CE_BREAK:檢測到中斷信號。意思是說檢測到某個字節數據缺少合法的停止位。 
   2-CE_FRAME:硬件檢測到幀錯誤。 
   3-CE_IOE:通信設備發生輸入/輸出錯誤。 
   4-CE_MODE:設置模式錯誤,或是hFile值錯誤。 
   5-CE_OVERRUN:溢出錯誤,緩沖區容量不足,數據將丟失。 
   6-CE_RXOVER:溢出錯誤。 
   7-CE_RXPARITY:硬件檢查到校驗位錯誤。 
   8-CE_TXFULL:發送緩沖區已滿。 

lpStat為指向_COMSTAT結構的指針,保存通訊狀態。一般我們只關心這個結構中的兩個成員:cbInQue、cbOutQue,分別表示輸入緩沖區中的字節數、輸出緩沖區中的字節數。

③讀寫串口數據

調用WriteFile()向串口中寫數據,

ReadFile()從串口讀數據,函數執行成功返回TRUE,失敗返回FALSE。

需要注意的有兩點:

     如果想要異步讀寫操作,則lpOverlappen參數不能為NULL,而且在CreateFile()打開文件時應指定FILE_FLAG_OVERLAPPEN標記。在異步讀寫操作的時候,ReadFile()和WriteFile()返回FALSE時應調用GetLastError函數分析返回的結果,如果是ERROR_IO_PENDING,這說明異步I/O操作正在進行。

     在用ReadFile()讀文件時,如果想要讀取的數據大小比文件內容大,則只會讀取文件大小的數據。而讀串口時,如果想要讀取的數據比緩沖區中數據大,則ReadFile()會阻塞,直到數據到達或者超時。

函數WriteFileEx()與ReadFileEx()只能用於異步讀寫操作,而且可以設置一個讀寫完成后自動調用的回調函數,函數執行成功返回TRUE,表示異步I/O操作開始,出錯返回FALSE。

BOOL WINAPI ReadFile(
                     _In_         HANDLE hFile,//文件句柄
                     _Out_        LPVOID lpBuffer,//指向一個緩沖區,保存讀取的數據
                     _In_         DWORD nNumberOfBytesToRead,//要讀取數據的字節數,如果實際讀取的字節數小於這個數的話函數會一直等待直到超時
                     _Out_opt_    LPDWORD lpNumberOfBytesRead,//實際讀取的字節數
                     _Inout_opt_  LPOVERLAPPED lpOverlapped//指向一個OVERLAPPED結構,用於異步操作
                     );

BOOL WINAPI WriteFile(
                      _In_         HANDLE hFile,//文件句柄
                      _In_         LPCVOID lpBuffer,//指向一個緩沖區,包含要寫入的數據
                      _In_         DWORD nNumberOfBytesToWrite,//要寫入數據的字節數
                      _Out_opt_    LPDWORD lpNumberOfBytesWritten,//實際寫入的字節數
                      _Inout_opt_  LPOVERLAPPED lpOverlapped//指向一個OVERLAPPEN結構體,用於異步操作
                      );

下面為兩個讀寫文件的示例:

#include "stdafx.h"
#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hFile; 
    hFile = CreateFile(_T("test.txt"),                // name of the write
        GENERIC_WRITE,          // open for writing
        0,                      // do not share
        NULL,                   // default security
        OPEN_ALWAYS,             // create new file if it not exist
        FILE_ATTRIBUTE_NORMAL,  // normal file
        NULL);                  // no attr. template
    if (hFile == INVALID_HANDLE_VALUE) 
    { 
        printf("CreateFile() error:%d", GetLastError());
        return -1;
    }

    BOOL bErrorFlag = FALSE;
    char DataBuffer[] = "This is some test data to write to the file.";
    DWORD dwBytesToWrite = (DWORD)strlen(DataBuffer);
    DWORD dwBytesWritten = 0;
    bErrorFlag = WriteFile( 
        hFile,           // open file handle
        DataBuffer,      // start of data to write
        dwBytesToWrite,  // number of bytes to write
        &dwBytesWritten, // number of bytes that were written
        NULL);            // no overlapped structure
    if (FALSE == bErrorFlag)
    {
        printf("Terminal failure: Unable to write to file.\n");
    }
    else
    {
        if (dwBytesWritten != dwBytesToWrite)
        {
            printf("Error: dwBytesWritten != dwBytesToWrite\n");
        }
        else
        {
            printf("Wrote %d bytes to test.txt successfully.\n", dwBytesWritten);
        }
    }

    CloseHandle(hFile);

    return 0;
}


#include "stdafx.h"
#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])
{

    HANDLE hFile; 
    hFile = CreateFile(_T("test.txt"),                // name of the write
        GENERIC_READ,          // open for writing
        0,                      // do not share
        NULL,                   // default security
        OPEN_ALWAYS,             // create new file if it not exist
        FILE_ATTRIBUTE_NORMAL,  // normal file
        NULL);                  // no attr. template
    if (hFile == INVALID_HANDLE_VALUE) 
    { 
        printf("CreateFile() error:%d", GetLastError());
        return -1;
    }

    DWORD dwFileSize;
    dwFileSize = GetFileSize(hFile, NULL);
    char *pReadBuf = new char[dwFileSize+1];
    memset(pReadBuf, 0, dwFileSize+1);
    DWORD dwSizeReaded = 0;
    BOOL bErrorFlag = FALSE;
    bErrorFlag = ReadFile(hFile, pReadBuf, dwFileSize, &dwSizeReaded, NULL);
    if (FALSE == bErrorFlag)
    {
        printf("Terminal failure: Unable to read file.\n");
    }
    else
    {
        if(dwSizeReaded != dwFileSize)
        {
            printf("Error: dwSizeReaded != dwFileSize\n");
        }
        else
        {
            printf("read %d bytes from test.txt:%s\n", dwSizeReaded, pReadBuf);
        }
    }

    delete[] pReadBuf;
    CloseHandle(hFile);

    return 0;
}

顯示代碼

下面是一個打開串口進行讀取操作的示例: 

//打開串口
HANDLE g_hCom = CreateFile(_T("COM7"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
if (g_hCom == INVALID_HANDLE_VALUE)
{
    int a = GetLastError();
    CString str;
    str.Format(_T("%d"), a);
    AfxMessageBox(str);
    return false;
}

//設置讀超時
COMMTIMEOUTS timeouts;
GetCommTimeouts(g_hCom, &timeouts);
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 1000;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(g_hCom, &timeouts);

//設置串口配置信息
DCB dcb;
if (!GetCommState(g_hCom, &dcb))
{
    AfxMessageBox(_T("GetCommState() failed"));
    CloseHandle(g_hCom);
    return false;
}
int nBaud = 9600;
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = nBaud;//波特率為9600
dcb.Parity = 0;//校驗方式為無校驗
dcb.ByteSize = 8;//數據位為8位
dcb.StopBits = ONESTOPBIT;//停止位為1位
if (!SetCommState(g_hCom, &dcb))
{
    AfxMessageBox(_T("SetCommState() failed"));
    CloseHandle(g_hCom);
    return false;
}

//設置讀寫緩沖區大小
static const int g_nZhenMax = 32768;
if (!SetupComm(g_hCom, g_nZhenMax, g_nZhenMax))
{
    AfxMessageBox(_T("SetupComm() failed"));
    CloseHandle(g_hCom);
    return false;
}

//清空緩沖
PurgeComm(g_hCom, PURGE_RXCLEAR|PURGE_TXCLEAR);

//清除錯誤
DWORD dwError;
COMSTAT cs;
if (!ClearCommError(g_hCom, &dwError, &cs))
{
    AfxMessageBox(_T("ClearCommError() failed"));
    CloseHandle(g_hCom);
    return false;
}

//讀取串口數據
char buf[101];
memset(buf, 0, 101);
DWORD nLenOut = 0;
if(ReadFile(g_hCom, buf, 100, &nLenOut, NULL))
{
    if(nLenOut)//成功
    {
        CString str(buf);
        MessageBox(str);
    }
    else//超時
    {
        MessageBox(_T("time out"));
    }
}
else
{
    //失敗
    MessageBox(_T("read() error!"));
}

//關閉串口
CloseHandle(g_hCom);

4、監聽串口事件和異步讀寫串口

在串口編程中,可以先設置好串口所關注的事件,然后啟動一個輔助線程來監聽該事件是否已經發生,如果沒有發生的話該線程就一直等待,當事件發生后,如讀緩沖區中收到數據,該線程可以向主線程窗體發送對應事件消息提示進行讀串口處理,或者在輔助線程中直接進行異步讀寫串口處理。SetCommMask()函數用來設置串口監聽事件GetCommMask()函數獲得通信設備上的事件掩碼

BOOL SetCommMask(HANDLE hFile,  DWORD dwEvtMask); 

參數hFile為串口句柄,dwEvtMask為要監視的串口事件掩碼,可以有以下位值:

EV_RXCHAR:輸入緩沖區中收到數據

EV_TXEMPTY:輸出緩沖區中的數據已被完全送出

EV_RXFLAG:使用SetCommState()函數設置的DCB結構中的事件字符已被傳入輸入緩沖區中

。。。。。。

串口事件設置好以后可以使用WaitCommEvent()來判斷事件是否已經發生。

BOOL WINAPI WaitCommEvent(
  _In_   HANDLE hFile,
  _Out_  LPDWORD lpEvtMask,
  _In_   LPOVERLAPPED lpOverlapped
);

   -hFile:串口句柄 
   -lpEvtMask:檢測到串口通信事件的話就將其寫入該參數中。 
   -lpOverlapped:指向一個重疊結構,如果串口打開時指定了FILE_FLAG_OVERLAPPED標志 ,則改參數不能為NULL,且重疊結構中 應該包含一個人工重置對象句柄(通過CreateEvent()創建)。

 如果不是異步讀寫的話,WaitCommEvent()會一直等待事件的發生,如果異步讀寫沒有立即完成的話函數會直接返回FALSE,調用GetLastError()會返回ERROR_IO_PENDING。

目前發現了一個BUG:如果CloseHandle()關閉串口的時候,WaitCommEvent()還在等待事件,那么程序就會出現卡死現象,而且在同步讀寫下很容易發生這種情況。 MSDN上說如果是重疊操作的話再次調用SetCommMask()改變事件掩碼將會使WaitCommEvent()立即返回,但我試了下在同步讀寫情況下這種方法不管用,不知道重疊操作的情況是否真的管用!

5、異步讀寫串口

重疊模型是異步I/O方式中一種,所以可以使用重疊操作來實現異步讀寫串口。前面說過,如果重疊操作不能立即完成,則WaitCommEvent()返回FALSE,GetLastError()會返回ERROR_IO_PENDING,表示操作正在后台進行,在WaitCommEvent返回之前,參數重疊結構中的hEvent成員會被設置為無信號狀態,如果當事件發生或錯誤發生時,其被設置為有信號狀態,應用程序可以調用wait functions(WaitForSingleObject、WaitForSingleObjectEx等)來判斷事件對象的狀態,而WaitCommEvent()的參數lpEvtMask會保存具體發生的事件。

有兩種方法可以等待或者判斷重疊操作是否完成,一種是使用WaitForSingleObject()來等待讀寫函數中OVERLAPPED類型的參數的hEvent成員:當調用ReadFile, WriteFile 函數的時候,該成員會自動被置為無信號狀態;當重疊操作完成后,該成員變量會自動被置為有信號狀態。

另一種方法是調用GetOverlappedResult()獲得重疊操作的狀態,來判斷重疊操作是否完成,函數原型:

BOOL WINAPI GetOverlappedResult(
                                _In_   HANDLE hFile,//文件句柄
                                _In_   LPOVERLAPPED lpOverlapped,//指向欲檢查的重疊結構
                                _Out_  LPDWORD lpNumberOfBytesTransferred,//返回重疊操作(讀或寫)的字節數
                                _In_   BOOL bWait
                                );

如果參數bWait為TRUE則函數會一直等待直到重疊結構中的hEvent變成有信號,即一直等到重疊操作完成;FALSE為如果檢測到pending狀態則立即返回,此時函數返回FALSE,GetLastError()返回值為ERROR_IO_INCOMPLETE。

下面為一個異步讀寫串口的示例:

 

/******************主線程*********************/

//以重疊方式打開串口
g_hCom = CreateFile(_T("COM7"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (g_hCom == INVALID_HANDLE_VALUE)
{
    int a = GetLastError();
    CString str;
    str.Format(_T("%d"), a);
    AfxMessageBox(str);
    return false;
}

//設置讀超時
COMMTIMEOUTS timeouts;
GetCommTimeouts(g_hCom, &timeouts);
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 60000;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(g_hCom, &timeouts);

//設置讀寫緩沖區大小
static const int g_nZhenMax = 32768;
if (!SetupComm(g_hCom, g_nZhenMax, g_nZhenMax))
{
    AfxMessageBox(_T("SetupComm() failed"));
    CloseHandle(g_hCom);
    return false;
}

//設置串口配置信息
DCB dcb;
if (!GetCommState(g_hCom, &dcb))
{
    AfxMessageBox(_T("GetCommState() failed"));
    CloseHandle(g_hCom);
    return false;
}
int nBaud = 115200;
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = nBaud;//波特率為115200    
dcb.Parity = 0;//校驗方式為無校驗
dcb.ByteSize = 8;//數據位為8位
dcb.StopBits = ONESTOPBIT;//停止位為1位
if (!SetCommState(g_hCom, &dcb))
{
    AfxMessageBox(_T("SetCommState() failed"));
    CloseHandle(g_hCom);
    return false;
}

//清空緩沖
PurgeComm(g_hCom, PURGE_RXCLEAR|PURGE_TXCLEAR);

//清除錯誤
DWORD dwError;
COMSTAT cs;
if (!ClearCommError(g_hCom, &dwError, &cs))
{
    AfxMessageBox(_T("ClearCommError() failed"));
    CloseHandle(g_hCom);
    return false;
}

//設置串口監聽事件
SetCommMask(g_hCom, EV_RXCHAR);


HANDLE hThread1 = CreateThread(NULL, 0, ThreadSendMsg, NULL, 0, NULL);
CloseHandle(hThread1);

/******************輔助線程********************/
DWORD WINAPI ThreadSendMsg(LPVOID lpParameter) 
{
    while(1)
    {
        OVERLAPPED osWait;   
        memset(&osWait,0,sizeof(OVERLAPPED));   
        osWait.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);   
        DWORD dwEvtMask;

        if (WaitCommEvent(g_hCom, &dwEvtMask, &osWait)) 
        {
            if (dwEvtMask & EV_RXCHAR) 
            {
                DWORD dwError;
                COMSTAT cs;
                if (!ClearCommError(g_hCom, &dwError, &cs))
                {
                    AfxMessageBox(_T("ClearCommError() failed"));
                    CloseHandle(g_hCom);
                    return false;
                }

                char buf[101] = {0};
                DWORD nLenOut = 0;
                DWORD dwTrans;
                OVERLAPPED osRead; 
                memset(&osRead,0,sizeof(OVERLAPPED));   
                osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); 

                BOOL bReadStatus = ReadFile(g_hCom, buf, cs.cbInQue, &nLenOut,&osRead);
                if(!bReadStatus)    
                {
                    if(GetLastError()==ERROR_IO_PENDING)//重疊操作正在進行 
                    {
                        //GetOverlappedResult(g_hCom,&osRead2,&dwTrans,true);判斷重疊操作是否完成

                        //To do
                    } 
                }
                else//操作已完成
                {
                    //To do
                }

            }
        }
        else
        {
            if(GetLastError()==ERROR_IO_PENDING) 
            {
                WaitForSingleObject(osWait.hEvent, INFINITE);
                if (dwEvtMask & EV_RXCHAR) 
                {
                    DWORD dwError;
                    COMSTAT cs;
                    if (!ClearCommError(g_hCom, &dwError, &cs))
                    {
                        AfxMessageBox(_T("ClearCommError() failed"));
                        CloseHandle(g_hCom);
                        return false;
                    }

                    char buf[101] = {0};
                    DWORD nLenOut = 0;
                    DWORD dwTrans;
                    OVERLAPPED osRead; 
                    memset(&osRead,0,sizeof(OVERLAPPED));   
                    osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); 

                    BOOL bReadStatus = ReadFile(g_hCom, buf, cs.cbInQue, &nLenOut,&osRead);
                    if(!bReadStatus)    
                    {
                        if(GetLastError()==ERROR_IO_PENDING)//重疊操作正在進行 
                        {
                            //GetOverlappedResult(g_hCom,&osRead2,&dwTrans,true);判斷重疊操作是否完成

                            //To do
                        } 
                    }
                    else//操作已完成
                    {
                        //To do
                    }

                }
            }    
        }
    }    

    return 1;
}

 在異步編程中我發現在讀事件發生后,利用ClearCommError()獲得的緩沖區內數據大小有時會比對方WriteFile()指定發送的數據大小小,我猜是因為這個時候數據還沒有全部發送到緩沖區內,這點需要注意。 

 

ClearCommError()函數原型

清除串行端口錯誤或讀取串行端口現在的狀態時,可用函數ClearCommError。Windows系統利用此函數清除硬件的通訊錯誤以及獲取通訊設備的當前狀態

 

 

BOOL ClearCommError(
                    HANDLE hFile,   //通信設備的句柄
                    LPDWORD lpErrors,//接收錯誤代碼變量的指針
                    LPCOMSTAT lpStat  //通信狀態緩沖區的指針
);

ClearCommError()函數參數說明:

    • hFile:串行端冂的Handle值,此值即為使用CreateFile函數后所返回的值。
    • lpError:返回錯誤數值,錯誤常數如下:
      CE_BREAK:檢測到中斷信號。
      CE_DNS:Windows95專用,未被選擇的並行端口。
      CE_FRAME:硬件檢到框架錯誤
      CE_IOE:通信設備發生輸入/輸出綹誤,
      CE_MODE:設置模式錯誤,或是hFile值錯誤。
      CE_OOP:Wmdows95專用,並行端口發生缺紙錯誤。CE_OVERRUN:緩沖區容量不足,數據將遺失。
      CE_PTO:Windows95專用,並行端口發生超時錯誤。
      CE_RXOVER:接收區滿溢或在文件結尾被接收到后仍有數據發送過來。
      CE_RXPARITY:硬件檢測到校驗位檢查錯誤。
      CE_TXFULL:發送緩存區已滿后,應用程序仍要發送數據。
    • lpStat:指向通信端口狀態的結構變量。此結構的原始聲明如下:
    • typedef struct _COMSTAT {  //cst
          DWORD fCtsHold : 1;  //Tx正在等待CTS信號  
          DWORD fDsrHold : 1;  //Tx正在等待DSR信號
          DWORD fRlsdHold : 1; //Tx正在等待RLSD信號
          DWORD fXoffHold : 1;  //Tx由於接收XOFF字符而在等待
          DWORD fXoffSent : 1;   //Tx由於發送XOFF字符而在等待
          DWORD fEof : 1;      //發送EOF字符
          DWORD fTxim : 1;     //字符在等待Tx
          DWORD fReserved : 25;   //保留
          DWORD cbInQue;    //輸入緩沖區中的字節數
          DWORD cbOutQue;     //輸出緩沖區中的字節數
      } COMSTAT, *LPCOMSTAT;

      此結構屮有關參數說明如下:
      fCtsHold:是否正在等待CTS信號。占一個位的位置。
      fDsrHold:是否正在等待DSR信號。占一個位的位置。
      fRlsdHoId:是否正在等待RLSD信號。占一個位的位置。
      fXoftHoId:是否因收到xoff字符而在等待。占一個位的位置。
      fXoffHold:是否因送出xoff字符而使得發送的動作在等待。占一個位置
      cbInQue:在輸入緩沖區尚未被ReadFile函數讀取的數據字節數。這個參數經常被用來進行狀態檢查。
      cbOutQue:在發送緩沖區而尚未被發送的據字節數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM