Zeichenkette einlesen, zerteilen und in float umwandeln

Hallo Gemeinde,

ich hoffe ihr könnt mir helfen. Ich bin relativ neu in der Arduino Programmierung und komme nicht weiter.

Mein Ziel ist es, mit einem Arduino über die serielle Schnittstelle eine Zeichenkette einzulesen (die unterschiedlich lang sein kann), und so aussehen kann "1.234;2.345;3.456". Das Semikolon dient als Seprator der einzelnen Zahlen. Am Ende möchte 3 float variaben haben à la float1=1.234, float2=2.345, float3=3.456.

Ich habe viel rumprobiert, gegoogelt, noch mehr probiert usw. Zwischendurch bin ich dem Ergebnis näher gekommen, dann wieder weiter weg. Jetzt sehe ich den Wald vor lauter Bäumen nicht mehr und bin bei diesem (wahrscheinlich völlig falschem) Code stehen geblieben:

Ich hoffe ihr seid gnädig mit mir und könnt mir helfen.

VG/Lukas

char inputString[] = "";

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

void loop() {

  int i=0;

  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString[i] =inChar;i=i+1;
  }
  
  Serial.print("Input String: ");Serial.println(inputString);

  char* ch;
  ch = strtok (inputString,";");
  
  while (ch != NULL) {
    Serial.println(ch);
    
    float f=atof(ch);
    Serial.println(f,6);
  
    delay(1000);
    ch = strtok (NULL,";");
  }
  delay(1000);
}

1.) Du beachtest nicht wie elendig langsam Serial ist. Bei 9600 Baud dauert ein Zeichen 1ms. Da kannst du das nicht in einer while-Schleife einlesen.
2.) Du hast nicht verstanden wie Arrays funktionieren. Oder Strings in C. Wie du das Array verwendet ist totaler Quatsch.

Das macht man so dass man das Ende des Strings mit einem CR oder LF markiert (kann man wenn man es per Hand macht auch im Serial Monitor einstellen). Und dann liest man solange ein bis das Endzeichen kommt. Zwischendurch kann man dann andere Sachen machen.

Hier ist es korrekt:
http://forum.arduino.cc/index.php?topic=329469.msg2273780#msg2273780

SERIAL_BUFFER_SIZE musst du etwas erhöhen! z.B. auf 30

Und parse_serial() dann einfach so:

void parse_serial()
{
   float value1 = atof(strtok(serial_buffer, ";"));
   float value2 = atof(strtok(NULL, ";"));
   float value3 = atof(strtok(NULL, ";"));

   ....
}

Wenn du weißt dass du immer 3 Floats hast brauchst du da nicht unbedingt eine while Schleife. Der Teil ist aber nicht falsch. Eine while-Schleife da ist praktisch wenn man die Anzahl der Werte nicht kennt.

Wie gesagt CR oder LF (nicht beides) beim Senden nicht vergessen! Sonst geht es nicht

Hi,

vielen Dank für deine Hilfe. Ich habe mir das Thema Arrays nochmal angeshene und deinen Code studiert. Das hilft mir sehr für das Vertändnis.

Irgendwo hakt es aber noch. Es passiert bei einer Eingabe im Serial Monitor nichts, egal was ich eingebe (zum Beispiel 1.2;3.4;5.6;\n). Was mache ich hier falsch? Es kommt auch keine Meldung über "Read Serial True) im Serial Monitor.

Mein (dein) Code sieht jetzt so aus:

const int SERIAL_BUFFER_SIZE = 30;
char serial_buffer[SERIAL_BUFFER_SIZE];
float value1 = 0, value2 = 0, value3 = 0;

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

void loop()
{
  if (read_serial())
  {
    Serial.println("READ_SERIAL TRUE");

    parse_serial();
  }
}

bool read_serial()
{
  static byte index;

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

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serial_buffer[index++] = c;
    }
    else if (c == '\r' || c == '\n')
    {
      serial_buffer[index] = '\0';
      index = 0;
      return true;
    }
  }

  return false;
}

void parse_serial()
{
  value1 = atof(strtok(serial_buffer, ";"));
  value2 = atof(strtok(NULL, ";"));
  value3 = atof(strtok(NULL, ";"));
  
  Serial.println(value1,6);
}

jazzkeller:
Irgendwo hakt es aber noch. Es passiert bei einer Eingabe im Serial Monitor nichts, egal was ich eingebe

Bei dem Code, den Du verwendest, müßtest Du im Seriellen Monitor nicht nur die Baudrate eingestellt haben, sondern auch das Zeilenende-Zeichen.

Im Seriellen Monitor schaue mal rechts unten neben der Baudrate (das wäre die englische Beschriftung):

  • "Newline" ==> funktioniert
  • "Carriage Return" ==> funktioniert
  • "Both NL & CR" ==> funktioniert, liefert aber einmal Nuller-Werte hintendran
  • "No line ending" ==> funktioniert NICHT

Also eine reine Einstellungssache.

Das darf doch nicht wahr sein :smiley: Es funktioniert. Danke.

Wenn die die Zeichenkette aber von einem anderen Arduino kommt muss ich das Zeilende in den String mit einfügen, richtig?

jazzkeller:
Wenn die die Zeichenkette aber von einem anderen Arduino kommt muss ich das Zeilende in den String mit einfügen, richtig?

Richtig, dann mußt Du das Zeilenende mitsenden, entweder Carriage Return ('\r', 0x0D, ASCII-13) oder Newline ('\n', 0x0A, ASCII-10).

Ginge es auch, Serial.println zu verwenden? Kommen da die Zeilen Ende Zeichen nicht automatisch mit?

ElEspanol:
Ginge es auch, Serial.println zu verwenden?

println() hängt immer beides an, Carriage Return und Newline.

Das ist bei seinem Code äußerst nachteilig, weil wenn zwischen Carriage Return und Newline keine drei float-Zahlen gesendet werden, bekommt er immer einen Satz Nuller zwischen den gültigen Zahlen, die er eigentlich bekommen möchte.

Also bei seinem Code besser nur ein Steuerzeichen senden, Serial.print("...\n"); oder Serial.print("...\r"); und nicht zwei Steuerzeichen hintereinander. Oder den Code modifizieren, so dass unmittelbar aufeinanderfolgende Steuerzeichen keine Nuller-Datensätze rauswerfen.

ok, verstehe. Das könnte auch bei mir dann manchmal das Problem gewesen sein.

Wenn du CR + LF sendest dann ändere das:

else if (c == '\r' || c == '\n')

in:

else if (c == '\n')

Dann passt es :slight_smile:

Du kannst auch vor der Auswertung prüfen ob was im String steht:

if(*serial_buffer)

oder

if(*serial_buffer != NULL)

oder

if(serial_buffer[0] != '\0')

Und noch ein paar ähnliche Varianten. Dadurch verhinderst du dass ein leerer String ausgewertet wird. Was passiert wenn einfach so ein einzelnes CR oder LF ankommt

Praktisch z.B. so:

if (read_serial() && *serial_buffer)
{
}

Du kannst auch dein

if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)

in

    if (c > 32 && index < SERIAL_BUFFER_SIZE - 1)

ändern, dann werden Leerzeichen auch ignoriert. Da die schlecht sichtbar sind, sollte man vermeiden, dass sie für seltsames Verhalten sorgen können.