2012-07-12 10 views
6

Sto usando il seguente codice per leggere i valori da una porta COM:Come leggere la comunicazione porta seriale nel buffer e analizzare i messaggi completi

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 

Questo funziona bene, ma ogni tanto non ottiene tutti i dati e in cambio risulta in due stringhe invece di una sola.

Un esempio potrebbe essere se la porta COM stava mandando sopra la parola "HELLO2YOU" era simile a:

HEL 
LO2YOU 

o

HELLO2 
YOU 

come posso mettere un buffer in là in modo che si assicura che abbia tutti i dati letti prima di visualizzarli?

Grazie!

risposta

9

È necessario pensare alle comunicazioni della porta seriale come dati di streaming. Ogni volta che ricevi dati, devi aspettarti che possa trattarsi di un messaggio completo, solo un messaggio parziale o più messaggi. Tutto dipende dalla velocità con cui i dati arrivano e dalla velocità con cui l'applicazione è in grado di leggere dalla coda. Pertanto, hai ragione nel pensare che hai bisogno di un buffer. Tuttavia, ciò che potrebbe non essere ancora realizzato, è che non c'è modo di sapere, rigorosamente tramite, il Serial Port, dove ogni messaggio inizia e finisce. Questo deve essere gestito tramite un protocollo concordato tra il mittente e il destinatario. Ad esempio, molte persone usano i caratteri standard di inizio testo (STX) e di fine testo (ETX) per indicare l'inizio e la fine di ogni messaggio inviato. In questo modo, quando ricevi i dati, puoi dire quando hai ricevuto un messaggio completo.

Per esempio, se si è utilizzato caratteri STX e ETX, si potrebbe fare una classe come questa:

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 

Vorrei anche ricordare che, in protocolli di comunicazione, è tipico di avere un byte di checksum con cui è possibile determinare se il messaggio è stato danneggiato durante la trasmissione tra il mittente e il destinatario.

3

Ciò è abbastanza normale, le porte seriali sono dispositivi molto lenti. Con baudrate come 9600 e la macchina non impantanarsi troppo, si otterrà solo uno o due byte dalla porta quando si utilizza ReadExisting(). Debug.Print() emette un terminatore di riga in modo che tu possa vedere tutto ciò che è stato smembrato in pezzi.

Il modo più semplice per risolverlo è utilizzare ReadLine(). Ciò richiede che i dispositivi inviino un carattere speciale alla fine della riga, uno che corrisponda al valore della proprietà SerialPort.NewLine. Che è abbastanza comune, un avanzamento di riga è boilerplate.

In caso contrario, è necessario un altro tipo di schema di bufferizzazione.

+0

Hans è corretto che ReadLine() è un modo semplice per ottenere i messaggi completi terminati da un valore "newline". Tuttavia, eviterei di utilizzare ReadLine poiché è normalmente implementato come una funzione di blocco e peggiorerà le prestazioni della GUI e altre attività. Normalmente memorizzerei i caratteri in un array fino a quando non avessi ricevuto il carattere di terminazione, quindi chiamerei il parser del comando. – Jeff

+0

No, non quando lo si chiama nel gestore di eventi DataReceived, viene eseguito su un thread del threadpool. –

Problemi correlati