Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
SerialPort konstant auslesen
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

SerialPort konstant auslesen

beantworten | zitieren | melden

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");
            }
private Nachricht | Beiträge des Benutzers
trib
myCSharp.de - Member



Dabei seit:
Beiträge: 697

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

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(); }));
            }
        }
    }
}
private Nachricht | Beiträge des Benutzers
Wilfried
myCSharp.de - Member

Avatar #2TnJ7IKlYXgOor5sZSIA.jpg


Dabei seit:
Beiträge: 173
Herkunft: Radeberg

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

Ich glaube ich habe verstanden.
ich habe aendereLabel entfernt und den DataReceivedHandler eingefügt. Jetzt kommt diese Ausnahme:
Fehler
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
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 16.312

beantworten | zitieren | melden

[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.
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

Ja stimmt, sry. Das habe ich wohl übersehen.
Dankeschön!
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

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;
        }
    }
}
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

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!
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

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.
Zitat von AceTecNic"
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.
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

Zitat von Th69
Du meinst "an den SerialPort übermittle"!?
Ja genau also vom MC an SerialPort (zur Weiterverarbeitung mit C#).

Zitat von Th69
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
private Nachricht | Beiträge des Benutzers
trib
myCSharp.de - Member



Dabei seit:
Beiträge: 697

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
AceTecNic
myCSharp.de - Member



Dabei seit:
Beiträge: 40
Herkunft: Bayern

Themenstarter:

beantworten | zitieren | melden

Zitat
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.
private Nachricht | Beiträge des Benutzers