Serielle Schnittstelle Bytes empfangen

  • VB.NET

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von fraju.

    Serielle Schnittstelle Bytes empfangen

    Hallo Leute!

    Ich bin neu im Forum und in VB. Ich arbeite derzeit an einem kleinen Projekt, bei dem ein Mikrocontroller (AVR) Messwerte an den PC senden soll. Es sind insgesamt 5 Byte die der Kontroller ununterbrochen an den Pc sendet. Das abfragen für ein Byte habe ich bereits in einem Testprogramm erfolgreich gelöst. Das war kein Problem. Doch jetzt möchte ich ein Programm schreiben, dass alle Bytes auswerten kann und das auch noch in einem Intervall von 0,1 Sekunden. Ich weis einfach nicht wie ich erkennen soll, wann die 5 Byte anfangen und wann sie enden. Ich habe bereits daran gedacht, einfach 6 Byte zu senden und am Anfang immer den binären Wert 83 (ASCII für S) zu verwenden. Ist das eine gute Methode? Hat jemand sowas schonmal gemacht und eventuell ein Beispielprogramm?


    Ich habe bereits sehr viel zu dem Thema gelesen allerdings komme ich einfach nicht weiter...

    Hier ist der Code den ich bereits fertig habe:

    VB.NET-Quellcode

    1. 'Importierte Systemdateien und HelferklassenImports SystemImports System.IO.Ports
    2. 'LeistungsprüfstandklassePublic Class Leistungspruefstand
    3. 'Variablen deklarationen Dim takt As Integer = 0 'takt (Takt) als Integer Dim t As Double = 0 't (Zeit in Sekunden) als Double Dim index As Integer = 0 Dim mw(50000) As Integer 'mw (Messwerte) als array
    4. 'Einstellungsvariablen Dim autoStop As Integer 'autoStop (automatisches Anhalten in s) Dim cPortName As String 'cPortName (Com Port Name) Dim cPortBaudRate As Integer 'cPortBaudRate (Com Port Baudrate) Dim r As Double 'r (Radius in m) Dim l As Double 'l (Länge in m) Dim dichte As Double 'dichte (Dichte in kg/m³) Dim traegheit As Double 'traegheit (Trägheit in kg/m²)
    5. 'Sobald das Formular geladen wird Private Sub Leistungspruefstand_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Funktion zur Ermittlung der ComPorts aufrufen comPortsErmitteln() 'Funktion zum Setzen der Standartwerte stdWerteSetzen() End Sub
    6. 'Anzeigen aller verfügbaren ComPorts Private Sub comPortsErmitteln() 'Schleife durch alle verfügbaren ComPorst For Each sp As String In My.Computer.Ports.SerialPortNames 'Wenn ein ComPort gefunden wurde, zur Liste hinzufügen ComPortBox.Items.Add(sp) Next End Sub
    7. 'Standartwerte eintragen Private Sub stdWerteSetzen() 'Automatisch stoppen auf 10s setzen autoStop = 10 txtMessdauer.Text = autoStop 'Den ersten verfügbaren ComPort in der ComPortListe auswählen ComPortBox.SelectedIndex = 2 cPortName = ComPortBox.Text 'Die dritte BaudRate (9600) auswählen BaudRateBox.SelectedIndex = 2 cPortBaudRate = BaudRateBox.Text 'Den Radius auf 0,125m setzen r = 0.149 txtRadius.Text = r 'Die Länge auf 0,3m setzen l = 0.3 txtLaenge.Text = l 'Die Dichte auf 7850(Stahl) setzen dichte = 7850 txtDichte.Text = dichte 'Die Trägheit berechnen traegheit = traegheitBerechnen(r, l, dichte) txtTraegheit.Text = traegheit 'Graphen typ auf Linie setzen Chart1.Series(0).ChartType = DataVisualization.Charting.SeriesChartType.FastLine
    8. End Sub
    9. 'Wenn der Button zur Speicherung der Einstellungswerte gedrückt wird Private Sub neueWerte_CLick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles neueWerte.Click 'Funktion zum Setzen der neuen Werte neueWerteSetzen() End Sub
    10. 'Neue Werte setzen Private Sub neueWerteSetzen() 'ComPort und Baudrate in die Einstellungsvariablen schreiben cPortName = ComPortBox.Text cPortBaudRate = BaudRateBox.Text 'Radius, Länge, Dichte und Trägheit in die Einstellungsvariablen schreiben r = txtRadius.Text l = txtLaenge.Text dichte = txtDichte.Text 'Trägheit berechnen und anzeigen traegheit = traegheitBerechnen(r, l, dichte) txtTraegheit.Text = traegheit End Sub
    11. 'Funktion zum Berechnen der Trägheit Private Function traegheitBerechnen(ByVal r As Double, ByVal l As Double, ByVal dichte As Double) Dim traegheit As Double 'Formel zur Berechnung der Trägheit traegheit = ((Math.PI * r ^ 2 * l * dichte) * r ^ 2) / 2 'Trägheit zurückgeben Return traegheit End Function
    12. 'Wenn der Start Button geklickt wird Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click 'Reset alle Werte messungReset() 'Start Button deaktiviereun und Stop Button aktivieren btnStart.Enabled = False checkAutostop.Enabled = False txtMessdauer.Enabled = False btnStop.Enabled = True 'ComPort einstellen With cPort .PortName = cPortName .BaudRate = cPortBaudRate .Parity = IO.Ports.Parity.None .ReadBufferSize = 1024 .ReadTimeout = 1000 .ReceivedBytesThreshold = 1 .DataBits = 8 .StopBits = IO.Ports.StopBits.One End With If cPort.IsOpen Then cPort.Close() End If cPort.Open() 'Wenn die Checkbox Autostop ausgewählt wurde If checkAutostop.Checked Then fortschritt.Maximum = txtMessdauer.Text * 10 End If 'Messung starten Timer.Start() End Sub
    13. 'Wenn der Stop Button geklickt wird Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click messungStoppen() End Sub
    14. 'Subrutine zum Stoppen der Messung Private Sub messungStoppen() Timer.Stop() MsgBox("Messung gestoppt! Sie können die Ergebnisse nun speichern.") 'Start Button aktiviereun und Stop Button deaktivieren btnStart.Enabled = True checkAutostop.Enabled = True txtMessdauer.Enabled = True btnStop.Enabled = False cPort.Close() End Sub
    15. 'Subrutine zum Resetten der Messung Private Sub messungReset() fortschritt.Value = 0 takt = 0 t = 0 index = 0 Chart1.Series(0).Points.Clear() DataGridView1.Rows.Clear() End Sub
    16. '======================================================================================================================
    17. Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer.Tick If checkAutostop.Checked Then If takt = fortschritt.Maximum Then messungStoppen() Else takt += 1 t += 1 fortschritt.Value += 1 End If Else takt += 1 t += 1 End If Counter.Text = t / 10 End Sub
    18. Public Sub mwVerarbeiten(ByVal takt As Integer, ByVal messwert As Integer) If index = takt Then 'ListBox1.Items.Add("Takt: " & takt & " Messwert: " & messwert) 'ListBox1.SelectedIndex = takt Dim dgvRow As New DataGridViewRow Dim dgvCell As DataGridViewCell 'Setze WErt für Spalte takt dgvCell = New DataGridViewTextBoxCell() dgvCell.Value = takt dgvRow.Cells.Add(dgvCell) 'Setze WErt für Spalte Zeit dgvCell = New DataGridViewTextBoxCell() dgvCell.Value = takt / 10 dgvRow.Cells.Add(dgvCell) 'Setze WErt für Spalte Messergebnis dgvCell = New DataGridViewTextBoxCell() dgvCell.Value = messwert dgvRow.Cells.Add(dgvCell) 'Zeile hinzufügen DataGridView1.Rows.Add(dgvRow) 'Letzte Zeile anzeigen DataGridView1.FirstDisplayedScrollingRowIndex = DataGridView1.RowCount - 1 'Neuen Punkt im Graphen setzen Chart1.Series(0).Points.AddXY(takt, messwert) End If End Sub
    19. Private Sub cPort_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles cPort.DataReceived If cPort.IsOpen Then Dim buffer(4) As Byte 'Deklariere Buffer Variable cPort.Read(buffer, 0, 1) 'Speichere die ankommenden Daten in den Buffer Dim count As Integer = buffer(0) 'Deklariere die Variable count und setze ihren wert gleich dem Wert im Buffer
    20. If index = takt Then Me.Invoke(Sub() mwVerarbeiten(takt, count) End Sub) index += 1 End If End If
    21. End Sub
    22. End Class


    EDIT: Irgendwie geht die Formatierung immer verloren...
    So sind alle Bytes in Deinem Buffer (Beispiel ohne Invoke):

    VB.NET-Quellcode

    1. Private Buffer() As Byte
    2. Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    3. Dim ToRead As Integer = SerialPort1.BytesToRead
    4. If ToRead > 0 Then
    5. 'Neu empfangene Zeichen an die Empfangspuffer anhängen
    6. ReDim Buffer(ToRead - 1)
    7. SerialPort1.Read(Buffer, 0, ToRead)
    8. End If
    9. End Sub
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Danke Rod!

    Danke für die Hilfe aber das löst mein Problem glaube ich nicht wirklich...

    Ich will jede zehntel Sekunde einen Messwert abfragen der aus insgesamt 5 Byte besteht. Allerdings weis ich nicht wo der Anfang bzw. das Ende der ankommenden bytes ist, da der Mikrocontroller ununterbrochen sendet.

    Ich sende die Bytes so:


    _____________________________________
    |Startflag |Byte 2 |Byte 3 |Byte 4 |Byte 5 |


    Jedoch kommen die Bytes manchmal auch so an:


    ____________________________________
    Byte 4 |Byte 5 |Startflag |Byte 2 |Byte 3 |

    Was nun? Ich muss irgendwie warten, dass der Buffer in dem ich die ankommenden Werte speichere 5 Bytes beträgt und das der erste Byte ein Startflag ist. Ich habe probiert mit einer If anweisung zu checken ob der erste Byte das Startflag ist, doch das ist sehr ungenau. Das bringt meine messzeit durcheinander...
    Das ist ja auch der Grund warum man Handshakes verwendet.

    Früher oder später werden dir zwischendurch Bytes verloren gehen.

    Das Auslesen mit deinem Programm muss immer schneller sein als
    das Senden vom Microcontroller, sonst kannst du die Daten auch
    kaum in der Zwischenzeit verarbeiten.

    Wenn du die Messung nicht ewig laufen lassen willst, könntest du ja auch
    einen extrem großen Puffer füllen, den du erst später gemütlich ausliest.

    Wenn das alles mit VB zu langsam ist, musst du eventuell auf eine
    andere Sprache zurück greifen, und dir einen eigenen Treiber schreiben.

    Aber 50Byte/sek sind ja nicht besonders viel.

    Warum kommen die Byte manchmal falsch an, verwendest du keine CR/LF?
    Hey Lightsource!

    Also ich benutze, um dem Programm zu sagen wann die Datenreihe die ich schicke beginnt, ein "S" für Start. Ich weis das ist eine sehr suboptimale variante doch kenne leider keinen besseren weg. (Ich bin ein Neuling in diesem Bereich)

    Die Sache ist die:

    Ich sende mit einem Microkontroller 4 Bytes zum Pc. Zwei dieser Bytes kommen von einem Temperatur und zwei von einem Drucksensor. Das möchte ich nun z.B. einfach in einer ListBox speichern.

    So dass ich später eine Listbox habe in der Steht:

    Zeit: 0.0 Temperatur: 20 Druck: 1020
    Zeit: 0.1 Temperatur: 22 Druck: 1018

    Zeit: 0.2 Temperatur: 20 Druck: 1015
    .
    .
    .
    Zeit: 10 Temperatur: 21 Druck: 1034

    BroOf schrieb:

    wann die 5 Byte anfangen und wann sie enden.

    Da du das senden selbst auch im Griff hast,
    ...einfach 5 Byte senden kleine Pause!

    Alle 5 Byte Event auslösen und auswerten,
    oder 6 Byte mit Prüfbyte.
    ..ist ein Fehler ,FehlerRoutine zum erkennen.
    Du kannst auch bei einen Fehler eine Quittierung machen ,dann wird nochmals gesendet.
    ...auch aus dem 5 Byte Prüfsumme bilden und damit Quittieren.
    ..es gibt viele noch andere Möglichkeiten.
    Gruß
    Für eine brauchbare, gesicherte Übertragung verwendet man Übertragungsprotokolle.
    Darüber haben sich vor zig Jahren schon Leute den Kopf zerbrochen.
    Z.B. BSC von IBM.
    Die Grundidee davon verwende ich heute noch für Datenkommunikation.
    Ganz so aufwendig brauchst du es nicht gestalten, aber die Grundlagen würde ich schon berücksichtigen:

    - Pack deinen Text ein und sichere ihn
    STX <DatenBytes> ETX BCC
    STX = StartOfText 02 (Hex)
    ETX = EndOfText 03 (Hex)
    BCC = BlockCheckCharacter, zB. LRC; alternativ (etwas aufwendiger) ein CRC.

    - Bestätige den Block (Rückweg)
    Mit ACK0 und ACK1 im Wechsel bzw. Mit NAK, wenn der Block fehlerhaft ist.
    Wenn es nicht darauf ankommt, dass auch mal ein Messwert verloren geht, kannst du dir das ACK sparen.

    Wenn du Binärdaten übertragen willst, musst du darauf achten, dass sie nicht mit Control-Characters verwechselt werden können.
    Entweder duch Base64-Codierung oder mittels DLE (DataLinkEscape) oder über beliebige andere Verfahren.


    Du kannst dir auch ein anderes Protokoll ausdenken, aber irgendwie wird es immer auf so etwas Ähnliches herauslaufen, wenn es praxistauglich sein soll.

    Verfahren mit Pause vor und nach dem Text funktionieren auch, sind aber nur dann sicher, wenn die Pause mit Sicherheit erkannt werden kann, also Verzögerungen bei der Verarbeitungszeit ausgeschlossen werden können.
    Solche Verfahren verwendet man im Allgemeinen nur in Verbindung mit Hardware-gestützten Timern auf einem Micro-Controller.
    Da du nicht sicherstellen kannst, dass dein Port-Treiber die Datenpakete nicht verzögert weiterleitet, musst die die Pause unverhältnismäßig groß machen und deshalb würde ich diese Methode verwerfen.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „petaod“ ()

    Wenn es Meßwerte geben kann, welche mit einem Steuerzeichen oder gar dem Startzeichen übereinstimmen, kommt es zu Problemen, wenn Sender und Empfänger nicht syncron laufen. Ich würde einfach alle 5 Meßwerte kommagetrennt in einem String abschicken und diesen ganz normal mit einem chr(13) abschließen. Allerdings wird so bis zu dem 4-fachen an Daten übertragen. Die Trennung geht dann per Split recht einfach.

    Wenn ständig Daten kommen, dann würde ich diese erst in einen Puffer schieben und von dort dann weiterverarbeiten. Evtl. lässt sich auch vom Mikrocontroller schon einiges an "Arbeit" abnehmen.
    Gruß
    Peterfido

    Keine Unterstützung per PN!

    peterfido schrieb:

    Ich würde einfach alle 5 Meßwerte kommagetrennt in einem String abschicken und diesen ganz normal mit einem chr(13) abschließen
    Ja. Hat was.
    Ist zwar ein wenig Overhead, aber spart die Codierung.
    Und bei 10 Messreihen pro Sekunde dürfte das locker noch beherrschbar sein.

    Stoppzeichen CR ist auch OK.

    Trotzdem würde ich auf ein Startzeichen nicht verzichten. Vorschlag: LineFeed.
    Und auf eine Prüfzahl.
    Bei der angedachten Methode meinetwegen als sechste Zahl die Summe der 5 Messwerte.

    Das wäre dann ein Übertragungsprotokoll ganz ohne Bit-Fummelei.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „petaod“ ()

    @TE: Da haste den Salat.
    Der häufigste Fehler in der Softwareentwicklung ist ein Spezifikationsfehler, also man soll etwas programmieren, obwohl man vom Hintergrund und oder den Möglichkeiten keine Ahnung hat.
    Setz Dich hi und male Dir den Informationsfluss auf.
    Wenn Du beide Seiten der Kommunikation in Deiner Hand hast: Herzlichen Glückwunsch.
    Mach aber was sinnvolles draus, dass andere Nutzer damit noch gut arbeiten können.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    BroOf schrieb:

    Zeit: 10 Temperatur: 21 Druck: 1034



    Wenn du nur Zahlenwerte übertragen möchtest,ist eine sehr einfache und sichere Art die Einzelbyte Codierung.
    Haben wir vor über 20 Jahren schon eingesetzt.

    Jedes Byte beinhaltet eine Codierung ,z.B.Bit 0-3.
    Damit kann man 16 Hexzeichen codieren.
    Das obere Nippel ist die Zahl 0-9 und A-F.
    Jede Stelle ist durch das untere Nippel Bit o-3 festgelegt.
    In deinem Fall musst du 6 Byte senden.
    Brauchst du nur 8 Stellen,kannst du 32 verschiedene Zeichen übertrage,(5Bit ASCII)

    Es würde sich heute als eine Art Einbyte Befehl darstellen.
    Einbyte Befehle werde noch heute oft angewendet.

    Die einfachen Möglichkeiten sollten nicht vergessen werden.
    Gruß

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „fraju“ ()