串口学习(三)

串口学习之三——Win32编程(C++)

一、Win32 串口理论

  在Win32下,可以使用两种编程方式实现串口通信,其一是使用ActiveX控件MScomm,这种方法程序简单,但欠灵活。其二是调用Windows的API函数,这种方法可以清楚地掌握串口通信的机制,并且自由灵活。本文我们只介绍API串口通信部分。
  串口的操作可以有两种操作方式:同步操作方式和重叠操作方式(又称为异步操作方式)。同步操作时,API函数会阻塞直到操作完成以后才能返回(在多线程方式中,虽然不会阻塞主线程,但是仍然会阻塞监听线程);而重叠操作方式,API函数会立即返回,操作在后台进行,避免线程的阻塞。
  无论那种操作方式,一般都通过四个步骤来完成:

1 .打开串口

Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的原型为:

HANDLE CreateFile( LPCTSTR lpFileName,
              DWORD dwDesiredAccess,
              DWORD dwShareMode,
              LPSECURITY_ATTRIBUTESlpSecurityAttributes,
              DWORD dwCreationDistribution,
              DWORD dwFlagsAndAttributes,
              HANDLE hTemplateFile);

lpFileName:将要打开的串口逻辑名,如“COM1”;
dwDesiredAccess:指定串口访问的类型,可以是读取、写入或二者并列;
dwShareMode:指定共享属性,由于串口不能共享,该参数必须置为0;
lpSecurityAttributes:引用安全性属性结构,缺省值为NULL;
dwCreationDistribution:创建标志,对串口操作该参数必须置为OPEN_EXISTING;
dwFlagsAndAttributes:属性描述,用于指定该串口是否进行异步操作,该值为FILE_FLAG_OVERLAPPED,表示使用异步的I/O;该值为0,表示同步I/O操作;
hTemplateFile:对串口而言该参数必须置为NULL;
同步I/O方式打开串口的示例代码:

        HANDLE hCom;  //全局变量,串口句柄
        hCom=CreateFile("COM1",//COM1口
                        GENERIC_READ|GENERIC_WRITE, //允许读和写
                        0, //独占方式
                        NULL,
                        OPEN_EXISTING, //打开而不是创建
                        0, //同步方式
                        NULL);
            if(hCom==(HANDLE)-1)
            {
                AfxMessageBox("打开COM失败!");
                return FALSE;
            }
            return TRUE;

重叠I/O打开串口的示例代码:

    HANDLE hCom;  //全局变量,串口句柄
    hCom =CreateFile("COM1",  //COM1口
                     GENERIC_READ|GENERIC_WRITE, //允许读和写
                     0,  //独占方式
                     NULL,
                     OPEN_EXISTING,  //打开而不是创建
                     FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式
                     NULL);
        if(hCom ==INVALID_HANDLE_VALUE)
        {
            AfxMessageBox("打开COM失败!");
            return FALSE;
        }
        return TRUE;                
2. 配置串口
(1).串口通信结构体意义解析
typedef struct _DCB
{ 
    DWORD DCBlength;//DCB结构大小,即sizeof(DCB),在调用SetCommState来更新DCB前必须作设置
    DWORD BaudRate; //波特率,指定当前采用的波特率,应与所连接的通讯设备相匹配 
    DWORD fBinary :1; //指定是否允许二进制模式。Win32 API不支持非二进制模式传输,应设置为true
    DWORD fParity :1; //指定奇偶校验是否允许,在为true时具体采用何种校验看Parity 设置
    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; //若为TRUE,通讯驱动程序对DSR信号状态敏感。驱动程序将忽略任何接收的字节数,除非DSR调制解调器的输入线为高。
    DWORD fTXContinueOnXoff :1; //为TRUE,输入缓冲区内字节已经满XoffLim及驱动程序已经发送XoffChar停止接收字节时,仍然继续发送。为FALSE,输入缓冲区内XonLim是空的,及驱动程序已经发送XonChar字符恢复接收的字节传输后,才会继续接收。
    DWORD fOutX :1; //发送方的行为定义,为TRUE时,接收到XoffChar之后便停止发送,接收到XonChar之后将重新开始发送;
    DWORD fInX :1;  //接收方的行为定义,为TRUE时,接收缓冲区接收到代表缓冲区满的XoffLim之后,XoffChar发送出去;接收缓冲区空的Buffer达到XonLim之后,XonChar发送出去。
    DWORD fErrorChar :1; //该值为TRUE,则用ErrorChar指定的字符代替奇偶校验错误的接收字符
    DWORD fNull :1; //为TRUE时,接收时自动去掉空(0值)字节
    DWORD fRtsControl :2; // RTS Control Flow
    //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; //保留,未启用
    WORD wReserved; //未启用,必须设置为0 
    WORD XonLim; //指定在XON字符发送之前接收缓冲区中空缓冲区可允许的最小字节数
    WORD XoffLim; //指定在XOFF字符发送这前接收缓冲区中数据缓冲可允许的最小字节数
    BYTE ByteSize; 
    BYTE Parity; /*奇偶校验方式NOPARITY(无校验(0)),ODDPARITY(奇校验(1)),EVENPARITY(偶校验(2)),MARKPARITY(标记校验,所发信息帧第9位恒为1(3)*/
    BYTE StopBits; //停止位
    char XonChar;  //请求发送方继续发送时的字符 0x11
    char XoffChar; //请求发送方停止发送时的字符 0x13
    char ErrorChar; //指定ErrorChar字符(代替接收到的奇偶校验发生错误时的字节)
    char EofChar; //指定用于标示数据结束的字符
    char EvtChar; //当接收到此字符时,会产生一个EV_RXFLAG事件,如果用SetCommMask函数中指定了EV_RXFLAG ,则可用WaitCommEvent 来监测该事件 
    WORD wReserved1;//保留,未启用 
    } DCB, *LPDCB;

winbase.h文件中定义了以上用到的常量。如下:

// Comm provider settable parameters.
//

#define SP_PARITY         ((DWORD)0x0001)
#define SP_BAUD           ((DWORD)0x0002)
#define SP_DATABITS       ((DWORD)0x0004)
#define SP_STOPBITS       ((DWORD)0x0008)
#define SP_HANDSHAKING    ((DWORD)0x0010)
#define SP_PARITY_CHECK   ((DWORD)0x0020)
#define SP_RLSD           ((DWORD)0x0040)

//
// Settable baud rates in the provider.
//

#define BAUD_075          ((DWORD)0x00000001)
#define BAUD_110          ((DWORD)0x00000002)
#define BAUD_134_5        ((DWORD)0x00000004)
#define BAUD_150          ((DWORD)0x00000008)
#define BAUD_300          ((DWORD)0x00000010)
#define BAUD_600          ((DWORD)0x00000020)
#define BAUD_1200         ((DWORD)0x00000040)
#define BAUD_1800         ((DWORD)0x00000080)
#define BAUD_2400         ((DWORD)0x00000100)
#define BAUD_4800         ((DWORD)0x00000200)
#define BAUD_7200         ((DWORD)0x00000400)
#define BAUD_9600         ((DWORD)0x00000800)
#define BAUD_14400        ((DWORD)0x00001000)
#define BAUD_19200        ((DWORD)0x00002000)
#define BAUD_38400        ((DWORD)0x00004000)
#define BAUD_56K          ((DWORD)0x00008000)
#define BAUD_128K         ((DWORD)0x00010000)
#define BAUD_115200       ((DWORD)0x00020000)
#define BAUD_57600        ((DWORD)0x00040000)
#define BAUD_USER         ((DWORD)0x10000000)

//
// Settable Data Bits
//

#define DATABITS_5        ((WORD)0x0001)
#define DATABITS_6        ((WORD)0x0002)
#define DATABITS_7        ((WORD)0x0004)
#define DATABITS_8        ((WORD)0x0008)
#define DATABITS_16       ((WORD)0x0010)
#define DATABITS_16X      ((WORD)0x0020)

//
// Settable Stop and Parity bits.
//

#define STOPBITS_10       ((WORD)0x0001)
#define STOPBITS_15       ((WORD)0x0002)
#define STOPBITS_20       ((WORD)0x0004)
#define PARITY_NONE       ((WORD)0x0100)
#define PARITY_ODD        ((WORD)0x0200)
#define PARITY_EVEN       ((WORD)0x0400)
#define PARITY_MARK       ((WORD)0x0800)
#define PARITY_SPACE      ((WORD)0x1000)

typedef struct _COMMPROP {
    WORD wPacketLength;
    WORD wPacketVersion;
    DWORD dwServiceMask;
    DWORD dwReserved1;
    DWORD dwMaxTxQueue;
    DWORD dwMaxRxQueue;
    DWORD dwMaxBaud;
    DWORD dwProvSubType;
    DWORD dwProvCapabilities;
    DWORD dwSettableParams;
    DWORD dwSettableBaud;
    WORD wSettableData;
    WORD wSettableStopParity;
    DWORD dwCurrentTxQueue;
    DWORD dwCurrentRxQueue;
    DWORD dwProvSpec1;
    DWORD dwProvSpec2;
    WCHAR wcProvChar[1];
} COMMPROP,*LPCOMMPROP;

//
// Set dwProvSpec1 to COMMPROP_INITIALIZED to indicate that wPacketLength
// is valid before a call to GetCommProperties().
//
#define COMMPROP_INITIALIZED ((DWORD)0xE73CF52E)

typedef struct _COMSTAT {
    DWORD fCtsHold : 1;
    DWORD fDsrHold : 1;
    DWORD fRlsdHold : 1;
    DWORD fXoffHold : 1;
    DWORD fXoffSent : 1;
    DWORD fEof : 1;
    DWORD fTxim : 1;
    DWORD fReserved : 25;
    DWORD cbInQue;
    DWORD cbOutQue;
} COMSTAT, *LPCOMSTAT;

//
// DTR Control Flow Values.
//
#define DTR_CONTROL_DISABLE    0x00
#define DTR_CONTROL_ENABLE     0x01
#define DTR_CONTROL_HANDSHAKE  0x02

//
// RTS Control Flow Values
//
#define RTS_CONTROL_DISABLE    0x00
#define RTS_CONTROL_ENABLE     0x01
#define RTS_CONTROL_HANDSHAKE  0x02
#define RTS_CONTROL_TOGGLE     0x03

typedef struct _DCB {
    DWORD DCBlength;      /* sizeof(DCB)                     */
    DWORD BaudRate;       /* Baudrate at which running       */
    DWORD fBinary: 1;     /* Binary Mode (skip EOF check)    */
    DWORD fParity: 1;     /* Enable parity checking          */
    DWORD fOutxCtsFlow:1; /* CTS handshaking on output       */
    DWORD fOutxDsrFlow:1; /* DSR handshaking on output       */
    DWORD fDtrControl:2;  /* DTR Flow control                */
    DWORD fDsrSensitivity:1; /* DSR Sensitivity              */
    DWORD fTXContinueOnXoff: 1; /* Continue TX when Xoff sent */
    DWORD fOutX: 1;       /* Enable output X-ON/X-OFF        */
    DWORD fInX: 1;        /* Enable input X-ON/X-OFF         */
    DWORD fErrorChar: 1;  /* Enable Err Replacement          */
    DWORD fNull: 1;       /* Enable Null stripping           */
    DWORD fRtsControl:2;  /* Rts Flow control                */
    DWORD fAbortOnError:1; /* Abort all reads and writes on Error */
    DWORD fDummy2:17;     /* Reserved                        */
    WORD wReserved;       /* Not currently used              */
    WORD XonLim;          /* Transmit X-ON threshold         */
    WORD XoffLim;         /* Transmit X-OFF threshold        */
    BYTE ByteSize;        /* Number of bits/byte, 4-8        */
    BYTE Parity;          /* 0-4=None,Odd,Even,Mark,Space    */
    BYTE StopBits;        /* 0,1,2 = 1, 1.5, 2               */
    char XonChar;         /* Tx and Rx X-ON character        */
    char XoffChar;        /* Tx and Rx X-OFF character       */
    char ErrorChar;       /* Error replacement char          */
    char EofChar;         /* End of Input character          */
    char EvtChar;         /* Received Event character        */
    WORD wReserved1;      /* Fill for now.                   */
} DCB, *LPDCB;

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;

typedef struct _COMMCONFIG {
    DWORD dwSize;               /* Size of the entire struct */
    WORD wVersion;              /* version of the structure */
    WORD wReserved;             /* alignment */
    DCB dcb;                    /* device control block */
    DWORD dwProviderSubType;    /* ordinal value for identifying
                                   provider-defined data structure format*/
    DWORD dwProviderOffset;     /* Specifies the offset of provider specific
                                   data field in bytes from the start */
    DWORD dwProviderSize;       /* size of the provider-specific data field */
    WCHAR wcProviderData[1];    /* provider-specific data */
} COMMCONFIG,*LPCOMMCONFIG;

GetCommState函数可以获得COM口的设备控制块,从而获得相关参数:

BOOL GetCommState(
        HANDLE hFile, //标识通讯端口的句柄
        LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
  );


SetCommState函数设置COM口的设备控制块:
BOOL SetCommState(
   HANDLE hFile, 
   LPDCB lpDCB 
  );

  除了在BCD中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。

BOOL SetupComm(
HANDLE hFile,   // 通信设备的句柄 
DWORD dwInQueue,    // 输入缓冲区的大小(字节数) 
DWORD dwOutQueue    // 输出缓冲区的大小(字节数)
);
(2).设置流控制属性:

  我们在串行通讯处理中,常常看到RTS/CTS和XON/XOFF这两个选项,这就是两个流控制的选项,目前流控制主要应用于调制解调器的数据通讯中,但对普通RS232编程,了解一点这方面的知识是有好处的。那么,流控制在串行通讯中有何作用,在编制串行通讯程序怎样应用呢?这里我们就来谈谈这个问题。

1.流控制在串行通讯中的作用
  这里讲到的“流”,当然指的是数据流。数据在两个串口之间传输时,常常会出现丢失数据的现象,或者两台计算机的处理速度不同,如台式机与单片机之间的通讯,接收端数据缓冲区已满,则此时继续发送来的数据就会丢失。现在我们在网络上通过MODEM进行数据传输,这个问题就尤为突出。流控制能解决这个问题,当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据。因此流控制可以控制数据传输的进程,防止数据的丢失。 PC机中常用的两种流控制是硬件流控制(包括RTS/CTS、DTR/CTS等)和软件流控制XON/XOFF(继续/停止),下面分别说明。

2.硬件流控制
  硬件流控制常用的有RTS/CTS流控制和DTR/DSR(数据终端就绪/数据设置就绪)流控制。
  硬件流控制必须将相应的电缆线连上,用RTS/CTS(请求发送/清除发送)流控制时,应将通讯两端的RTS、CTS线对应相连,数据终端设备(如计算机)使用RTS来起始调制解调器或其它数据通讯设备的数据流,而数据通讯设备(如调制解调器)则用CTS来起动和暂停来自计算机的数据流。这种硬件握手方式的过程为:我们在编程时根据接收端缓冲区大小设置一个高位标志(可为缓冲区大小的75%)和一个低位标志(可为缓冲区大小的25%),当缓冲区内数据量达到高位时,我们在接收端将CTS线置低电平(送逻辑0),当发送端的程序检测到CTS为低后,就停止发送数据,直到接收端缓冲区的数据量低于低位而将CTS置高电平。RTS则用来标明接收设备有没有准备好接收数据。

  PC端处理:
  发. 当 发现(不一定及时发现) CTS (-3v to -15v)无效时,停止发送,
      当 发现(不一定及时发现) CTS (3v to 15v)有效时,恢复发送;
  收. 0<M<N<LEN_OF_RX_BUFFERS
      当接收buffers中的bytes<M 时,给 RTS 有效信号(+3v to +15v),
      当接收buffers中的bytes>N 时,给 RTS 无效信号(-3v to -15v);

  常用的流控制还有还有DTR/DSR(数据终端就绪/数据设置就绪)。我们在此不再详述。由于流控制的多样性,我个人认为,当软件里用了流控制时,应做详细的说明,如何接线,如何应用。

3.软件流控制
  由于电缆线的限制,我们在普通的控制通讯中一般不用硬件流控制,而用软件流控制。一般通过XON/XOFF来实现软件流控制。常用方法是:当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发出XOFF字符(十进制的19或Control-S,设备编程说明书应该有详细阐述),发送端收到XOFF字符后就立即停止发送数据;当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发出XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。一般可以从设备配套源程序中找到发送的是什么字符。
  应该注意,若传输的是二进制数据,标志字符也有可能在数据流中出现而引起误操作,这是软件流控制的缺陷,而硬件流控制不会有这个问题。

  顺便说明一下,有不少朋友问到,为什么不在我编写的软件串口调试助手中将流控制加进去,我最初将这个调试工具定位在各种自动控制的串口程序调试上,经过计算和实验验证,在设置的特定采样周期内可以完成通讯任务,就干脆不用流控制。而且在工控中您即使不懂流控制,也能编写出简单的串口通讯程序来,就如我写的串口调试助手。

 dcb.fDsrSensitivity = FALSE;          
 dcb.fTXContinueOnXoff = FALSE;
 dcb.fRtsControl = RTS_CONTROL_DISABLE;
 dcb.fDtrControl = DTR_CONTROL_ENABLE;
 switch (g_lpInst->flowControl)
 {
     case NoFlowControl:
     {
         dcb.fOutxCtsFlow = FALSE;
         dcb.fOutxDsrFlow = FALSE;
         dcb.fOutX = FALSE;
         dcb.fInX = FALSE;
         break;
     }
     case CtsRtsFlowControl:
     {
         dcb.fOutxCtsFlow = TRUE;
         dcb.fOutxDsrFlow = FALSE;
         dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
         dcb.fOutX = FALSE;
         dcb.fInX = FALSE;
         break;
     }
     case CtsDtrFlowControl:
     {
         dcb.fOutxCtsFlow = TRUE;
         dcb.fOutxDsrFlow = FALSE;
         dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
         dcb.fOutX = FALSE;
         dcb.fInX = FALSE;
         break;
     }
     case DsrRtsFlowControl:
     {
         dcb.fOutxCtsFlow = FALSE;
         dcb.fOutxDsrFlow = TRUE;
         dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
         dcb.fOutX = FALSE;
         dcb.fInX = FALSE;
         break;
     }
     case DsrDtrFlowControl:
     {
         dcb.fOutxCtsFlow = FALSE;
         dcb.fOutxDsrFlow = TRUE;
         dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
         dcb.fOutX = FALSE;
         dcb.fInX = FALSE;
         break;
     }
     case XonXoffFlowControl:
     {
         dcb.fOutxCtsFlow = FALSE;
         dcb.fOutxDsrFlow = FALSE;
         dcb.fOutX = TRUE;
         dcb.fInX = TRUE;
         dcb.XonChar = 0x11;
         dcb.XoffChar = 0x13;
         dcb.XoffLim = 100;
         dcb.XonLim = 100;
         break;
     }
(3).COMMTIMEOUTS详解

  在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。

  COMMTIMEOUTS 结构体被用在SetCommTimeouts和GetCommTimeouts 函数中,以便设置和查询通讯设备的超时参数。这个参数决定ReadFile,WriteFile,ReadFileEx, 和WriteFileEx 操作设备的行为。

/*成员用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为:*/  
typedef struct _COMMTIMEOUTS {
  DWORD ReadIntervalTimeout; //读间隔超时。 接收时,两字符间最大的时延。
  DWORD ReadTotalTimeoutMultiplier; //读时间系数。读取每字节的超时。
  DWORD ReadTotalTimeoutConstant; //读时间常量。读串口数据的固定超时。
   // 总超时  =  ReadTotalTimeoutMultiplier  *  字节数  +  ReadTotalTimeoutConstant  
  DWORD WriteTotalTimeoutMultiplier; //写时间系数。写每字节的超时。
  DWORD WriteTotalTimeoutConstant; //写时间常量。写串口数据的固定超时。
   // 总超时  =    WriteTotalTimeoutMultiplier *  字节数   +   WriteTotalTimeoutConstant 
  }COMMTIMEOUTS,*LPCOMMTIMEOUTS;

COMMTIMEOUTS  comTimeOut;   //  COMMTIMEOUTS对象 
SetCommTimeouts(handlePort_,&comTimeOut);  //  将超时参数写入设备控制

  ReadIntervalTimeout:指定通讯线上两个字符到达的最大时延,以毫秒为单位。在ReadFile操作期间,时间周期从第一个字符接收到算起。如果收到的两个字符之间的间隔超过该值,ReadFile操作完毕并返回所有缓冲数据。如果ReadIntervalTimeout为0,则该值不起作用。如果值为MAXDWORD, 并且ReadTotalTimeoutConstant和ReadTotalTimeoutMultiplier两个值都为0, 则指定读操作携带已经收到的字符立即返回,即使没有收到任何字符。

  ReadTotalTimeoutMultiplier:指定以毫秒为单位的累积值。用于计算读操作时的超时总数。对于每次读操作,该值与所要读的字节数相乘。

  ReadTotalTimeoutConstant:指定以毫秒为单位的常数。用于计算读操作时的超时总数。对于每次读操作,ReadTotalTimeoutMultiplier与所要读的字节数相乘后与该值相加。如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则在读操作时忽略总超时数。

  WriteTotalTimeoutMultiplier:指定以毫秒为单位的累积值。用于计算写操作时的超时总数。对于每次写操作,该值与所要写的字节数相乘。

  WriteTotalTimeoutConstant:指定以毫秒为单位的常数。用于计算写操作时的超时总数。对于每次写操作, WriteTotalTimeoutMultiplier与所要写的字节数相乘后与该值相加。如果 WriteTotalTimeoutMultiplier 和 WriteTotalTimeoutConstant都为0,则在写操作时忽略总超时数。

提示:用户设置通讯超时后,如没有出错,串口已经被打开。

  COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是:总超时=时间系数×要求读/写的字符数 + 时间常量  
  例如,如果要读入10个字符,那么读操作的总超时的计算公式为:读总超时ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

  ReadIntervalTimeout在通讯过程中接收两个字符之间的最长超时时间,按毫秒计算。在ReadFile操作,当接收到第一个字符时,开始一个计时周期。如果接收任意两个字符之间的时隔超过本限制,ReadFile操作将完成并返回任何已缓冲的数据。0代表本参数未设置。如果设置MAXDWORD, 并且ReadTotalTimeoutConstant和ReadTotalTimeoutMultiplier成员为0,代表读取操作立即返回那些已接收的数据,即使没有收到任何字符。(两个字符之间的接收间隔)
ReadTotalTimeoutMultiplier乘数用于计算读取操作的总超时时间,按毫秒计算。对于每个读取操作,这个值将乘以要读取的字节数。(读取单个字符的最大超时)ReadTotalTimeoutConstant一个用于计算对于读取操作的总超时周期的常数,按毫秒计算。对每次读取操作,实际总超时时间为ReadTotalTimeoutMultiplier 成员与请求的字节数年的乘积加此值。

ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员为0代表总读取总超时时间无效(读取所有字节的时间
ReadTotalTimeoutMultiplier*BytesToRead+ReadTotalTimeoutConstant)。
WriteTotalTimeoutMultiplier乘数用来计算写操作的总超时周期,按毫秒计算。对每个写操作,这个值将乘以要写入的字节数。(写单个字符的最大超时)

  WriteTotalTimeoutConstant一个用于计算写入操作的总超时周期的常数,按毫秒计算。对于每一次写入操作,实际总超时时间为WriteTotalTimeoutMultiplier 成员与要写入字节的乘积再加此值.
WriteTotalTimeoutMultiplier和WriteTotalTimeoutConstant成员为0代表总写入时间无效(写入所有字节的时间为WriteTotalTimeoutMultiplier*BytesToWrite+WriteTotalTimeoutConstant)。
备注
  如果一个应用程序设置ReadIntervalTimeout和ReadTotalTimeoutMultiplier为 MAXDWORD并且设置ReadTotalTimeoutConstant 为一个大于零且小于MAXDWORD的值, 在调用ReadFile时将会发生如下现象:

  如果在输入缓冲区中有任何字符,ReadFile 立即返回缓冲区中的内容。
  如果在缓冲区中没有任何字符,ReadFile 将等待接收到一个字符并立即返回.
  如果在ReadTotalTimeoutConstant指定的时间值内无任何字节返回,ReadFile超时.

  如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。
  在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。
配置串口的示例代码:

SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024

COMMTIMEOUTS TimeOuts;
//设定读超时
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000;
//设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
SetCommTimeouts(hCom,&TimeOuts); //设置超时

DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=8; //每个字节有8位
dcb.Parity=NOPARITY; //无奇偶校验位
dcb.StopBits=TWOSTOPBITS; //两个停止位
SetCommState(hCom,&dcb);

PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);

  在读写串口之前,还要用PurgeComm()函数清空缓冲区,该函数原型:

BOOL PurgeComm(
HANDLE hFile,   //串口句柄
DWORD dwFlags   // 需要完成的操作

);
参数dwFlags指定要完成的操作,可以是下列值的组合:
PURGE_TXABORT 中断所有写操作并立即返回,即使写操作还没有完成。
PURGE_RXABORT 中断所有读操作并立即返回,即使读操作还没有完成。
PURGE_TXCLEAR 清除输出缓冲区
PURGE_RXCLEAR 清除输入缓冲区

3、 读写串口

我们使用ReadFile和WriteFile读写串口,下面是两个函数的声明:

BOOL ReadFile(
HANDLE hFile,   //串口的句柄
// 读入的数据存储的地址,
// 即读入的数据将存储在以该指针的值为首地址的一片内存区
LPVOID lpBuffer,    
DWORD nNumberOfBytesToRead, // 要读入的数据的字节数

// 指向一个DWORD数值,该数值返回读操作实际读入的字节数
LPDWORD lpNumberOfBytesRead,    

// 重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL。
LPOVERLAPPED lpOverlapped); 

    BOOL WriteFile(HANDLE hFile,    //串口的句柄
// 写入的数据存储的地址,
// 即以该指针的值为首地址的nNumberOfBytesToWrite
// 个字节的数据将要写入串口的发送数据缓冲区。
LPCVOID lpBuffer,   

DWORD nNumberOfBytesToWrite,    //要写入的数据的字节数

// 指向指向一个DWORD数值,该数值返回实际写入的字节数
LPDWORD lpNumberOfBytesWritten, 

// 重叠操作时,该参数指向一个OVERLAPPED结构,
// 同步操作时,该参数为NULL。
LPOVERLAPPED lpOverlapped    );

  在用ReadFile和WriteFile读写串口时,既可以同步执行,也可以重叠执行。在同步执行时,函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,这两个函数也会立即返回,费时的I/O操作在后台进行。
  ReadFile和WriteFile函数是同步还是异步由CreateFile函数决定,如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的操作就应该是重叠的;如果未指定重叠标志,则读写操作应该是同步的。ReadFile和WriteFile函数的同步或者异步应该和CreateFile函数相一致。
  ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。
  如果操作成功,这两个函数都返回TRUE。需要注意的是,当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。这说明重叠操作还未完成。

  同步方式读写串口比较简单,下面先例举同步方式读写串口的代码:

(1)同步读串口
char str[100];
DWORD wCount;//读取的字节数
BOOL bReadStat;
bReadStat=ReadFile(hCom,str,100,&wCount,NULL);
if(!bReadStat)
{
    AfxMessageBox("读串口失败!");
    return FALSE;
}
return TRUE;
(2)同步写串口
char lpOutBuffer[100];
DWORD dwBytesWrite=100;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL);
if(!bWriteStat)
{
    AfxMessageBox("写串口失败!");
}
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

在重叠操作时,操作还未完成函数就返回。

  重叠I/O非常灵活,它也可以实现阻塞(例如我们可以设置一定要读取到一个数据才能进行到下一步操作)。有两种方法可以等待操作完成:一种方法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员;另一种方法是调用GetOverlappedResult函数等待,后面将演示说明。
  下面我们先简单说一下OVERLAPPED结构和GetOverlappedResult函数:
OVERLAPPED结构,OVERLAPPED结构包含了重叠I/O的一些信息,定义如下:

typedef struct _OVERLAPPED { // o  
    DWORD  Internal; 
    DWORD  InternalHigh; 
    DWORD  Offset; 
    DWORD  OffsetHigh; 
    HANDLE hEvent; 

  在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状态,该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该事件得知是否读写完毕。
  当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态。
GetOverlappedResult函数

BOOL GetOverlappedResult(
HANDLE hFile,   // 串口的句柄     
// 指向重叠操作开始时指定的OVERLAPPED结构
LPOVERLAPPED lpOverlapped,  

// 指向一个32位变量,该变量的值返回实际读写操作传输的字节数。
LPDWORD lpNumberOfBytesTransferred, 

// 该参数用于指定函数是否一直等到重叠操作结束。
// 如果该参数为TRUE,函数直到操作结束才返回。
// 如果该参数为FALSE,函数直接返回,这时如果操作没有完成,
// 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。
BOOL bWait  
 ); 

  该函数返回重叠操作的结果,用来判断异步操作是否完成,它是通过判断OVERLAPPED结构中的hEvent是否被置位来实现的。

(3)异步读串口:
char lpInBuffer[1024];
DWORD dwBytesRead=1024;
COMSTAT ComStat;
DWORD dwErrorFlags;
OVERLAPPED m_osRead;
memset(&m_osRead,0,sizeof(OVERLAPPED));
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

ClearCommError(hCom,&dwErrorFlags,&ComStat);
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead)
return FALSE;
BOOL bReadStatus;
bReadStatus=ReadFile(hCom,lpInBuffer,
                     dwBytesRead,&dwBytesRead,&m_osRead);

if(!bReadStatus) //如果ReadFile函数返回FALSE
{
    if(GetLastError()==ERROR_IO_PENDING)
    //GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行读操作    
    {
        WaitForSingleObject(m_osRead.hEvent,2000);
        //使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟
        //当串口读操作进行完毕后,m_osRead的hEvent事件会变为有信号
        PurgeComm(hCom, PURGE_TXABORT|
            PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
        return dwBytesRead;
    }
    return 0;
}
PurgeComm(hCom, PURGE_TXABORT|
          PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead;

  对以上代码再作简要说明: 在使用ReadFile 函数进行读操作前,应先使用ClearCommError函数清除错误。ClearCommError函数的原型如下:
BOOL ClearCommError(

HANDLE hFile,   // 串口句柄
LPDWORD lpErrors,   // 指向接收错误码的变量
LPCOMSTAT lpStat    // 指向通讯状态缓冲区

);
该函数获得通信错误并报告串口的当前状态,同时,该函数清除串口的错误标志以便继续输入、输出操作。
  参数lpStat指向一个COMSTAT结构,该结构返回串口状态信息。 COMSTAT结构 COMSTAT结构包含串口的信息,结构定义如下:

typedef struct _COMSTAT { // cst  
DWORD fCtsHold : 1;   // Tx waiting for CTS signal 
DWORD fDsrHold : 1;   // Tx waiting for DSR signal 
DWORD fRlsdHold : 1;  // Tx waiting for RLSD signal 
DWORD fXoffHold : 1;  // Tx waiting, XOFF char rec''d 
DWORD fXoffSent : 1;  // Tx waiting, XOFF char sent 
DWORD fEof : 1;       // EOF character sent 
DWORD fTxim : 1;      // character waiting for Tx 
DWORD fReserved : 25; // reserved 
DWORD cbInQue;        // bytes in input buffer 
DWORD cbOutQue;       // bytes in output buffer 
} COMSTAT, *LPCOMSTAT; 

本文只用到了cbInQue成员变量,该成员变量的值代表输入缓冲区的字节数。

  最后用PurgeComm函数清空串口的输入输出缓冲区。
  这段代码用WaitForSingleObject函数来等待OVERLAPPED结构的hEvent成员,下面我们再演示一段调用GetOverlappedResult函数等待的异步读串口示例代码:

char lpInBuffer[1024];
DWORD dwBytesRead=1024;
BOOL bReadStatus;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osRead;

ClearCommError(hCom,&dwErrorFlags,&ComStat);
if(!ComStat.cbInQue)
    return 0;
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead,
    &dwBytesRead,&m_osRead);
if(!bReadStatus) //如果ReadFile函数返回FALSE
{
    if(GetLastError()==ERROR_IO_PENDING)
    {
        GetOverlappedResult(hCom,
            &m_osRead,&dwBytesRead,TRUE);
       // GetOverlappedResult函数的最后一个参数设为TRUE,
       //函数会一直等待,直到读操作完成或由于错误而返回。

        return dwBytesRead;
    }
    return 0;
}
return dwBytesRead;
(4)异步写串口的示例代码:
char buffer[1024];
DWORD dwBytesWritten=1024;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osWrite;
BOOL bWriteStat;

bWriteStat=WriteFile(hCom,buffer,dwBytesWritten,
    &dwBytesWritten,&m_OsWrite);
if(!bWriteStat)
{
    if(GetLastError()==ERROR_IO_PENDING)
    {
        WaitForSingleObject(m_osWrite.hEvent,1000);
        return dwBytesWritten;
    }
    return 0;
}
return dwBytesWritten;
4、 关闭串口

  利用API函数关闭串口非常简单,只需使用CreateFile函数返回的句柄作为参数调用CloseHandle即可:

BOOL CloseHandle(HANDLE hObject; //handle to object to close);

二、Win32串口编程的一个实例

三、Win32串口经典C++类

naughter的串口库,很强大
SerialPort库,有网友一直在更新维护
SerialPort库,有网友一直在更新维护
网友的库,未使用过,异步通信

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值