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?
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
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(); }));
}
}
}
}
- 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"
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.
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?
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;
}
}
}
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?
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.
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
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.
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
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.