天天看點

Win32 同步方式打開序列槽,位元組流方式讀寫序列槽

1. 前言,最近接到一個很無語的需求,要求在一台電腦中,同時操作5個序列槽,本以為很簡單,那知道,中間遇到一些坑。在平時,通過Win api 操作序列槽都是操作一個序列槽的,而且習慣使用異步方式操作,以為多線程,同時這樣子操作,使用異步方式也會很友善,測試最終發現,還是會出現問題。最終使用同步方式操作,發現問題解決了。

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

1.打開序列槽,配置序列槽

//序列槽初始化函數
int UartInit(HANDLE *pUartHandle, int port, int baud)
{
    char *szPort = NULL;
    char szPortName[255];

    HANDLE idComDev = INVALID_HANDLE_VALUE;
    DWORD err, readdelay;
    DCB dcb;
    COMMTIMEOUTS CommTimeOuts;

    if (port<0) 
        return DEVICE_ERROR_INVALID_DATA;    

    if(port == 100)
        return DEVICE_ERROR_INVALID_DATA;            //USB接口

    switch(port)
    {
    case 0: szPort="COM1"; break;
    case 1: szPort="COM2"; break;
    case 2: szPort="COM3"; break;
    case 3: szPort="COM4"; break;
    case 4: szPort="COM5"; break;
    case 5: szPort="COM6"; break;
    case 6: szPort="COM7"; break;
    case 7: szPort="COM8"; break;
    case 8: szPort="COM9"; break;
    
    default:
        memset(szPortName, 0, sizeof(szPortName));
        sprintf(szPortName, "\\\\.\\COM%d", port + 1);
        szPort = szPortName;
        break;
    }

//打開序列槽檔案,同步方式操作
    idComDev=
        CreateFile( szPort,
        GENERIC_READ|GENERIC_WRITE,  //have right to read and write.
        0,                           //exclusive access;
        NULL,                        //no security attrs;
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,    //FILE_FLAG_OVERLAPPED,       // FILE_ATTRIBUTE_NORMAL ,not over lappped i/o;
        NULL );

    if (idComDev == INVALID_HANDLE_VALUE)
    {
        if ((err = GetLastError()) == 5)          //ERROR_ALREADY_EXISTS

            return DEVICE_ERROR_INVALID_HANDLE;
    }

    
//設定序列槽緩存
    SetupComm(idComDev,2124,2124);
    
//設定同步讀寫從逾時時間,1個位元組越50ms
    CommTimeOuts.ReadIntervalTimeout         = 100;
    CommTimeOuts.ReadTotalTimeoutMultiplier  = 25;
    CommTimeOuts.ReadTotalTimeoutConstant    = 25;
    CommTimeOuts.WriteTotalTimeoutMultiplier = 200;
    CommTimeOuts.WriteTotalTimeoutConstant   = 200;
    
    SetCommTimeouts(idComDev, &CommTimeOuts);
    err = GetCommState(idComDev, &dcb);
    if (!err)
    {
        CloseHandle(idComDev);
        return DEVICE_ERROR_INVALID_DATA;
    }

    switch(baud)
    {
    case 1200:        break;
    case 9600:        break;
    case 14400:        break;
    case 19200:        break;
    case 28800:        break;
    case 38400:        break;
    case 57600:        break;
    case 115200:    break;
    default:
    baud = 9600;    break;
    } 

    dcb.BaudRate = baud;
    dcb.ByteSize = 8;
    dcb.Parity   = 0;
    dcb.StopBits = ONESTOPBIT;
    dcb.fOutX    = 0;
    dcb.fInX     = 0;
    dcb.fBinary  = 1;

//設定序列槽通訊波特率
    err = SetCommState(idComDev, &dcb);
    if (!err)
    {
        CloseHandle(idComDev);
        return DEVICE_ERROR_INVALID_DATA;
    }

    err = SetCommMask(idComDev, EV_TXEMPTY);
    if (!err)
    {
        CloseHandle(idComDev);
        return DEVICE_ERROR_INVALID_DATA;
    }

//清空讀寫緩存
    PurgeComm(idComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ); 
    
    pUartHandle[0] = idComDev;

    return DEVICE_ERROR_OK;    
}
           

上面最主要的時,

①設定逾時

 在調用ReadFile()和WriteFile()讀寫序列槽的時候,如果沒有指定異步操作的話,讀寫都會一直等待指定大小的資料,這時候我們可能想要設定一個讀寫的逾時時間。調用SetCommTimeouts()可以設定序列槽讀寫逾時時間,GetCommTimeouts()可以獲得目前的逾時設定,一般先利用GetCommTimeouts獲得目前逾時資訊到一個COMMTIMEOUTS結構,然後對這個結構自定義,再調用SetCommTimeouts()進行設定。

COMMTIMEOUTS結構如下:

typedef struct _COMMTIMEOUTS {

    DWORD ReadIntervalTimeout;          

    DWORD ReadTotalTimeoutMultiplier;  

    DWORD ReadTotalTimeoutConstant;    

    DWORD WriteTotalTimeoutMultiplier;  

    DWORD WriteTotalTimeoutConstant;    

} 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的傳回時間。

參考《使用Windows API進行序列槽程式設計》

2. 位元組流方式讀序列槽,位元組流操作序列槽,就是每次讀1個位元組

//序列槽接收函數
int UartReceiveData(HANDLE uartHandle,  int port, UCHAR *recData, int *recLength, DWORD maxLen, int timeout)
{
    if(port < 0 || port > 128)
        return DEVICE_ERROR_INVALID_DATA;

    if(uartHandle == INVALID_HANDLE_VALUE)
        return DEVICE_ERROR_NO_CONNECT;

    DWORD dwread = 1;
    unsigned char buffer[2048] = {0};
    
    DWORD dwErrorFlags = 0;
    COMSTAT ComStat; 
    
    memset(&osRead[port], 0, sizeof(OVERLAPPED));
    osRead[port].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    ClearCommError(uartHandle, &dwErrorFlags, &ComStat);
    BOOL bReadStatus;
    DWORD start;

    int count = 0, offset = 0;
    start = GetTickCount();
    
    while(TRUE)
    {
        dwread = 1;

        bReadStatus = ReadFile(uartHandle, buffer + offset, dwread, &dwread, &osRead[port]);
        if(dwread == 1)
        {
            //有資料,儲存
            count = 0;
            offset++;
        }    

        //50 ms 一次
        count++;
        
        if(count == timeout)
            break;
    }

    int end = GetTickCount() - start;

    if(!bReadStatus)
    {
        if(GetLastError() == ERROR_IO_PENDING) 
        {
            DWORD waitReturn = WaitForSingleObject(osRead[port].hEvent, timeout);
            if(waitReturn == WAIT_OBJECT_0)
            {
                if(maxLen < osRead[port].InternalHigh)
                    return DEVICE_ERROR_OUT_OF_SIZE;

                memcpy(recData, buffer, osRead[port].InternalHigh);
                recLength[0] = osRead[port].InternalHigh;

                return DEVICE_ERROR_OK;
            }
            else
            {
                return DEVICE_ERROR_TIMEOUT;
            }
        }
    }

    if(maxLen < dwread)    
        return DEVICE_ERROR_OUT_OF_SIZE;


    memcpy(recData, buffer, offset);        
    recLength[0] = offset;

    return DEVICE_ERROR_OK;
}
           

3. 寫序列槽

//序列槽發送函數
int UartSendData(HANDLE uartHandle, int port, UCHAR *data, DWORD len)
{
    if(port < 0 || port > 128)
        return DEVICE_ERROR_INVALID_DATA;

    if(uartHandle == INVALID_HANDLE_VALUE)
        return DEVICE_ERROR_NO_CONNECT;

    DWORD dwErrorFlag = 0;
    COMSTAT comStat;
    DWORD dwwrite = 0;

    ClearCommError(uartHandle, &dwErrorFlag, &comStat);
    PurgeComm(uartHandle, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

    memset(&osWrite[port], 0, sizeof(OVERLAPPED));
    osWrite[port].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    BOOL bWriteStat = WriteFile(uartHandle, data, len, &dwwrite, &osWrite[port]);
    if(!bWriteStat)
    {
        if(GetLastError() == ERROR_IO_PENDING)
        {
            DWORD waitReturn = WaitForSingleObject(osWrite[port].hEvent, UART_TIME_OUT);
            if(waitReturn == WAIT_OBJECT_0)
                return DEVICE_ERROR_OK;
            else
                return DEVICE_ERROR_TIMEOUT;
        }
    }

    return DEVICE_ERROR_OK;   
}
           

繼續閱讀