Daten aus dem Serial auslesen und splitten

Hi bin dabei Daten, die von dem Arduino über Serial(XBEE) empfangen werden zu splitten.

Die Daten werden folgendermaßen gesendet z.b.: A12.23;23.2...... +
Ich benutze das A als Startdelimiter und das + wenn signalisiert werden soll, dass alles gesendet wurde.

Problem ist, ich bekomme eine Leere eingabe in den Arrays bzw. nicht alle Werte.

float Accel_B; //Variable um ersten Stringteil(Accel) abzuspeichern
float Gyro_B; //Variable um zweiten Stringteil(Gyro) abzuspeichern



int put_indexB =0; //Counter für Arrays Ringbuffer Brustkorb

int Init_IndexB = 0; //Counter Brustkorb Array
/*********Brustkorb***********/

float MagAccel_B[50];
float MagGyro_B[50];
/*********Oberschenkel********/
float MagAccel_O[50];
float MagGyro_O[50];
int maxIndex = 50;
/*****************************/
void setup() 
{
  Serial.begin(9600);
  //while (!Serial);  // wait for flora/leonardo
}

void loop() 
{
  char nextchar;
  // put your main code here, to run repeatedly:
 while(Serial.available())
 {
   nextchar = Serial.read();
   if(nextchar == 'A')
   {
      do
      {
        Accel_B = Serial.parseFloat();
        Gyro_B = Serial.parseFloat();
        MagAccel_B[put_indexB] = Accel_B;
        MagGyro_B[put_indexB] = Gyro_B;
        Serial.print(MagAccel_B[put_indexB]);
        Serial.print(";");
        Serial.println(MagGyro_B[put_indexB]);
        put_indexB = (put_indexB +1) % maxIndex;
        nextchar = Serial.read();
      }while(nextchar == '+');
    }
  }

  
}

Kann mir jemand helfen , wo der Fehler liegt?

Serial ist sehr, sehr langsam. Bei 9600 Baud dauert ein Zeichen ca. 1ms! Du kannst das daher nicht in einer while-Schleife einlesen. Durch parseFloat() verzögerst du das allerdings auch eine lange Zeit. Trotzdem scheint mit die Logik da falsch. Vor allem die Abfrage auf das Endzeichen.

Besser ist man schaut immer wieder noch ob was da ist und meldet dann wenn das Endzeichen da ist:

const int SERIAL_BUFFER_SIZE = 20;
char serialBuffer[SERIAL_BUFFER_SIZE];

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

void loop()
{
  if (readSerial(Serial))
  {
    Serial.print("Read: "); Serial.println(serialBuffer);
    parseSerial();
  }

}

void parseSerial()
{
  float value1 = atof(strtok(serialBuffer, ","));
  float value2 = atof(strtok(NULL, ","));

  Serial.println(value1);
  Serial.println(value2);
  Serial.println("--");
}

bool readSerial(Stream& stream)
{
  static byte index;
  static bool start;

  while (stream.available())
  {
    char c = stream.read();

    if (!start && c == 'A')
    {
      start = true;
    }
    else if (start && c == '+' && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
      start = false;
      return true;
    }
    else if (start && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }
  }
  return false;
}

Probier das erst mal im Serial Monitor aus. serialBuffer muss groß genug für alle Zeichen + 1 sein

Als Endzeichen würde sich ein LF oder CR besser anbieten. Ein Startzeichen bräuchte man nicht unbedingt.

Und ich weiß nicht wie du das angeschlossen hast, aber Serial ist die Verbindung über USB zum PC. Und das ist auch auf Pins 0/1. Du kannst daher da kein weiteres Gerät anschließen! Sondern du braucht einen Prozessor mit mehreren Hardware Schnittstellen oder SoftwareSerial oder besser AltSoftSerial

HI danke für deine Antwort und für den Tipp, es geht darum, dass ich von einem Arduino Fio daten via XBEE an einem Anderen Arduino Sende und dazu verwende ich XBEE , die über Serial laufen und mein Arduino nur 1 Serial interface hat. Die daten werden gesendet, doch es kommt nichts an :frowning:

Das sind eben zwei Fehlerquellen:
1.) Deine Einlese-Routine sind nicht gut aus
2.) Es ist falsch angeschlossen

Punkt 2 ist hier erst mal wichtiger. Aber auf parseFloat() solltest du verzichten wenn du irgendwas willst das performant ist. Das blockiert bis ein Zeichen kommt dass keine Zahl ergibt. Während der Zeit wird aber weiter in den seriellen Eingangspuffer geschrieben. Dadurch können auch Daten verloren gehen!

Nimm das:
https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
Auf dem UNO ist dann TX = Pin 8. Auf anderen Boards anders. Siehe Tabelle auf der Seite

An readSerial() kannst du genauso wie Serial eine Instanz von AltSoftSerial übergeben

An Serial kannst du nur ein externes Gerät anschließen wenn der Arduino nicht an USB hängt!

Nochwas:
readSerial() liest erst mal alles in ein char Array und wertet das dann aus. Statt einen riesig langen String zu Senden und den komplett zwischenzuspeichern ist es besser du sendest nur die Werte für einen Index zusammen und schließt diese mit dem Endzeichen ab. Also z.B. 2 oder 4 Werte und dann '+'. Und dann die nächsten Werte für den nächsten Index. So hält sich die Länge des Strings in Grenzen.

Hi danke wie meinst du das mit den Werte senden um einen Index zusammenzubekommen?

Sowie du es aussiehst hast du 2 oder 4 Ringpuffer.

Für zwei Werte machst du es dann so:
A1.1,2.2+
A3.3,4.4+
A5.5,6,6+
etc.

So muss man nur den String für zwei Werte zwischenspeichern. Dann konvertiert man, inkrementiert den Index und der nächste Datensatz kommt in den nächsten Index des Ringpuffers.

Also nicht alle 50 Werte in einen Datensatz schreiben. Da war mir nicht ganz klar wie du das geplant hattest.

Ok d.h. du meisnt alles aufeinmal lesen in einem String bis das Plus kommt? Und dann splitten.
Ich hatte es folgendermaßen vor:

Lese die Werte ein und direkt in zwei Variabeln speichern -> Accel_B und Gyro_B.
und diese in die Arrays.
Die sache is je nachdem manchmal werden 10 Datensätze: Wert1;Wert2.... oder 20 werte gesendet und abschließend ein +

Und du wolltest das mit parseFloat() machen. Was Schrott ist. Vor allem wenn man nicht die Timeout Zeit gewaltig nach unten setzt. parseFloat() wartet eine bestimmte Zeit bis nichts mehr ankommt oder ein Zeichen kommt dass keine Ziffer ist. Schau dir die Funktion mal genau an.

Meine Funktion liest ein was auf Serial vorhanden ist und kehrt dann nach loop() zurück wo man andere Dinge tun kann. Und sie reagiert sofort wenn das Endzeichen ankommt.

Die sache is je nachdem manchmal werden 10 Datensätze: Wert1;Wert2.... oder 20 werte gesendet und abschließend ein +

Das kannst du doch frei entscheiden. Besser ist du schließt jeden Datensatz mit + ab. Das Ende der Datensätze abzuschließen sollte unnötig sein. Das Hauptmerkmal eines Ringpuffers ist ja, dass immer die ältesten Daten überschrieben werden. Oder du nimmst nochmal ein anderes Zeichen um das zu markieren wenn das wirklich gebraucht wird.

Ok danke sehr für die sehr gute Erklärung. Werde es probieren und Feedback dazu geben.
Zu der Abfrage --> if (readSerial(Serial)) rufst du da die Funktion weiter unten bei dir auf bool readSerial(Steam& steam) ? oder wie habe ich das zu verstehen mit (Steam& steam)?

Stream& bedeutet dass eine Referenz auf das Objekt übergeben wird von dem man einliest. Stream ist dabei die Oberklasse von Klassen wie Serial (einschließlich SoftwareSerial und AltSoftSerial), SD oder EthernetClient. Das geht also auch mit anderen Dingen.

Mit dem if wird abgefragt ob das Endzeichen angekommen ist. Die Funktion gibt solange false zurück bis es noch nicht da ist. Wenn true kommt ist man fertig und kann den String verarbeiten.

So habe es ausprobiert, doch bekomme die Fehlermeldung, dass strok nicht erkannt wird. --> not declared.
Habe auch die String.h includiert und auch strok_r versucht erkennt er nicht. Woran könnte das liegen?

Die entsprechenden Header sind normal schon inkludiert!

Wenn du das in einem anderen Modul machst, musst du nicht String.h, sondern string.h inkludieren:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html
Groß-/Klein-Schreibung ist wichtig

Probier es halt erst mal ein einem eigenen Sketch, nur mit dem Serial Monitor und gebe deine Daten per Hand ein.

Danke für den tipp hab das auch ausprobiert mit der Kleinschreibung geht nicht :frowning:

ach habs gelöst hatte strok anstatt strtok

strtok = string tokenizer

Kann man sich ganz leicht merken :slight_smile:

JA danke sehr Serenify :D.

So hab es getestet und es Funktioniert doch sobald ich eine dreistellige Zahl eingebe z.B: A234.23;23.23+ dann setzt er den ersten wert auf 0.00 siehe bild im Nahng habe auch den Buffer vergrössert woran kann das liegen?

^test.png

Bei mir geht es. Der Parser Funktion ist die Länge egal, solange der String komplett eingelesen wurde

void parseSerial()
{
  float value1 = atof(strtok(serialBuffer, ";,"));
  float value2 = atof(strtok(NULL, ";,"));

  Serial.println(value1);
  Serial.println(value2);
  Serial.println("--");
}

Achte darauf dass du strtok() korrekt verwendest. Beim ersten Aufruf wird der String übergeben. Bei allen weiteren Aufrufen die diesen String verwenden übergibt man NULL.

ja genau so habe ich habe als delimiter halt nur ; anstatt ,;. das macht doch kein unterschied oder?
Habe es im Ardunio Monitor versucht.

sobald ich jedesmal einen dreistelligen wert 234.23 dann setzt er diesen 0 und der zweite wert wird erkannt liegt es an meinem serial-monitor`?

So lag daran , dass ich start vergessen hatte auf falls zu setzen, sobald der ganze string bzw das plus angekommen ist und er hat es dann immer überschrieben. Werde es in dem COde integrieren und testen jetzt.

Ja, mir ist gerade aufgefallen dass da am Anfang ein 'A' eingelesen wurde. Das darf nicht sein. atof() gibt völlig korrekt 0.0 zurück wenn der Teil-String mit einem Buchstaben anfängt.

Wieso hast du die Einlese-Funktion geändert? Das einzige was man da vielleicht einpassen müsste ist dass man statt '+' ein CR oder LF als Endzeichen nimmt. Und dann in dem Fall eine Abfrage hinzufügt dass Steuerzeichen nicht abgespeichert werden. Dann kann man einfach println() als Abschluss machen.

Also so:

    if (!start && c == 'A')
    {
      start = true;
    }
    else if (start && c == '\n' && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
      start = false;
      return true;
    }
    else if (start && c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }

Die Abfrage auf index > 0 kann man hier eigentlich weglassen. Das verhindert dass ein leerer String verarbeitet wird. Das ist nicht so relevant wenn man mit einem Startzeichen arbeitet.