Forum: PC-Programmierung C# UART timing Problem?


von Walt N. (belayason)


Angehängte Dateien:

Lesenswert?

Guten Morgen Leute!

Ich habe ein Problem. Und zwar sende ich Daten von einem Sensor auf 
einem Breakoutboard, der mit einem NUCLEOF411RE MC verbunden ist, per 
UART an meinen PC. Funktioniert auch alles richtig gut. Mit Putty kann 
ich mir die Daten anschauen die ankommen( siehe Bild ). Jetzt habe ich 
noch eine Anwendung in C# programmiert die mir den String aufteilt und 
in Textboxen anzeigt ( siehe Bild ). Das Funktioniert auch. Nur ist die 
Aktualisierung dieser werte ziemlich schlecht. Die neuen Werte sollten 
eigentlich die alten ziemlich schnell überschreiben so das es wie eine 
Echtzeitübertragung wirkt. Manchmal werden die Werte überhaupt nicht 
erneuert und manchmal mehrere hintereinander. Mein UART auf dem MC sowie 
mein UART in meiner C# anwendung ist auf eine BaudRate von 115200 
eingestellt. Hat jemand vielleicht einen Tipp?

: Verschoben durch User
von H.Joachim S. (crazyhorse)


Lesenswert?

Echte UART auf dem PC oder ein USB-Wandler?

von kabelklaus (Gast)


Lesenswert?

Moin,

gabs da nicht nen Bug in ner Version des SerialPort - DataReceiveEvent 
(falls du das nutzt) ?

Grüße

von Walt N. (belayason)


Lesenswert?

H.Joachim S. schrieb:
> Echte UART auf dem PC oder ein USB-Wandler?

USB Wandler also einen Virtual COM Port

von Walt N. (belayason)


Lesenswert?

kabelklaus schrieb:
> SerialPort - DataReceiveEvent
> (falls du das nutzt) ?

Ob es da einen Bug gibt weiß ich nicht. Bin neu in der C# und 
Applikation Programmierung. habe in der Form1.cs halt einen serialPort 
hinzugefügt und auf Buttonklick dann das hier.

        private void button1_Click(object sender, EventArgs e)
        {
            serial.Open();

            try
            {
                while (true)
                {
                    string A = serial.ReadLine();
                    char[] Leerzeile = new char[] { ' ', '\t' };
                    String[] Stringsizes = A.Split(Leerzeile);
                    textBox1.Text = Stringsizes[0];
                    textBox2.Text = Stringsizes[1];
                    textBox3.Text = Stringsizes[2];
                    textBox4.Text = Stringsizes[3];
                    textBox5.Text = A;
                    Task.Delay(500);
                }
             }
            catch (System.IO.IOException)
            {

            }
        }

von Daniel -. (root)


Lesenswert?

Wie liest du die serielle Schnittstelle aus?
Kannst du deinen Code zeigen?

von Walt N. (belayason)


Lesenswert?

Daniel -. schrieb:
> Wie liest du die serielle Schnittstelle aus?
> Kannst du deinen Code zeigen?

string A = serial.ReadLine();

genau so eigentlich

von Cyblord -. (cyblord)


Lesenswert?

Walt N. schrieb:
> Daniel -. schrieb:
>> Wie liest du die serielle Schnittstelle aus?
>> Kannst du deinen Code zeigen?
>
> string A = serial.ReadLine();
>
> genau so eigentlich

Ja super. Und wann immer?
Nutze ein Event.

Das Problem liegt sicher nicht am ComPort oder an C# sondern an deinem 
Programm.

von Daniel -. (root)


Lesenswert?

Readline habe ich nie benutzt. Ich habe read benutzt und die Daten in 
Buffer
gesammelt. Anderes mal habe ich readexisting benutzt.
Hast du versucht timeoutread kleiner zu setzen?

von Johnny B. (johnnyb)


Lesenswert?

Ein While und darin ein Task.Delay sind natürlich sehr, sehr übel und 
gehören so nicht in eine C# Applikation.
Du musst Dir unbedingt angewöhnen, mit Events zu arbeiten.

Für Deinen konkreten Fall machst Du einen Event-Handler und meldest ihn 
beim SerialPort an (dieser Code wird automatisch erzeugt, wenn Du Dir 
das im Designer zusammenklickst):
1
serialPort = new System.IO.Ports.SerialPort();
2
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

Der Handler zum empfangen der Daten könnte etwa so aussehen:
1
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
2
{
3
  int bytecount = ((SerialPort)sender).BytesToRead;
4
  byte[] rxbuffer = new byte[bytecount];
5
  ((SerialPort)sender).Read(rxbuffer, 0, bytecount);
6
7
  // Im Array "rxbuffer" stehen nun die empfangenen Daten drin
8
}

von Donni D. (Gast)


Lesenswert?

Erstmal würde ich sagen falsches Forum, zweitens: Nutz doch deinen alten 
Thread weiter?

Ansonsten wurde alles gesagt, nutze Events, du kannst auch thresholds 
setzen und ganze Blöcke auslesen. Somit wird das ganze recht performant.

von Bülent C. (mirki)


Lesenswert?

so ähnlich musst Du das machen
1
        SerialPort ComPort = new SerialPort();
2
        internal delegate void SerialDataReceivedEventHandlerDelegate(
3
         object sender, SerialDataReceivedEventArgs e);
4
        delegate void SetTextCallback(string text);
5
6
        string InputData = String.Empty;
7
        string[] daten = new string[200000];
8
        int pointer = 0;
9
        Dictionary<double,double> Datenreihe;
10
        Series kurve;
11
        Series kurve_soll;
12
13
 private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
14
        {
15
            
16
            InputData = ComPort.ReadExisting();
17
            if (InputData != String.Empty)
18
            {
19
                this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
20
            }
21
             
22
23
        }
24
        private void SetText(string text)
25
        {
26
27
            if (text.StartsWith("+M"))
28
            {
29
                string[] roh_daten = text.Split('\r');
30
31
                int i = 0;
32
                var bilgi = new Dictionary<double, double>();
33
                while(i<roh_daten.Length && roh_daten[i].StartsWith("+M"))
34
                {
35
                    /*
36
                    string MS = roh_daten[i].Substring(3);
37
                    string MM = roh_daten[i+1].Substring(3);
38
                    string MT = roh_daten[i+2].Substring(3);
39
                    */
40
41
                    double Zeitstempel = (Convert.ToDouble(roh_daten[i].Substring(3)) + (Convert.ToDouble(roh_daten[i + 1].Substring(3)) / 1000));
42
                    double Temperatur = Convert.ToDouble(roh_daten[i + 2].Substring(3));
43
44
                    bilgi.Add(Zeitstempel, Temperatur);
45
46
47
                    i += 3;
48
                }
49
50
                Datenreihe = bilgi;
51
                UpdateGraph();
52
53
            }
54
55
            if (text.StartsWith("+P"))
56
            {
57
58
                string[] roh_daten = text.Split('\r');
59
                textBox1.Text = roh_daten[0].Substring(3);
60
                textBox2.Text = roh_daten[1].Substring(3);
61
                textBox3.Text = roh_daten[2].Substring(3);
62
                textBox4.Text = roh_daten[3].Substring(3);
63
64
65
            }
66
67
            if (text.Equals("_A"))
68
                lbl_auto.Visible = true;
69
            if (text.Equals("_M"))
70
                lbl_auto.Visible = false;
71
72
            if (text.StartsWith(":I"))
73
                lbl_Phase.Text = text.Substring(2);
74
                
75
76
            
77
78
79
        }

: Bearbeitet durch User
von Johnny B. (johnnyb)


Lesenswert?

Mit ReadLine() und ReadExisting() wäre ich sehr vorsichtig, denn das 
Problem damit ist, dass diese noch mit dem auf dem PC eingestellten 
Textencoding zusammenhängen (Codepage, UTF, etc.). Es kann dann 
passieren, dass Dein programmierter Textparser z.B. auf einem Deutschen 
Windows geht und auf einem Englischen nicht oder umgekehrt.
Daher würde ich solche Daten immer unverfälscht mit Read() einlesen und 
danach erst das Bytearray mit dem passenden Encoding in einen String 
umwandeln. Für ASCII, wie es von einem Mikrocontroller kommt, so:
System.Text.Encoding.ASCII.GetString(rxbuffer);

von Dirk (Gast)


Lesenswert?

Die API kann ich Dir empfehlen. Kannst Du einfach per Nuget Package zu 
deinem Projekt adden.


https://github.com/jcurl/serialportstream

Beispiele:

https://github.com/jcurl/SerialPortStream/wiki

von Walt N. (belayason)


Lesenswert?

Ich habe jetzt einen Event erstellt. Nur bin ich mir noch nicht so 
sicher wie das ganze funktioniert mit den Events. In diesem Eventhandler 
von dir lese ich quasi jedesmal ein und mehr nicht. Die Umwandlung in 
den String und die ausgabe an die Textboxen mache ich wohl eher nicht 
auch noch in dem Eventhandler? Wie komme ich jetzt außerhalb dieses 
eventhandlers an diesen rxbuffer um damit die umwandlungen zu machen und 
kontinuierlich die neuen werte an die textboxen weitergebe

von Cyblord -. (cyblord)


Lesenswert?

Walt N. schrieb:
> Ich habe jetzt einen Event erstellt. Nur bin ich mir noch nicht so
> sicher wie das ganze funktioniert mit den Events. In diesem Eventhandler
> von dir lese ich quasi jedesmal ein und mehr nicht. Die Umwandlung in
> den String und die ausgabe an die Textboxen mache ich wohl eher nicht
> auch noch in dem Eventhandler? Wie komme ich jetzt außerhalb dieses
> eventhandlers an diesen rxbuffer um damit die umwandlungen zu machen und
> kontinuierlich die neuen werte an die textboxen weitergebe

Lern erst mal mal ein paar Grundlagen. Wie wär das?

von Bülent C. (mirki)


Lesenswert?

Walt N. schrieb:
> Ich habe jetzt einen Event erstellt. Nur bin ich mir noch nicht so
> sicher wie das ganze funktioniert mit den Events. In diesem Eventhandler
> von dir lese ich quasi jedesmal ein und mehr nicht. Die Umwandlung in
> den String und die ausgabe an die Textboxen mache ich wohl eher nicht
> auch noch in dem Eventhandler? Wie komme ich jetzt außerhalb dieses
> eventhandlers an diesen rxbuffer um damit die umwandlungen zu machen und
> kontinuierlich die neuen werte an die textboxen weitergebe

Du hast es mundgerecht serviert bekommen, und kannst damit immer noch 
nichts anfangen? Wenn man einige Techniken nicht kennt, sollte man aber 
zumindestens Code lesen können, wenn man an sowas rangeht.... Geh Tee 
kochen

: Bearbeitet durch User
von Bert3 (Gast)


Lesenswert?

> Die Umwandlung in
> den String und die ausgabe an die Textboxen mache ich wohl eher nicht
> auch noch in dem Eventhandler?

auf jeden Fall ist der Event-Handler die Quelle deiner Daten und dann 
rufst du dort eben die Funktion welche die Daten weiter verarbeiten soll 
auf - ob du dann irgendwann darunter eine Textbox fuellst oder sonstiges 
machst ist völlig egal

ganz wichtig: serielle Kommunikation ist kein Protokoll mit festen 
Datengrößen oder irgendwelchen Garantien sondern ein Stream wie TCP/IP

wenn der Sender "hallo" schickt kann es sein das dein Event-Handler 
mehrfach aufgerufen wird

1. Event "ha"
2. Event "ll"
3. Event "o"

und alle anderen Varianten - das MUSS deine weiter Verarbeitung können
(denk nicht nur weil das jetzt so aussieht als wenn alles als Block 
kommt das es auch so bleibt - GRÖSSTER Fehler in der RS232 
programmierung)

und über RS232 gibt es keinen Protokollstandard - d.h. es ist total 
abhängig vom Sender wie die Verarbeitung aussehen muss - hast du eine 
Protokollbeschreibung?

folge Varianten könnte es geben

du schickst per RS232 ein Befehl und das Gerät antwortet (oder sendet 
z.B. auch ständig Daten):
dabei kann es sein:
-du kennst die notwendige Datenmenge aufgrund des abgesetzen Befehls 
(und wartes auf die Menge)
-die ist irgendwo am Anfang der Antwort mit eingebettet (du merkst dir 
die Anzahl und wartest so lange)
-es gibt eine Endekennung (Zeichenfolge), du wartest auf die Endekennung

damit kannst du die Daten die per seriell kommen wieder in logische 
Pakete zerlegen

Wenn du uns die Beschreibung zu dem RS32 Protokoll zeigst wäres es 
einfacher - oder einen Gerätenamen usw.

von W.S. (Gast)


Lesenswert?

Walt N. schrieb:
> Jetzt habe ich
> noch eine Anwendung in C# programmiert die mir den String aufteilt und
> in Textboxen anzeigt

und
1
 private void button1_Click(object sender, EventArgs e)
2
 { serial.Open();
3
   ...
4
   while (true)
5
   { string A = serial.ReadLine();
6
     ...
7
   }
8
  ...

Dein Programm ist es, was den Mist baut. Nun hab ich mich bislang NICHT 
mit Cdoppelvollpfosten befaßt, aber so ganz allgemein macht man das etwa 
so:

1. man kreiert einen zweiten Thread, der rein prozedural läuft (also 
NICHT ereignisgesteuert) und der in sinnvollen Zeitabschnitten (20..50 
ms) den seriellen Kanal (serial.xxxx...) pollt und bei Vorliegen von 
Empfangszeichen diese zu einer Art Kommandozeile, also einem String 
zusammenfaßt. Wenn diese Zeile voll ist (entweder ein Zeilenende erkannt 
oder eine Maximalanzahl von Zeichen drin), dann übergibt dieser Thread 
dem Hauptthread die Zeile. Entweder per Event oder wenn's geht, eben 
direkt, z.B. Refresh oder per Invalidate eines der Anzeigen oder so 
ähnlich.

W.S.

von Bert3 (Gast)


Lesenswert?

>1. man kreiert einen zweiten Thread, der rein prozedural läuft (also
>NICHT ereignisgesteuert) und der in sinnvollen Zeitabschnitten (20..50
>ms) den seriellen Kanal (serial.xxxx...) pollt und bei Vorliegen von
>Empfangszeichen diese zu einer Art Kommandozeile, also einem String
>zusammenfaßt.

Äußerst unnötig verkompiliziert, warum sollte er dafür einen Thread 
brauchen - die Unabhängigkeit von seiner Konsole/UI ist doch schon durch 
den Event gegeben, du erweiterst das Push-Prinzip des Events noch mit 
einem pollenden Pull-Prinzip und machst das ganze dann auch noch gleich 
Data-Race-Condition freundlich

Er ist doch schon genug am rudern da sind solche Tips nicht wirklich 
hilfreich

von Bülent C. (mirki)


Lesenswert?

W.S. schrieb:
> 1. man kreiert einen zweiten Thread, der rein prozedural läuft (also
> NICHT ereignisgesteuert) und der in sinnvollen Zeitabschnitten (20..50
> ms) den seriellen Kanal (serial.xxxx...) pollt

zu kompliziert
1
 private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
2
        {
3
            
4
            InputData = ComPort.ReadExisting();
5
            if (InputData != String.Empty)
6
            {
7
                this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
8
            }

: Bearbeitet durch User
von Johnny B. (johnnyb)


Lesenswert?

Walt N. schrieb:
> Ich habe jetzt einen Event erstellt. Nur bin ich mir noch nicht so
> sicher wie das ganze funktioniert mit den Events. In diesem Eventhandler
> von dir lese ich quasi jedesmal ein und mehr nicht. Die Umwandlung in
> den String und die ausgabe an die Textboxen mache ich wohl eher nicht
> auch noch in dem Eventhandler? Wie komme ich jetzt außerhalb dieses
> eventhandlers an diesen rxbuffer um damit die umwandlungen zu machen und
> kontinuierlich die neuen werte an die textboxen weitergebe

Für einfache Dinge kannst die Verarbeitung und Ausgabe des Strings schon 
im Eventhandler erledigen. Der Computer erledigt das ja normalerweise im 
(geschätzt) Millisekundenbereich.
Wie bereits erwähnt wurde, weisst Du nicht, wievielmal der Eventhandler 
aufgerufen wird, bis Deine Daten vollständig empfangen wurden; daher 
wirst Du noch einen Buffer brauchen, welcher alles empfangene sammelt. 
Das geht gut mit z.B. einer Liste:
1
List<byte> empfangsbuffer = new List<byte>();

Im Eventhandler kannst Du dann einfach die empfangenen Daten zufügen:
1
empfangsbuffer.AddRange(rxbuffer);
2
3
// Nun auswerten ob Du ein komplettes Datenpacket hast und wenn ja, dieses verarbeiten und aus "empfangsbuffer" löschen.

Da der Eventhandler asynchron von einem anderen Thread aufgerufen wird, 
kannst Du darin nicht einfach GUI-Elemente ansprechen, sonst stürtzt das 
Programm ab. In einem Beispiel oben findest Du die Lösung dafür mit 
"BeginInvoke"...

von Donni D. (Gast)


Lesenswert?

Ich würde lieber diese Eigenschaft nutzen: 
https://msdn.microsoft.com/de-de/library/system.io.ports.serialport.receivedbytesthreshold(v=vs.110).aspx

Threshold so setzen, das Minimum ein Paket da ist, auslesen, fertig.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.