Forum: PC-Programmierung WinForms C# System.IO.Ports


von StarWars (Gast)


Lesenswert?

Hallo und guten Morgen,

in einer WinForms C# Anwendung wird die Bibliothek "System.IO.Ports" 
verwendet serielle Daten von der UART eines Mikrocntrollers zu 
empfangen.
Für den Empfang von den seriellen Daten wird der Receive Hndler 
eingesetzt.

Der Mikrocontroller versendet zyklisch immer 10 Bytes. Der Empfang 
funktioniert nicht so stabil. Es kommen manchmal nur 2 Bytes an oder 5 
Bytes. Wenn ich das Hterm Tool benutzte sind alle Daten korrekt. WIe 
kann das sein.
1
private void Init_Click(object sender, EventArgs e)
2
{
3
  myPort = new SerialPort("COM2");
4
5
  myPort.BaudRate = 115200;
6
  myPort.Parity = Parity.None;
7
  myPort.StopBits = StopBits.One;
8
  myPort.DataBits = 8;
9
  myPort.Handshake = Handshake.None;
10
  myPort.ReadTimeout = 2000;
11
12
  myPort.Open();
13
  myPort.DataReceived += new SerialDataReceivedEventHandler(OnReceiveSerialData);
14
}
15
16
public void OnReceiveSerialData(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
17
{
18
  SerialPort sp = (SerialPort)sender;
19
  string indata = sp.ReadExisting();
20
  
21
  this.Invoke((MethodInvoker)delegate()
22
  {
23
    rtb_RecvData.Text += indata;
24
  });
25
}

von StarWars (Gast)


Lesenswert?

Die Daten müssten eigentlich immer so angezeigt werden:
1012345678910

Wie kann es sein dass die Daten ab und zu so empfangen werden?
1
1012345678910
2
10123456
3
78910
4
1012345678910

von falscher Ansatz (Gast)


Lesenswert?

Sobald der serial Port Daten empfängt, wird ein Interrupt erzeugt und Du 
liest sie aus. Ob die Daten dann schon vollständig sind, weißt Du zu dem 
Zeitpunkt nicht.

von StarWars (Gast)


Lesenswert?

Ja und was kann ich da nun konkret tun?

WIe gesagt mit dem Hterm Tool funktioniert das ohne Probleme.

von abc (Gast)


Lesenswert?

Einfach Bytes zählen

von Dirk K. (merciless)


Lesenswert?

In der Doku

https://docs.microsoft.com/de-de/dotnet/api/system.io.ports.serialport?view=dotnet-plat-ext-6.0

erzeugen sie einen separaten Thread und lesen kontinuierlich.
Ausprobieren?

merciless

von Axel R. (axlr)


Lesenswert?

sp.Readexisting  muss ja erstmal mit "to String" gewandelt werden, oder? 
So werden deine chars entsprechend der Nummerirung der Ascii-tabelle mit 
+= aufaddiert. Daher die unterschiedlich langen Zahlen, die du dort 
siehst. Was schickt der µC denn für Daten? "Hallo World"?

von Jim M. (turboj)


Lesenswert?

StarWars schrieb:
> Wie kann es sein dass die Daten ab und zu so empfangen werden?
> 1012345678910
> 10123456
> 78910
> 1012345678910

Du hast einen USB2UART Chip dazwischen. Der fasst die Daten zu USB 
Paketen zusammen - aber er ist dumm und weiss nicht wo die Pakete 
anfangen und aufhören sollen.

Wenn dem µC beim Senden ein Interrupt dazwischen funkt, wird er die 10 
Bytes in 2 Pakete aufteilen, da ja für kuze zeit der UART Datenstrom 
unterbrochen ist.

Abhilfe: Auf PC Anwendungsseite nochmal vernünftig puffern und Daten neu 
zusammen setzen.

von blubb (Gast)


Lesenswert?

Hier mal einen Code, mit dem ich einen LDM ausgelesen habe. Ist 
natürlich viel Overhead, aber das Prinzip ist klar.
1
using System;
2
using System.Collections.Generic;
3
using System.Collections.Concurrent;
4
using System.Data;
5
using System.Linq;
6
using System.IO.Ports;
7
8
/// <summary>
9
/// Stellt Methoden und Funktionen zur Nutzung des optoNCDT ILR 1181 Laser-Distanzmesser bereit.
10
/// </summary>
11
namespace DriverExtension
12
{
13
    public class LaserDistanceMeter : IDisposable
14
    {
15
        // SerialPort _serialPort = new SerialPort();
16
        Measurement _measurement = new Measurement();
17
        optoNCDT _distanceMeter;
18
19
        bool _continuous = false;
20
21
        public event EventHandler<Measurement> PrecisionMeasurementCompleted;
22
        public event EventHandler<Measurement> SerialDataArrived;
23
        public event EventHandler<Measurement> SingeShotDataArrived;
24
        public event EventHandler<string> LaserDistanceMeterException;
25
26
        /// <summary>
27
        /// Standardkonstruktor
28
        /// </summary>
29
        public LaserDistanceMeter()
30
        {
31
            try
32
            {
33
                _distanceMeter = new optoNCDT();
34
35
                _distanceMeter.OnValueReceived += LDM_OnValueReceived;
36
                _distanceMeter.OnSeriesMeasurementCompleted += LDM_OnSeriesMeasurementCompleted;
37
            }
38
            catch (Exception ex)
39
            {
40
                throw ex;
41
            }
42
        }
43
44
        /// <summary>
45
        /// Standardkonstruktor
46
        /// </summary>
47
        /// <param name="serialPortName">Erwartet den Namen eines gültigen COM-Port.</param>
48
        public LaserDistanceMeter(string serialPortName)
49
        {
50
            try
51
            {
52
                _distanceMeter = new optoNCDT(serialPortName);
53
54
                _distanceMeter.OnValueReceived += LDM_OnValueReceived;
55
                _distanceMeter.OnSeriesMeasurementCompleted += LDM_OnSeriesMeasurementCompleted;
56
            }
57
            catch (Exception ex)
58
            {
59
                throw ex;
60
            }
61
        }
62
63
        /// <summary>
64
        /// Ereignis, das nach erfolgter Serienmessung aufgerufen wird.
65
        /// </summary>
66
        private void LDM_OnSeriesMeasurementCompleted()
67
        {
68
            if (PrecisionMeasurementCompleted != null)
69
                PrecisionMeasurementCompleted(this, _measurement);
70
        }
71
72
        /// <summary>
73
        /// Wird aufgerufen, wenn die serielle Schnittstelle Daten empfangen hat, die als Messwerte ausgewertet werden konnten.
74
        /// </summary>
75
        private void LDM_OnValueReceived()
76
        {
77
            ConcurrentQueue<optoNCDT.ComPortDataAnalyzer> buffer;
78
            optoNCDT.ComPortDataAnalyzer data;
79
80
            _distanceMeter.Queue(out buffer);
81
82
            while (buffer.TryDequeue(out data))
83
            {
84
                List<double> lstBuffer = data.Values;
85
86
                _measurement.Values.AddRange(lstBuffer);
87
88
                string err = _distanceMeter.GetErrorDescription;
89
90
                if (err != null)
91
                {
92
                    if (LaserDistanceMeterException != null)
93
                        LaserDistanceMeterException(this, err);
94
                }
95
                else
96
                {
97
                    if (_continuous)
98
                    {
99
                        if (SerialDataArrived != null)
100
                            SerialDataArrived(this, _measurement);
101
                    } else
102
                    {
103
                        if (SingeShotDataArrived != null)
104
                            SingeShotDataArrived(this, _measurement);
105
                    }
106
                }
107
            }
108
        }
109
110
        /// <summary>
111
        /// Führt eine Einzelmessung durch.
112
        /// Achtung: Die Messgenauigkeit ist stark eingeschränkt!
113
        /// </summary>
114
        public void SingleShot()
115
        {
116
            // Felder bereinigen
117
            Clear();
118
119
            // Einzelmessung
120
            _continuous = false;
121
122
            // Einzelmessung durchführen
123
            _distanceMeter.OneShot();
124
        }
125
126
        /// <summary>
127
        /// Löscht alle aktuell vorhandenen Messwerte
128
        /// </summary>
129
        public void Clear()
130
        {
131
            _measurement.Values.Clear();
132
        }
133
134
        /// <summary>
135
        /// Setzt den Offset des optoNCDT 1181 auf die aktuelle Position.
136
        /// </summary>
137
        public void Zeroize()
138
        {
139
            _distanceMeter.SetNull();
140
        }
141
142
        /// <summary>
143
        /// Startet eine einzelne Präzisionsmessung.
144
        /// </summary>
145
        public void StartPrecisionMeasurement()
146
        {
147
            this.Clear();
148
            _distanceMeter.EnableAutoStop(5);
149
            _distanceMeter.DistanceTracking();
150
        }
151
152
        /// <summary>
153
        /// Startet eine Intervallmessung.
154
        /// </summary>
155
        public void StartContinuousMeasurement()
156
        {
157
            _continuous = true;
158
159
            this.Clear();
160
            _distanceMeter.DistanceTracking();
161
        }
162
163
        /// <summary>
164
        /// Beendet eine Intervallmessung.
165
        /// </summary>
166
        public void StopContinuousMeasurement()
167
        {
168
            _continuous = false;
169
170
            this.Clear();
171
            _distanceMeter.Stop();
172
        }
173
174
        /// <summary>
175
        /// Schließt die Verbindung zum optoNCDT ILR 1181.
176
        /// </summary>
177
        public void Dispose()
178
        {
179
            _distanceMeter.Stop();
180
            _distanceMeter.Dispose();
181
        }
182
    }
183
184
    /// <summary>
185
    /// Hilfsobjekt zur einfachen Handhabung der Messdaten
186
    /// </summary>
187
    public class Measurement
188
    {
189
        List<double> _measuredValues = new List<double>();
190
191
        double _min, _max, _diff, _avg, _mdn;
192
        double _fmin, _fmax, _fdiff, _favg, _fmdn;
193
194
        /// <summary>
195
        /// Hält die Messwerte, die übergeben wurden.
196
        /// </summary>
197
        public List<double> Values { get { return _measuredValues; } set { _measuredValues = value; } }
198
        /// <summary>
199
        /// Gibt den Minimalwert alle Elemente von <see cref="Values"/> zurück.
200
        /// </summary>
201
        public double Min { get { Calc(); return _min; } set { _min = value; } }
202
        /// <summary>
203
        /// Gibt den Maximalwert alle Elemente von <see cref="Values"/> zurück.
204
        /// </summary>
205
        public double Max { get { Calc(); return _max; } set { _max = value; } }
206
        /// <summary>
207
        /// Gibt die Differenz zwischen <see cref="Max"/> und <see cref="Min"/> zurück.
208
        /// </summary>
209
        public double Diff { get { Calc(); return _diff; } set { _diff = value; } }
210
        /// <summary>
211
        /// Gibt den Mittelwert alle Elemente von <see cref="Values"/> zurück.
212
        /// </summary>
213
        public double Avg { get { Calc(); return _avg; } set { _avg = value; } }
214
        /// <summary>
215
        /// Gibt den Median alle Elemente von <see cref="Values"/> zurück.
216
        /// </summary>
217
        public double Mdn { get { Calc(); return _mdn; } set { _mdn = value; } }
218
219
        /// <summary>
220
        /// Gibt den Minimalwert der letzten zehn Elemente von <see cref="Values"/> zurück.
221
        /// </summary>
222
        public double FloatingMin { get { Calc(); return _fmin; } set { _fmin = value; } }
223
        /// <summary>
224
        /// Gibt den Maximalwert der letzten zehn Elemente von <see cref="Values"/> zurück.
225
        /// </summary>
226
        public double FloatingMax { get { Calc(); return _fmax; } set { _fmax = value; } }
227
        /// <summary>
228
        /// Gibt die Differenz zwischen <see cref="Max"/> und <see cref="Min"/> zurück.
229
        /// </summary>
230
        public double FloatingDiff { get { Calc(); return _fdiff; } set { _fdiff = value; } }
231
        /// <summary>
232
        /// Gibt den Mittelwert der letzten zehn Elemente von <see cref="Values"/> zurück.
233
        /// </summary>
234
        public double FloatingAvg { get { Calc(); return _favg; } set { _favg = value; } }
235
        /// <summary>
236
        /// Gibt den Median der letzten zehn Elemente von <see cref="Values"/> zurück.
237
        /// </summary>
238
        public double FloatingMdn { get { Calc(); return _fmdn; } set { _fmdn = value; } }
239
240
        /// <summary>
241
        /// Führt die Berechnungen der einzelnen Parameter durch.
242
        /// </summary>
243
        private void Calc()
244
        {
245
            if (_measuredValues.Count == 0)
246
                return;
247
248
            // Berechnung über alle Werte
249
            _min = _measuredValues.Min();
250
            _max = _measuredValues.Max();
251
            _diff = _max - _min;
252
            _avg = _measuredValues.Average();
253
254
            List<double> median = _measuredValues;
255
256
            _mdn = Median(median.ToArray());
257
258
            // Berechnung über die letzten 10 Werte
259
            int offset = 0;
260
            int count = 0;
261
262
            offset = _measuredValues.Count < 5 ? 0 : _measuredValues.Count - 5;
263
            count = _measuredValues.Count < 5 ? _measuredValues.Count : 5;
264
265
            List<double> buffer = _measuredValues.GetRange(offset, count);
266
267
            _fmin = buffer.Min();
268
            _fmax = buffer.Max();
269
            _fdiff = _fmax - _fmin;
270
            _avg = buffer.Average();
271
272
            List<double> floatingMedian = buffer;
273
274
            _fmdn = Median(floatingMedian.ToArray());
275
        }
276
277
        /// <summary>
278
        /// Berechnet den Median aus einem Array.
279
        /// </summary>
280
        /// <param name="xs"></param>
281
        /// <returns></returns>
282
        private double Median(double[] xs)
283
        {
284
            var ys = xs.OrderBy(x => x).ToList();
285
            double mid = (ys.Count - 1) / 2.0;
286
            return (ys[(int)(mid)] + ys[(int)(mid + 0.5)]) / 2;
287
        }
288
    }
289
290
    /// <summary>
291
    /// Diese Klasse regelt den Zugriff auf das optoNCDT ILR 1181.
292
    /// </summary>
293
    sealed class optoNCDT : IDisposable
294
    {
295
        #region Fields
296
297
        /// <summary>
298
        /// Instanz des ComPortDataAnalyzer-Objektes.
299
        /// </summary>
300
        ComPortDataAnalyzer _comPortDataAnalyzer = new ComPortDataAnalyzer();
301
302
        /// <summary>
303
        /// Threadsichere <see cref="ConcurrentQueue{T}"/> zur Übergabe der Daten zwischen Listener-, ausführendem, und UI-Thread.
304
        /// </summary>
305
        ConcurrentQueue<ComPortDataAnalyzer> _dataQueue = new ConcurrentQueue<ComPortDataAnalyzer>();
306
307
        /// <summary>
308
        /// Hält, ob die Messung automatisch beendet wird (Intervallmessung).
309
        /// </summary>
310
        private bool _autoStop = false;
311
312
        /// <summary>
313
        /// Gibt die Anzahl an Messwerten an, ab der eine Serienmessung beendet wird.
314
        /// </summary>
315
        private int _autoStopNumOfMeasurements;
316
317
        /// <summary>
318
        /// Messwertzähler
319
        /// </summary>
320
        private int _autoStopCounter = 0;
321
322
        /// <summary>
323
        /// Signalisiert das Ende eines Befehls.
324
        /// </summary>
325
        private string _endOfCommand = "\r";
326
327
        /// <summary>
328
        /// Gibt an, ob Daten empfangen wurden. Falls true, finden sich die Werte in <see cref="_comPortDataAnalyzer.Data"/>.
329
        /// </summary>
330
        private bool _dataReceived = false;
331
332
        /// <summary>
333
        /// Beinhaltet die dem Fehlercode entsprechende Fehlerbeschreibung des optoNCDT Laser-Entfernungsmesser.
334
        /// </summary>
335
        private string _errorDescription = null;
336
337
        /// <summary>
338
        /// Speichert das aktive <see cref="SerialPort"/>-Objekt.
339
        /// </summary>
340
        private SerialPort _serialPort;
341
342
        /// <summary>
343
        /// Hält, ob ein Port gefunden wurde, und damit aktiv bleiben soll.
344
        /// </summary>
345
        private bool _dontClosePort = false;
346
347
        /// <summary>
348
        /// Hält, ob es sich um den Initialisierungsvorgang handelt.
349
        /// </summary>
350
        private bool _isInit = true;
351
        #endregion
352
353
        #region Constructors
354
355
        /// <summary>
356
        /// Erstellt eine neue Instanz der <see cref="optoNCDT"/>-Klasse.
357
        /// </summary>
358
        /// <param name="serialPort">Erwartet einen gültigen Bezeichner für eine serielle Schnittstelle.</param>
359
        public optoNCDT(string serialPort)
360
        {
361
            _serialPort = new SerialPort();
362
363
            // Events bei Dateneingang feuern
364
            _serialPort.DataReceived += SerialPort_DataReceived;
365
366
            // COM-Port vorbereiten
367
            SetUpSerialPort();
368
369
            // Alle Ports durchlaufen
370
            _serialPort.PortName = serialPort;
371
372
            try
373
            {
374
                // Versuche den COM-Port zu öffnen
375
                _serialPort.Open();
376
377
                // Befehl "id" schreiben, um Type des Sensors zu ermitteln
378
                _serialPort.Write("id" + _endOfCommand);
379
380
                int delayBeforeTrigger = 4000;
381
                int timeElapsed = 0;
382
                bool timeOut = false;
383
384
                // Warten, bis Daten angekommen sind
385
                while (_dataReceived == false && timeOut == false)
386
                {
387
                    System.Threading.Thread.Sleep(delayBeforeTrigger / 100);
388
389
                    timeElapsed += delayBeforeTrigger / 100;
390
391
                    // Wenn mittlerweile Daten empfangen wurden -> abbrechen
392
                    if (_dataReceived) break;
393
394
                    // Timeout?
395
                    if (timeElapsed >= delayBeforeTrigger)
396
                        timeOut = true;
397
                }
398
399
                // Auf empfangene Daten prüfen
400
                if (_dataReceived)
401
                {
402
                    ComPortDataAnalyzer data;
403
404
                    _dataQueue.TryDequeue(out data);
405
406
                    // Auswerten, ob die Daten von dem Laser-Distanzmesser stammen
407
                    if (data.Data.Left(7) == "ILR1181" || IsErrorCode(data.Data))
408
                    {
409
                        // Ausgewählten Port nicht schließen
410
                        _dontClosePort = true;
411
412
                    }
413
                }
414
415
                // Auf timeout prüfen
416
                if (timeOut)
417
                {
418
                    throw new optoNCDTTimeoutException();
419
                }
420
421
                // Puffer leeren
422
                _dataReceived = false;
423
424
            }
425
            catch (Exception ex)
426
            {
427
                throw ex;
428
            }
429
            finally
430
            {
431
                // Port nicht gefunden?
432
                if (_dontClosePort == false)
433
                {
434
                    // Falschen COM-Port schließen
435
                    _serialPort.Close();
436
                }
437
                else
438
                {
439
                    // Port gefunden -> Initialisierung fertig
440
                    _isInit = false;
441
                }
442
            }
443
        }
444
445
        /// <summary>
446
        /// Erstellt eine neue Instanz der <see cref="optoNCDT"/>-Klasse.
447
        /// </summary>
448
        public optoNCDT()
449
        {
450
            // Hält, ob der Port mit angeschlossenem LDM gefunden wurde
451
            bool portFoundSuccessfully = false;
452
453
            _serialPort = new SerialPort();
454
455
            // Events bei Dateneingang feuern
456
            _serialPort.DataReceived += SerialPort_DataReceived;
457
458
            // COM-Port vorbereiten
459
            SetUpSerialPort();
460
461
            foreach (string port in GetAvailableComPorts().OrderBy(x => x).ToList())
462
            {
463
                // Abbrechen?
464
                if (portFoundSuccessfully)
465
                    break;
466
467
                // Alle Ports durchlaufen
468
                _serialPort.PortName = port;
469
470
                try
471
                {
472
                    // Versuche den COM-Port zu öffnen
473
                    _serialPort.Open();
474
475
                    // Befehl "id" schreiben, um Type des Sensors zu ermitteln
476
                    _serialPort.Write("id" + _endOfCommand);
477
478
                    int delayBeforeTrigger = 4000;
479
                    int timeElapsed = 0;
480
                    bool timeOut = false;
481
482
                    // Warten, bis Daten angekommen sind
483
                    while (_dataReceived == false && timeOut == false)
484
                    {
485
                        System.Threading.Thread.Sleep(delayBeforeTrigger / 100);
486
487
                        timeElapsed += delayBeforeTrigger / 100;
488
489
                        // Wenn mittlerweile Daten empfangen wurden -> abbrechen
490
                        if (_dataReceived) break;
491
492
                        // Timeout?
493
                        if (timeElapsed >= delayBeforeTrigger)
494
                            timeOut = true;
495
                    }
496
497
                    // Auf empfangene Daten prüfen
498
                    if (_dataReceived)
499
                    {
500
                        ComPortDataAnalyzer data;
501
502
                        _dataQueue.TryDequeue(out data);
503
504
                        // Auswerten, ob die Daten von dem Laser-Distanzmesser stammen
505
                        if (data.Data.Left(7) == "ILR1181" || IsErrorCode(data.Data))
506
                        {
507
                            // Ausgewählten Port nicht schließen
508
                            _dontClosePort = true;
509
510
                            // Weitern Suchvorgang abbrechen
511
                            portFoundSuccessfully = true;
512
                        }
513
                    }
514
515
                    // Auf timeout prüfen
516
                    if (timeOut)
517
                    {
518
                        throw new optoNCDTTimeoutException();
519
                    }
520
521
                    // Puffer leeren
522
                    _dataReceived = false;
523
524
                }
525
                catch (Exception ex)
526
                {
527
                    throw ex;
528
                }
529
                finally
530
                {
531
                    // Port nicht gefunden?
532
                    if (_dontClosePort == false)
533
                    {
534
                        // Falschen COM-Port schließen
535
                        _serialPort.Close();
536
                    }
537
                    else
538
                    {
539
                        // Port gefunden -> Initialisierung fertig
540
                        _isInit = false;
541
                    }
542
                }
543
            }
544
        }
545
        #endregion
546
547
        #region Destructors
548
549
        /// <summary>
550
        /// Alle Verbindungen schließen und Daten verwerfen
551
        /// </summary>
552
        public void Dispose()
553
        {
554
            // Stopp-Befehl senden
555
            Stop();
556
557
            // Puffer leeren
558
            _serialPort.DiscardOutBuffer();
559
            _serialPort.DiscardInBuffer();
560
561
            // Ereignisdelegat abmelden
562
            _serialPort.DataReceived -= SerialPort_DataReceived;
563
564
            // Seriellen Port schließen und verwerfen
565
            _serialPort.Close();
566
            _serialPort.Dispose();
567
        }
568
        #endregion
569
570
        #region Delegates
571
572
        /// <summary>
573
        /// Delegat, der beim Auslösen des <see cref="OnSeriesMeasurementCompleted"/>-Event aufgerufen wird.
574
        /// </summary>
575
        public delegate void SeriesMeasurementCompleted();
576
577
        /// <summary>
578
        /// Delegat, der beim Auslösen des <see cref="OnValueReceived"/>-Event aufgerufen wird.
579
        /// </summary>
580
        public delegate void ValueReceived();
581
582
        #endregion
583
584
        #region Events
585
586
        /// <summary>
587
        /// Wird ausgelöst, wenn Werte über die RS232-Schnittstelle empfangen wurden.
588
        /// </summary>
589
        public event ValueReceived OnValueReceived;
590
591
        /// <summary>
592
        /// Wird aufgerufen, wenn die Serienmessung abgeschlossen wurde.
593
        /// </summary>
594
        public event SeriesMeasurementCompleted OnSeriesMeasurementCompleted;
595
596
        #endregion
597
598
        #region Properties
599
600
        /// <summary>
601
        /// Gibt die Beschreibung des zuletzt aufgetretenen Fehler zurück.
602
        /// </summary>
603
        public string GetErrorDescription
604
        {
605
            get { return _errorDescription; }
606
        }
607
608
        /// <summary>
609
        /// Liefert den Gerätenamen des angeschlossenen Gerätes.
610
        /// </summary>
611
        public string GetDeviceName
612
        {
613
            get { return "ILR1181"; }
614
        }
615
616
        #endregion
617
618
        #region Methods
619
620
        /// <summary>
621
        /// Führt eine Einzelmessung durch.
622
        /// </summary>
623
        public void OneShot()
624
        {
625
            _serialPort.WriteLine("dm" + _endOfCommand);
626
        }
627
628
        /// <summary>
629
        /// Führt das Distanztracking aus.
630
        /// </summary>
631
        public void DistanceTracking()
632
        {
633
            _serialPort.WriteLine("dt" + _endOfCommand);
634
        }
635
636
        /// <summary>
637
        /// Setzt den Nullpunkt des LDM.
638
        /// </summary>
639
        public void SetNull()
640
        {
641
            _serialPort.WriteLine("so" + _endOfCommand);
642
        }
643
644
        /// <summary>
645
        /// Beendet die Serienaufnahme.
646
        /// </summary>
647
        public void Stop()
648
        {
649
            _serialPort.WriteLine("lo" + _endOfCommand);
650
        }
651
652
        /// <summary>
653
        /// Liefert ein Objekt aus der <see cref="ComPortDataAnalyzer"/>-Queue.
654
        /// </summary>
655
        /// <param name="retval">Referenz auf ein <see cref="ComPortDataAnalyzer"/>-Objekt.</param>
656
        /// <returns>True, falls ein Element aus der Queue gelesen wurde.</returns>
657
        public bool Queue(out ComPortDataAnalyzer retval)
658
        {
659
            return _dataQueue.TryDequeue(out retval);
660
        }
661
662
        /// <summary>
663
        /// Liefert eine Referenz auf eine <see cref="ConcurrentQueue{T}"/>.
664
        /// </summary>
665
        /// <param name="retval"></param>
666
        public void Queue(out ConcurrentQueue<ComPortDataAnalyzer> retval)
667
        {
668
            retval = _dataQueue;
669
        }
670
671
        /// <summary>
672
        /// Aktiviert die Autostop-Funktion für Serienmessungen.
673
        /// </summary>
674
        /// <param name="numOfMeasurements">Die Anzahl an Messungen, nach der die Datenerfassung angehalten werden soll.</param>
675
        public void EnableAutoStop(int numOfMeasurements = 10)
676
        {
677
            _autoStopNumOfMeasurements = numOfMeasurements;
678
            _autoStopCounter = 0;
679
            _autoStop = true;
680
        }
681
682
        /// <summary>
683
        /// Liefert alle verfügbaren COM-Ports zurück.
684
        /// </summary>
685
        /// <returns></returns>
686
        private string[] GetAvailableComPorts()
687
        {
688
            return SerialPort.GetPortNames();
689
        }
690
691
        /// <summary>
692
        /// Setzt alle Einstellung zur Nutzung des COM-Port
693
        /// </summary>
694
        private void SetUpSerialPort()
695
        {
696
            _serialPort.ReadTimeout = 1000;
697
            _serialPort.WriteTimeout = 1000;
698
            _serialPort.BaudRate = 9600;
699
            _serialPort.DataBits = 8;
700
            _serialPort.StopBits = StopBits.One;
701
            _serialPort.Parity = Parity.None;
702
            _serialPort.DiscardNull = true;
703
        }
704
705
        /// <summary>
706
        /// Prüft, ob es sich bei einem String um einen Fehlercode handelt.
707
        /// </summary>
708
        /// <param name="stringToMatch">Der String, der untersucht werden soll.</param>
709
        /// <returns>Wahr, falls es sich um einen gültigen Fehlercode handelt.</returns>
710
        private bool IsErrorCode(string stringToMatch)
711
        {
712
            try
713
            {
714
                if (stringToMatch.Left(1) == "E")
715
                {
716
                    string substring = stringToMatch.Substring(1, 2);
717
                    int number;
718
719
                    if (int.TryParse(substring, out number))
720
                    {
721
                        string substring2;
722
                        substring2 = stringToMatch.Substring(3, 2);
723
724
                        if (substring2 == "\r\n")
725
                        {
726
                            return true;
727
                        }
728
                    }
729
                }
730
            }
731
            catch
732
            {
733
                return false;
734
            }
735
736
            return false;
737
        }
738
739
        /// <summary>
740
        /// Ereignis, welches beim Eintreffen von Daten am COM-Port ausgelöst wird.
741
        /// </summary>
742
        /// <param name="sender"></param>
743
        /// <param name="e"></param>
744
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
745
        {
746
            // Hält, ob die Datenaufzeichnung der Serienmessung abgeschlossen ist
747
            bool aquisitionFinished = false;
748
749
            // Lokale Instanz des Com
750
            ComPortDataAnalyzer comPortDataAnalyzer = new ComPortDataAnalyzer();
751
752
            // Lesepuffer der Daten
753
            string readBuffer;
754
755
            SerialPort sp = (SerialPort)sender;
756
757
            string[] lines;
758
759
            // Seriellen puffer für weiteren Zugriff sperren und fortfahren
760
            lock (_comPortDataAnalyzer)
761
            {
762
                // Daten in Puffer schreiben
763
                readBuffer = sp.ReadExisting();
764
                _comPortDataAnalyzer.Data += readBuffer;
765
766
                // Warten auf CRLF
767
                if (_comPortDataAnalyzer.Data.Right(2) == "\r\n")
768
                {
769
                    // Prüfen, ob der letzte Datenblock eine Fehlermeldung ist
770
                    lines = _comPortDataAnalyzer.Data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
771
772
                    // Letzten Eintrag extrahieren
773
                    string lastEntry = lines.Last().ToString();
774
775
                    // Fehlercode - falls vorhanden - umwandeln
776
                    if (lastEntry.IsErrorCode())
777
                    {
778
                        // Fehlercode parsen
779
                        _errorDescription = lastEntry.ToErrorDescription();
780
                    }
781
                    else {
782
                        _errorDescription = null;
783
                    }
784
785
                    // Angeben, dass Daten empfangen wurden
786
                    _dataReceived = true;
787
788
                    // Autostop durchführen?
789
                    if (_autoStop)
790
                    {
791
                        // Zähler aktualisieren
792
                        _autoStopCounter += lines.Count();
793
794
                        // Prüfen, ob Zählerendstand erreicht wurde
795
                        if (_autoStopCounter >= _autoStopNumOfMeasurements)
796
                        {
797
                            // Messdatenaufnahme beenden
798
                            _autoStop = false;
799
                            _autoStopCounter = 0;
800
                            Stop();
801
802
                            // angeben, dass die Messung vollständig ist
803
                            aquisitionFinished = true;
804
                        }
805
                    }
806
                }
807
                else {
808
                    // Die empfangenen Daten sind unvollständig, da der Abschlus "\r\n" fehlt
809
                    _dataReceived = false;
810
                }
811
            }
812
813
            // Prüfen, ob Daten *inklusive* EOB empfangen wurden
814
            if (_dataReceived)
815
            {
816
                // Daten aus lokalem Hilfsobjekt übernehmen
817
                comPortDataAnalyzer.Data = _comPortDataAnalyzer.Data;
818
819
                // Puffer leeren
820
                _comPortDataAnalyzer.Data = "";
821
822
                // ComPortDataAnalyzer-Objekt in die Queue einfügen
823
                _dataQueue.Enqueue(comPortDataAnalyzer);
824
825
                // Wird der COM-Port initialisiert?
826
                if (_isInit == false)
827
                {
828
                    // Empfangsereignis auslösen
829
                    OnValueReceived?.Invoke();
830
831
                    // Ereignis ausführen, wenn die Messung vollständig ist
832
                    if (aquisitionFinished)
833
                        OnSeriesMeasurementCompleted?.Invoke();
834
                }
835
            }
836
        }
837
838
        #endregion
839
840
        #region Classes
841
842
        /// <summary>
843
        /// Fehler wird ausgelöst, wenn ein Timeout bei der Suche nach einem Laser-Distanzsensor auftritt
844
        /// </summary>
845
        [Serializable]
846
        private class optoNCDTTimeoutException : Exception
847
        {
848
            public optoNCDTTimeoutException() : base() { }
849
        }
850
851
        /// <summary>
852
        /// Fehler wird ausgelöst, wenn kein Laser-Distanzsensor gefunden werden kann
853
        /// </summary>
854
        [Serializable]
855
        public class optoNCDTNotFoundException : Exception
856
        {
857
            public optoNCDTNotFoundException() : base() { }
858
        }
859
860
        /// <summary>
861
        /// Hilfsobjekt zur Datenverarbeitung und -auswertung.
862
        /// </summary>
863
        public class ComPortDataAnalyzer
864
        {
865
            /// <summary>
866
            /// Hält lokal in der Klasse die empfangenen Daten
867
            /// </summary>
868
            string _data = "";
869
            List<double> _values = new List<double>();
870
871
            /// <summary>
872
            /// Hält die vom COM-Port empfangenen Daten
873
            /// </summary>
874
            public string Data
875
            {
876
                get { return _data; }
877
                set { _data = value; ParseData(); }
878
            }
879
880
            /// <summary>
881
            /// Gibt die aus den empfangenen Daten geparsten Messwerte zurück.
882
            /// </summary>
883
            public List<double> Values
884
            {
885
                get { return _values; }
886
            }
887
888
            /// <summary>
889
            /// Wandelt die empfangenen Daten in eine <see cref="List{T}"/> um.
890
            /// </summary>
891
            public void ParseData()
892
            {
893
                double numBuffer;
894
895
                _values.Clear();
896
897
                // Sind Daten vorhanden?
898
                if (_data != "")
899
                {
900
                    // Daten am EOB-Separator auftrennen
901
                    string[] lines = _data.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
902
903
                    // Alle Einzelwerte durchlaufen
904
                    foreach (string line in lines)
905
                    {
906
                        // Prüfen, ob es sich um Messwerte handelt
907
                        if (double.TryParse(line, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out numBuffer))
908
                        {
909
                            // Wert in Liste einfügen
910
                            _values.Add(numBuffer);
911
                        }
912
                    }
913
                }
914
            }
915
        }
916
917
        #endregion
918
    }
919
920
    /// <summary>
921
    /// Erweiterung der String-Klasse um einige zusätzliche Funktionen
922
    /// </summary>
923
    public static class StringExtensions
924
    {
925
        /// <summary>
926
        /// Wandelt einen String mit einem optoNCDT ILR 1181 / 1182 Fehler in eine Fehlerbeschreibung um.
927
        /// </summary>
928
        /// <param name="value">Der String, der geprüft werden soll.</param>
929
        /// <returns>Gibt die Fehlerbeschreibung zurück.</returns>
930
        public static string ToErrorDescription(this string value)
931
        {
932
            int errorCode = int.Parse(value.Right(2));
933
            string errorDescription;
934
935
            switch (errorCode)
936
            {
937
                case 15:
938
                    errorDescription = "Zu schwache Reflexe; Abstand Sensor (Vorderkante) gegen Ziel < 0,1m.";
939
                    break;
940
                case 16:
941
                    errorDescription = "Zu starke Reflexe.";
942
                    break;
943
                case 17:
944
                    errorDescription = "Gleichlicht zu stark (zum Beispiel Sonneneinstrahlung).";
945
                    break;
946
                case 18:
947
                    errorDescription = "Nur im DX-Mode (50 Hz): zu große Abweichungen zwischen gemessenem und vorberechnetem Wert.";
948
                    break;
949
                case 19:
950
                    errorDescription = "Nur im DX-Mode (50 Hz): Verfahrgeschwindigkeit > 10 m/s.";
951
                    break;
952
                case 23:
953
                    errorDescription = "Temperatur unter -10°C.";
954
                    break;
955
                case 24:
956
                    errorDescription = "Temperatur über +60°C.";
957
                    break;
958
                case 31:
959
                    errorDescription = "Prüfsumme EEPROM falsch; Hardware-Fehler.";
960
                    break;
961
                case 51:
962
                    errorDescription = "Avalanche-Spannung der Laserdiode konnte nicht eingestellt werden; Ursache kann\n1. Fremdlich oder\n2. Hardware-Fehler sein.";
963
                    break;
964
                case 52:
965
                    errorDescription = "Laserstrom zu hoch / defekter Laser.";
966
                    break;
967
                case 53:
968
                    errorDescription = "Ein oder mehrere Parameter im EEPROM nicht gesetzt (Folge: Division durch 0).";
969
                    break;
970
                case 54:
971
                    errorDescription = "Hardwarefehler (PLL).";
972
                    break;
973
                case 55:
974
                    errorDescription = "Hardwarefehler.";
975
                    break;
976
                case 61:
977
                    errorDescription = "Verwendeter Parameter unzulässig; ungültiges Komando gesendet.";
978
                    break;
979
                case 62:
980
                    errorDescription = "1. Hardwarefehler\n2. Falscher Wert in Schnittstellenkommunikation (Paritätsfehler SIO).";
981
                    break;
982
                case 63:
983
                    errorDescription = "Überlauf SIO.";
984
                    break;
985
                case 64:
986
                    errorDescription = "Framing-Error SIO.";
987
                    break;
988
                default:
989
                    errorDescription = "Es trat ein unbekannter Fehler auf.";
990
                    break;
991
            }
992
993
            return errorDescription;
994
        }
995
996
        /// <summary>
997
        /// Prüft, ob es sich um einen Fehler der optoNCDT ILR 1181 / 1182 handelt.
998
        /// </summary>
999
        /// <param name="value">Der String, der geprüft werden soll.</param>
1000
        /// <returns>Wahr, falls es sich um einen gültigen Fehlercode handelt.</returns>
1001
        public static bool IsErrorCode(this string value)
1002
        {
1003
            Int16 errorCode;
1004
1005
            // Stimmt das Format?
1006
            if (value.Length != 3)
1007
                return false;
1008
1009
            if (value.Left(1) == "E" && Int16.TryParse(value.Substring(1, 2), out errorCode))
1010
            {
1011
                // Es handelt sich um das Format "E##"
1012
                return true;
1013
            }
1014
            else {
1015
1016
                return false;
1017
            }
1018
        }
1019
1020
        /// <summary>
1021
        /// Gibt die angegebene Anzahl von Zeichen von links aus einem String zurück.
1022
        /// </summary>
1023
        /// <param name="value">Der String, der bearbeitet werden soll.</param>
1024
        /// <param name="maxLength">Die Länge in Zeichen, die zurückgegeben werden soll.</param>
1025
        /// <returns></returns>
1026
        public static string Left(this string value, int maxLength)
1027
        {
1028
            if (string.IsNullOrEmpty(value)) return value;
1029
            maxLength = Math.Abs(maxLength);
1030
1031
            return (value.Length <= maxLength
1032
                   ? value
1033
                   : value.Substring(0, maxLength)
1034
                   );
1035
        }
1036
1037
        /// <summary>
1038
        /// Gibt die angegebene Anzahl von Zeichen von rechts aus einem String zurück.
1039
        /// </summary>
1040
        /// <param name="value">Der String, der bearbeitet werden soll.</param>
1041
        /// <param name="maxLength">Die Länge in Zeichen, die zurückgegeben werden soll.</param>
1042
        /// <returns></returns>
1043
        public static string Right(this string value, int maxLength)
1044
        {
1045
            if (string.IsNullOrEmpty(value)) return value;
1046
            maxLength = Math.Abs(maxLength);
1047
1048
            return (value.Length <= maxLength
1049
                   ? value
1050
                   : value.Substring(value.Length - maxLength, maxLength)
1051
                   );
1052
        }
1053
1054
        /// <summary>
1055
        /// Konvertiert eine Zeichenfolge in einen HEX-String
1056
        /// </summary>
1057
        /// <param name="value">String, der konvertiert werden soll.</param>
1058
        /// <returns>Liefert den String in der Form 0x00..0xFF zurück.</returns>
1059
        public static string ToHex(this string value)
1060
        {
1061
            char[] charValues = value.ToCharArray();
1062
            string hexOutput = "";
1063
1064
1065
            foreach (char _eachChar in charValues)
1066
            {
1067
                int val = Convert.ToInt32(_eachChar);
1068
                string buffer = String.Format("{0:X}", val);
1069
1070
                if (buffer.Length == 1)
1071
                    buffer = "0" + buffer;
1072
1073
                hexOutput += "0x" + buffer + " ";
1074
            }
1075
1076
            return hexOutput;
1077
        }
1078
    }
1079
}

von blubb (Gast)


Lesenswert?

Die Codeansicht ist ja ne Vollkatastrophe -.-

von Axel R. (axlr)


Lesenswert?

Jim M. schrieb:
> Wenn dem µC beim Senden ein Interrupt dazwischen funkt, wird er die 10
> Bytes in 2 Pakete aufteilen, da ja für kuze zeit der UART Datenstrom
> unterbrochen ist.

Ist das so? Die Wartezeit steht bei original 16ms oder 4096Zeichen 
Puffergröße. Wenn einer der beiden Punkte erfüllt ist, schickt der 
Wandler die Daten raus. der weiss schon, was er macht. ganz so dumm ist 
er nicht. das ist jedenfalls nicht die Ursache, denke ich. Sber: sicher 
in ich mir auch nicht.
Wann wird denn "INTI_CLICK" aufgerufen. Muss er schon klicken, bevor das 
was reinkommt. Und nach den zehn Bytes rtb_RecvData.Text auch wieder auf 
"" setzen. Sonst werden die immer wieder draufaddiert.
Müsste man mal aufsetzen. Geht nur gerade nicht..

von StarWars (Gast)


Lesenswert?

ALso der Mikrocontroller sendet zyklisch alle 500ms folgende Daten:
1
Data[0] = 10;
2
for(int i = 1; i < 11; i++)
3
{
4
  Data[i] = i;
5
}

Jim M. schrieb:
Abhilfe: Auf PC Anwendungsseite nochmal vernünftig puffern und Daten neu
zusammen setzen.

Was meinst du mit vernünftig puffern?

von Axel R. (axlr)


Lesenswert?

StarWars schrieb:
> ALso der Mikrocontroller sendet zyklisch alle 500ms folgende Daten:
> Data[0] = 10;
> for(int i = 1; i < 11; i++)
> {
>   Data[i] = i;
> }

Welche Ausgabe erwartest Du am PC?
1
2
3
4
5
6
7
8
9
10usw?

von Wikipedia (Gast)


Lesenswert?

Und was erwartest Du jetzt, das passiert? Hättest Du den obigen Code mal 
angesehen, wäre Dir längst aufgefallen, dass er auf einen CR/LF wartet.

Wenn Du deine Daten ohne Stopp-Byte schickst, dann wirst Du niemals 
erreichen, dass die Zeichenkette so ankommt, wie Du es gerne hättest.

Schicke ein CR/LF/00 oder sonstwas mit, das Du nicht verwendest und 
werte im C# code auf dieses Stoppbyte aus.

von StarWars (Gast)


Lesenswert?

Das erste Byte hat den Wert 10 (Data[0] = 10;)
Danach folgen 10 Datenbytes.

Muss eine Endsequenz immer angehängt werden? Ich meine das muss doch 
nicht sein.

von StarWars (Gast)


Lesenswert?

Habs nun gelöscht mit einem Thread.

von StarWars (Gast)


Lesenswert?

Hab es nun gelöt mit einem Thread. Allerdings erhalte ich nicht die 
Daten ab dem ersten Byte. In msg steht dann immer die Daten ab n+1. WIe 
kann das sein?
1
public void OnReadSerialData()
2
        {
3
            while (true)
4
            {
5
                try
6
                {
7
8
                    string msg= mySerialPort.ReadLine();
9
                }
10
                catch (TimeoutException) { }
11
            }
12
        }

von StarWars (Gast)


Lesenswert?

Die Daten sollen immer so ankommen:

10
1
2
3
4
5
6
7
8
9
10

von StarWars (Gast)


Lesenswert?

In _inBuffer liegen die Daten korrekt an. WIe kann das sein?
Ich möchte nur Daten richtig und korrekt empfangen können.

von StarWars (Gast)


Lesenswert?

Gibt es eigentlich eine bessere Alternative?

von Meine Güte! (Gast)


Lesenswert?

Meine Güte liest Du eigentlich die Antworten?

Dein µC sendet asynchron, Dein Programm empfängt asynchron. Wie in 
gottes Namen stellst Du dir vor, dass die Daten bei asynchronem Senden 
und asynchronem Empfangen synchron ankommen?

Threats helfen Dir da überhaupt nicht. UART ist in C# bereits in einem 
eigenen Thread. Wenn Du im UI-Thread die Daten verarbeiten willst, musst 
Du sicherstellen, dass Du keine Race Condition erhälst und Du Daten 
verarbeitest, die gerade aktualisiert wurden.

Dein ReadLine() Befehl ist Grütze, weil Du kein CrLf (Newline) als 
Separator hast. Les doch mal die MSDN.

Bau Dir einen Ereignisdelegaten und werte den Interrupt der UART aus. 
https://docs.microsoft.com/de-de/dotnet/api/system.io.ports.serialdatareceivedeventhandler?view=dotnet-plat-ext-6.0

Dann hängst Du einfach solange alle Daten zusammen, bis du 10...10 
empfangen hast. Dann hast Du Deinen Datenblock.

von c-hater (Gast)


Lesenswert?

StarWars schrieb:

> ALso der Mikrocontroller sendet zyklisch alle 500ms folgende Daten:
>
>
1
> Data[0] = 10;
2
> for(int i = 1; i < 11; i++)
3
> {
4
>   Data[i] = i;
5
> }
6
>
>
> Jim M. schrieb:
> Abhilfe: Auf PC Anwendungsseite nochmal vernünftig puffern und Daten neu
> zusammen setzen.
>
> Was meinst du mit vernünftig puffern?

Du hast da auf µC-Seite (wahrscheinlich ohne dir Gedanken darüber zu 
machen) ein Protokoll aufgesetzt, welches die Nachrichtensynchronisation 
allein über einen Timeout sicherstellt. Um das für dich zu übersetzen: 
Es gibt da eine große zeitliche "Lücke", in der keine Daten ankommen und 
diese Lücke (und nur diese Lücke!!!) ist ein zuverlässiger Trenner für 
die Nachrichten, da der Payload ganz offensichtlich auch die vermutlich 
als Trennzeichen vorgesehene 10 enthalten kann.

"Vernünftig puffern" bedeutet nun einfach, alle eingehenden Daten 
erstmal aneinander zu hängen und neu zu zerteilen, nämlich immer dann, 
wenn so eine Lücke zu sehen ist.

Mein Gott, ist das trivial.

von StarWars (Gast)


Lesenswert?

Hi,

zunächst habe ich nun den Empfang über einen RecieveHandler realisiert.
Damit bekomme ich nun mal die empfangenen Daten.
1
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
2
  {
3
4
    SerialPort sp = (SerialPort)sender;
5
    string rxdata = sp.ReadExisting();
6
7
    StringBuilder myBuilder = new StringBuilder();
8
    foreach (byte feb in rxdata)
9
    {
10
        myBuilder.AppendFormat("{0}", feb);
11
    }
12
13
    Debug.WriteLine("Data received:");
14
    Debug.Write(myBuilder.ToString() + "\n");
15
  }

Jetzt gibt es auch noch eine Möglichkeit die empfangenen Daten durch 
einen Puffer auszulsen mit:
1
mySPort.Read(buffer, 0, buffer.Length);

Wenn ich den Puffer mir anschauen ändern sich ja die Daten die dort 
abgelegt werden. Dies bedeutet es wird nicht immer ganz am Anfang die 
Daten im Puffer abgelegt.

von StarWars (Gast)


Lesenswert?

Der buffer hat eine Größe von 20

Hier ist zu sehen, das manchmal die Daten nicht alle im buffer enthalten 
sind sondern nur ein Teil.
1
1012345678910000000000
2
1012345678910000000000
3
1012345678910000000000
4
1012345678910000000000
5
1012345678910000000000
6
1012345678910000000000
7
101230000000000000000
8
456789100000000000000
9
1012345678910000000000
10
1012345678910000000000
11
1012345678910000000000

von Genug gesagt (Gast)


Lesenswert?

Es steht alles schon dar. Lesen, verstehen, anpassen.

von StarWars (Gast)


Lesenswert?

Ja es steht vieles hier drin.

Wie kann ich ein delay festellen von Telegramm zu Telegramm?
Da weiß ich nicht wie ich das umsetzen könnte.

von Klaus P. (kpi6288)


Lesenswert?

StarWars schrieb:
> this.Invoke((MethodInvoker)delegate()
>   {
>     rtb_RecvData.Text += indata;
>   });

Invoke ruft zwar eine Methode im GUI Thread auf, wartet aber, bis sie 
abgearbeitet ist. In dieser Zeit können weitere Events mit neuen Daten 
verloren gehen.

Du kannst das MSDN Beispiel mit dem einfachen Kommandozeilenprogramm 
nicht 1:1 in eine GUI Anwendung übernehmen. Du musst das GUI vom 
Daten-Thread trennen.

Dafür kannst Du die ankommenden Daten z.B. in eine ConcurrentQueue 
schreiben und im GUI Thread auslesen. Das Auslesen kannst Du z.B. mit 
einem Timer oder mit einem ResetEvent (AutoResetEvent / ManualResetEvent 
je nach Anforderungen) durchführen.

Hier ist ein Beispiel mit Read() statt ReadExisting(): 
https://stackoverflow.com/a/45432817/10035674

von c-hater (Gast)


Lesenswert?

Klaus P. schrieb:

> StarWars schrieb:
>> this.Invoke((MethodInvoker)delegate()
>>   {
>>     rtb_RecvData.Text += indata;
>>   });
>
> Invoke ruft zwar eine Methode im GUI Thread auf, wartet aber, bis sie
> abgearbeitet ist. In dieser Zeit können weitere Events mit neuen Daten
> verloren gehen.

So ist es. Kann man mit minimaler Änderung des vorhandenen Code lösen, 
indem man anstatt Invoke einfach BeginInvoke aufruft. Das kehrt sofort 
zurück und nicht erst nach Abarbeitung des Delegaten.

von Klaus P. (kpi6288)


Lesenswert?

c-hater schrieb:
> Kann man mit minimaler Änderung des vorhandenen Code lösen,
> indem man anstatt Invoke einfach BeginInvoke aufruft.

Ja, in diesem einfachen Fall (und beim klassischen .NET bis 4.8) reicht 
das sicher aus.
Ganz sauber ist das aber nicht, weil die Reihenfolge der Abarbeitung 
nicht definiert ist, wenn BeginInvoke mehrfach aufgerufen wird. Deshalb 
würde ich eine ConcurrentQueue benutzen - damit ist die Reihenfolge 
garantiert.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Klaus P. schrieb:

> Ganz sauber ist das aber nicht, weil die Reihenfolge der Abarbeitung
> nicht definiert ist, wenn BeginInvoke mehrfach aufgerufen wird.

Doch, ist sie. Das kommt genau in der Reihenfolgen der Aufrufe von 
BeginInvoke beim GUI-Thread an (wird intern letztlich mittels 
PostMessage() über die ganz normale MessageQueue des Win32-API 
verschickt).

Und da hier die Aufrufe aus einem einzigen Thread stammen (nämlich dem 
Reader-Thread des SerialPort), kann es nicht zu "überlappenden" Aufrufen 
von BeginInvoke kommen. Es ist absolut sicher, dass die Message geposted 
wurde, bevor der Aufruf zurückkehrt und damit der Readerthread überhaupt 
die Chance bekommt, in seiner Queue nach weiteren Bytes Ausschau zu 
halten und dann bei positivem Resultat erneut BeginInvoke aufzurufen.

> Deshalb
> würde ich eine ConcurrentQueue benutzen - damit ist die Reihenfolge
> garantiert.

Ist sie, wie oben gesagt, auch bei der Verwendung von BeginInvoke.

Ein ganz andere Sache ist allerdings, wenn das Ziel garnicht der 
GUI-Thread mit seinen Fenstern ist, die an der MessageQueue lauschen. 
Dann gibt's allerdings auch weder Invoke noch BeginInvoke. Dann muss man 
natürlich anders synchronisieren.

von StarWars (Gast)


Lesenswert?

Hi ich melde mich nun doch nochmal.

ALso das Empfangen von Daten mittels eigenen Task und BlockingCollection 
funktioniert. Allerdings gibt es nun ein weiteres Problem sobald ich 
Daten versenden möchte. Da läuft das mit dem EMpfang nicht mehr richtig.

von Klaus P. (kpi6288)


Lesenswert?

c-hater schrieb:
>> Ganz sauber ist das aber nicht, weil die Reihenfolge der Abarbeitung
>> nicht definiert ist, wenn BeginInvoke mehrfach aufgerufen wird.
>
> Doch, ist sie.

Hast Du eine Quelle für Deine Behauptung? Soweit ich weiß, ist 
BeginInvoke derzeit (im klassischen .NET Framework) zwar so 
implementiert, aber das Verhalten ist nicht definiert. Es kann im Grunde 
beliebig geändert werden.

Bei einer ConcurrentQueue ist es anders. Da ist das Verhalten definiert.

von c-hater (Gast)


Lesenswert?

Klaus P. schrieb:

> Hast Du eine Quelle für Deine Behauptung?

Nein, habe ich nicht, ich bin aber ziemlich sicher, dass sie sich mit 
hinreichender Motivation finden lassen würde, weil...

> Soweit ich weiß, ist
> BeginInvoke derzeit (im klassischen .NET Framework) zwar so
> implementiert, aber das Verhalten ist nicht definiert. Es kann im Grunde
> beliebig geändert werden.

...weil: in mono/xamarin ist es mit den Mitteln der unterstützten 
Targets jedenfalls immer adäquat implentiert. Die haben dann zwar kein 
Win32-API, aber es wird immer irgendwas benutzt, was sich effektiv im 
GUI-Thread so verhält, wie das PostMessage() aus diesem API.

Irgendwo ist also vermutlich niedergeschrieben, dass das so sein muss.

Die genaue Doku-Stelle darfst du gern selbst suchen. Ich bin nicht dein 
Such-Sklave.

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.