Forum: Mikrocontroller und Digitale Elektronik Serielle Kommunikation mit 2 Kanälen - es spukt


von Carsten St. (Gast)


Lesenswert?

Hallo Freunde,
ich stehe einem magischen, spukhaften Problem gegenüber.
Ausgangslage: Arduino (Nano), den ich für eine serielle Kommunikation 
nutzen will, dabei sind 2 Kanäle im Betrieb: Der hardwareseitige UART 
und ein Digitalpin (D10) , der mittels der SoftwareSerial.h Library 
bedient wird. Beide Kanäle haben unterschiedliche Baudrate. Der UART 
sendet Daten (Bytes), die über den Digitalpin empfangen werden nach 
einer Analyse und ich stelle diese Daten in der Arduino EDI mittels 
seriellem Monitor dar. Das Gesamte läuft innerhalb der Loop() Schleife 
des Nanos ab. Dabei soll die Logik das 50.Byte erkennen/abfangen und 
eine LED (Pin D2) schalten, wenn das Byte einem bestimmten Wert 
entspricht.

Problem: Nur wenn ich innerhalb der While Schleife, die die Daten von 
dem Digitalpin liest ein Serial.print(<param>) absetze, kann ich diesen 
<param> in der Loop() und außerhalb der While Schleife auswerten, 
ansonsten wird der Parameter nicht gesetzt/gefüllt.

ganz simpler Code, aber es spukt für mich:
1
void setup()
2
{
3
  // set the data rate for the SoftwareSerial port
4
  pinMode(10, INPUT); //software RX
5
  pinMode(11, OUTPUT);
6
  pinMode(2, OUTPUT); //LED Anschluss
7
  param = 0;
8
  mySerial.begin(9600); //über Pin 10 lesen
9
  Serial.begin(19200); //über UART ausgeben
10
11
}
12
13
void loop() // run over and over
14
{
15
  i = 0; 
16
  if ( param == 10 ) {  --> param nur aus While gesetzt, wenn printf  
17
    digitalWrite(2, HIGH);
18
   }
19
  else  {
20
    digitalWrite(2, LOW);
21
   }
22
23
  while (mySerial.available() > 0) {
24
    i++;
25
    // lese byte:
26
    myByte = mySerial.read();
27
    Serial.print(myByte, HEX);
28
    if (i == 50 ) {  // wenn das 50.Byte erreicht
29
    param = myByte;  // setze param
30
    Serial.println(param);  --> diese Zeile ist nötig, damit param 
31
                                 ausserhalb der While verfügbar ist, 
32
                                 ansonsten bleibt param in der loop 
33
                                 oben = 0 (= init Wert)
34
    }
35
  }
36
}
Wie beschrieben muss in der While Schleife für das Lesen des Bytestromes 
Serial.print(param) abgesetzt werden ansonsten ist ausserhalb der While 
aber innerhalb der Loop die param Variable = 0 (init Wert).
Warum ???

Für jeden Tipp dankbar, verzweifle bald...
Beste Grüsse
Carsten Steffen

: Bearbeitet durch Moderator
von uff basse (Gast)


Lesenswert?

Carsten St. schrieb:
> Für jeden Tipp dankbar, verzweifle bald...

Wo ist param definiert? Zeige deinen vollständigen Code.

von Frank E. (Firma: Q3) (qualidat)


Lesenswert?

Die Deklaration der SoftSerial sieht auch komisch aus, zumindest nach 
dem abgebildeten Code. Man muss z.B. die Pins der SoftSerial nicht extra 
auf Input/Output setzen, das macht die Lib schon alleine ...

Es müsste so aussehen:
1
#include <SoftwareSerial.h>
2
#define s_RX 10
3
#define s_TX 11
4
5
SoftwareSerial mySerial (s_RX, s_TX);
6
7
void Setup()
8
{
9
  mySerial.begin(9600);
10
11
}
12
13
void Loop()
14
{
15
  if(mySerial.available()>0)
16
  {
17
    char c = mySerial.read();
18
    ...
19
  }
20
}

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Carsten St. schrieb:
> Für jeden Tipp dankbar
Bitte das nächste Mal selber die [c] Tags um den Code setzen. Siehe die 
paar Zeilen über jeder Texteingabebox.

von Carsten St. (Gast)


Lesenswert?

uff basse schrieb:
> Wo ist param definiert? Zeige deinen vollständigen Code.
Hier der Kopfteil noch, das ist dann alles...

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // RX, TX
int i;
int myByte;
int param;

von Carsten Steffen (Gast)


Lesenswert?

Hier der gesamte Code (formatiert):
1
#include <SoftwareSerial.h>
2
SoftwareSerial mySerial(10, 11); // RX, TX
3
int i;
4
int myByte;
5
int param;
6
void setup()
7
{
8
  // set the data rate for the SoftwareSerial port
9
  pinMode(10, INPUT); //software RX
10
  pinMode(11, OUTPUT);
11
  pinMode(2, OUTPUT); //LED Anschluss
12
  param = 0;
13
  mySerial.begin(9600); //über Pin 10 lesen
14
  Serial.begin(19200); //über UART ausgeben
15
}
16
void loop() // run over and over
17
{
18
  i = 0; 
19
  if ( param == 10 ) {  --> param nur aus While gesetzt, wenn printf  
20
    digitalWrite(2, HIGH);
21
   }
22
  else  {
23
    digitalWrite(2, LOW);
24
   }
25
  while (mySerial.available() > 0) {
26
    i++;
27
    // lese byte:
28
    myByte = mySerial.read();
29
    Serial.print(myByte, HEX);
30
    if (i == 50 ) {  // wenn das 50.Byte erreicht
31
    param = myByte;  // setze param
32
    Serial.println(param);  --> diese Zeile ist nötig, damit param 
33
                                 ausserhalb der While verfügbar ist, 
34
                                 ansonsten bleibt param in der loop 
35
                                 oben = 0 (= init Wert)
36
    }
37
  }
38
}

von Carsten Steffen (Gast)


Lesenswert?

Frank E. schrieb:
> if(mySerial.available()>0)

Ich will ja zunächst alles lesen was reinkommt und dann den gesamten 
Bytestream analysieren/verarbeiten. Mit einem "If" statt "while" an der 
ist Stelle, geht das nicht, weil dann zwischendurch im wieder die Loop() 
komplett durchlaufen würde statt zunächst ALLES zu lesen.
Die Deklaration der SoftwareSerial ist bei mir korrekt wie bei dir auch.

von bitsy (Gast)


Lesenswert?

Logikfehler, i wird immer dann zu 0, wenn kein Byte seriell verfügbar 
ist, Soll das so sein?

von Helmut H. (helmuth)


Lesenswert?

bitsy schrieb:
> Soll das so sein?

wahrscheinlich nicht, erklärt aber das Verhalten:

Wenn  Serial.println(param) ausgeführt wird, dann dauert das eine 
gewisse Zeit,in der neue Zeichen reinkommen, so dass die while-Schleife 
nicht verlassen wird da (mySerial.available() > 0).
Wenn  Serial.println(param) nicht ausgeführt wird, dann wird die 
while-Schleife verlassen da (mySerial.available() == 0);
dann wird i wieder auf 0 gesetzt und kann nie 50 werden.

von Mario M. (thelonging)


Lesenswert?

Verschiebe das "i = 0" in setup() und kopiere es in den Body von "if 
(i==50)", dann sollte es funktionieren.

von Gerald K. (geku)


Lesenswert?

Arduino unterstützt kein Muktitasking, siehe :

https://iotspace.dev/arduino-vs-raspberry-pi-unterschiede-und-vergleich/

Aduino ist ein kooperatives System.

Daher sollte es innerhalb von loop() zu keinen größeren Verzögerungen 
kommen (möglichst keine Schleifen in der Schleife loop() ).

Am Ende von loop() kommt es zum Taskwechsel. Es kann sein, dass manche 
Aduinofunktionen diesen Taskwechsel ebenfalls zur Verfügung stellen, 
eben z.B. Serial.println().

Beim Rasperry Pi hat man dieses Problem nicht, da Linux preemtiv ist.

: Bearbeitet durch User
von Georg G. (df2au)


Lesenswert?

Es kann helfen, wenn du an Stelle des println() einen yield() Aufruf 
einfügst. Dann hat die SoftSerial bessere Chancen, Zeichen zu sammeln, 
bis du wieder available() aufrufst.

von Wurstlöffel (Gast)


Lesenswert?

Gerald K. schrieb:
> Arduino unterstützt kein Muktitasking, siehe :
Aha.

> Am Ende von loop() kommt es zum Taskwechsel.
Ja wie jetzt nun doch? So ganz ohne Unterstützung?

von Carsten Steffen (Gast)


Lesenswert?

bitsy schrieb:
> Logikfehler, i wird immer dann zu 0, wenn kein Byte seriell verfügbar
> ist, Soll das so sein?

Das soll so sein. Es werden Byteströme zyklisch empfangen. Mich 
interessiert innerhalb jedes Datenstromes nur EIN Byte ( 50.te Byte). 
Dieses fange ich in der while Schleife ab und gebe es nach "aussen" zur 
Ausgabe. So ein Bytestrom hat etwa 200 Byte und wird mehrmals pro Minute 
mit aktualisierten Daten versendet. "i" soll mir die Position innerhalb 
des Datenstroms liefern. Ist ein Datenstrom voll gelesen, so soll i 
wieder für den nächsten Datenstrom zurückgesetzt werden. Insofern sehe 
ich keinen Logikfehler hier !?
Es sei denn die while Bedingung wird innerhalb der loop verlassen bevor 
das "read" abgeschlossen ist bzw. obwohl der aktuelle Datenstrom noch 
nicht zu ende gelesen ist - aber warum sollte das passieren ?

von Mario M. (thelonging)


Lesenswert?

Weil mySerial.available()/read() im Normalfall nur 1 Zeichen zurück 
gibt.

von Carsten Steffen (Gast)


Lesenswert?

Mario M. schrieb:
> Weil mySerial.available()/read() im Normalfall nur 1 Zeichen zurück
> gibt.

Aha, nach meinem Verständnis werden die empfangenen Daten in einem 
Puffer des "Softwarekanals" gespeichert und solange auch nur ein Byte in 
dem Puffer ist mySerial.available() = true. Ansonsten bräuchte ich ja 
nur die Read() Methode, die entweder was liest (sofern da) oder nicht 
liest. Ich schiebe mal den i-Reset in die IF Abfrage rein, dann wäre 
dein Tipp goldrichtig.

von EAF (Gast)


Lesenswert?

Carsten Steffen schrieb:
> Ansonsten bräuchte ich ja
> nur die Read() Methode,

Das ist der erste richtige Gedanke, den ich von dir hier lese!
Wenn Daten da sind, liefert Stream::read() das nächste Datum, jeweils 
byte weise.
Wenn nix da, eine -1
Und genau das solltest du nutzen.

Und raus aus der Loop Funktion, mit dem Parser

von Carsten Steffen (Gast)


Lesenswert?

Mario M. schrieb:
> Verschiebe das "i = 0" in setup() und kopiere es in den Body von "if
> (i==50)", dann sollte es funktionieren.

Dann wird aber nicht nur das 50.Byte des 200 Byte Paketes jeweils 
identifiziert sondern auch das 100., 150. und 200. innerhalb jedes 
Paketes. Zurücksetzen darf ich erst, wenn ein Paket ganz durch ist. Aber 
wenn das Problem damit erkannt ist, sollte ein passendes i-Reset auch 
kein Problem mehr sein...

von Gerald K. (geku)


Lesenswert?

Carsten Steffen schrieb:
> Ansonsten bräuchte ich ja nur die Read() Methode, die entweder
> was liest (sofern da) oder nicht liest

Die Funktion Read() liefert immer etwas zurück, auch wenn der Puffer 
leer ist. Man kann dann nicht sicher sein, dass das gelese Zeichen 
gültig ist.
Daher braucht man die Funktion mySerial.available() die zurückliefert, 
ob ein Read() sinnvoll ist, da ein Zeichen im Puffer ist.

von EAF (Gast)


Lesenswert?

Gerald K. schrieb:
> Man kann dann nicht sicher sein, dass das gelese Zeichen
> gültig ist.
Das stimmt nicht.
(erklärte ich eben schon)

von Mario M. (thelonging)


Lesenswert?

Carsten Steffen schrieb:
> Dann wird aber nicht nur das 50.Byte des 200 Byte Paketes jeweils
> identifiziert sondern auch das 100., 150. und 200.

Ah, OK. Das hatte ich so verstanden. Dann muss das Rücksetzen eben an 
einer anderen, passenden Stelle erfolgen.

von EAF (Gast)


Lesenswert?

Carsten Steffen schrieb:
> Es werden Byteströme zyklisch empfangen. Mich
> interessiert innerhalb jedes Datenstromes nur EIN Byte ( 50.te Byte).
> Dieses fange ich in der while Schleife ab und gebe es nach "aussen" zur
> Ausgabe. So ein Bytestrom hat etwa 200 Byte und wird mehrmals pro Minute
> mit aktualisierten Daten versendet. "i" soll mir die Position innerhalb
> des Datenstroms liefern. Ist ein Datenstrom voll gelesen, so soll i
> wieder für den nächsten Datenstrom zurückgesetzt werden. Insofern sehe
> ich keinen Logikfehler hier !?

Ich schon!
Woran erkennt man den Anfang eines 200Byte Blockes?
Woran das Ende?
Woran die Güligkeit?

von Carsten Steffen (Gast)


Lesenswert?

EAF schrieb:
> Woran erkennt man den Anfang eines 200Byte Blockes?
Wie erwähnt handelt es sich um Datenpakete, die zyklisch (mit je 
aktualisiertem Inhalt aber fester Struktur) mehrmals pro Minute gesendet 
werden. D.h. es sind auch mehrere Sekunden Abstand zwischen den Paketen. 
Sobald ein mySerial.available() true liefert, startet ein Paket. Da die 
200 Bytes ohne Unterbrechung gesendet werden, sollte available auch 
solange true sein - mit einer Ausnahme wie ich jetzt verstanden habe, 
und zwar dann, wenn die Verarbeitungslogik in der "while" zu schnell 
abläuft.

> Woran das Ende?
Wird klar aus oben geschriebenem

> Woran die Güligkeit?
Das Byte an der 50.Stelle kann nur 2 verschiedene Werte annehmen, wenn 
es nicht den abgeprüften Wert hat, spielt es eh keine Rolle (LED bleibt 
aus)

von Peter D. (peda)


Lesenswert?

Carsten Steffen schrieb:
> D.h. es sind auch mehrere Sekunden Abstand zwischen den Paketen.

Und wie erfolgt die Erkennung dieser Pause?
Allgemein sind Protokolle mit Pause aus der Steinzeit der 
Programmierung. Heutzutage benutzt man Protokolle mit eindeutiger 
Paketerkennung (Z.B. Startzeichen, Längeninformation, Datenblock, 
Prüfsumme, Endezeichen). Kann der Datenblock Binärdaten enthalten, 
benutzt man Escapezeichen, falls es Steuerzeichen sind.

von EAF (Gast)


Lesenswert?

Carsten Steffen schrieb:
> Wird klar aus oben geschriebenem

Dann ist der Abstand, die Pause, das einzige Erkennungsmerkmal, um einen 
Anfang zu erkennen.
Das sehe ich nicht bei dir.

Zudem kann nicht gewährleistet werden, dass alle Bytes auch ankommen.
Wenn der Block also != 200 lang ist, weiß man nicht ob das Byte an 
Stelle 50 gültig ist.
Auch das sehe ich noch nicht bei dir.

Mit dem erfüllen der beiden Bedingungen sollte sich ein einfacher Parser 
bauen lassen.
Der berühmte endliche Automat.

von Peter D. (peda)


Lesenswert?

Carsten Steffen schrieb:
> while (mySerial.available() > 0) {

Das kann nicht funktionieren. Das while wird schneller durchlaufen, als 
neue Zeichen reintröpfeln. i wird also maximal 1 erreichen, aber nie 50.

von Mario M. (thelonging)


Lesenswert?

Carsten Steffen schrieb:
> D.h. es sind auch mehrere Sekunden Abstand zwischen den Paketen.

Dann musst Du die Zeit erfassen, die seit dem letzten empfangenen 
Zeichen vergangen ist und wenn eine bestimmte Differenz überschritten 
wird i=0 setzen.

von Carsten Steffen (Gast)


Lesenswert?

Peter D. schrieb:
> Das kann nicht funktionieren. Das while wird schneller durchlaufen, als
> neue Zeichen reintröpfeln. i wird also maximal 1 erreichen, aber nie 50.

Es funktioniert aber mit dem beschriebenem Serial.println(x) Befehl. Ich 
hatte es tagelang erfolgreich am Laufen und Testen. Den println hatte 
ich nur für das Monitoring drin und habe es heute beim Refakturieren 
entfernt und dann ging es plötzlich spukhaft nicht mehr, weil wie oben 
beschrieben die Verzögerung in der Schleife gefehlt hat.

Ich sehe hier jetzt zusammenfassend 2 Lösungsmöglichkeiten:
- Verzögerung beibehalten (mit print wie es jetzt ist und funktioniert) 
oder den Vorschlag von Georg verfolgen mit einem anderen Befehl statt 
print eine Verzögerung erzielen
- i-Reset an die neue Erkenntnis der Fehlerursache anpassen, wobei ich 
dabei um das Zählen der Gesamtzahl des Datenstroms nicht umhin komme...

Ein Parsen des kompletten Paketes wäre natürlich die 100% Lösung aber 
per Arduino zu aufwendig, teuer ... wobei ich mir gar nicht so sicher 
bin, ob der Speicherbedarf des Nano Arduinos hierfür überhaupt 
ausreichend wäre..

Aber vielen Dank an alle, die mich die Ursache des Problems gebracht 
haben und auch Lösungsvorschläge parat hatten, insbesondere Danke an 
Helmut für die Ursachenerkennung und Mario und Georg !!

von Peter D. (peda)


Lesenswert?

Carsten Steffen schrieb:
> Ein Parsen des kompletten Paketes wäre natürlich die 100% Lösung aber
> per Arduino zu aufwendig, teuer ... wobei ich mir gar nicht so sicher
> bin, ob der Speicherbedarf des Nano Arduinos hierfür überhaupt
> ausreichend wäre..

Das ist Quatsch, der Arduino schafft das spielend. 9600 Baud ist 
schnarchlahm.
Nur mit dem Protokoll bist Du sicher, daß Du auch immer das richtige 
Byte liest.
Benutzt der Sender ein Protokoll, muß er die Daten ja nicht am Stück 
senden, d.h. er kann eilige Sachen dazwischen schieben. Es können also 
durchaus größere Pausen innerhalb der 200 Byte auftreten.

Benutzt Du eine UART nur für Debugausgaben, dann ist es sinnvoller, 
diese in Software machen zu lassen. Läuft die Empfangsuart in Hardware, 
dann kann sie per Interrupt eine FIFO benutzen, d.h. es gehen garantiert 
keine Zeichen verloren.

von EAF (Gast)


Lesenswert?

Carsten Steffen schrieb:
> . wobei ich mir gar nicht so sicher
> bin, ob der Speicherbedarf des Nano Arduinos hierfür überhaupt
> ausreichend wäre..

Ich dachte, du willst nur ein Byte.

von Frank E. (Firma: Q3) (qualidat)


Lesenswert?

Carsten Steffen schrieb:
> Frank E. schrieb:
>> if(mySerial.available()>0)
>
> Ich will ja zunächst alles lesen was reinkommt und dann den gesamten
> Bytestream analysieren/verarbeiten. Mit einem "If" statt "while" an der
> ist Stelle, geht das nicht, weil dann zwischendurch im wieder die Loop()
> komplett durchlaufen würde statt zunächst ALLES zu lesen.
> Die Deklaration der SoftwareSerial ist bei mir korrekt wie bei dir auch.

Ich habe ja mit den "..." gemeint, dass da noch was kommt. Üblicherweise 
addiert man die hereinkommenden Zeichen (evtl. nach Prüfung) in jedem 
Loop auf einen String oder ein Char-Array, bis ein Ende-Kennzeichen 
erkannt wird oder die zulässige Länge überschritten ist. Eventuell 
begrenzt man auch die Anzahl der Loops, damit die Routine nicht 
dauerhaft hängen bleiben kann.

DANN "macht man Irgendwas" mit den Daten und löscht den Puffer, um 
wieder von Vorne anzufangen ...

von Jester (Gast)


Lesenswert?

Carsten Steffen schrieb:
> Ein Parsen des kompletten Paketes wäre natürlich die 100% Lösung aber
> per Arduino zu aufwendig, teuer ... wobei ich mir gar nicht so sicher
> bin, ob der Speicherbedarf des Nano Arduinos hierfür überhaupt
> ausreichend wäre..

200 Zeichen zwischenspeichern brauchts nicht. Es reicht, wenn du das 50. 
Zeichen dir merkst und nach Abschluss des Frames (Anzahl der Zeichen == 
200? / Checksum?/ BREAK? / was auch immer ...) das gemerkte Zeichen 
analysierst.

BREAK, siehe 
https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter#Break_condition 
:
" A break condition occurs when the receiver input is at the "space" 
(logic low, i.e., '0') level for longer than some duration of time, 
typically, for more than a character time. This is not necessarily an 
error, but appears to the receiver as a character of all zero-bits with 
a framing error. "

Also ziemlich schwammig ...

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.