Daten aus einer CSV lesen

Moin Leute,
hab da so ein kleines Problem wo ich nicht weiter komme, ich hoffe Ihr könnt mir da helfen.
Ich möchte aus einer CSV Datei die auf einer Micro SD Karte ist Daten auslesen.
Das ausgelesen soll dann in eine Variabel geschrieben werden. Die Daten oder besser gesagt die Zahlen sind mit ; getrennt. die Kommunikation läuft über SPI zwischen Arudino und dem Shield.
Ich habe mir schon paar englischen Seiten angeguckt aber so recht komm und versteh ich da nicht weiter.

Ich danke schon mal im vorraus. :slight_smile:

Hardware:
Arudino UNO R3
Arduino Wireless SD Shield
Micro SD Karte 2GB

TEST.csv:
Datum; Nummer; Nummer; Nummer;
Beispiel: 26.032018; 1; 0; 1;

Schaue Dir mal von der Programmbibliothek SdFat das Beispiel readCSV.ino an, das tut prinzipiell, was Du möchtest.

Beim Datum dürfte ein Punkt fehlen: 26.03.2018;1;0;1

calik97:
Das ausgelesen soll dann in eine Variabel geschrieben werden.

Datum und die drei Werte wirklich in eine Variable oder doch mehr?

Ja das hab ich gesehen aber ich habe nicht ganz verstanden was ich davon unbedingt brauche.

// Functions to read a CSV text file one field at a time.
//
#include <limits.h>
#include <SPI.h>

// next line for SD.h
//#include <SD.h>

// next two lines for SdFat
#include <SdFat.h>
SdFat SD;

#define CS_PIN SS

// example can use comma or semicolon
#define CSV_DELIM ','

File file;

/*
 * Read a file one field at a time.
 *
 * file - File to read.
 *
 * str - Character array for the field.
 *
 * size - Size of str array.
 *
 * delim - csv delimiter.
 *
 * return - negative value for failure.
 *          delimiter, '\n' or zero(EOF) for success.           
 */
int csvReadText(File* file, char* str, size_t size, char delim) {
  char ch;
  int rtn;
  size_t n = 0;
  while (true) {
    // check for EOF
    if (!file->available()) {
      rtn = 0;
      break;
    }
    if (file->read(&ch, 1) != 1) {
      // read error
      rtn = -1;
      break;
    }
    // Delete CR.
    if (ch == '\r') {
      continue;
    }
    if (ch == delim || ch == '\n') {
      rtn = ch;
      break;
    }
    if ((n + 1) >= size) {
      // string too long
      rtn = -2;
      n--;
      break;
    }
    str[n++] = ch;
  }
  str[n] = '\0';
  return rtn;
}
//------------------------------------------------------------------------------
int csvReadInt32(File* file, int32_t* num, char delim) {
  char buf[20];
  char* ptr;
  int rtn = csvReadText(file, buf, sizeof(buf), delim);
  if (rtn < 0) return rtn;
  *num = strtol(buf, &ptr, 10);
  if (buf == ptr) return -3;
  while(isspace(*ptr)) ptr++;
  return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadInt16(File* file, int16_t* num, char delim) {
  int32_t tmp;
  int rtn = csvReadInt32(file, &tmp, delim);
  if (rtn < 0) return rtn;
  if (tmp < INT_MIN || tmp > INT_MAX) return -5;
  *num = tmp;
  return rtn;
}
//------------------------------------------------------------------------------
int csvReadUint32(File* file, uint32_t* num, char delim) {
  char buf[20];
  char* ptr;
  int rtn = csvReadText(file, buf, sizeof(buf), delim);
  if (rtn < 0) return rtn;
  *num = strtoul(buf, &ptr, 10);
  if (buf == ptr) return -3;
  while(isspace(*ptr)) ptr++;
  return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadUint16(File* file, uint16_t* num, char delim) {
  uint32_t tmp;
  int rtn = csvReadUint32(file, &tmp, delim);
  if (rtn < 0) return rtn;
  if (tmp > UINT_MAX) return -5;
  *num = tmp;
  return rtn;
}
//------------------------------------------------------------------------------
int csvReadDouble(File* file, double* num, char delim) {
  char buf[20];
  char* ptr;
  int rtn = csvReadText(file, buf, sizeof(buf), delim);
  if (rtn < 0) return rtn;
  *num = strtod(buf, &ptr);
  if (buf == ptr) return -3;
  while(isspace(*ptr)) ptr++;
  return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadFloat(File* file, float* num, char delim) {
  double tmp;
  int rtn = csvReadDouble(file, &tmp, delim);
  if (rtn < 0)return rtn;
  // could test for too large.
  *num = tmp;
  return rtn;
}
//------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  
  // Wait for USB Serial 
  while (!Serial) {
    yield();
  }
  Serial.println("Type any character to start");
  while (!Serial.available()) {
    yield();
  }
  // Initialize the SD.
  if (!SD.begin(CS_PIN)) {
    Serial.println("begin failed");
    return;
  }
  // Remove existing file.
   SD.remove("READTEST.TXT"); 
   
  // Create the file.
  file = SD.open("READTEST.TXT", FILE_WRITE);
  if (!file) {
    Serial.println("open failed");
    return;
  }
  // Write test data.
  file.print(F(
#if CSV_DELIM == ','
    "36,23.20,20.70,57.60,79.50,01:08:14,23.06.16\r\n"
    "37,23.21,20.71,57.61,79.51,02:08:14,23.07.16\r\n"
#elif CSV_DELIM == ';'
    "36;23.20;20.70;57.60;79.50;01:08:14;23.06.16\r\n"
    "37;23.21;20.71;57.61;79.51;02:08:14;23.07.16\r\n"
#else
#error "Bad CSV_DELIM"
#endif
));

  // Rewind the file for read.
  file.seek(0);

  // Read the file and print fields.
  int16_t tcalc; 
  float t1, t2, h1, h2;
  // Must be dim 9 to allow for zero byte.
  char timeS[9], dateS[9];
  while (file.available()) {
    if (csvReadInt16(&file, &tcalc, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &t1, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &t2, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &h1, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &h2, CSV_DELIM) != CSV_DELIM
      || csvReadText(&file, timeS, sizeof(timeS), CSV_DELIM) != CSV_DELIM
      || csvReadText(&file, dateS, sizeof(dateS), CSV_DELIM) != '\n') {
      Serial.println("read error");
      int ch;
      int nr = 0;
      // print part of file after error.
      while ((ch = file.read()) > 0 && nr++ < 100) {
        Serial.write(ch);
      }
      break;            
    }
    Serial.print(tcalc);
    Serial.print(CSV_DELIM);
    Serial.print(t1);
    Serial.print(CSV_DELIM);
    Serial.print(t2);
    Serial.print(CSV_DELIM);
    Serial.print(h1);
    Serial.print(CSV_DELIM);
    Serial.print(h2);
    Serial.print(CSV_DELIM);
    Serial.print(timeS);
    Serial.print(CSV_DELIM);
    Serial.println(dateS);
  }
  file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

Beim Datum dürfte ein Punkt fehlen: 26.03.2018;1;0;1

Was meinst du mit Punkt?

Datum und die drei Werte wirklich in eine Variable oder doch mehr?

Nein jeweils in eine Variable also 4 Variable

calik97:
Was meinst du mit Punkt?

Bei Dir fehlt zwischen Monat und Jahr vermutlich ein Punkt.

calik97:
Nein jeweils in eine Variable also 4 Variable

Gut.

calik97:
… aber ich habe nicht ganz verstanden was ich davon unbedingt brauche.

Welche Werte können die drei Werte haben? Davon hängt der zu nutzende Datentyp ab. Also int16, uint32 oder float? Je Datentyp wird eine Funktion angeboten, im Funktionsnamen steht auch der Typ.

ich habe nicht ganz verstanden was ich davon unbedingt brauche

Das Beispiel soll nur für dein Verständnis sein, und sollte natürlich funktionieren, also - wenn du es laufen lässt- nichts mit dem Wort "error" auf Serial ausgeben, sondern die Testdaten bis zum Text "23.07.16" anzeigen.

Ob du dann, nachdem du gesehen und verstanden hast, wie das Beispielprogramm es macht, es genauso machst, und evtl. dessen Funktionen wie csvReadInt32 übernimmst, bleibt natürlich dir überlassen.

Auch solltest du das Beispiel mit dem Trennzeichen ';' statt ',' laufen lassen und froh sein, dass du keine Gleitpunktzahlen mit deutschem Dezimalkomma in deinen Testdaten hast.

Das Beispiel löscht evtl. eine Testdatei, schreibt sie dann neu und liest das gerade eben geschriebene wieder ein. Das willst du vermutlich anders haben ( nur lesen ).
Damit du deine Testdaten nicht kaputt machen kannst, wirst du wohl eher

 file = SD.open("TEST.csv", FILE_READ);

verwenden...

Hi,

Wenn du die gesamte Zeile als String vorliegen hast kannst Du mit String.indexOf() ab einer Startposition nach dem Semikolon suchen, dabei bekommst Du die Position des Zeichens zurück.

In einer Schleife must Du also zunächst die Position des ersten ; finden.

Dann mit String.substring() den ersten Teilstring bestimmen das ist dann Deine erste Variable

dann von der ersten gefunden Position als Startwert wieder mit .indexOF() die position vom nächsten ; suchen.

wieder mit .substring() den teilstring bestimmen das ist dann die zweite Variable

usw.

In der Hilfe ist das unter String. Objekt gut erklärt.

wenn Du die SD Karte byteweise ausliest kannst Du Dir das Suchen sparen und in der Leseschleife gleich auf das Semikolon abfragen und ab da die nächsten Byte in die nächste Variable schreiben.

Auf den Arduinos ist die Verwendung der Klasse String nicht zu empfehlen, da dadurch in der Regel der Speicher (RAM) fragmentiert wird. Es sollte Char-Arrays der Vorzug gegeben werden.

Zu Zeichenketten in C habe ich einige Hinweise aufgeschrieben.

Gruß Tommy

Danke an alle mal.
Das hab ich jetzt mir mal raus geschnitten bin mir aber nicht ganz sicher ob es richtig ist.
Da ein Fehler begin failed kommt.

// Functions to read a CSV text file one field at a time.
//
#include <limits.h>
#include <SPI.h>

// next line for SD.h
//#include <SD.h>

// next two lines for SdFat
#include <SdFat.h>
SdFat SD;

#define CS_PIN SS

// example can use comma or semicolon
#define CSV_DELIM ';'

File file;

/*
 * Read a file one field at a time.
 *
 * file - File to read.
 *
 * str - Character array for the field.
 *
 * size - Size of str array.
 *
 * delim - csv delimiter.
 *
 * return - negative value for failure.
 *          delimiter, '\n' or zero(EOF) for success.           
 */
int csvReadText(File* file, char* str, size_t size, char delim) {
  char ch;
  int rtn;
  size_t n = 0;
  while (true) {
    // check for EOF
    if (!file->available()) {
      rtn = 0;
      break;
    }
    if (file->read(&ch, 1) != 1) {
      // read error
      rtn = -1;
      break;
    }
    // Delete CR.
    if (ch == '\r') {
      continue;
    }
    if (ch == delim || ch == '\n') {
      rtn = ch;
      break;
    }
    if ((n + 1) >= size) {
      // string too long
      rtn = -2;
      n--;
      break;
    }
    str[n++] = ch;
  }
  str[n] = '\0';
  return rtn;
}
//------------------------------------------------------------------------------
int csvReadInt32(File* file, int32_t* num, char delim) {
  char buf[20];
  char* ptr;
  int rtn = csvReadText(file, buf, sizeof(buf), delim);
  if (rtn < 0) return rtn;
  *num = strtol(buf, &ptr, 10);
  if (buf == ptr) return -3;
  while(isspace(*ptr)) ptr++;
  return *ptr == 0 ? rtn : -4;
}
//------------------------------------------------------------------------------
int csvReadFloat(File* file, float* num, char delim) {
  int32_t tmp;
  int rtn = csvReadInt32(file, &tmp, delim);
  if (rtn < 0)return rtn;
  // could test for too large.
  *num = tmp;
  return rtn;
}
void setup() {
  Serial.begin(9600);
  
  // Wait for USB Serial 
  while (!Serial) {
    yield();
  }
  Serial.println("Type any character to start");
  while (!Serial.available()) {
    yield();
  }
  // Initialize the SD.
  if (!SD.begin(CS_PIN)) {
    Serial.println("begin failed");
    return;
  }
  // Create the file.
  file = SD.open("3.CSV", FILE_READ);
  if (!file) {
    Serial.println("open failed");
    return;
  }

  // Read the file and print fields.
  int32_t tcalc; 
  float t1, t2, h1, h2;
  // Must be dim 9 to allow for zero byte.
  char timeS[9], dateS[9];
  while (file.available()) {
    if (csvReadInt32(&file, &tcalc, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &t1, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &t2, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &h1, CSV_DELIM) != CSV_DELIM
      || csvReadFloat(&file, &h2, CSV_DELIM) != CSV_DELIM
      || csvReadText(&file, timeS, sizeof(timeS), CSV_DELIM) != CSV_DELIM
      || csvReadText(&file, dateS, sizeof(dateS), CSV_DELIM) != '\n') {
      Serial.println("read error");
      int ch;
      int nr = 0;
      // print part of file after error.
      while ((ch = file.read()) > 0 && nr++ < 100) {
        Serial.write(ch);
      }
      break;            
    }
    Serial.print(tcalc);
    Serial.print(CSV_DELIM);
    Serial.print(t1);
    Serial.print(CSV_DELIM);
    Serial.print(t2);
    Serial.print(CSV_DELIM);
    Serial.print(h1);
    Serial.print(CSV_DELIM);
    Serial.print(h2);
    Serial.print(CSV_DELIM);
    Serial.print(timeS);
    Serial.print(CSV_DELIM);
    Serial.println(dateS);
  }
  file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

ich wäre froh wenn einer ein funktionierendes Programm für mich hat damit ich es versteh und sehe ob es funktioniert.

Da ein Fehler begin failed kommt

...muss man gucken was in SdFat::begin (byte SSpin) falsch ist.

Beispiele solltest du genug haben, die mindestens über das begin hinaus kommen.

Hallo zusammen

Das was Tommy56 da geschrieben hat ist sicher richtig, bringt talik97 aber nicht weiter.

das was ich da heute nachmittag so vor mich hin gedacht habe, habe ich mal in einen Sketch geschrieben.

ich denke der ist einfach zu verstehen und ist natürlich nur als Beispiel gedacht. Kann also nicht allgemein verwendet werden. Insofern also als q&d Software zu verstehen. :slight_smile:

Das was für die Aufgabe wichtig ist hab ich in eine Funktion Zerlegen() zusammen gepackt. Die Zuordnung zu den globalen Variablen in dem Switch Teil muss man anpassen.

Gruß Heinz

// String S1 u S2 beinhalten verschiedene Varialbe
// die durch ";" getrennt werden. 
// Sie werden in einzelne Teile zur weiteren Verarbeitung
// zerlegt.


// zwei Strings mit unterschiedlichem Format der Variablene

String s1 = "03-03-18;17:35;1.15;1024;5.21";
String s2 = "03.03.2018;07:00:01;1000.2;9;100.0";



String datum, zeit;   // Datum Zeit als String
float messw1, messw3;  // Messwert 1 u 3 als float
int messw2;            // Messwert 2 als Integer


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  zerlegen(s1);
  ausgabe(s1);

  zerlegen(s2);
  ausgabe(s2);

}

void loop() {
  // put your main code here, to run repeatedly:



}
// =============== Funktionen ======================


// -----------In die geforderte teile zerlegen
void zerlegen(String st) {
  int startpos, endpos;
  String teil;
  startpos = 0;
  endpos = 0;


  // es gibt 5 teilstrings es sind 4 trennzeichen vorhanden
  
  Serial.println("-------------------------------");
  Serial.println(st);
  for (int i = 0; i <= 4; i++) {

    endpos = st.indexOf(";", startpos);
    if (i == 4) { // beil letzten bis zum Ende
      endpos = (st.length());
    }

    teil = st.substring(startpos, endpos);

    // Anzeige zum testen
    Serial.print ( "von:"); Serial.print(startpos);
    Serial.print (" bis: "); Serial.print(endpos);
    Serial.print (" Teilstrig ist "); Serial.println(teil);

    startpos = endpos + 1;

    // Teile den Variablen zuordnen
    switch (i) {
      case 0:
        datum = teil;
        break;
      case 1:
        zeit = teil;
        break;
      case 2:
        messw1 = teil.toFloat();
        break;
      case 3:
        messw2 = teil.toInt();
        break;
      case 4:
        messw3 = teil.toFloat();
        break;
    }

  }
}
//
void ausgabe(String s) {

  Serial.println("-------------------------------");
  Serial.print ("Engangsstring  "); Serial.println(s);
  Serial.print ("Datum  "); Serial.println(datum);
  Serial.print ("Zeit   "); Serial.println(zeit);
  Serial.print ("Messw1 Float   "); Serial.println(messw1);
  Serial.print ("Messw2 Integer "); Serial.println(messw2);
  Serial.print ("Messw3 Float   "); Serial.println(messw3);
}

Rentner:
Das was Tommy56 da geschrieben hat ist sicher richtig, bringt talik97 aber nicht weiter.

Doch, es bewahrt ihn davor auf den falschen Rat zu hören und sich später mit sehr schwer zu ergründenden Programmfehlern herumschlagen zu müssen.

Gruß Tommy