Serielle Daten auswerten

Hallo zusammen,
ich bin noch ein "Anfänger" auf dem Arduino Gebiet, habe allerdings auch schon einiges gebastelt.
Für mein jetziges Wetterdisplay-Projekt, habe ich bereits mehrere Temperatur Sensoren und einen Luftdruck-Sensor eingebunden und die Werte auf einem LCD 20x4 Display ausgegeben.

Nun sollen noch Werte von externen Sensoren einfließen, diese kann mittels Serial.read auch schon unformatiert auf dem Display anzeigen lassen, allerdings ist das noch nicht so wie ich es mir vorstelle.

Es sollen also Serielle Daten ausgewertet und gespeichert werden.
Die Daten kommen im Form von: OC317.25
Hierbei steht "OC3" für den Namen (Name kann beliebig geändert werden) des externen Sensors und "17.25" ist in diesem Fall eine Temperatur.

Nun möchte ich die Zeile "OC317.25" zerpflücken und sagen:
Nimm die Stellen zwischen "OC3" und dem "." plus die zwei Stellen nach dem "." und packe diese wieder korrekt zusammengesetzt in eine Variable/Array.
Als Ergebnis soll also z.B. in einer Variable OC3 nur die Temperatur in Form von "17.25" stehen, damit ich diesen Wert frei auf meinem Display setzen kann.

Einen ähnlichen Fall habe ich hier gefunden, damit komme ich allerdings nicht weiter.

Kann mir da eventuell jemand auf die Sprünge helfen.
Danke Euch im Voraus.

Hast du C Strings/char Arrays oder die dämliche Arduino String Klasse, wie man sie leider in den meisten Anfänger-Beispielen sieht?

Mit C-Strings ist das trivial. Einfach einen Offset auf das Array (da Array Variablen Zeiger auf das erste Element sind) und atof() (Array to Float) für die Konvertierung.

@Serenifly
hmm wie erkenne ich den unterschied zwischen C Strings/char Arrays und der Arduino String Klasse. Sorry wie gesagt “Anfänger”.
Hast Du eventuell ein Beispiel dazu?

Zeig mal deinen Sketch, dann kann man dir weiterhelfen.

Poste halt mal deinen Code wie du die serielle Schnittstelle ausliest.

Bei C-Strings hast du eine char Array:

char buffer[20];
buffer[i++] = Serial.read()

Bei der String Klasse hast du ein String Objekt:

String str = "";
str += Serial.read();

Die String Klasse ist Schrott weil sie massig Ressourcen verschwendet, zu nichts kompatibel ist und selbst nur wenige Funktionen implementiert.

Für C-Strings musst du nur das machen:

float temp = atof(buffer + 3);

Sorry hatte leider in letzten Tagen keine Zeit weiter zu machen. Ich bin nun schon weiter gekommen, allerdings blicke ich noch nicht ganz warum es wie folgt funktioniert.

#include <LiquidCrystal_I2C.h> 
#include <Wire.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup(void) {
Serial.begin(9600);
lcd.begin(20,4);
} 

void loop(void) {

char input[16];
byte i = 0;
char *v;  

 while (i < 15) {
 char c = Serial.read();
 Serial.write(c);  // WARUM GEHT DAS SO????
 if (c == '\n') break;
 else input[i++] = c;
 }
 input[i] = 0;

 v = strchr(input,'OC3'); // Zeichen suchen

   if (v++) { // wenn Zeichen gefunden, dann eine Stelle weiter
    float t;
     t = atof(v); // Umwandlung des Strings in einen Float-Wert

    lcd.setCursor(0, 0); 
    lcd.print(t);
    lcd.setCursor(0, 1); 
   }
  }

Wenn ich nun die Zeile
“Serial.write(c);”
rausnehme geht es nicht mehr.

Danke für Eure Hilfe

Das mit der while-Schleife sieht verdächtig falsch aus. Serial ist so langsam (1ms pro Zeichen bei 9600 Baud), dass du lange Zeit auf das nächste Zeichen warten musst.

Außerdem wird deine Zählvariable i bei jedem Schleifendurchlauf wieder auf 0 gesetzt

Wenn am Ende ein Linefeed kommt, kannst du das machen:

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

void loop()
{
  if(checkSerial() == true)
  {
        //Auswertung hier
  }
}

bool checkSerial()
{
  static byte index;

  if(Serial.available() > 0)
  {		
    char c = Serial.read();
		
    if(c != '\n' && index < SERIAL_BUFFER_SIZE - 1)
    {
       serialBuffer[index++] = c;
    }
    else
    {
       serialBuffer[index] = '\0';
       index = 0;
       return true;
    }
  }
  return false;
}

Das lagert das Auslesen in eine eigene Funktion aus. Diese gibt true zurück wenn das LF gesendet wurde. Das geht wie gesagt davon aus dass der Sensor ein LF sendet.

Wenn da kein LF am Ende ist und du statt dessen Abfragen willst ob eine bestimmte Anzahl von Zeichen da ist, darfst du nicht while verwenden! Sondern musst sowas wie if(Serial.avaiblale() >= 15) machen. Dann wird gewartet bis auch wirklich so viele Zeichen im Eingangspuffer sind. Das geht dann aber auch nicht mit obiger Funktion! Statt dessen kann man es in einem Rutsch in loop() einlesen.

Dann ein paar weitere Sachen:
1.) strchr() ist völlig falsch verwendet. Damit suchst du nach einem char. ‘OC3’ mag kompilieren, aber ist eigentlich nur ein ‘O’.
2.) um nach Teil-Strings zu such gibt es strstr(). Das liefert dir einen Zeiger auf den Anfang des Strings. Also auf das ‘O’
3.) Wenn der Anfang immer konstant 3 Zeichen lang ist kannst du einfach einen Offset von 3 drauf addieren:
atof(stringBuffer + 3); Das geht natürlich auch mit Zeiger den strstr() liefert

Du kannst dann z.B. strstr() verwenden um den Sensor zu identifizieren. strncmp() mit 3 als Länge würde hier vielleicht auch gehen wenn der gesuchte String immer am Anfang steht. Das vergleicht die ersten 3 Zeichen. Der Unterschied ist dass strstr() den ganzen String durchsucht, während strncmp() vom Anfang an vergleicht und abbricht sobald eine Abweichung da ist. Wenn der Teil-String aber auch weiter hinten stehen kann, dann geht strncmp() nicht und strstr() ist die korrekte Wahl.

@Serenifly
Tausend Dank für deine Erläuterung, das hat mir weiter geholfen.

Ich habe es nun wie folgt gelöst:

#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup()
{
  Serial.begin(9600);
  lcd.begin(20,4);
  lcd.backlight();
}

void loop()
{
  {
    char input[16];
    byte i = 0;
    char *octemp;
    char *ocws;
// when characters arrive over the serial port...
    if (Serial.available()) {
// wait a bit for the entire message to arrive
      delay(100);
// read all the available characters
      while (Serial.available() > 0) {
        char c = Serial.read();
          if (c == '\n') break;
           else input[i++] = c;
        }
   input[i] = 0;  // nach jesem durchlauf auf 0 setzen
      
   if (strncmp(input, "OC1",3)==0) { // Sensor Namen "OC1" suchen, wenn gefunden dann weiter
     octemp = (input+3); // Sensor Namen abschneiden
      float octemp;
       octemp = atof(input+3); // wandeln in float
       
     lcd.setCursor(0, 0);
     lcd.print(octemp); // Wert des Sensors auf LCD
     lcd.print((char)223);
     lcd.print("C");
}
if (strncmp(input, "OC2",3)==0) { // Sensor Namen "OC2" suchen, wenn gefunden dann weiter
     ocws = (input+3); // Sensor Namen abschneiden
      float ocws;
       ocws = atof(input+3); // wandeln in float
       
     lcd.setCursor(0, 1);
     lcd.print(ocws); // Wert des Sensors auf LCD
     lcd.print("km/h");
   }
  }
 }
}

Bisher läuft es sauber.

:slight_smile:

Das ist die dritte Möglichkeit. Abfragen ob ein Zeichen da ist und dann warten bis der Rest eingetroffen ist. In diesem Fall völlig ok, aber wenn das Programm noch was anderes tun soll, ist es oft nicht so gut wenn man es da für ein paar ms völlig blockiert. Kommt halt auf die Anwendung an.

Was dir allgemein noch klar sein sollte, ist dass loop() im wieder von vorne durchläuft. Variablen verlieren also ihren Wert von einem Durchgang zum nächsten! Bei deiner Version spielt das jetzt keine Rolle, da der ganze Vorgang innerhalb eines Durchlaufs beendet ist. Bei deiner vorherigen Version allerdings hätte die Einlese-Routine mehrere loop-Durchläufe benötigt. Ähnlich wie bei meiner Version wie immer wieder die Funktion aufgerufen wird bis sie fertig ist. Du hättest also die Zähl-Variable als static deklarieren müssen, damit sie ihren Wert behält und nicht beim nächsten Durchlauf wieder 0 ist.
Wie gesagt, in deinem Code jetzt nicht nötig, aber bei einem anderen Problem vielleicht.

Das hier ist allerdings überflüssig:

octemp = (input+3);
ocws = (input+3);

Du kannst dir wahrscheinlich sogar die Wandlung in Float sparen wenn du nichts damit rechnest. Das hier sollte auch gehen:

lcd.print(input + 3)