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;          /* 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为读操作时两个字符间的间隔超时,如果两个字符之间的间隔超过本限制则读操作立即返回。

扫描二维码关注公众号,回复: 13466582 查看本文章

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;   
}

猜你喜欢

转载自blog.csdn.net/gd6321374/article/details/107359582