vb.net 如何将串口通信读入缓冲区并解析出完整的消息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11458850/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 16:12:14  来源:igfitidea点击:

How to read serial port communication into buffer and parse out complete messages

vb.netserial-port

提问by StealthRT

I am using the following code to read values from a com port:

我正在使用以下代码从 com 端口读取值:

Private port As New SerialPort("COM13", 9600, Parity.None, 8, StopBits.One)

Private Sub port_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
    Debug.Print(port.ReadExisting())
End Sub

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    AddHandler port.DataReceived, New SerialDataReceivedEventHandler(AddressOf port_DataReceived)
    port.Open()
End Sub

This works just fine, but every now and then it doesnt get all the data and in return results in two strings instead of just one.

这工作得很好,但它时不时地获取所有数据,并返回两个字符串而不是一个字符串。

An example would be if the com port was sending over the word "HELLO2YOU" it was look like:

一个例子是,如果 com 端口通过“HELLO2YOU”这个词发送,它看起来像:

HEL
LO2YOU

or

或者

HELLO2
YOU

How can i place a buffer in there so that it makes sure it has all the data read before displaying it?

我如何在那里放置一个缓冲区,以确保它在显示之前读取了所有数据?

Thanks!

谢谢!

回答by Steven Doggart

You have to think of Serial Port communications as streaming data. Any time you receive data, you have to expect that it may be a complete message, only a partial message, or multiple messages. It all depends how fast the data is coming in and how fast you application is able to read from the queue. Therefore, you are right in thinking you need a buffer. However, what you may not be realizing yet, is that there is no way to know, strictly via, the Serial Port, where each message begins and ends. That has to be handled via some agreed upon protocol between the sender and the receiver. For instance, many people use the standard start-of-text (STX) and end-of-text (ETX) characters to indicate the beginning and ending of each message send. That way, when you receive the data, you can tell when you have received a complete message.

您必须将串行端口通信视为流数据。任何时候您接收数据时,您都必须预料到它可能是一条完整的消息、只是部分消息或多条消息。这完全取决于数据传入的速度以及您的应用程序能够从队列中读取的速度。因此,您认为需要缓冲区是正确的。但是,您可能还没有意识到,严格通过串行端口,无法知道每条消息的开始和结束位置。这必须通过发送方和接收方之间商定的协议来处理。例如,许多人使用标准的文本开始 (STX) 和文本结束 (ETX) 字符来表示每条消息发送的开始和结束。这样,当您收到数据时,您就可以知道何时收到了完整的消息。

For instance, if you used STX and ETX characters, you could make a class like this:

例如,如果你使用 STX 和 ETX 字符,你可以创建一个这样的类:

Public Class DataBuffer
    Private ReadOnly _startOfText As String = ASCII.GetChars(New Byte() {2})
    Private ReadOnly _endOfText As String = ASCII.GetChars(New Byte() {4})

    Public Event MessageReceived(ByVal message As String)
    Public Event DataIgnored(ByVal text As String)

    Private _buffer As StringBuilder = New StringBuilder

    Public Sub AppendText(ByVal text As String)
        _buffer.Append(text)
        While processBuffer(_buffer)
        End While
    End Sub

    Private Function processBuffer(ByVal buffer As StringBuilder) As Boolean
        Dim foundSomethingToProcess As Boolean = False
        Dim current As String = buffer.ToString()
        Dim stxPosition As Integer = current.IndexOf(_startOfText)
        Dim etxPosition As Integer = current.IndexOf(_endOfText)
        If (stxPosition >= 0) And (etxPosition >= 0) And (etxPosition > stxPosition) Then
            Dim messageText As String = current.Substring(0, etxPosition + 1)
            buffer.Remove(0, messageText.Length)
            If stxPosition > 0 Then
                RaiseEvent DataIgnored(messageText.Substring(0, stxPosition))
                messageText = messageText.Substring(stxPosition)
            End If
            RaiseEvent MessageReceived(messageText)
            foundSomethingToProcess = True
        ElseIf (stxPosition = -1) And (current.Length <> 0) Then
            buffer.Remove(0, current.Length)
            RaiseEvent DataIgnored(current)
            foundSomethingToProcess = True
        End If
        Return foundSomethingToProcess
    End Function


    Public Sub Flush()
        If _buffer.Length <> 0 Then
            RaiseEvent DataIgnored(_buffer.ToString())
        End If
    End Sub
End Class

I should also mention that, in communication protocols, it is typical to have a checksum byte by which you can determine if the message got corrupted during its transmission between the sender and the receiver.

我还应该提到,在通信协议中,通常有一个校验和字节,您可以通过它来确定消息在发送方和接收方之间的传输过程中是否被破坏。

回答by Hans Passant

This is quite normal, serial ports are very slow devices. With baudrates like 9600 and the machine not bogged down too much, you'll get only one or two bytes from the port when you use ReadExisting(). Debug.Print() outputs a line terminator so you'll see whatever is received broken up in pieces.

这是很正常的,串口是非常慢的设备。使用 9600 等波特率并且机器不会陷入太多困境,当您使用 ReadExisting() 时,您只能从端口获得一两个字节。Debug.Print() 输出一个行终止符,因此您会看到收到的任何内容都被分成几部分。

The easiest way to fix it is by using ReadLine() instead. That requires that the devices sends a special character at the end of the line, one that matches the SerialPort.NewLine property value. Which is quite common, a line feed is boilerplate.

修复它的最简单方法是改用 ReadLine()。这要求设备在行尾发送一个特殊字符,该字符与 SerialPort.NewLine 属性值匹配。这是很常见的,换行符是样板文件。

If not then you'll need some other kind of buffering scheme.

如果没有,那么您将需要一些其他类型的缓冲方案。