Arduino and SD card help

Hey,

I have a few issues with my program...

I have on it a file called jessica.txt and can read from it fine digit by digit onto the serial monitor but I can't seem to store into a variable correctly.

The file contains

12 01 00 1 0

The code so far is as follows

#include <SD.h>

const int chipSelect = 4;
int i = 0;

class details
{
  public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

void setup()
{

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  pinMode(10, OUTPUT);
  
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
}


void loop()
{
  
  int num1;
  int num2;
  int num3;
  int num4;
  
  details jessica;
  
  File jessicaFile;
  
  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    while( jessicaFile.available())
    {
      
      
      switch(i)
      {
        case 0:
          jessica.hours = jessicaFile.read();
          break;
        case 1:
          jessica.mins = jessicaFile.read();
          break;
        case 2:
          jessica.secs = jessicaFile.read();
          break;
        case 3:
          jessica.pillOne = jessicaFile.read();
          break;
        case 4:
          jessica.pillTwo = jessicaFile.read();
          break;
        default:
          break;
      }
      i++;
      Serial.write(jessicaFile.read());
      
      delay(1000);
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  
    
  }
  Serial.println("Hours:");
  Serial.write(jessica.hours);
  Serial.println("\n Minutes:");
  Serial.write(jessica.mins);
  Serial.println("\n Seconds:");
  Serial.write(jessica.secs);
  Serial.println("\n Pill One:");
  Serial.write(jessica.pillOne);
  Serial.println("\n Pill Two");
  Serial.write(jessica.pillTwo);
  Serial.println("\n");
  delay(10000);
}

and i know it shouldn't be all in the loop, just got lazy, and it will eventually be combined with other parts for a project

Thanks for your help!

strtok should be able to parse that for you.
http://www.cplusplus.com/reference/clibrary/cstring/strtok/

I have on it a file called jessica.txt and can read from it fine digit by digit onto the serial monitor but I can't seem to store into a variable correctly.

You have some underlying assumptions reflected in your code that are wrong.

The file contains

12 01 00 1 0

          jessica.hours = jessicaFile.read();

The hours member of the jessica instance will now contain '1', not 12.

          jessica.mins = jessicaFile.read();

This will read the '2', not the 01.

You need to learn how to read a complete record (which means you need to understand what a complete record is, specifically when one ends), storing the data in an array (NULL terminated at all times), and then, as SurferTim mentions, use strtok() to parse that array.

PaulS:

I have on it a file called jessica.txt and can read from it fine digit by digit onto the serial monitor but I can't seem to store into a variable correctly.

You have some underlying assumptions reflected in your code that are wrong.

You need to learn how to read a complete record (which means you need to understand what a complete record is, specifically when one ends), storing the data in an array (NULL terminated at all times), and then, as SurferTim mentions, use strtok() to parse that array.

Thanks for your condescending help Paul, obviously I am unsure of how to do it, otherwise I wouldn't have asked...

And thanks for the link Tim.

I still can't get it to work, could you see where I have gone wrong?

#include <SD.h>
#include <String.h>

const int chipSelect = 4;
int i = 0;
int a = 0;

class details
{
public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

char str[12];
char * pch;

void setup()
{

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
}


void loop()
{

  details jessica;

  File jessicaFile;

  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    while( jessicaFile.available())
    {
      i++;
      str[i] = jessicaFile.read();
      Serial.write(str[i]);

      delay(1000);
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  

  }

  pch = strtok(str," ,.-");
  while(pch != NULL)
  {
    Serial.write(pch);
 //   a = atoi(pch);
    pch = strtok(NULL," ,.-");
    
   // Serial.write(a);
    Serial.println("is this working?\n");

  } 

  Serial.println("didn't Work!!!");
  delay(10000);

}

Thanks

I would try this:

  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    i = 0;
    while( jessicaFile.available())
    {
      char ch = jessicaFile.read();
      if(i < 11) {
        str[i] = ch;
        Serial.write(str[i]);
        i++;
        str[i] = 0;
      }
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  
  }

Or are you getting a "error opening jessica.txt"?

edit: modified the read so it would read the entire file, even tho after 11 characters it will not store it.

This is what i have now. And it opens and reads the numbers and prints at the pch serial.write's but not quite sure if what ive done to convert to integers is correct, well i know its not cause it doesn't come out correctly. I've tried subtracting 48, '0', casting... Or maybe im going about it all wrong?

Thanks for your condescending help Paul, obviously I am unaware of how to do it, otherwise I wouldn't have asked...

I still can't get it to work, could you see where I have gone wrong?

[code]
#include <SD.h>
#include <String.h>

const int chipSelect = 4;
int i = 0;
int a = 0;

class details
{
public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

char str[12];
char * pch;

void setup()
{

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
}


void loop()
{

  details jessica;

  File jessicaFile;

  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    while( jessicaFile.available())
    {
      i++;
      str[i] = jessicaFile.read();
      Serial.write(str[i]);

      delay(1000);
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  

  }

  pch = strtok(str," ,.-");
  while(pch != NULL)
  {
    Serial.write(pch);
 //   a = atoi(pch);
    pch = strtok(NULL," ,.-");
    
   // Serial.write(a);
    Serial.println("is this working?\n");

  } 

  Serial.println("didn't Work!!!");
  delay(10000);

}

Thanks[/code]

Did you see my post above? There are major things wrong.

  if(jessicaFile)
  {
    // initialize i here

    while( jessicaFile.available())
    {
      // here are two. Increment i after the read, and limit i to (the size of your buffer) - 1
      i++;
      str[i] = jessicaFile.read();
      Serial.write(str[i]);
      // here is another. You should insure the text data is zero terminated.

      // and here is another. Why the delay?
      delay(1000);
    }
    jessicaFile.close();
  }

Reading files into variables is often easier using the C++ Standard I/O Streams Library. SdFat implements much of this library.

Here is a sketch that read a line from the jessica.txt file into the details structure, prints the structure, rewinds the file and lists the file as ascii text.

#include <SdFat.h>
SdFat sd;
const int chipSelect = 4;

class details {
public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

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

  // SdFat will initialize pin 10, SS, as an output and set it high 
  if (!sd.init(SPI_FULL_SPEED, chipSelect)) sd.initErrorHalt();
  
  // open file
  ifstream jessicaFile("jessica.txt");
  if (!jessicaFile.is_open()) {
    Serial.println("open failed");
    return;
  }
  // structure for data
  details jessica;

  // read line
  jessicaFile >> jessica.hours >> jessica.mins >> jessica.secs >> jessica.pillOne >>jessica.pillTwo;

  // check for read error
  if (!jessicaFile) {
    Serial.println("read error");
    return;
  }
  // print result
  Serial.print("Hours: ");
  Serial.println(jessica.hours);
  Serial.print("Minutes: ");
  Serial.println(jessica.mins);
  Serial.print("Seconds: ");
  Serial.println(jessica.secs);
  Serial.print("Pill One: ");
  Serial.println(jessica.pillOne);
  Serial.print("Pill Two: ");
  Serial.println(jessica.pillTwo);
  
  // list the file content
  Serial.println("___________________");
  // rewind the file
  jessicaFile.seekg(0);
  int c;
  // print file
  while ((c = jessicaFile.get()) >= 0) Serial.write(c);
}
void loop() {}

Here is the output from the sketch:

Hours: 12
Minutes: 1
Seconds: 0
Pill One: 1
Pill Two: 0


12 01 00 1 0

The SdFat library is here Google Code Archive - Long-term storage for Google Code Project Hosting.

Sorry Tim,

I posted the wrong code...

#include <SD.h>
#include <String.h>

const int chipSelect = 4;
int i = 0;
int a = 0;

class details
{
public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

char str[12];
char * pch;

void setup()
{

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
}


void loop()
{

  details jessica;

  File jessicaFile;

  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    i = 0;
    while( jessicaFile.available())
    {
    //  char ch = jessicaFile.read();
      while(i<11)
      {
      str[i] = jessicaFile.read();
      Serial.write(str[i]);
      i++;
      str[i] = 0;
      delay(1000);
      }
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  

  }
  
  i=0;
  Serial.println("\n");
  pch = strtok(str," ,.-");
  while(pch != NULL)
  {
    switch(i)
    {
      case 0:
        jessica.hours = (int)(pch - 48);
        break;
      case 1:
        jessica.mins = (int) pch;
        break;
      case 2:
        jessica.secs = (int) pch;
        break;
      case 3:
        jessica.pillOne = (int) pch;
        break;
      case 4:
        jessica.pillTwo = (int) pch;
        break;
      default:
        break;
       
    }
    i++;
    
    Serial.write(pch);
 //   a = atoi(pch);
    pch = strtok(NULL," ,.-");
    
   // Serial.write(a);
    Serial.println("is this working?\n");

  } 
  
  Serial.write(jessica.hours);
  Serial.write(jessica.mins);

  delay(10000);

}

and using the SDFat library is a cool idea, but i don't really have any idea what is going on there but il look into it.

Using the C++ I/O streams library can be very powerful but most old-timers and hobbyists don't have the knowledge to effectively use this library so maybe sticking with the simpler Arduino SD API will be best for you.

The Arduino API is a simple wrapper for an old version of SdFat that does not expose I/O streams.

The I/O streams library is nice since one line can read text for an entire structure, convert the text to binary, store the result in program variables, and check for errors.

Here is the statement for your data:

  jessicaFile >> jessica.hours >> jessica.mins >> jessica.secs >> jessica.pillOne >> jessica.pillTwo;

You can test for success of the above read statement like this:

  if (!jessicaFile) {
    // error occurred 
  }

You may have problems here if the file has more than 11 characters. It will not exit the while loop because it will not read the entire file. That is why I put the read outside the save routine. That way it will read the entire file and not stay in the loop.

    while( jessicaFile.available())
    {
    // If the file is larger than 11 bytes, it will lock here
    //  char ch = jessicaFile.read();

    // and this should be if(i < 11), not while
      while(i<11)
      {
      str[i] = jessicaFile.read();
      Serial.write(str[i]);
      i++;
      str[i] = 0;

      // remove this delay!
      delay(1000);
      }
    }

Remove that delay! It is in the wrong place. An eleven character file will take ten seconds to read. ??
If you want a second between file reads, put it after the jessicaFile.close().

This is just an SD shield? No ethernet onboard?

edit: I missed the while(i < 11). It should be if(i < 11).

This is the way I see it:

    while( jessicaFile.available())
    {
      char ch = jessicaFile.read();

      if(i<11)
      {
        str[i] = ch;
        Serial.write(str[i]);
        i++;
        str[i] = 0;
      }
    }

That part works, and the delay is just so i can see it, not for any other reason. and it needs to be

if(i<=11) or just if(i<12) because there are 12 characters in there... if its less than 11 than its only doing it 11 times

I just can't figure out how to divide them into my class? It works to divide them using the strtok and when i print out the pch. it doesn't work when I have casted them back to int so it can be stored using the switch...

char str[12];

If you are getting 12 ascii type characters that you plan on printing, then you better increase the size of str[] by one character. 12 characters along with the terminating zero makes 13 characters total. You will overflow your array.

Why do you need a terminating zero?

And I am only printing the numbers so I can see it working, I really just want them put into the class "details" so that I can use them later

Then you don't need the terminating zero. But I don't think strtok will work with a string that is not zero terminated, so you may need to find another way of parsing the array.

strtok works, i have it printing out the numbers... Im just not sure how to cast them so i can store them as an integer?

atoi

Thanks for all your help Tim,

I was just being a rookie towards the end and using the serial commands...

But here is the working code for anyone interested

#include <SD.h>
#include <String.h>

const int chipSelect = 4;
int i = 0;
int a = 0;

class details
{
public:
  int hours;
  int mins;
  int secs;
  int pillOne;
  int pillTwo;
};

char str[13];
char * pch;

void setup()
{

  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
}


void loop()
{

  details jessica;

  File jessicaFile;

  jessicaFile = SD.open("jessica.txt");
  if(jessicaFile)
  {
    i = 0;
    while( jessicaFile.available())
    {
      char ch = jessicaFile.read();
      if(i<13)
      {
        str[i] = ch;
        Serial.write(str[i]);
        i++;
        str[i] = 0;
        delay(1000);
      }
    }
    jessicaFile.close();
  }
  else
  {
    Serial.println("error opening jessica.txt");  

  }

  i=0;
  Serial.println("\n");
  pch = strtok(str," ,.-");
  while(pch != NULL)
  {
    switch(i)
    {
    case 0:
      jessica.hours = atoi(pch);
      break;
    case 1:
      jessica.mins = atoi(pch);
      break;
    case 2:
      jessica.secs = atoi(pch);
      break;
    case 3:
      jessica.pillOne = atoi(pch);
      break;
    case 4:
      jessica.pillTwo = atoi(pch);
      break;
    default:
      break;

    }
    i++;

    Serial.write(pch);
    pch = strtok(NULL," ,.-");

  } 
  Serial.println("\nHours: ");
  Serial.print(jessica.hours);
  Serial.println("\nMinutes: ");
  Serial.print(jessica.mins);
  Serial.println("\nSeconds:");
  Serial.print(jessica.secs);
  Serial.println("\nPill One:");
  Serial.print(jessica.pillOne);
  Serial.println("\nPill Two: ");
  Serial.print(jessica.pillTwo);
  Serial.println();

  delay(10000);

}

Good to hear it is working, but you are still overflowing the str array.

// this is set for 13 characters
char str[13];

// then later in the code
      if(i<13)
      {
        str[i] = ch;
        Serial.write(str[i]);
        i++;
        // If i == 12, this will store zero in str[13] (14th position)
        str[i] = 0;
        delay(1000);
      }

add: Overflowing arrays can have embarrassing results. Try this code. :astonished:

char test[4] = {'a','b','c','d'};
char between[3] ={'1','2','3'};
char test2[4] = {'d','u','c','k'};
char testend = 0;

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

  Serial.println(test);    
  Serial.println(between);
  Serial.println(test2);
  // this is changing the test array, not test2
  test[7] = 'f';
  // now print test2
  Serial.println(test2);
}

void loop() {
}

But I don't think strtok will work with a string that is not zero terminated, so you may need to find another way of parsing the array.

It will, but the results will not be what you expect. Somewhere in memory, after the address that is the start of the array, there will be a NULL. strtok() will keep processing until it finds it. The NULL is its clue (as with all string functions) that it has reached the end of the array.

If you really are too lazy to increase the size of the array by 1, and to NULL terminate the array, that's fine. Simply do not use any string handling functions (strtok, strcat, strcpy, atoi(), etc.).

Really, learning to deal with NULL terminated arrays of chars will be quite useful in most everything you do with the Arduino, and is worth taking the time and effort to learn how to do it properly.