3.2 警告

使用EV_RXCHAR标志可以在每个字节到达端口时通知线程。与ReadFile配合使用,可以让程序在数据到达接收缓冲区后立即被读取;这与提交读取操作请求,然后等待数据到达是不同的。这对于以非重叠方式打开的端口特别有用,因为程序在数据到达时被EV_RXCHAR事件通知,而不需要轮询操作。这样可以得到下列伪代码:

DWORD dwCommEvent;
DWORD dwRead;
char  chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
   // Error setting communications event mask.

for ( ; ; ) {
   if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
      if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
         // A byte has been read; process it.
      else
         // An error occurred in the ReadFile call.
         break;
   }
   else
      // Error in WaitCommEvent.
      break;
}

上面的代码等待EV_RXCHAR事件发生,然后读取接收到的一个字节,随后继续循环,等待下一个EV_RXCHAR事件。当一个或者两个字节快速连续到达时,这段代码工作得很好。收到一个字节导致EV_RXCHAR发生,代码读取该字节。如果在下一次调用WaitCommEvent之前没有其他字节到达,一切都好:下一个字节的到达将使WaitCommEvent收到EV_RXCHAR事件。如果在下一次调用WaitCommEvent之前,另一个字节到达,一切也都很好:第一个字节仍然正常读取;第二个字节的到达导致内部设定EV_RXCHAR标志;当代码再次调用WaitCommEvent时,仍然可以收到EV_RXCHAR事件,然后通过ReadFile读取第二个字节。

当三个或者更多个字节连续快速到达时,上面的代码就有问题了。第一个字节使得EV_RXCHAR事件发生;第二个字节使得内部设定EV_RXCHAR标志,下一次调用WaitCommEvent时,这个标志指示EV_RXCHAR事件发生。现在,第三个字节到达通信端口,系统试图在内部设置EV_RXCHAR标志;但是第二个字节到达时EV_RXCHAR标志已经被设置,所以第三个字节的到达是不被注意的。代码最终正常读取第一个字节;此后调用WaitCommEvnet时,EV_RXCHAR事件(由第二个字节到达引发)使得第二个字节被读取,第三个字节保持在接收缓冲区中。此时,系统和代码就失去同步了。当第四个字节到达导致EV_RXCHAR发生时,代码读取的是第三个字节。这种情况将一直持续下去。

似乎只要增加读取操作请求的字节数就可以解决这个问题:不是请求一个字节,而是请求两个、十个或者其他数目的字节。此方法的问题是,超过请求的字节数两个或者更多个字节快速连续到达时,代码还是会失败。就是说,如果请求两个字节,那么,四个字节快速连续到达会导致问题;如果请求10个字节,那么12个字节快速连续到达会导致问题。

真正的解决方法是从端口读取数据,直到没有数据可读取,下面的代码展示了这一方法。另一种可能的方法是调用ClearCommError确定缓冲区中有多少个字节,然后一次读取所有数据。这种方法要求更复杂的缓冲区管理,但是在大量数据一次到达时可以减少读取操作次数。

DWORD dwCommEvent;
DWORD dwRead;
char  chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
   // Error setting communications event mask

for ( ; ; ) {
   if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
      do {
         if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
            // A byte has been read; process it.
         else
            // An error occurred in the ReadFile call.
            break;
      } while (dwRead);
   }
   else
      // Error in WaitCommEvent
      break;
}

如果没有正确的通信超时值,上面的代码也不能正常工作。后文的“通信超时”节将讨论通信超时,它会影响到ReadFile的行为,使得ReadFile不等待字节到达就返回。上述关于EV_RXCHAR的警告也适用于EV_RXFLAG。如果标志字符连续快速到达,可能每个字符都不会触发EV_RXFLAG。同样,最好的解决方案是读取数据直到没有可读数据了。上述警告同样也适用于与字符接收无关的其他事件。如果其他事件连续快速发生,则某些通知可能丢失。比如说,如果CTS信号线电平开始是高,然后变为低,变为高,再变为低,则会发生EV_CTS事件;如果CTS线电平改变发生得太快,则无法保证WaitCommEvent最终可以检测到多少个EV_CTS事件。因此,不能用WaitCommEvent来保持信号线的状态。线路状态将在本文随后的“Modem状态”节讲述。

4 错误处理和通信状态

调用SetCommMask时可以指定EV_ERR这个事件标。EV_ERR事件表示通信端口存在错误条件,然而端口发生的某些错误不会导致EV_ERR的发生。通信端口相关的错误将导致所有I/O操作被挂起,直到移除了错误条件为止。ClearCommError用于检测错误和清除错误条件。ClearCommError也可以提供通信状态,以指示传输为何终止;它还可以指示收发缓冲区中各有多少个字节。传输终止的原因可能是存在错误,或者因为流控制。本文随后将讨论流控制。下列代码展示了ClearCommError的使用:

    COMSTAT comStat;     DWORD   dwErrors;     BOOL    fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;     BOOL    fBREAK, fDNS, fFRAME, fIOE, fMODE;     // Get and clear current errors on the port.     if (!ClearCommError(hComm, &dwErrors, &comStat))         // Report error in ClearCommError.         return;     // Get error flags.     fDNS = dwErrors & CE_DNS;     fIOE = dwErrors & CE_IOE;     fOOP = dwErrors & CE_OOP;     fPTO = dwErrors & CE_PTO;     fMODE = dwErrors & CE_MODE;     fBREAK = dwErrors & CE_BREAK;     fFRAME = dwErrors & CE_FRAME;     fRXOVER = dwErrors & CE_RXOVER;     fTXFULL = dwErrors & CE_TXFULL;     fOVERRUN = dwErrors & CE_OVERRUN;     fRXPARITY = dwErrors & CE_RXPARITY;     // COMSTAT structure contains information regarding     // communications status.     if (comStat.fCtsHold)         // Tx waiting for CTS signal     if (comStat.fDsrHold)         // Tx waiting for DSR signal     if (comStat.fRlsdHold)         // Tx waiting for RLSD signal     if (comStat.fXoffHold)         // Tx waiting, XOFF char rec'd     if (comStat.fXoffSent)         // Tx waiting, XOFF char sent          if (comStat.fEof)         // EOF character received          if (comStat.fTxim)         // Character waiting for Tx; char queued with TransmitCommChar     if (comStat.cbInQue)         // comStat.cbInQue bytes have been received, but not read     if (comStat.cbOutQue)         // comStat.cbOutQue bytes are awaiting transfer

4.1 Modem状态(线路状态)

SetCommMask的调用可能包含EV_CTSEV_DSREV_RINGEV_RLSD等标志,这些标志指示串口信号线电平的改变,但仅仅指示发生了改变,不能指示信号线的实际状态。GetCommModemStatus函数可以获取这些状态线的实际状态,它返回一个比特掩码用以表示每个信号线的状态。下面的代码展示了GetCommModemStatus的使用:

   DWORD dwModemStatus;
   BOOL  fCTS, fDSR, fRING, fRLSD;

if (!GetCommModemStatus(hComm, &dwModemStatus))
      // Error in GetCommModemStatus;
      return;

fCTS = MS_CTS_ON & dwModemStatus;
   fDSR = MS_DSR_ON & dwModemStatus;
   fRING = MS_RING_ON & dwModemStatus;
   fRLSD = MS_RLSD_ON & dwModemStatus;

// Do something with the flags.

4.2 扩展函数

某些时候可能要用应用程序来代替串口通信驱动程序对控制线进行控制,比如说,当应用要实现自己的流控制时。此时应用必须负责RTS和DTR信号线的状态改变。EscapeCommFunction可以让通信驱动程序进行这些扩展操作。它还可以让驱动程序执行一些其他功能,如设置和清除BREAK条件。关于此函数的更多信息,请参考平台SDK文档,Win32 SDK知识库和MSDN。

5 串口设置

5.1 DCB设置

设备控制块(Device Control Block,DCB)的设置是串口编程中最重要的部分,很多通常的错误都跟没有正确设置DCB结构有关。GetCommState()函数可以获取当前正在使用的DCB结构;BuildCommDCB()函数可以填充DCB结构的波特率、校验类型、停止位数、数据位数字段;SetCommState()用于设置新的DCB结构。DCB设置的一般方法如下所示:

DCB dcb;

FillMemory(&dcb, sizeof(dcb), 0);
   if (!GetCommState(hComm,& dcb))     // get current DCB
      // Error in GetCommState
      return FALSE;

// Update DCB rate.
   dcb.BaudRate = CBR_9600 ;

// Set new state.
   if (!SetCommState(hComm,& dcb))
      // Error in SetCommState. Possibly a problem with the communications
      // port handle or a problem with the DCB structure itself.

6 流控制

流控制可以在通信的某一方忙或者由于其他原因不能进行通信时暂停通信。通常有两种流控制:硬件流控制和软件流控制。串行通信中一个通常的问题是写操作实际上并没有把数据写入到设备中。通常,这是流控制的效果。此时,DCB结构的下列字段可能是TRUE:fOutxCtsFlowfOutxDsrFlow或者fOutX。另一个确定流控制启用的方法是调用ClearCommError()并检查COMSTAT结构体,它可以反映传输因为流控制而暂停。

在详细讨论流控制前,最好了解下相关术语。串行通信发生在两个设备间,通常是PC和调制解调器或者打印机。PC称作数据终端设备(Data Terminal Equipment,DTE),有时也称为主机(host);调制解调器,打印机,或者其他外设称作数据通信设备(Data Communications Equipment,DCE),有时也称为设备(device)。

6.1 硬件流控制

硬件流控制使用串行线路中控制线的电平来控制收发。DTE和DCE必须就通信会话中使用的流控制类型进行协商。设置DCB结构体以启用流控制只是配置了DTE。此外还需要配置DCE以保证DTE和DCE使用相同类型的流控制,然而Win32没有提供设置DCE流控制的机制。通常要使用设备上的DIP开关,或者向设备发生命令来进行流控制配置。下表描述了控制线、流控制方向和线路对DTE与DCE的影响。

Line and Direction Effect on DTE/DCE
CTS
(Clear To Send)
Output flow control
DCE sets the line high to indicate that it can receive data. DCE sets the line low to indicate that it cannot receive data.

If the fOutxCtsFlow member of the DCB is TRUE, then the DTE will not send data if this line is low. It will resume sending if the line is high.

If the fOutxCtsFlow member of the DCB is FALSE, then the state of the line does not affect transmission.

DSR
(Data Set Ready)
Output flow control
DCE sets the line high to indicate that it can receive data. DCE sets the line low to indicate that it cannot receive data.

If the fOutxDsrFlow member of the DCB is TRUE, then the DTE will not send data if this line is low. It will resume sending if the line is high.

If the fOutxDsrFlow member of the DCB is FALSE, then the state of the line does not affect transmission.

DSR
(Data Set Ready)
Input flow control
If the DSR line is low, then data that arrives at the port is ignored. If the DSR line is high, data that arrives at the port is received.

This behavior occurs if the fDsrSensitivity member of the DCB is set to TRUE. If it is FALSE, then the state of the line does not affect reception.

RTS
(Ready To Send)
Input flow control
The RTS line is controlled by the DTE.

If the fRtsControl member of the DCB is set to RTS_CONTROL_HANDSHAKE, the following flow control is used: If the input buffer has enough room to receive data (at least half the buffer is empty), the driver sets the RTS line high. If the input buffer has little room for incoming data (less than a quarter of the buffer is empty), the driver sets the RTS line low.

If the fRtsControl member of the DCB is set to RTS_CONTROL_TOGGLE, the driver sets the RTS line high when data is available for sending. The driver sets the line low when no data is available for sending. Windows 95 ignores this value and treats it the same as RTS_CONTROL_ENABLE.

If the fRtsControl member of the DCB is set to RTS_CONTROL_ENABLE or RTS_CONTROL_DISABLE, the application is free to change the state of the line as it needs. Note that in this case, the state of the line does not affect reception.

The DCE will suspend transmission when the line goes low. The DCE will resume transmission when the line goes high.

DTR
(Data Terminal Ready)
Input flow control
The DTR line is controlled by the DTE.

If the fDtrControl member of the DCB is set to DTR_CONTROL_HANDSHAKE, the following flow control is used: If the input buffer has enough room to receive data (at least half the buffer is empty), the driver sets the DTR line high. If the input buffer has little room for incoming data (less than a quarter of the buffer is empty), the driver sets the DTR line low.

If the fDtrControl member of the DCB is set to DTR_CONTROL_ENABLE or DTR_CONTROL_DISABLE, the application is free to change the state of the line as it needs. In this case, the state of the line does not affect reception.

The DCE will suspend transmission when the line goes low. The DCE will resume transmission when the line goes high.

可以简单地认为CE_RXOVER错误发生时,就需要流控制了。CE_RXOVER错误表示接收缓冲区溢出,数据丢失。如果数据到达端口的速度比它被读取的数据快,则可能发生CE_RXOVER错误。增加输入缓冲区大小可能减小错误发生的频率,但不能完全解决问题。这时需要输入流控制。驱动程序检测到输入缓冲区快满的时候,会拉低输入流控制线电平,使得DCE停止传输,让DTE有足够的时间从输入缓冲区读取数据。当输入缓冲区有足够的空闲区域时,流控制线电平被设置为高,DCE继发送数据。

CE_OVERRUN是一种类似的错误,它表示数据在通信硬件和驱动程序完全接收原数据前,新的数据到达。当传输速度对于通信硬件或者CPU而言太快时可能发生CE_OVERRUN错误;操作系统没有时间为通信硬件服务时也可能发生这个错误。解决此问题的方法是降低传输速度,替换硬件,或者提升CPU速度。有时候第三方硬件驱动程序不能有效使用CPU资源也可能导致此错误。流控制可以减低CE_OVERRUN发生的频度,但不能完全解决问题。

6.2 软件流控制

软件流控制使用通信流中的数据来控制收发操作。因为软件流控制使用XOFF和XON这两个特殊字符,所以不能应用于二进制传输。软件流控制对基于文本的通信,或者不使用XOFF和XON的传输有效。要启用软件流控制,需设置DCB结构的fOutX和fInX字段为TRUE:fOutX控制输出流控制;fInX控制输入流控制。程序可以动态指定流控制字符,DCB结构的XoffChar字段指示输入和输出流控制使用的XOFF字符,XonChar则指定XON字符。对于输入流控制,XoffLim字段指示发送XOFF字符前输入缓冲区允许的最小可用空间大小;如果输入缓冲区可用空间大小小于这个值,则会发送XOFF字符。XonLim字段指示在发送XON字符前输入缓冲区中最小的数据字节数;如果输入缓冲区中的数据量小于此值,则会发送XON字符。下面描述了使用XOFF/XON流控制时DTE的行为

Table 4. Software flow-control behavior

Flow-control character Behavior
XOFF received by DTE DTE transmission is suspended until XON is received. DTE reception continues. ThefOutX member of the DCB controls this behavior.
XON received by DTE If DTE transmission is suspended because of a previous XOFF character being received, DTE transmission is resumed. ThefOutX member of the DCB controls this behavior.
XOFF sent from DTE XOFF is automatically sent by the DTE when the receive buffer approaches full. The actual limit is dictated by theXoffLim member of the DCB. The fInX member of the DCB controls this behavior. DTE transmission is controlled by thefTXContinueOnXoff member of the DCB as described below.
XON sent from the DTE XON is automatically sent by the DTE when the receive buffer approaches empty. The actual limit is dictated by theXonLim member of the DCB. The fInX member of the DCB controls this behavior.

如果输入控制启用了软件流控制,则DCB的fTXContinueOnXoff字段有效,它控制是否在系统自动发送XOFF字符后暂停传输。如果fTXContinueOnXoff为TRUE,则在接收缓冲区满,发送了XOFF字符后继续传输;否则暂停传输直到系统自动发送XON字符。使用软件流控制的DCE设备会在接收到XOFF字符后暂停发送。某些设备会在DTE发送XON字符后恢复发送,然而,有些DCE设备会在接收到任何字符后恢复发送。如果DTE在自动发送XOFF后继续传输,DCE会继续发送,使得XOFF失效。Win32 API没有提供让DTE与这些设备行为相同的机制。DCB结构没有提供字段以指示在接收到任何字符后恢复被暂停的传输。只有XON字符可以恢复传输。接收到XON和XOFF字符会让未决的读取操作返回零字节而完成,但应用程序不会读取到XON和XOFF字符,因为它们不在输入缓冲区中。很多程序,包括Windows中的超级终端,都可以让用户选择流控制类型:硬件流控制,软件流控制,或者不使用流控制。实际上,可以自由设置DCB结构中影响流控制的各个字段,来进行各种流控制配置,需要遵循的限制只是便于最终用户使用,当然也要考虑设备是否支持所有类型的流控制。

Win32 串口编程(三)相关推荐

  1. Linux 串口编程三 使用termios与API进行串口程序开发

    在 termios 结构体以及内部终端控制标志中,并非所有的参数对于实际的物理串口都是有效的,在使用过程中也不需要对于所有标志的作用都有所理解.事实上,快速掌握一项技术的核心点也是一种学习能力.对于使 ...

  2. Win32 串口编程(一)

    翻译自:ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.WIN32COM.v10.en/dnfiles/html/msdn_serial.htm 老外写的文章, ...

  3. Win32 串口编程笔记1

    .打开和关闭串口 在Win32中,串口是作为文件处理的,使用CreateFile()函数可以打开串口,进行读写访问操作.CreateFile()返回串口句柄,可以在以后的端口操作中使用.关闭端口使用C ...

  4. Win32 串口编程(二)

    3 串口状态 有两种获取通信端口状态的方法.第一种方法是设置事件掩码,当指定事件发生时应用程序会收到通知.SetCommMask函数用于设置事件掩码,WaitCommEvent用于等待指定的事件发生. ...

  5. Win32 串口编程(四)

    7 通信超时 通信超时是影响读写操作的另一个重要方面.如果操作所用时间大于超时值,则操作完成,ReadFile.WriteFile.GetOverlappedResule或者WaitForSingle ...

  6. POSIX 串口编程指南

    介绍 POSIX 串口编程指南将教会你在你的 UNIX 工作站或者 PC 上面如何成功.有效以及可移植性的对串口编程.每一章提供了使用 POSIX 终端控制函数的编程例程,可以基本不经修改地工作在 I ...

  7. Linux 串口编程四 串口设备程序开发

    Linux 串口编程和程序相对来说是很简单的,之所以用博客连载来展示,主要是想在学会使用的基础上掌握相关背景,原理以及注意事项.相信在遇到问题的时候,我们就不会对于技术的概念和 API 的使用浅尝辄止 ...

  8. Linux 串口编程二 深入了解 termios

    前言 这一系列串口编程重点在应用层编程,但是在讲解原理与相关概念时需要对驱动框架有个基础的认识.如果只是浅尝辄止,以后在遇到串口驱动与应用层程序调试难免遇到瓶颈.关于 tty驱动架构参见我的其他博客: ...

  9. Linux 串口编程一 一些背景

    在大部分讲解 Linux 编程书籍的时候会发现没有单独的串口编程章节,实际上串口编程已经被概括在了"终端"或者"终端IO"章节里面.在上一篇博客中对经常出现的几 ...

最新文章

  1. 邁向IT專家成功之路的三十則鐵律 鐵律十四:IT人言談之道-守中
  2. ArcGIS Server SOE开发之奇怪异常:
  3. 推荐20款基于 jQuery CSS 的文本效果插件
  4. csharp:Convert Image to Base64 String and Base64 String to Image
  5. 微软-IT-解决方案-统一沟通-发布会
  6. 自定义Stack接口
  7. MySQL查看数据库、表的占用空间大小
  8. PlantUML 简明教程
  9. 前端入门: 用css设置文字样式
  10. linux下virtualbox安装win7虚拟机无法调整分辨率
  11. 数据库基础知识七:同义词
  12. 面试中单例模式有几种写法
  13. MTK移植大全(参考)建议收藏!
  14. Python TimedRotatingFileHandler 修改suffix后无法自动删除文件
  15. 打地鼠游戏(2D)学习笔记
  16. java 子类属性覆盖_java子类和父类属性重复问题
  17. 主板有电无法启动_电脑主板通电但是开不了机是什么原因?
  18. R报错|Package ‘MASS’ version 7.3.55 cannot be unloaded
  19. 2021年高考成绩查询安徽繁昌一中,安徽高中成绩排名2021,安徽中考分数线排行榜...
  20. 【外贸】英文缩写(包装信息、日常沟通邮件)

热门文章

  1. iOS 应用性能测试的相关方法、工具及技巧
  2. 利用JavaScript在ASP.NET中动态生成系统菜单
  3. 使用Jsonp(JQ+C#)
  4. Dynamics CRMのアップデートインポートツール移植的准备步骤与有用的网页
  5. python 字符编码判断 chardet评测
  6. 04 Django之模板系统
  7. hello,world———C++入门有感
  8. Luogu P1091 合唱队形
  9. [今日干货]短视频获得种子用户的途径
  10. 网络通信:单播、广播、组播