Win32 串口编程(三)
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. |
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); ?? // 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的影響。
?
| 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
| 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 串口编程(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue 组件的自定义事件
- 下一篇: js 原型以及原型链