Laden...

SerialPort konstant auslesen

Erstellt von AceTecNic vor einem Jahr Letzter Beitrag vor einem Jahr 815 Views
A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr
SerialPort konstant auslesen

Hallo Zusammen,

Ich möchte eine kleine Anwendung erstellen, mit dem ich Werte (aktuell Arduino) via USB auslesen und verarbeiten kann.
Der Arduino sendet einen Messwert mit der Funktion Serial.println(value).

Ich habe es bisher halbwegs hinbekommen, den Wert vom SerialPort auszulesen und an ein Label weiterzugeben.
Allerdings übernimmt er den Wert nur wenn ich den button drücke, was soweit in Ordnung wäre.

Ich möchte den IST-Wert vom Arduino in Echtzeit auf dem UI haben und nach der fertigen Messung den Wert speichern.

Nach welcher Funktion muss ich denn suchen um den Wert konstant auszulesen?

Vielen Dank 🙂

Aktueller Codeschnipsel


        public void btn_portOpen_Click(object sender, EventArgs e)
        {
            SerialPort comport = new SerialPort("COM3");
            comport.BaudRate = 19200;
            comport.ReadTimeout = 1000;
            comport.Parity = Parity.None;
            comport.StopBits = StopBits.One;
            try
            {
                //lbl_messwert.Text = "";
                comport.Open();
                int messwert = comport.ReadChar();
                lbl_echtzeit.Text = messwert.ToString();
                //string indata = comport.ReadExisting();
                //Console.Write(indata);
                //lbl_messwert.Text = indata.ToString();
                //comport.Close();
            }
            
            catch (Exception)
            {
                MessageBox.Show("Fehler bei der Portverbindung");
            }

T
708 Beiträge seit 2008
vor einem Jahr

Moin,

anstelle von ReadChar kannst Du ein Event vom ComPort verwenden.
Dies wird aufgerufen, sobald Daten eingehen. Nun läuft das aber aynchron zu dem Main-Thread der Form, was Dir einen Fehler liefern würde.
Dazu musst Du Dich dann mit

Invoke

auseinandersetzen um den Wert an das Label zu übergeben.

Es gab mal ein Template, wo man das abschauen konnte...
Hier isses:
Template SerialPort

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Vielen Dank für den Tipp mit dem Template! Im Prinzip genau das was ich vor habe!

Das mit dem Invoke habe ich mir angeschaut und versucht zu verstehen. Anhand einem Beispiels habe ich das mal versucht einzubinden, bisher ohne Erfolg.
Allerdings hänge ich gerade auch noch an dem einfachen ändern des Label fest...
Könntest du dir das mal ansehen und mir einen Tipp geben warum sich mein lbl_test nicht umschreiben lässt?

In der Konsole bekomme ich den passenden Wert angezeigt, nur das Label zeigt nichts an.


using System;
using System.IO.Ports;
using System.Windows.Forms;
using System.Globalization;
using System.Linq;

namespace Prüfprogramm
{
    public partial class PP : Form
    {
        string portName;
        int baudRate = 19200;
        SerialPort SerialPort = new SerialPort();
       
        public PP()
        {
            InitializeComponent();

            fillComPortComBoBox();

        }

        private void btn_portVerbinden_Click(object sender, EventArgs e)
        {
            try
            {
                SerialPort.PortName = combox_Portliste.SelectedItem.ToString();
                SerialPort.BaudRate = baudRate;

                SerialPort.Open();
                Console.WriteLine(SerialPort.ReadExisting());
                string messwert = SerialPort.ReadExisting();
                aendereLabel(lbl_Anzeige, messwert);
                lbl_test.Text = messwert;
                //SerialPort.Close();
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace, "Fehler", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
            
        }

        private void fillComPortComBoBox()
        {
            combox_Portliste.Items.AddRange(SerialPort.GetPortNames());
            combox_Portliste.SelectedItem = this.portName;
        }

        public void aendereLabel(Label label, string str)
        {
            if (label.InvokeRequired)
            {
                string mw = str;
                label.Invoke(new MethodInvoker(delegate { label.Text = mw.ToString(); }));
            }
        }
    }
}

190 Beiträge seit 2012
vor einem Jahr

so war das wohl nicht gemeint. Schau dir mal den Link an: SerialPort.DataReceived Ereignis und hier
Vom Seriellen Port empfangene Daten in eine Textbox schreiben

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF

4.931 Beiträge seit 2008
vor einem Jahr

Hallo,

da du (weiterhin) im UI-Thread die Methode aendereLabel aufrufst, ist InvokeRequired nicht nötig, und es wird der if-Block übersprungen (und demnach nichts ausgegeben) - s.a. [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke).

Wie von Wilfried schon geschrieben, verwende das DataReceived-Ereignis - dieses läuft in einem anderen Thread und erfordert daher für die UI-Synchronisation den Invoke-Aufruf.

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Ich glaube ich habe verstanden.
ich habe aendereLabel entfernt und den DataReceivedHandler eingefügt. Jetzt kommt diese Ausnahme:

Fehlermeldung:
System.InvalidOperationException: "Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lbl_test erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde."

Damit das dann funktioniert muss ich mit Invoke jetzt weiter machen. Ich lese mich dazu mal ein. Ein paar Beispiele habe ich gefunden, allerdings nützen die mir nichts wenn ich nicht weiß was Invoke alles bewirkt. Gibt es irgendwo eine bessere Doku dazu als bei learn.microsoft.com?

Dankeschön fürs helfen 🙂

16.807 Beiträge seit 2008
vor einem Jahr

[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)
Wurde dir bereits im Thread davor gegeben. Musst die Links schon lesen, die Dir Leute geben.

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Ja stimmt, sry. Das habe ich wohl übersehen.
Dankeschön!

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Jetzt funktioniert es, wunderbar! Dankeschön!

Eine Frage hätte ich noch dazu: Der Wert den ich anzeige "stockt" bzw. blinkt als ob er die Werte nicht sauber bekommt. In der Konsolenausgabe passiert das gleiche.
Es werden 3 Werte pro Sekunde übertragen. In dem SerialTemplate funktioniert das einwandfrei - allerdings komme ich nicht dahinter warum das da besser funktioniert...

Kann mir jemand einen Denkanstoß geben?


using System;
using System.IO.Ports;
using System.Windows.Forms;
using System.Globalization;
using System.Linq;
namespace Schmid_Prüfprogramm
{
    public partial class Schmid_PP : Form
    {
        string portName;
        int baudRate = 19200;
        SerialPort SerialPort = new SerialPort();
        public Schmid_PP()
        {
            InitializeComponent();
            fillComPortComBoBox();
        }
        private void btn_portVerbinden_Click(object sender, EventArgs e)
        {
            try
            {
                SerialPort.PortName = combox_Portliste.SelectedItem.ToString();
                SerialPort.BaudRate = baudRate;
                SerialPort.Open();
                Console.WriteLine(SerialPort.ReadExisting());
                SerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace, "Fehler", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
        }
        private void fillComPortComBoBox()
        {
            combox_Portliste.Items.AddRange(SerialPort.GetPortNames());
            combox_Portliste.SelectedItem = portName;
        }
        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort serialPort = (SerialPort)sender;
            string indata = serialPort.ReadExisting();
            Console.WriteLine(indata);
            updateMessung(indata);
        }
        private delegate void threadSicher(string messung);
        private void updateMessung(string indata)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new threadSicher(updateMessung), new object[] {indata});
            }
            lbl_Anzeige.Text = indata;
        }
    }
}

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Lösung:

Code im Microcontroller:


Serial.println(winkelWert); //Wichtig: Neue Linie!

Zeile 41 im vorherigen Beitrag: Ändern von ReadExisting() auf ReadLine()

Damit wird gewartet bis die Zeile fertig ist. Sehr schön! 🙂

Frage an die Profis: Kann ich mit der gleichen Methode einen weiteren Wert auslesen bzw. abfragen?
Solange ein Knopf am MC gedrückt ist, soll das Programm die Zeit stoppen. Wenn der Knopf losgelassen wird, soll die gestoppte Zeit angezeigt werden und die gemessenen Grad auch.

Dachte an etwas wie:


while (SerialPort.ReadLine("Spezifische Linie auslesen?"))
            {
                //Zeit stoppen irgendwie? Muss ich noch schauen.
            }

Bzw. ist es denn generell Möglich definiert einen Wert mit SerialPort auszulesen? Aktuell liest er ja alles bis zur nächsten Linie und dann? Muss ich dann die kommende Linie schnell von der anderen Funktion auslesen damit die nicht in meine Anzeige rutscht?

Danke an alle!

4.931 Beiträge seit 2008
vor einem Jahr

Generell fällt das unter dem Begriff "Protokoll".
Wenn du unterschiedliche Daten empfängst, dann benötigst du eine entsprechende Auswertung dieser Daten (üblicherweise wird dazu ein Parser eingesetzt).
Wenn auch noch unterschiedliche Anfragen (requests) gesendet werden, dann benutzt man häufig eine/n Endlicher Automat/Zustandsmaschine (state machine).

Fürs Zeit messen kannst du die Klasse Stopwatch benutzen.

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Das mit der Stopwatch hab ich gleich eingebaut, funktioniert ja sehr simpel dankeschön! 😁

Jetzt habe ich mich bezüglich der StateMachine etwas eingelesen bzw. etwas gefunden. Ich habe die Vermutung, dass das bei mir zutreffen wird.
Ich habe einen String und einen State den ich an C# übermittle und zwei "Werte" (Oder was auch immer ich dann brauche wenn ich soweit bin).

Danke für den Tipp mit der StateMachine! Dann kann ich mich dazu etwas einlesen und versuchen 🙂
Funktioniert es denn, wenn ich eine neue Klasse (StateMachine.cs) anlege und das darin schreibe? Der Übersichthalber 🙂

4.931 Beiträge seit 2008
vor einem Jahr

Ja, das Auslagern in eine eigene Klasse ist quasi Pflicht, denn es ist ja die Logik (s.a. [Artikel] Drei-Schichten-Architektur) deines Programms, also unabhängig von der UI.

Ich habe einen String und einen State den ich an C# übermittle

Du meinst "an den SerialPort übermittle"!?

Wenn dies jeweils unterschiedliche Strings und State-Werte sind, dann sind dies die Zustände der State Machine und je nach zuletzt geschickten Werten (requests) würdest du dann die Antworten empfangen und entsprechend auswerten. Wenn du mit Werten (gemessene Grade) Zahlen im Textformat meinst, dann reicht wahrscheinlich eine der TryParse-Methoden (z.B. für Double).
Nur falls die Daten im Binärformat verschickt werden, bräuchtest du BinaryReader.

Je nach Umfang deines Protokolls kannst du auch eine externe Komponente für die State Machine benutzen, z.B. Stateless - ist aber wahrscheinlich erst mal zu viel für dein Projekt.

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Du meinst "an den SerialPort übermittle"!?

Ja genau also vom MC an SerialPort (zur Weiterverarbeitung mit C#).

Wenn du mit Werten (gemessene Grade) Zahlen im Textformat meinst, dann reicht wahrscheinlich eine der TryParse-Methoden (z.B. für Double).

den Wert(string) vom Winkelmesser habe ich ja. Prinzipiell würde ich den Status von einem Knopf(Totmannschalter) noch benötigen - den könnte ich aber auch als string/int übergeben und im Programm mit bsp.: if(zustandKnopf == 1) abfragen? Das würde mit dem Parser funktionieren? Die StateMachine zu bauen ist schon ein gewaltiger Akt (für mich), evtl geht das Parsen einfacher 😁

Dann bleibt mir noch die Frage wie ich das später mit den zwei anderen Werten (string/int) löse die ich von C# an den MC übertragen möchte. Aber um das kümmere ich mich sobald alles andere Funktioniert 😉

T
708 Beiträge seit 2008
vor einem Jahr

Moin,

es gibt natürlich etliche Möglichkeiten für ein Protokoll zur Übermittlung der Daten.
Tatsächlich kann man eine Bibliothek für Json auf den Arduino ziehen und damit Klassen serialisieren. Ist aber riesen groß (aus Sicht des Mikrocontrollers).

Daher würde ich das Ganze so simpel wie möglich halten und sowas bauen:
text|5,389|1\n\r

wobei der erste Teil Deinen string darstellt, der Zweite den Winkel und der Dritte den Status des Schalters (1 oder 0).
Als Trennzeichen bietet sich eines an, welches in den Daten nicht vorkommt.
Abgeschlossen wir das Ganze dann mit einem Zeilenumbruch.

Oder Du kreierst mehrere "Datenpakete" und kombinierst nur einen Indentifier und den Wert
w|32,5\n\r -> Winkel
s|0\n\r -> State des Schalters
t|HalloWelt\n\r -> Text

In #c splitttest Du den gesamten String mit der Pipe, prüfst den ersten Teil auf den Buchstaben und je nach dem was dort drin steht, wandelst Du den zweiten Teil um.

A
AceTecNic Themenstarter:in
51 Beiträge seit 2018
vor einem Jahr

Moin,

Moin, Bist du zufällig der TriB aus dem Arduino-Forum? 😁 😁

Ich habe das jetzt ähnlich gemacht:
Der Controller sendet an den SP Zeilenweise
a: winkelVariable
b: btnStatus

C# Code:


        private void updateMessung(string indata)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new threadSicher(updateMessung), new object[] {indata});
            }

            switch (indata)
            {
                case string a when a.Contains("a"):
                    //Console.WriteLine("a gefunden");
                    lbl_Anzeige.Text = a;
                    break;
                case string b when b.Contains("b"):
                    //Console.WriteLine("b gefunden");
                    lbl_Grad1.Text = b;
                    break;

            }
        }

Wobei die Zeile 16 nur Testhalber drin steht aktuell.

Was mich noch stört ist, dass der indentifier noch mit dran hängt.