serielle Daten kontrolliert zerlegen

Hallo,
ich fummel gerade mit meinen Xbee´s rum. Die empfangen und senden wie die
wilde Wutz- alles gut.

Aber wie picke ich die Daten raus, die ich haben möchte?
Ich kann den Wert1 senden, z.B. 4711'\n'
Danach den Wert2, z.B. 471111'\n'
Danach den Wert3, z.B. 1147'\n'
Als Terminator wird ein '\n' genutzt.

Nun könnte man ja z.B. alles in eine String legen.
Sendet dann 47114711111147 in einem Stück.
Das setzt aber vorraus, das alle Werte die gleiche Länge haben. Ist ein
Wert kürzer, dann muß mit etwas aufgefüllt werden.

Der Empfänger muß das ganze Zeugs dann wieder zerlegen, das in die richtige
Länge zerhacken, und dann den z.B. Wert2 abzählen und ausgeben.

Nun habe ich aber festgestellt, das sich der Terminator zählen läßt.
Also, wenn Zähler für '\n' gleich 2, dann hast Du den Wert2.
Das sollte ja über eine einfache Abfrage funktionieren…

Wie macht man das denn üblicher Weise? Bin ich mit meinen Gedanken auf dem
Holzweg.
Gruß und Dank
Andreas

Du kannst auch alles in einer Zeile senden und per Komma oder Strichpunkt trennen. Dann kann man das ganz einfach per strtok() zerlegen. Das bietet sich an wenn man nur wenige Werte auf einmal sendet, so dass man nicht erst mal hunderte von Zeichen abspeichern muss.

Ansonsten kann man auch abfragen ob ein Komma da ist und die Zahl auswerten und beim Linefeed ganz aufhören. So muss man nur die Zeichen für eine Zahl speichern. Das kann man auch damit kombinieren dass man direkt die Zahl aufaddiert wie sie reinkommt statt erst erst mal einen String zu speichern (aktuelle Zahl * 10 plus aktuelle Ziffer)

Hallo,
die Geschichte mit strtok() ist ja das, was ich für hier nutzlos halte.
Bei Text wäre es ideal.
Aber ich habe Zahl und Ziffer.
Ich müßte aus einer Zahl einen Buchstaben machen, und auf der anderen Seite
wieder aus einem Buchstaben eine Zahl. Ich glaube das ist blöde…

Aber-
Dein zweiter Absatz der gefällt mir. Das werde ich machen.
Vielen Dank für den Tip.
Gruß und Spaß
Andreas

Es ist in beiden Fällen ASCII Text! Der Unterschied ist nur ob man erst mal den String abspeichert und dann konvertiert und sich das Speichern spart.

strtok() + atoi() (oder ähnliches) ist absolut trivial, aber man braucht halt einen Puffer für den String. Der kann aber auch klein sein, wenn man immer nur eine Zahl abspeichert statt mehrere.

Wenn du dir die Text-Konvertierungen sparen willst musst du Binär-Daten senden. Das ist bei sehr großen Datenmengen natürlich zu empfehlen, aber Text hat auch seine Vorteile. Man kann z.B. sagen was die Daten sind ("T253" oder "L1000") und sie so unterschiedlich behandeln. Kommt immer auf die Anwendung an.

Hallo,
der Sender führt die Zeilen,

Serial3.print("4711");
Serial3.print('\n');

oder

Serial3.print(Wert);
Serial3.print('\n');

aus.

Der Empfänger holt char c über einen Index.
Dann mit atoi wieder in Zahl.

Das läuft so richtig gut, und ist für mich ein hartes Stück Arbeit gewesen.
Ich bin dankbar, das Das, was ich sende- auf der anderen Seite auch ankommt.
Ich wollte das nun noch verfeinern, ordnen- es kommt mir wie Bastellei vor-
funktioniert aber.
Über Binär hatte ich auch schon nachgedacht, da habe ich aber noch weniger
Ahnung von. Ich bin schon froh, das ich soweit gekommen bin.
Gruß und Dank
Andreas

Dir ist sicher bewusst, dassprinteine Zahl erstmal in einen Text wandelt, bevor der dann mitwritegesendet wird.

Beim Empfangen musst du den Text natürlich richtig aufteilen und bei Bedarf wieder in Zahlen wandeln.
Und beachten, dass es zwar ziemlich schnell, aber doch in merkbarer Zeit passiert.

Das kannst du alles in deinem eigenen Code, basierend auf dem grundlegenden char c = Serial.read(); berücksichtigen und es als Gebastel empfinden, oder "höhere" Funktionen wie parseInt verwenden. Die dann natürlich ein eventuell nicht gewünschtes blockierendes Verhalten haben.

Wenn es dir reicht zu wissen, welcher Wert in welcher Zeile kommt, und du kein Problem mit Synchronisation hast, ist es gut. Sonst musst du neben den Zahlenwerten und Trennzeichen ( '\n' ) noch mehr senden. Wie z.B. so was:

A:1234
B:0

mit den Newline-Zeichen am Ende

Hallo,
dann ist also das, was ich bis jetzt zustande gebracht habe, nicht mal so
schlecht. Ich liefer und bekomme ja, was ich möchte.
Die Zeile reicht mir im Moment völlig aus, es kam mir nur zu umständlich vor,
wie man das ganze in seine Bestandteile zerlegt.

"aber doch in merkbarer Zeit passiert"
Das habe ich auch gelernt, es ist etwas völlig anderes, die Werte im SerielleMonitor durchrauschen
zu sehen- und dann anzunehmen man schafft es selbst so schnell. Denkste Puppe, das braucht
seine Zeit.

Das Trennzeichen ('\n') habe ich genutzt um zu wissen, wann der Wert fertig
übertragen ist, nur- der Wert.
Ich habe aber noch einen Terminator für "Übertragung abgeschlossen"- der kommt
vom XBee, und einen- richtige Daten empfangen, der kommt vom Nextion.
Ich kann mir vorstellen, das ich damit eine gute Sicherheit herstellen kann.
Dank eurer Hilfe bekomme ich das jetzt ja hin. Ich melde mich aber gerne wieder, wenn ich Äpfel
sende- und es kommen Zitronen an.
Andreas sagt Danke schön.

Die Daten als Text zu übertragen ist zwar einfacher lesbar beim debuggen, aber ziemlich kompliziert beim aufspalten und wieder umwandeln in die einzelnen Werte.
Besser geht das, wenn man eine Daten-Struktur erstellt und dann die Struktur komplett übertragen lässt: Serial.write((byte*)&SendData, sizeof(SendData));
Auf der Empfängerseite hast Du dann die gleiche Struktur und kannst problemlos darauf zugreifen.
Man muss nichts aufspalten und alle Datentypen passen. Das finde ich deutlich praktischer.

Das hatten wir schon als Alternative in#3 (Serenifly)

skobimobil:
Über Binär hatte ich auch schon nachgedacht, da habe ich aber noch weniger Ahnung von. Ich bin schon froh, das ich soweit gekommen bin.

"besser" und "deutlich praktischer" ist z.T. auch Vorliebe und Geschmacksfrage, wenn es per Text schon "schnell wie die Wutz" ist.

Du hast insoweit Recht, slartibartfast, dass man Andreas SkobiMobil durchaus ermuntern sollte:
Ist gar nicht soo kompliziert, es mal auszuprobieren. Wenn die Datenstruktur < 64 byte ist, passt sie auch komplett in den standard HardwareSerial Empfangspuffer und man kann die Synchronisation einfach gestalten.

Hallo,
den zweiten Absatz von Serenifly habe ich gefressen, das bekomme ich auch im
"Kopf" hin. Da werde ich mal mit anfangen.

Maximal (in der Regel) werden 9 int Werte übertragen, der längste davon
besteht aus 5 Ziffern je Wert.
Ausserhalb der Regel werden 48 int Werte übertragen mit einer Länge von
5 Ziffer je Wert.

Das dauert jetzt, analog gestoppt, 1,3 bis 1,5 Sekunden. (9600Bd)
Schei.. der Hund drauf…

Ich möchte das nur einfach haben, und auch wissen was da abläuft.

Serial.write((byte*)&SendData, sizeof(SendData))

Mit dem Ding kann ich noch nichts anfangen, ich probiere das aber mal aus.
Vielleicht ist es ja einfacher.
Gruß und Dank
Andreas

Wie lange das dauert hängt hauptsächlich von der Baudrate ab, wenn man sonst keine Blockierungen drin hat. Die kann man sehr hoch setzten.

Bei der Menge Zahlen (und weil es nur Zahlen sind), würde ich alle zusammen mit ',' getrennt
in einer Zeile übertragen und beim Einlesen sofort konvertieren.

bool zeileFertig;
byte zahlenBekommen;
int zahlen[50];

void setup() {
  Serial.begin(250000);
}

void loop() {
  if (Serial.available()) {
    char got = Serial.read();
    if (isdigit(got)) {
      zahlen[zahlenBekommen] *= 10;
      zahlen[zahlenBekommen] += got - '0';
    } else if (got == 10) {
      zahlenBekommen++;
      zeileFertig = true;
    } else if (got == ',') {
      if (++zahlenBekommen >= sizeof(zahlen) / sizeof(zahlen[0])) {
        zeileFertig = true;
      }
      zahlen[zahlenBekommen] = 0;
    }
  }
  if (zeileFertig) {
    for (byte i = 0; i < zahlenBekommen; i++) {
      Serial.print(zahlen[i]);
      if (i != zahlenBekommen - 1) {
        Serial.print(F(", "));
      }
    }
    zeileFertig = false;
    zahlenBekommen = 0;
    zahlen[0] = 0;
  }
}

Hallo Whandall,
der ist auch nicht schlecht...
Aber- auf die String Character Analysis (isdigit) hätte ich selbst ja mal drauf kommen können.
Das kann viel Nachbearbeitung sparen.
Vielen Dank dafür.
Gruß und Spaß
Andreas

Deineint Werte sind alle zwischen 0 und 32767?

Beim großen Paket musst du auf jedenn Fall sicherstellen, dass der Empfänger rechtzeitig zu lesen anfängt, da weder als Text noch Binär alles in den Empfangspuffer passt.
Das andere Problem ist, dass wenn der Empfänger erstmal zu lesen angefangen hat, er schneller ist als die Übertragung und also etwas warten muss bis die Daten da sind.
Das macht Whandalls Vorschlag schon richtig, wenn sonst in loop nichts lange blockierendes eingebaut wird. Das Erkennen des Zeilenendes ist auch da, allerdings gut versteckt inif (got == 10), finde ich.

In Zeile 21 würde ich ein else einfügen, sonst schreibst du in diesem Fehlerfall ausserhalb des definierten Arrays:

      if (++zahlenBekommen >= sizeof(zahlen) / sizeof(zahlen[0])) {
        zeileFertig = true;
      } else
         zahlen[zahlenBekommen] = 0; // nur wenn Array-Ende noch nicht ereicht war

Maximal (in der Regel) werden 9 int Werte übertragen, der längste davon
besteht aus 5 Ziffern je Wert.
Ausserhalb der Regel werden 48 int Werte übertragen mit einer Länge von
5 Ziffer je Wert.

Woran kann der Empfänger die zwei Paket-Formate unterscheiden? Bei Text am '\n' Zeichen, aber im Binär-Fall ?

Mit dem Ding kann ich noch nichts anfangen, ich probiere das aber mal aus.

slartibartfast hat auch das wichtigste vergessen: Was ist SendData ?

könnte z.B. sowas sein:

// Berücksichtigt deine Erwähnung von zwei verschiedenen Paket-Formaten:
struct smallBuf{
  const byte len=sizeof(smallBuf); // zur Unterscheidung verschiedener PaketFormate 
  int wertA;
  int wertB;
  int wertC_Array[7];
} SendData;

struct BigBuffer{
   const byte len=sizeof(BigBuffer);
   int data[48];  
};
 ...
void sendSmallPacket(int a, int b, int c) {   
    // sendet 19 byte binär: 1 byte Länge, 9 int a 2 byte
    SendData.wertA= a;
    SendData.wertB= b;
    SendData.wertC_Array[0]= c;
    Serial3.write((byte*)&SendData, sizeof(SendData)); 
}
... // irgendwo im Code:
    Serial3.write((byte*)&buf, sizeof(buf); 
...
    sendSmallPacket( 100, 1234, 4711 );

Stimmt, bei Zeile 21 fehlt das else.

Um alle 5-stelligen Zahlen abzudecken müsste man zu longs wechseln.

Hallo,
ich habe das mal mehr als 4Std. laufen lassen.
Sender ist ein Mega mit XBee, Empfänger ein Uno mit XBee.
Ich sende 3x9 verschiedene Werte, alle 20Sek 9 Werte.
Also 00-9 Werte, 20-9 andere Werte und 40-wieder 9 andere Werte.
Die int Werte liegen in einem Bereich von -60 bis 10858.

Die Werte werden in einer bestimmten Reihenfolge gesendet und in ein
EmpfangsArray geschrieben.
Der Index des Array ändert sich mit einem '\n'-Zähler. Er zählt also immer
von 0 bis 8. Da ich auch '\n'-Gesamt zähle, kann ich einfach mit Modulo
feststellen ob ein '\n' verloren gegangen ist. Ist Modulo nicht 0 zählt
ein Fehlerzähler einen hoch.

Weiter sind die Summen der Pakete bekannt, 4819, 23584 und 942.
Stimmt eine Summe nicht mit dem Vergleich, dann zählt ein Fehlerzähler
einen hoch.

In 4Std habe ich so 720 mal ein Paket von 9 Werten gesendet und empfangen.
Die Fehlerzähler stehen auf 0.
Real werde ich alle 15Min senden. Legt man nun die 720 zu Grunde, also
720 mal 15 min dann komme ich auf eine Fehlerfreie Übertragung von
7,5 Tagen. Ja, ja, alles Theorie.
Ich- halte das schon für verdammt gut…
Ohne eure Tips und Anregungen wäre ich bei weitem noch nicht so weit.
Also mal ein großes DANKE SCHÖN an euch!

So wie es jetzt läuft, werde ich es mal 24Std laufen lassen, mal sehen was die
Fehlerzähler dann bringen.

Hier habe ich nur 9 Werte übertragen, mal sehen wie es bei 48 Werten läuft.

"Woran kann der Empfänger die zwei Paket-Formate unterscheiden? Bei Text am '\n' Zeichen, aber im Binär-Fall ?"

Da haben wird uns wohl falsch verstanden. Die Pakete haben alle das gleiche
Format, nur ein Paket enthält 9 Werte, das andere 48 Werte.
Die 9 Werte werden automatisch gesendet, die 48 manuell- wenn Taster, dann…

Bei den 48 Werten werde ich einmal den Sketch von Whandall probieren.
Das scheine ich begriffen zu haben. Glaube ich…

"Beim großen Paket musst du auf jedenn Fall sicherstellen"
"Problem ist, dass wenn der Empfänger erstmal zu lesen angefangen hat"

Da muß ich mich bei den XBee´s mal schlaulesen, vielleicht können die da
etwas abnehmen.

Wenn ich jetzt auf das Display des Uno schaue, dann fällt mit vor Freude
"ein Ei aus der Hose"

Vor der seriellen Kommunikation habe ich einen riesen Bammel gehabt, aber Dank
eurer Hilfe habe ich vieles verfeinern und sicherer machne können…
Man muß sich da nur mal mit beschäftigen, dann begreift man das auch.
Ich melde mich bestimmt wieder.
Also nochmals, Danke schön.
Gruß und Spaß
Andreas

Die int Werte liegen in einem Bereich von -60 bis 10858.

Wenn du Summierungs-Variante verwendest dann passe auch auf dass du das Vorzeichen abfragst und behandelst. Also abspeichern ob ein '-' da ist und wenn ja am Ende mit -1 multiplizieren

Wenn man erst mal einen String bis zum Komma einliest und dann mit atoi() konvertiert hat man das Problem nicht. Und kostet nur ein paar Byte für den Puffer.

Und ja, serielle Kommunikation ist recht trivial wenn man es gleich richtig macht. Da lagst du auch schon von Anfang an richtig mit Trennzeichen und Endzeichen. Statt irgendwelche Delays zu verwenden um zu warten dass alles da ist.
Es gibt auch mehrere Wege das zu lösen. Was am vernünftigsten ist hängt von der Anwendung ab. Strings sind nicht das effizienteste dabei, aber sehr flexibel, da man einfach die Auswertung ändern kann ohne den Einlese-Code zu ändern.

Binärübertragung:
Habe es zwar auch noch nicht gemacht, aber mal drüber gelesen, dass man das Endezeichen mit byte stuffing machen kann. D.h., man legt ein Zeichen als Endezeichen fest, und, falls das Zeichen im Datenstrom vorkommt, wird es mit mit byte stuffing codiert. Ich glaube wenigstens, dass es so hieß, musst mal googeln

Da haben wird uns wohl falsch verstanden. Die Pakete haben alle das gleiche Format, nur ein Paket enthält 9 Werte, das andere 48 Werte.

Ja, da haben wir uns wohl falsch verstanden: Das meinte ich mit "zwei verschiedene Formate"
Oder willst du jedesmal 48 Werte senden, wobei meist nur die ersten 9 interessieren ?

Bei Binär gibt es erstmal keine Sonderzeichen. Das '\n' ist eine dezimal-Zehn ( 0x0A ) und kann ohne weiteres das erste Byte eines int - Wertes sein. Das gleiche gilt für alle anderen übertragenen Werte.

Lösungen:

  • Wie ElEspanol vorgeschlagen hat: einem Zeichen Sonderbedeutung geben ( "Achtung" ) und dann (mindestens) zwei Extras definieren: "Nachricht-Ende" und "Achtung als Datenbyte auffassen" (Eins der beiden kann natürlich das gleiche Zeichen wie sein.

  • Wie ich angedeutet habe: Länge als erstes voranschicken. Da wir max 48 Werte haben, reicht dafür ein Byte

  • Sonderfall, hier möglich: nur Kennung vorausschicken: es kommt ein Lang- oder Kurz-Paket.

  • Warten, ob noch mehr kommt. Das findet nicht nur Serenifly nicht so gut. Aber was passiert im Fehlerfall? Ich kenne die xBees nicht gut genug: stellt diese Übertragung sicher, dass immer eine ganze Nachricht fehlerfrei ankommt? Oder wird da nur jedes Byte einzeln gesichert?

Dass so etwas 4 Stunden fehlerfrei läuft, ist ja schonmal gut. ( Glückwunsch :wink: )
Aber erst wenn Fehler passiert sind, kann man abschätzen, wie wahrscheinlich sie sind.
Was passiert, wenn Sender und Empfänger sehr weit auseinander sind?
Und wenn du dann mit einem passenden (anderes XBee?) oder unpassendem Störsender dazwischenfunkst ?

Dass mal garnix kommt, lässt sich nicht verhindern.
Dass auch mal was falsches oder unvollständiges kommt, wäre die nächste Schwierigkeit.
Dass nach einem unvollständigen Paket sich alles aufhängt, sollte man jedenfalls versuchen in den Griff zu kriegen.
Bei Textübertragung kein Problem: Wenn was kommt, kommt irgendwann auch ein '\n'.
Bei Binär wäre ElEspanols Lösung ( auf warten ), genauso gut.

michael_x:

  • Wie ElEspanol vorgeschlagen hat: einem Zeichen Sonderbedeutung geben ( "Achtung" ) und dann (mindestens) zwei Extras definieren: "Nachricht-Ende" und "Achtung als Datenbyte auffassen" (Eins der beiden kann natürlich das gleiche Zeichen wie sein.

Das ist nicht was vorgeschlagen wurde. Bei Byte Stuffing wird Null als Endzeichen genommen. Und wenn wenn im Datenstrom eine Null vorkommt wird es durch den Abstand zur nächsten Null ersetzt. Außerdem gibt das erste Byte die Position der ersten Null an. So weiß man immer wo Daten-Nullen sind.
Der Code dafür ist nicht ganz trivial, aber gibt es fertig.

Wenn alle Pakete immer die gleiche Länge haben braucht man das aber nicht. Dass mal ein Byte vollkommen verloren geht kommt eigentlich nicht vor. Vielleicht wenn die Gegenseite die Übertragung ganz abbricht. Aber das kann man mit Timeouts abfangen.