Serielle Schnittstelle auslesen Theorie

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 47 Antworten in diesem Thema. Der letzte Beitrag () ist von Haudruferzappeltnoch.

    @Haudruferzappeltnoch Also das das holen der verfügbaren SP-Namen nicht in einem anderen Thread läuft wurde schon mehrfach gesagt, es ging um das Event DataReceived, das wird in einem anderen Thread gefeuert. Und BeginInvoke ist Invoke vorzuziehen, weil das asyncron läuft, du wirst auch keinen spürbaren unterschied warnehmen. Beim testen von InvokeRequired hast du das vermutlich nicht gut angewendet. Kannst ja nun mal den Code aus dieser Sub in form load machen und schauen welche Text das Form dann hat, dann mit holen der SP-Namen.

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. If InvokeRequired Then
    3. BeginInvoke(Sub() Text = "With Invoke")
    4. Return
    5. End If
    6. Text = "Without Invoke"
    7. End Sub


    Edit @Haudruferzappeltnoch
    Hab das blöd gemacht fällt mir auf, so wird die Sub einfach nochmal im GUI-Thread gecallt, bei 2. call ist ja kein invoken mehr nötig und der Code hinter der Bedingun wird ausgeführt.

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. If InvokeRequired Then
    3. BeginInvoke(Sub() SerialPort1_DataReceived(sender, e))
    4. Return
    5. End If
    6. Text = "Ach so!"
    7. End Sub

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

    Achso, ich dachte ihr versucht mir zu erklären, warum da trotzdem ein Invoke hinkommt, und nicht dass das falsch ist.
    Ok, ich sehe das Invoke ist im DataReceived Sub erforderlich im Load-Sub nicht. Ich habs aber immer noch nicht verstanden sry.

    Vielen Dank, dass ihr mir weiterhelft.

    Ich habe trotz dessen ich nicht weiß was passiert, das Ganze ausprobiert und mit einem Barcodescanner versucht etwas in die Textbox zu scannen.
    Ich erhalte nicht jedes mal dasselbe Ergebnis, manchmal kommt ein Fragezeichen , manchmal "GGET0009bA0191910004", und manchmal kommt "910004", der Barcode ist "19190004"
    Das sagt euch nicht zufällig etwas?


    Habe nochmal alle Einstellungen des Scanners resettet. Jetzt kommt häufig der richtige Barcode "19190004", aber manchmal nur die "4"

    Wenn ich längere Codes scanne, z.B. Datamatrix, dann schneidet er auch den vorderen Teil ab, hat aber mehr als nur eine Zahl.

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    @Haudruferzappeltnoch Barcode-Scanner sind ein eigen Ding.
    Das System erkennt sie als Tastatur, und so funktionieren sie auch, wenn Du sie ansteckst.
    Der Code wird in das fokussierte Control eingetragen, Word, TextBox und solch.
    Wenn Du einen Barcode-Scanner für einen bestimmten Zweck brauchst, dann erkundige Dich in der Firma und lass Dir Infos zukommen:
    Ansprechen eines Barcode-Scanners mit einem geeigneten Treiber als SerialPort.
    Dort musst Du das Zeilenende-Zeichen (.NewLine) vorgeben und dann bekommst Du im DataReceived-Event den kompletten Scan übergeben.
    Den Barcode musst Du dann in den GUI-Thread zur Anzeige invoken.
    ====
    Wahrscheinlich genügt es, wenn Du einen bestimmten Code einscannst, sieh mal dazu in der Beschreibung nach.
    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!
    Ja so habe ich den Scanner eingestellt, der emuliert einen seriellen Anschluß, das mit der Tastatur kann man auch einstellen, da funktioniert bei mir aber garnichts.

    Ich habe einen vermeintlichen Fix hiermit:

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles Port1.DataReceived
    2. If Port1.IsOpen Then
    3. Select Case Port1.BytesToRead
    4. Case 9
    5. Dim COMBuffer() As Byte = New Byte(8) {}
    6. Port1.Read(COMBuffer, 0, 9)
    7. Me.Invoke(Sub() Read_Message(COMBuffer))
    8. Case 14
    9. Dim COMBuffer() As Byte = New Byte(13) {}
    10. Port1.Read(COMBuffer, 0, 14)
    11. Me.Invoke(Sub() Read_Message(COMBuffer))
    12. End Select
    13. End If
    14. End Sub
    15. Private Sub Read_Message(COMB As Byte())
    16. Dim enc As New System.Text.ASCIIEncoding()
    17. Dim str As String = enc.GetString(COMB)
    18. txtBarcode.Text = str
    19. End Sub


    Das funktioniert für 9-stellige und 14-stellige Barcodes jetzt so zuverlässig, dass ich keinen "zu kurzen" String mehr bekomme.

    Anstatt:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles Port1.DataReceived
    2. If Port1.IsOpen Then
    3. Dim Anzahl_Bytes As Integer = Port1.BytesToRead 'Ermitteln, wie viele Bytes kommen.
    4. Dim COMBuffer() As Byte = New Byte(Anzahl_Bytes - 1) {}
    5. Port1.Read(COMBuffer, 0, Anzahl_Bytes)
    6. End If
    7. End Sub


    Nachtrag: Ich habe jetzt ein Suffix Zeichen eingestellt "+" oder Chr(43) und es daraufhin mit SerialPort1.Newline = Chr(43) nochmal auf dem automatischen Wege versucht. Da schreibt er das + jetzt mit ans Ende und eben wieder der Effekt, dass er nach mehreren Scan nur noch das letzte Zeichen ausspuckt.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    Ich kenne ReadExisting()nicht, daher kann ich dir die Frage nicht beantworten. Der BarcodeScanner sendet über serielle Schnittstelle. Ich trau mich auch nicht das jetzt auszuprobieren da ich mein obiges Problem augenscheinlich behoben habe mit automatischer Byte-Erkennung:

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles Port1.DataReceived
    2. If Port1.IsOpen Then
    3. Dim prüf As Integer
    4. Thread.Sleep(100)
    5. Me.Invoke(Sub() prüf = Port1.BytesToRead)
    6. Dim COMBuffer() As Byte = New Byte(prüf - 1) {}
    7. Port1.Read(COMBuffer, 0, prüf)
    8. Me.Invoke(Sub() Read_Message(COMBuffer))
    9. End If
    10. End Sub


    Ob ich da richtig geraten habe? Mit Thread.Sleep dem DataReceived-Event mehr Zeit zu geben? Oder ist das Blödsinn und funktioniert nur ausversehen?

    Inwiefern funktioniert .ReadExisting anders? Ich weiß noch nicht sorecht wie das DataReceived-Event die Daten verarbeitet, bei Microsoft steht da auch garnicht soviel zu.
    Das Video mit dem RS232 Protokoll habe ich gesehen, aber da wurden nur die Hinweise für ein einzelnes DatenByte erklärt. Das Event scheint aber noch mehr zu erkennen, da es ja die ganze Byte"Gruppe" erkennt.

    Davon abgesehen soll es hier ja auch nicht nur auf BarcodeScanner beschränkt sein, ich habe das nur gemacht um ein Anwendungsbeispiel durchzugehen, ich dachte daran würde sich vielleicht noch die ein oder andere Erkenntnis ergeben.
    @Haudruferzappeltnoch Lies keine Bytes, lies den gescannten Text!
    Ein Barcode ist doch nix anderes als ein Strichcode-Font, genau wie Arial oder Courier New.

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. Dim txt As String = Me.SerialPort1.ReadExisting().Trim()
    3. Dim act As Action = Sub() Me.tbSerialNb.Text = txt
    4. Me.Invoke(act)
    5. End Sub
    und feddich.
    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!
    Ich werd es morgen ausprobieren, aber wärst du so nett und zu meiner Version noch zu sagen ob das Sinn ergibt?

    Denn das Thread.Sleep ist ja eig. im DataReceived-Event Sub und damit wäre das was, was im anderen Thread ausgeführt wird? Oder ist das automatisch der Hauptthread? Das scheint nämlich den Unterschied zu machen, ob ich immer die vollständigen Daten bekomme oder sporadisch nicht.

    ​Serialport.ReadExisting "Liest alle sofort verfügbaren Bytes auf Grundlage der Codierung sowohl im Stream als auch im Eingabepuffer des SerialPort-Objekts" Was ist denn der Stream?
    ​SerialPort.Read liest nur im Eingabepuffer so wie ich das gelesen hab. Aber was bedeutet das für den Unterschied zwischen den beiden Optionen?

    Ich weiß für euch ist das alles ganz einfach, aber mir gehts ums Verständnis nicht nur um die Funktionalität, damit ich auch selbst weiß was ich wann nutze. ​ReadExisting scheint ja bei andern Gerätschaften dann nicht zu funktionieren.
    @Haudruferzappeltnoch Deine kurzen Snippets sind iwie aus dem Kontext gelöst und nicht voll aussagekräftig. Wie sieht die Initialisierung des Ports aus?
    Du willst theoretisch über das SerialPort-Handling philosophieren und kommst dann sehr praktisch mit nem Barcodescanner.
    Ich schlage vor, Du machst diesen Thread zu, meldest ihn, um ihn ganz zuzumachen und erstellst einen neuen Thread mit praktischen Problemen und vollständigerem Code.
    Ansonsten wird dann in zwei Threads gepostet und die werden wieder zusammengeführt.
    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!
    Naja was heißt philosophieren. Den Barcodescanner können wir gerne weglassen, ich dachte damit kommen wir der Sache wieder näher, da ihr so auf den Anwendungsfall beharrt habt.
    Der Thread soll sich nicht um den Scanner drehen, deswegen heißts ja Theorie Serialport. Wie gesagt ihr habt mir signalisiert, dass ihr über was anderes reden wollt.

    Ich verwende ansonsten den Code wie Bartosz ihn eingangs gepostet hat, daher habe ich das nicht nochmal dazu getan. In dem Sinne ist hier alles vollständig.
    Aber hier nochmal alles.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Sub Load_Form1 (sender As Object, e as EventArgs) Handles Form1.Load
    2. ComboBox1.Items.AddRange(IO.Ports.SerialPort.GetPortNames())
    3. End Sub
    4. Private Sub Button_verbinden_Click(sender As Object, e As EventArgs) Handles Button_verbinden.Click
    5. If ComboBox1.SelectedItem Is Nothing Then
    6. MessageBox.Show("Erst den Port auswählen!", "Warnung", MessageBoxButtons.OK, MessageBoxIcon.Hand)
    7. Return
    8. End If
    9. If Not SerialPort1.IsOpen Then
    10. Button_verbinden.Enabled = False
    11. SerialPort1.PortName = ComboBox1.SelectedItem.ToString
    12. SerialPort1.BaudRate = 19200
    13. SerialPort1.DataBits = 8
    14. SerialPort1.Parity = IO.Ports.Parity.None
    15. SerialPort1.StopBits = IO.Ports.StopBits.One
    16. SerialPort1.DtrEnable = True
    17. SerialPort1.RtsEnable = True
    18. SerialPort1.Handshake = IO.Ports.Handshake.None
    19. SerialPort1.NewLine = NewLine
    20. SerialPort1.Open()
    21. End If
    22. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    23. If SerialPort1.IsOpen Then
    24. Dim prüf As Integer
    25. Thread.Sleep(100)
    26. Me.Invoke(Sub() prüf = SerialPort1.BytesToRead)
    27. Dim COMBuffer() As Byte = New Byte(prüf - 1) {}
    28. SerialPort1.Read(COMBuffer, 0, prüf)
    29. Me.Invoke(Sub() Read_Message(COMBuffer))
    30. End If
    31. End Sub
    32. Private Sub Read_Message(COMB As Byte())
    33. Dim enc As New System.Text.ASCIIEncoding()
    34. Dim str As String = enc.GetString(COMB)
    35. txtBarcode.Text = str
    36. End Sub
    @Haudruferzappeltnoch Liest Du echte Bytes oder liest Du Strings, deren Zeichen 8 Bit lang sind?
    Das kannst Du dann im Encoding bei der Initialisierung vorgeben und dann, wie gezeigt, mit Strings hantieren.
    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!
    Also ein Byte ist ein Byte, es kein String Byte!

    Ein String ist nur eine bestimmte Art Bytes zu interpretieren.

    Siehe mal im Anhang, ich habe einfach mal eine Textdatei erstellt mit dem Inhalt ABCD
    diese Datei habe ich im Hxd(Hexeditor) geöffnet, siehe mal genau hin, Decodierter Text(ASCII), genauso können diese 4 Byte ein Integer und mehr sein. Es hängt also nur davon ab wie du diese Bytefolgen interpretierst.
    Bilder
    • Unbenannt.jpg

      199,9 kB, 1.077×1.080, 57 mal angesehen

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

    Haudruferzappeltnoch schrieb:

    Woran erkenne ich ob ich "echte" Bytes oder String-Bytes lese?
    Beschreibe das, was Du sendest und das, was Du zu empfangen erwartest.
    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!
    Also z.B. als String kannst du direkt vom SP lesen, SP.ReadLine, SP.ReadExisting. Kannst auch mit SP.ReadChar Char für Char lesen und mit nem Stringbuilder zusammen setzen. Zahlen lassen sich so auch übertragen, müssen dann aber geparst werden.(Integer.Parse, Integer.TryParse, Single.Parse, Single.TryParse etc...) Strings senden geht auch so einfach.

    Wenn du mit Bytes arbeitest, SP.Read oder SP.ReadByte, kannst du mit der Bitconverter Klasse, alles konvertieren.
    BitConverter.ToInt32, Bitconverter.ToSingle etc.

    Willst du Zahlen als Bytefolge senden, geht das mit BitConverter.GetBytes(), die Function GetBytes nimmt sehr viele Typen als Argument.

    Läuft z.B. auf einem MircoController ein aufwendigeres Programm, wo du auch Strukturen hast, kannst auch eine Struktur als Bytes senden, diese Bytes kannst du dann mit Marshal auch in .Net zu dieser Struktur machen, musst also auf beiden Seiten diese Struktur haben, vorallem ist dann auf Größen von DatenTypen zu haben, auf'm Arduino z.B. ist ein Int nur 2 Bytes groß, dann musst du .Net Short statt Integer nehmen. Vice versa geht das auch, in .Net allerdings ein wenig aufwendiger als in C/C++. Mit Klassen geht das auch.

    Wie das mit Strukturen funtioniert, siehe hier:
    Arduino numericupdown led
    Das andere ist einfacher, soltlest du auch so hinbekommen, denke ich.

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

    Haudruferzappeltnoch schrieb:

    Also für String, für Zahlen, für was kann es denn sonst noch geben?
    Ein SerialPort kennt nur Bytes.
    Mit Hilfe der Property .Encoding kannst Du mit dem Port auch Strings senden und empfangen, das funktioniert genau dann, wenn beide Seiten dasselbe Encoding verwenden.
    Zahlen sind entweder Byte-codiert (4 Byte für ein Integer, 8 Byte für ein Long) oder werden als String-Repräsentation gehandelt ("3,1415").
    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!
    Ok, das sieht alles noch recht übersichtlich aus.
    Nur mal zur Klarifikation, was passiert denn, wenn ich .ReadExisting für nicht String repräsentierende Bytes verwende? Ist das nur ein Konvertierungsproblem oder passiert da mehr?

    Wenn ich nun für String repräsentierende Bytes .ReadExisting verwende, spare ich mir dann nur ein bisschen Code oder passieren da ganz andere Vorgänge als bei .Read?
    Klar isses mehr Code, bei SP.Read kommen Bytes rein, die solltest du dann in einem Byte() haben, diese Bytes werden dann mit System.Text.Encoding.ENCODING_DEINER_WAHL.GetString zu einem String gemacht. Das macht man wenn verschiedene Encodings auf beiden Seiten im Spiel sind. Wenn du nun bei SP.ReadExsiting was einliest, wenn die Bytes nicht für einen String gedacht sind, wird was unsinniges rauskommen, das einlesen allein wird dein Programm nicht zum Absturz bringen, je nachdem was du damit nach dem einlesen mit diesem Wert(String) machst kann es aber passieren, wenn du nicht aufpasst. Solange du aber anständig programmierst sollte das aber nicht passieren.

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