zoukankan      html  css  js  c++  java
  • 使用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.
    ");
        }
        else
        {
            if (dwBytesWritten != dwBytesToWrite)
            {
                printf("Error: dwBytesWritten != dwBytesToWrite
    ");
            }
            else
            {
                printf("Wrote %d bytes to test.txt successfully.
    ", 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.
    ");
        }
        else
        {
            if(dwSizeReaded != dwFileSize)
            {
                printf("Error: dwSizeReaded != dwFileSize
    ");
            }
            else
            {
                printf("read %d bytes from test.txt:%s
    ", 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:在发送缓冲区而尚未被发送的据字节数。

  • 相关阅读:
    托管和使用WCF服务:WAS(Windows激活服务)
    突发的灵感
    C# 常见图像处理效果
    C# WinForm TreeView 递归选择父节点和子节点
    C# WinForm ComboBox 枚举 选定值
    C# Socket 异步 UDP
    C# WinForm 判断窗体控件是否修改过
    C# 线程同步 信号量 Semaphore
    C# WinForm ComboBox Items 选定值
    C# WinForm ComboBox 自定义数据项 (ComboBoxItem )
  • 原文地址:https://www.cnblogs.com/rosesmall/p/14804682.html
Copyright © 2011-2022 走看看