串口学习(三)

串口学习之三——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
    评论
个人前几年学习Vxworks时,整理的笔记,总给大家了。 1 基于硬盘启动的Vxworks环境搭建 3 2 Vxworks引导盘制作 6 2.1 通过DOS加载VxWorks方法 6 2.2 Bootrom种类型 7 2.3 VxWorks映象 7 2.4 Bootrom.sys最快制作方法 8 2.5 从网络引导 8 2.6 从本地硬盘引导 9 2.7 制作bootrom文件 9 2.8 Bootrom编译步骤 11 2.9 用BSP生成Bootable工程(即我们的程序文件) 12 2.10 FTP Server下载VxWorks 14 3 Tornado调试环境的建立 17 3.1.1 配置文件config.h 17 3.1.2 网络连接 18 6 从主机搭接(attach)到目标机 21 7.1.3 串口连接 21 4 4.Config.h文件注释说明 22 4.1 启动行说明 #define DEFAULT_BOOT_LINE \: 22 4.2 Config.h 文件说明 23 4.3 启动参数结构体 BOOT_PARAMS 35 4.4 booting过程介绍,比较详细 38 5 sysClkRateGet();返回系统时钟每秒的tick数量, tick详解 43 6 中断应用设计要点 44 7 驱动程序设计 49 8 缩短vxworks的启动时间 51 9 调试篇 54 10 驱动编程步骤 54 10.1 将驱动程序增加到“系统驱动程序列表”中 54 10.2 将设备增加到“系统设备列表”中 55 10.3 打开设备,得到文件描述符 55 10.4 SELECT机制的使用 55 10.4.1 select( )函数翻译 57 11 VxWorks系统的网络驱动(END) 60 12 VXworks操作系统中信号量用于多任务同步与互斥的讨论 62 12.1 二进制信号量实现互斥和同步 64 12.1.1 互斥的实现: 64 12.1.2 同步的实现: 65 12.2 互斥信号量 67 13 Tornado的文件目说明录 68 14 Shell 内置命令说明 72 14.1 任务管理 72 14.2 任务状态信息 72 14.3 系统修改和调试 73 14.4 对象命令( WindSh Commands for Object Display ) 73 14.5 WindShell and Browser, Shell 命令 74 15 驱动篇 81 16 中断篇 83 16.1 中断服务程序ISR编写注意事项 83 16.2 中断号与中断向量的转换 83 16.3 安装中断服务程序 intConnect() 83 16.4 调试中断服务程序方法 84 17 mkboot批处理命令详细解释 84 18 MakeFile 说明 85 19 VxWorks5.4中的输入输出重定向 89 19.1 vxworks屏幕输出, 一般来说用printf都是串口/shell输出,串口输出就可以到屏幕上了? 89 19.2 20.2 VxWorks中针对X86开发时标准输入输出的重定向? 90 20 怎样加入外部.o文件? 92 21 如何在Vxworks中使用 cd ,pwd , ls 命令:启用File System and Disk Utilities组件(INCLUDE_DISK_UTIL),可在shell下用pwd/cd/ls等命令 93 22 Error: image is larger than 524288 bytes 94 23 proxyArpDefaultOn()未定义解决方法? 94 24 如何将VxWorks的系统定时间隔或系统Ticks设置为1ms????? 95 25 read/write、fread/fwrite、fopen/open有什么区别 96 26 快速启动??????????????? 100 27 启动时报ATA0a和硬盘启动相关问题 100 28 如何安装USB2.2新版本及编译USB驱动? 101 29 WindML、图形界面相关问题 102 29.1 WindML,ugldemo出错? 102 29.2 5101 VxWorks黑屏问题? 102 29.3 添加ugldemo.c后,编译报undefined reference to “ugltextdraw” 错误一大堆? 102 29.4 WindML 中文字库显示? 103 30 VxWork6.8相关问题 105 30.1 ELF和bin文件的区别? 105 30.2 diab和GNU的区别? 106 31 No such file or directory错误? 107
《精通Visual C 串口通信技术与工程实践(第版)》是一本深入介绍使用Visual C进行串口通信的实践教材。该书主要涵盖了以下内容:串口的基本原理、串口通信的相关知识、Windows API函数的应用、Visual C和串口通信的基础知识,以及实际的工程实践。 首先,该书对串口的基本原理进行了深入的介绍。串口通信是计算机与外部设备之间进行数据传输的一种方式,该书详细介绍了串口通信的工作原理、串口的硬件结构和时序等方面的知识。 其次,该书介绍了串口通信的相关知识。这包括了串口通信协议、数据帧的格式、数据的传输方式、错误检测和纠错等方面的内容。这些知识对于正确理解和应用串口通信技术非常重要。 接下来,该书详细介绍了在Visual C环境下使用Windows API函数进行串口通信的方法。Windows API函数是操作系统提供的一组函数接口,可以方便地进行串口通信的编程。该书详细介绍了如何使用Windows API函数来实现串口的打开、关闭、读写等操作,并给出了详细的示例代码供读者参考。 此外,该书还介绍了Visual C和串口通信的基础知识。包括如何创建一个Windows应用程序、如何在Visual C中设置串口参数、如何处理串口通信中的异常情况等。 最后,该书通过一系列的工程实践案例来巩固读者对串口通信技术的理解和应用能力。这些实践案例既包括了基础的串口通信实现,也包括了一些典型的串口通信应用,例如串口数据的采集、控制和传输等。通过这些实践案例,读者可以更好地掌握串口通信技术,并能够应用到实际工程中。 总之,《精通Visual C 串口通信技术与工程实践(第版)》是一本全面介绍串口通信技术及其在Visual C环境下的实践应用的优秀教材,适合从事嵌入式系统开发、通信设备开发等领域的技术人员和学习者阅读。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值