分类: C/C++
2012-12-05 15:13:53
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_CTS、EV_DSR、EV_RING、EV_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. |
某些时候可能要用应用程序来代替串口通信驱动程序对控制线进行控制,比如说,当应用要实现自己的流控制时。此时应用必须负责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); // Update
DCB rate. // Set new
state. |
6 流控制
流控制可以在通信的某一方忙或者由于其他原因不能进行通信时暂停通信。通常有两种流控制:硬件流控制和软件流控制。串行通信中一个通常的问题是写操作实际上并没有把数据写入到设备中。通常,这是流控制的效果。此时,DCB结构的下列字段可能是TRUE:fOutxCtsFlow、fOutxDsrFlow或者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. The fOutX 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. The fOutX 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 the XoffLim member of the DCB. The fInX member of the DCB controls this behavior. DTE transmission is controlled by the fTXContinueOnXoff 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 the XonLim 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结构中影 响流控制的各个字段,来进行各种流控制配置,需要遵循的限制只是便于最终用户使用,当然也要考虑设备是否支持所有类型的流控制。