Read csv and assign variables

I am struggeling to read a .csv file and assign them to different variables.
I’m logging different sensors from a Weatherstation.
In purpose of displaying them in a graph, I have to read a previously saved csv.
Each line consists of seven values as shown beneath:

36,23.20,20.70,57.60,79.50,01:08:14,23.06.16

DataType: int, float, float, float, char[8], char[8]
Variable: tcalc, t1, t2, h1, h2, timeS, dateS
Delimitor; ‘,’

I’ve been messing around with adapting the sdfat csv example, but it is all a mess.
Could someone be so kind to shed me some light on this?

//Nevermind - got it sortet out

Here is a demo of how you might read your file.

This program is complex since it does a lot of error checking GASP!

It is also not well tested.

I plan to post it as an SdFat example.

// 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

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(
    "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"
    ));

  // 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, ',') != ','
      || csvReadFloat(&file, &t1, ',') != ','
      || csvReadFloat(&file, &t2, ',') != ','
      || csvReadFloat(&file, &h1, ',') != ','
      || csvReadFloat(&file, &h2, ',') != ','
      || csvReadText(&file, timeS, sizeof(timeS), ',') != ','
      || csvReadText(&file, dateS, sizeof(dateS), ',') != '\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(',');
    Serial.print(t1);
    Serial.print(',');
    Serial.print(t2);
    Serial.print(',');
    Serial.print(h1);
    Serial.print(',');
    Serial.print(h2);
    Serial.print(',');
    Serial.print(timeS);
    Serial.print(',');
    Serial.println(dateS);
  }
  file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

Thank you very much, fat16lib, much appreciated.
Your code is much smaller than mine :slight_smile:

It works. I have defined the variable delimiter in the beginning and changed the code ‘,’ to the variable to be more flexible.
Thanks again!

// 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 53

File file;
char delim = ';';

/*
   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("Bigdata.csv", FILE_WRITE);
  if (!file) {
    Serial.println("open failed");
    return;
  }
  // Write test data.   (Disabled for my test with real data)
  //  file.print(F(
  //"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"
  //));

  // 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, delim) != delim
        || csvReadFloat(&file, &t1, delim) != delim
        || csvReadFloat(&file, &t2, delim) != delim
        || csvReadFloat(&file, &h1, delim) != delim
        || csvReadFloat(&file, &h2, delim) != delim
        || csvReadText(&file, timeS, sizeof(timeS), delim) != delim
        || csvReadText(&file, dateS, sizeof(dateS), 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(delim);
    Serial.print(t1);
    Serial.print(delim);
    Serial.print(t2);
    Serial.print(delim);
    Serial.print(h1);
    Serial.print(delim);
    Serial.print(h2);
    Serial.print(delim);
    Serial.print(timeS);
    Serial.print(delim);
    Serial.println(dateS);
  }
  file.close();
}
//------------------------------------------------------------------------------
void loop() {
}

Dear fat16lib,

on a Due, it doesn't compile.

if (csvReadInt16(&DataFile, &i, delim) != delim

^

exit status 1
cannot convert 'int*' to 'int16_t* {aka short int*}' for argument '2' to 'int csvReadInt16(File*, int16_t*, char)'

Is there any way to fix that?

Raptylos:
Dear fat16lib,

on a Due, it doesn't compile.

if (csvReadInt16(&DataFile, &i, delim) != delim

^

exit status 1
cannot convert 'int*' to 'int16_t* {aka short int*}' for argument '2' to 'int csvReadInt16(File*, int16_t*, char)'

Is there any way to fix that?

everywhere you see a declaration of int, change it to int16_t. The Due is a 32bit CPU, int is by 'C' definition the processor native integer format. On UNO's or Mega's it is 16bits long, on any of the ARM chip it is 32bits. So anywhere in the DUE code, an 'int' is 32bits, unless you actually need 32bit ints, change the declaration from int to int16_t.

Chuck.

That did the trick!

Thank you, Chuck

fat16lib,
thanks much for posting this. It works for my purpose which is to read MicroSD cards from a flight-Data recorder whose files include fields not needed by my analysis program, and is missing some which are, but can be defaulted. Now I can rearrange the fields to agree with what the analysis program is expecting, and spoof the fields which it expects to be there but are not collected in my flight data.

john