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.

    Serielle Schnittstelle auslesen Theorie

    Hallo,

    Also ich schaue gerade durch verschiedene Threads bezüglich seriellen Schnittstellen. Meist ist man da schon etwas weiter in der Materie, so wie ich das sehe.
    Ich würde so rein vom theoretischen her nur mal verstehen wollen, was so die untersten Notwendigkeiten sind um überhaupt mit einer seriellen Schnittstelle zu arbeiten.
    Ich denke mal der ComPort muss gewählt werden und dann gibts noch sowas wie BaudRate und Handshakes. Der Port muss geöffnet, gelesen und geschlossen werden.
    Ja und dann bin ich auf schon am Ende meiner Vorstellungen zu dem Thema. Also was braucht man und was nicht? Gibts da ein Tutorial zu? Wo fängt man an?

    Viele Grüße
    Das ist kein Problem, dazu eignet sich VB hervorragend! Das ist in der Tat das, was man als Erstes in der Fachhochschule beigebracht bekommt, wenn man mit VB startet. So'n typisches "Laboringenieur will Programm schreiben, um ein Labornetzteil anzusteuern und einen Pt100 messen lassen".

    First things first: Im Load-Eventhandler schreibst du diesen Befehl, der dir alle verfügbaren COM-Ports in eine Combobox schreibt. Das musst du invoken, da die SerialPort-Klasse auf einem anderen Thread läuft als der Main-Thread.

    VB.NET-Quellcode

    1. Me.Invoke(Sub() ComboBox1.Items.AddRange(IO.Ports.SerialPort.GetPortNames()))


    Dann nutzt du einen Button, um Bekanntschaft zu machen:

    VB.NET-Quellcode

    1. Private Sub Button_verbinden_Click(sender As Object, e As EventArgs) Handles Button_verbinden.Click
    2. If ComboBox1.SelectedItem Is Nothing Then
    3. MessageBox.Show("Erst den Port auswählen!", "Warnung", MessageBoxButtons.OK, MessageBoxIcon.Hand)
    4. Return
    5. End If
    6. If Not SerialPort1.IsOpen Then
    7. Button_verbinden.Enabled = False
    8. SerialPort1.PortName = ComboBox1.SelectedItem.ToString
    9. SerialPort1.BaudRate = 19200
    10. SerialPort1.DataBits = 8
    11. SerialPort1.Parity = IO.Ports.Parity.None
    12. SerialPort1.StopBits = IO.Ports.StopBits.One
    13. SerialPort1.DtrEnable = True
    14. SerialPort1.RtsEnable = True
    15. SerialPort1.Handshake = IO.Ports.Handshake.None
    16. SerialPort1.NewLine = NewLine
    17. SerialPort1.Open()
    18. End If
    19. 'Memo an mich xD
    20. MessageBox.Show("Wenn ein Netzteil gekauft wurde, muss dieses Programm noch an's Netzteil angepasst werden. Es fehlt der Code für «Wer da?», «auf Remote umschalten» und die Sleep-Befehle.", "Memo an mich", MessageBoxButtons.OK, MessageBoxIcon.Hand)
    21. If SerialPort1.IsOpen Then
    22. Label_Info.Text = "verbunden"
    23. Button_verbinden.BackColor = Color.FromArgb(0, 200, 0)
    24. End If
    25. Button_trennen.Enabled = True
    26. End Sub


    Button trennen:

    VB.NET-Quellcode

    1. Private Sub Button_trennen_Click(sender As Object, e As EventArgs) Handles Button_trennen.Click
    2. MessageBox.Show("Es fehlt noch der Code für «Reset» und «wieder auf lokal umschalten»", "Memo an mich", MessageBoxButtons.OK, MessageBoxIcon.Warning)
    3. If SerialPort1.IsOpen Then
    4. SerialPort1.Close()
    5. Timer1.Stop()
    6. Label_Info.Text = "getrennt"
    7. Button_verbinden.BackColor = Color.FromArgb(180, 191, 185)
    8. Button_verbinden.Enabled = True
    9. Button_trennen.Enabled = False
    10. End If
    11. End Sub


    FormClosing:

    VB.NET-Quellcode

    1. If SerialPort1.IsOpen Then SerialPort1.Close()


    Jetzt kommt das Wichtigste: Das Labornetzteil antwortet dir (aus irgendeinem Grunde). Man nutzt nun das Data-Received-Event. Das ist besser, als "auf Krampf" auf Antwort zu warten, weil sonst die GUI hängt und abstürzt.
    Du machst dir ein Byte-Array in der Größe, wie Bytes kommen.

    VB.NET-Quellcode

    1. Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
    2. If SerialPort1.IsOpen Then
    3. Dim Anzahl_Bytes As Integer = SerialPort1.BytesToRead 'Ermitteln, wie viele Bytes kommen.
    4. If Anzahl_Bytes > 0 Then
    5. Dim COMBuffer() As Byte = New Byte(Anzahl_Bytes - 1) {} 'Ein Byte-Array mit genau dieser Anzahl erstellen.
    6. SerialPort1.Read(COMBuffer, 0, Anzahl_Bytes) 'einlesen
    7. Me.Invoke(Sub() Read_Message(COMBuffer)) 'den Text ausgeben lassen (funktioniert nur mit Invoke, weil die SerialPort-Klasse einen eigenen Thread hat).
    8. End If
    9. End If
    10. End Sub

    VB.NET-Quellcode

    1. Private Sub Read_Message(COMB As Byte())
    2. Dim enc As New System.Text.ASCIIEncoding()
    3. Dim str As String = enc.GetString(COMB)
    4. TextBox_Ausgabe.Text = str
    5. End Sub


    In diesem Programm habe ich mit einem Timer bestimmte Befehle gesendet. Kannst du aber machen, wie du willst

    VB.NET-Quellcode

    1. Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    2. If SerialPort1.IsOpen Then
    3. Dim Buffer_Senden_Spannung_ein() As Byte = {&H48, &H65, &H6C, &H6C, &H6F} 'Was erwartet der Empfänger? Ändern auf den Text und vbCrLf!
    4. SerialPort1.Write(Buffer_Senden_Spannung_ein, 0, Buffer_Senden_Spannung_ein.Length)



    Und mehr ist das nicht. Du musst nur die ganzen Einstellungen wie BaudRate usw an deine Gerätschaft anpassen. Einige Geräte erwarten vbCrLf als Newline, andere halt nicht. Da gibt es oft viele Mätzchen; oft nachzulesen bei Mikrocontroller.net
    Man sollte auch den COM-Port im BIOS aktiviert haben :whistling:

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

    Bartosz schrieb:

    Das ist besser, als "auf Krampf" auf Antwort zu warten, weil sonst die GUI hängt und abstürzt.


    Also abstürzen tut da nix, die GUI friet ein ja, klickt man hier und da und wieder auf das gefrorene Fenster, kommt nur eine Meldung das das Programm nicht reagiert, was kein Absturz ist, sondern das program hängt fest. Du kannst auch timeouts(lesen/schreiben) für den Serialport festlegen.

    In einer meiner Anwendungen, muss ich sehr schnell kommuzieren, da kommt mir das mit dem SerialPort.DataReceived zu spät, ist mir zu träge. Daher kann ein Protokoll zu entwickeln Sinn machen, dann weiss du wann du eine Antwort nach dem Schreibvorgang erhälst und kannst lesen(oder auch prüfen ob was zu lesen da ist). Ich finde es kommt also auf den Anwendungsfall an, aber ohne das Event als "Auf Krampf" zu betiteln finde ich übertrieben, wenn man gescheit entwickelt hat man keine frierende GUI, auch nicht ohne dieses Event. Auch kann man Threads nutzen, da friert auch keine GUI, wenn du lesen willst aber nix da ist.

    Empfängst du nur Daten da macht diese Event Sinn, oder wenn du eine Anfrage zum bekommen der Daten schickst OK, aber bei zeitkritischen Sachen, ist das unbrauchbar.

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

    @Haudruferzappeltnoch Initialisieren und los, ganz einfach.
    Du musst wissen, ob Du Bytes oder Strings senden willst. Bei Strings muss das Encoding auf beiden Seiten gleich sein, bei Bytes kannst Du es ignorieren.
    Das DataReceived-Event kommt in einem anderen Thread, also musst Du invoken, wenn Du Daten an der GUI anzeigen willst.
    Im Gegensatz zu @Bartosz konvertiere ich die Bytes sofort zu einem String und invoke den Empfangstext, ansonsten halt die Bytes.
    Wenn Du mehrere Schritte einer wechselseitigen Kommunikation nacheinander ausführen musst, mach Dir solche Prozeduren, die kurz warten und den Antwort-String direkt ohne Event lesen:

    VB.NET-Quellcode

    1. Sub SendCommand(command As String)
    2. ' ausfüllen
    3. End Sub
    4. Function SendCommandWaitForAnswer(command As String) As String
    5. ' ausfüllen
    6. End Function
    Bei den sprechenden Namen wweißt Du, was sie tun
    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 hab mir erstmal angeschaut was ein Thread ist. Ich verstehe das Beispiel: docs.microsoft.com/de-de/dotne…ading.thread?view=net-5.0

    Aber was ein Thread genau ist ist mir noch nicht klar, das ist quasi ein Teil des Programms der das andere unterbricht, aber warum?

    Dann wird im Beispiel auch der Thread selbst "produziert" oder? Wie kommt es das manche Thread schon vorher da sind, wie beim SerialPort?

    Invoke heißt quasi den Thread zu starten? Im Beispiel wird mit Thread.Sleep der andere Thread gestartet, oder?

    "' Yield the rest of the time slice" was ist denn der Rest des Zeitintervalls?

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

    Ein Thread ist ein nebenläufiger Handlungsstrang. Du hast den Hauptthread (GUI-Thread). Und Du kannst zusätzliche erstellen (lassen). Wenn die dann bearbeitet werden, ist das quasi eine zeitgleiche Abarbeitung mit dem Hauptthread.

    Beispiel einfaches Frühstück vorbereiten:
    Eier kochen, Toast machen, Tisch vorbereiten.
    Wenn es alles in einem Thread abläuft, kochst Du die Eier, erst wenn die fertig sind, machst Du den Toast, erst wenn der fertig ist, deckst Du den Tisch.
    Dann sind die Eier abgekühlt und der Toast auch.

    Mit Threads machst Du es quasi gleichzeitig: Thread 1: Eier ins Wasser hauen. Während die vor sich hinköcheln, schiebst Du den Toast in den Toaster und schaltest den ein (Thread 2). Während die Eier weiter heiß werden und der Toaster die Toasts vor sich hintoastet (Thread 1 und 2 werkeln vor sich hin), bereitest Du in Thread 3 den Tisch vor. Kann also passieren, dass alles gleichzeitig fertig wird. Oder irgend eines davon früher oder später als die anderen.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @Haudruferzappeltnoch Das SerialPort wird zwar in Deinem GUI-Thread initialisiert und bedient, aber dann wird es von Dir "allein gelassen" und wartet auf eine Reaktion der Gegenstellle.
    Würde dieses Warten in Deinem GUI-Thread stattfinden, wäre diese Deine GUI blockiert. Das wollen wir doch nicht.
    Das System lässt also das SerialPort in einem anderen Thread warten um Dir dann bei Vorhandensein von Daten, normalerweise nach Vorliegen eines Zeilen-Ende-Zeichens (das kannst Du vorgeben), das DataReceived-Event zu senden.
    Klar, dass Du dann invoken musst, um die Empfangsdaten an Deiner GUI anzuzeigen.
    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 Invoke schiebt die Methode in den Serialport Thread?

    @VaporiZed Also in dem Beispiel von Microsoft, sieht es ja so aus, als würden sich die Thread abwechseln. Wenn die wirklich gleichzeitig laufen, dann kann das je nachdem wie lange eine einzelne Anweisung braucht auch weniger abwechselnd aussehen?

    Und dann ist mir glaub ich immer noch nicht klar was mit dem time slice gemeint ist

    Wie kann ich mir denn so ein DataReceived-Event vorstellen? Wenn da Daten über den Port kommen, dann läuft das Event doch nicht für jeden Byte einzeln? Zumindest sieht @Bartosz Code so aus als gibt es da einen automatischen Zusammenhang zwischen den ankommenden Bytes eines Events.

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

    Haudruferzappeltnoch schrieb:

    Also Invoke schiebt die Methode in den Serialport Thread?
    Nein.
    Aus dem Serial-Port-Thread in den GUI-Thread, wo Du die Info vom Port anzeigen kannst.
    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!
    Invoke schiebt die Methode in den Serialport Thread?
    Nein, vom SerialPort-Thread in den Main-Thread. Der Main-Thread hat als Hauptaufgabe die Kommunikation mit Windows und die Interaktion mit dem Nutzer, zum Beispiel Listboxen füllen. Wenn du dem Main-Thread etwas zusätzlich auf den Schreibtisch legst, arbeitet er das ab, auch schön nacheinander, aber irgendwann wird es ihm zu viel. Nach 60 Sekunden sagt Windows dann, dass dein Programm nicht mehr reagiert. So hatte sich Vaporized mal ausgedrückt. Beim SerialPort ist es ja nun so, dass nicht regelmäßig Daten gesendet werden. Wenn das auf dem Main-Thread liefe, würde der ständig unnütz auf Signal warten und die GUI wäre eingefroren.

    Edit: Rod war schneller.

    Wie kann ich mir denn so ein DataReceived-Event vorstellen? Wenn da Daten über den Port kommen, dann läuft das Event doch nicht für jeden Byte einzeln? Zumindest sieht @Bartosz Code so aus als gibt es da einen automatischen Zusammenhang zwischen den ankommenden Bytes eines Events.

    Serielle Schnittstelle bedeutet zwar, dass die Bytes hintereinander kommen (und nicht parallel), aber es wird einmal innerhalb der Nachricht ein Startbit vom Gerät an den PC gesandt; der PC (genauer die UART) weiß dann bescheid, dass da etwas kommt.
    https://www.youtube.com/watch?v=j4Hc0GZLeL0

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Bartosz“ ()

    @Haudruferzappeltnoch Nein. GetPortNames() ist eine statische (Shared) Funktion, die Du aus dem GUI-Thread aufrufen kannst.
    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!
    @Bartosz Häh? Nein, das ist nicht im anderen Thread. Wenn du das Data_Received Event nutzt wird in einem anderen Thread gelauscht, wenn gelesen werden konnte, wird das Event gefeuert. Willst du nun aber in dieser Sub auf Controls zugreifen musst du invoken, da dieses Event in einem anderen Thread gefeuert wurde.

    @Haudruferzappeltnoch
    Eine Beispielanwendung für einen Thread:

    Willst du da nun einen Serialport verwenden, deklariere und instanziere den SP in der Sub Work. Willst du was senden, nimm eine Queue(Klassenebene), im thread nimmste vor der Queue und schreibst, wartest einen kleinen Moment und guckst ob der SP was zu lesen hat, wenn nichts in der Queue nur schauen ob was zu lesen da ist(wenn zu erwarten ist, das was kommen könnte). Kannst aber auch noch diverse selbst erstellte Event feuern, z.B. in Kombination mit ner Modelleisenbahn wenn eine Signal von Hand umgestellt wurde oder eine Weiche

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System.ComponentModel
    3. Imports System.Threading
    4. Public Class Form1
    5. Private t As ThreadHandler
    6. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    7. t = New ThreadHandler(Me)
    8. t.Start()
    9. End Sub
    10. Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
    11. If Not t Is Nothing Then
    12. t.Stop()
    13. End If
    14. End Sub
    15. End Class
    16. Public Class ThreadHandler
    17. Private thread As Thread
    18. Private exitRequest As Boolean = False
    19. Private counter As Long = 0
    20. Private control As Control
    21. Public Sub New(parent As Control)
    22. control = parent
    23. End Sub
    24. Public Function IsAlive() As Boolean
    25. Return Not thread Is Nothing AndAlso thread.IsAlive
    26. End Function
    27. Public Sub Start()
    28. If IsAlive() Then
    29. Return
    30. End If
    31. exitRequest = False
    32. thread = New Thread(AddressOf Work)
    33. thread.Start()
    34. End Sub
    35. Public Sub [Stop]()
    36. exitRequest = True
    37. End Sub
    38. Private Sub Work()
    39. While (Not exitRequest)
    40. counter += 1
    41. Try 'control handle könnte null sein, ist hier ja ein form und wenn das geschlossen wird ist kein Handle mehr da
    42. control.BeginInvoke(Sub() control.Text = counter.ToString())
    43. Catch ex As InvalidOperationException
    44. 'wissen was los ist und in dem fall brauchen wir nichts weiteres machen
    45. End Try
    46. Thread.Sleep(1000)
    47. End While
    48. End Sub
    49. End Class



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

    Ohje, kann man denn eine Aussage treffen was in welchem Thread läuft? Ihr seht ja ich muss recht viel raten.

    Dann gibts jetz sicher noch nen wichtigen Unterschied zwischen Invoke und BeginInvoke, das verwirrt mich eher zusätzlich.

    Das .Sleep hat sicherlich mit dem Zeitinterval zu tun, das hatte ich auch nochmal gefragt oben.

    Also dann rate ich mal weiter: Mein Fenster loadet, das machts im GUI-Thread, das Load-Event wird gefeuert. Die Sub dazu läuft auch im GUI-Thread. Also der Invoke-Befehl kommt aus dem GUI-Thread
    und füllt die ListBox mit den Portnamen, die aber auch im GUI-Thread gelesen werden, wie @RodFromGermany schreibt. Wo ist denn dann der andere Thread?
    Also in meinem Code alles was in der Sub Work passiert läuft in einem anderen Thread. Ich habe auch bereits erwähnt, benutzt man das SerialPort.DataReceived-Event, wird in einem anderen Thread am Port gelauscht, der Thread wird im Hintergrund erstellt ohne das du es mitbekommst.

    Nun der Unterschied zwischen Control.Invoke und Control.BeginInvoke, bei BeginInvoke wird das Ansycron ausgeführt, bei Invoke nicht.

    docs.microsoft.com/de-de/dotne…ntrol.invoke?view=net-5.0
    docs.microsoft.com/de-de/dotne….begininvoke?view=net-5.0
    @Haudruferzappeltnoch Das sollte Dich zunächst nicht kümmern, lass das alles das System machen.
    Wichtig ffür Dich ist nur, ob ein Prozess im GUI-Thread oder aber nebenläufig (also in einem anderen Thread) läuft.
    Abfragen kannst Du das so:

    VB.NET-Quellcode

    1. If Me.InvokeRequired Then
    2. ' hier invoken
    3. ' ggf. Return
    4. End If
    Richtig Multi-Threading machst Du z.B. bei Parallel.For und Parallel.ForEach, da läuft dann jeder Laufindex in einem anderen Thread, was zu einem gewaltigen Performance-Gewinn fü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!
    Also ich habe jetzt das einmal mit und einmal ohne Invoke probiert: Also nur das Loading, da kann ich keinen Unterschied feststellen in der Ausführung, das heißt ich lass das System machen, aber in einem Fall ja scheinbar falsch, sonst hätte @Bartosz das wohl nicht geschrieben.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    3. Me.Invoke(Sub() cmbPort.Items.AddRange(IO.Ports.SerialPort.GetPortNames()))
    4. End Sub
    5. End Class


    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    3. cmbPort.Items.AddRange(IO.Ports.SerialPort.GetPortNames())
    4. End Sub
    5. End Class


    Mit InvokeRequired hingegen führt er das nicht mehr aus. Das heißt wohl das ist nicht Required, aber trotzdem kann man Invoken?
    InvokeRequired ist false, wenn der Befehl im selben Thread statt fand. Es muss eben nichts invoked werden.
    Damit beantwortet sich auch deine Frage ganz oben: Das sammeln der Serialport-Namen findet nicht in anderen Threads statt, die Controls (im GUI-Thread) müssen also nicht invoked werden (da alles im GUI-Thread passiert ist).

    Haudruferzappeltnoch schrieb:

    da kann ich keinen Unterschied feststellen in der Ausführung
    Klar.
    Wenn Du vom GUI-Thread in den GUI-Thread invokst, passiert nix, es dauert nur etwas länger.
    Lies mal hier: docs.microsoft.com/de-de/dotne…vokerequired?view=net-5.0
    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!