[SOLVED] Working with strings

Hi,

I wan't to read the content of a text file from an SD card. The content would look like this:

odometer: 4343789

I then want to do something to the equivalent of this in bash:

value = $(grep odometer dataString.txt | cut -d " " -f2)

The value needs to be converted to an integer too.

I'm studying this as much as I can now, but hopefully someone can step in here to help.

Take a look at the strtok() and strtoul() functions. For example:

Also, an odometer with over 4 million miles...really?

I said it would look like that :wink: Thanks.

But "that" will influence whether you use strtol(), atoi(), strtoul(), or dtostrf(). It's always good to be as specific as you can be.

OK - so It's unlikely that I'll travel over 999,999 miles, and although it matters to optimise, at the moment I'm just after a proof of coding.

#include <SD.h>

const int SD_CS_PIN = 9; // SD

File dataFile;

char distance;
char *token;
char delim[] = " ";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
 
  SD.begin(SD_CS_PIN);
  dataFile = SD.open("odometer.txt");
  if (dataFile) {
    while (dataFile.available()) {
      token = strtok(dataFile, delim);
    }
  }

}

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

}

But this errors:

sketch_nov03a.ino: In function 'void setup()':
sketch_nov03a:19: error: cannot convert 'SDLib::File' to 'char*' for argument '1' to 'char* strtok(char*, const char*)'
       token = strtok(dataFile, delim);
                                     ^
exit status 1
cannot convert 'SDLib::File' to 'char*' for argument '1' to 'char* strtok(char*, const char*)'

But I thought SDLib::File was a char... can't find out what it should be.

Hmm to Files are their own thing, Opaque Pointers... this is so beyond me.

How can I extract some data from an SD card and assign said data to an integer or char respectively?

Usually, you read data from a file on the SD card, put it into a string, then operate on the string with the functions mentioned above.

Thanks. Getting there..

if (dataFile) {
    while (dataFile.available()) {
      char distance = dataFile.read();
      //int distance = atoi(dataFile.read());
      Serial.print(distance);
    }
  }

Now sends 18937493 to the serial console when set as a char, but when I uncomment the int line the serial console prints 000000000.
.

strtok will come in useful later, but at the moment I've stripped my work right back to get an idea.

Does this help?

No, it doesn't say how to convert the File read to an integer.

I've tried this below, too, and it still prints a row of zeros:

  if (dataFile) {
    while (dataFile.available()) {
      char distanceChar = dataFile.read();
      int distanceInt = atol(distanceChar);
      Serial.print(distanceInt);
    }
  }

"atol" accepts a string (a null-terminated char array), not a single char.

Have a look at Robin2's serial input basics thread - it's equally relevant here.

If that's the basics, then coding really isn't for me, it just gives a headache trying to work out what's going on.

In other news, I changed the char to char array and now atol will convert char array to a long array, but I want it just to be 1 integer.

Is it really this complex; ie running things through arrays, and loops, etc, just to fetch a value?

and now atol will convert char array to a long array, but I want it just to be 1 integer.

If you've got atol producing a long array, you've done something wrong.

  SD.begin(SD_CS_PIN);
  dataFile = SD.open("odometer.txt");
  if (dataFile) {
    while (dataFile.available()) {
      char distanceChar[] = {dataFile.read()};
      int distanceInt = atol(distanceChar);
      Serial.print(distanceInt);
    }
  }

This produces 189374930 but that end zero is a null. Also, if I run the same code but in a println instead, I get this:

1
8
9
3
7
4
9
3
0

If I do it like this:

  SD.begin(SD_CS_PIN);
  dataFile = SD.open("odometer.txt");
  if (dataFile) {
    while (dataFile.available()) {
      distance = dataFile.read();
      int distanceInt = strtoul (distance, NULL, 0);
      Serial.print(distanceInt);
    }
  }

It then prints out a row of zeros 000000000 again.

I'm getting closer:

  if (dataFile) {
    while (dataFile.available()) {
      x = dataFile.read();
      if ( x >= 48 && x <= 57 ) {
        Serial.print("x: ");
        Serial.println(x);
        y = x - 48;
        Serial.print("y: ");
        Serial.println(y);
      }
    }
  }

Outputs:

x: 49
y: 1
x: 56
y: 8
x: 57
y: 9
x: 51
y: 3
x: 55
y: 7
x: 52
y: 4
x: 57
y: 9
x: 51
y: 3

At least now I know what beast I'm dealing with. I now need an elegant way to concatenate them together.

In C, strings are arrays of char that are terminated with a an ascii NUL '\0'.

Read one character at a time.
Put the characters into an array of char[] that will be big enough to hold all the characters plus one.
When you get to the end of the line or the end of the file, add a terminating '\0' to the string.
This creates a C string that the various string.h functions will work with, eg "9999.7\0".

For extra credit:

Do not use a floating point number for any number that needs to be precise. For an odo reading, read in the characters, skip the decimal point, and if necessary pad out the number to a fixed number of decimal places. Convert that number into a long rather than a float.

So if the file contains 123.45, put "12345\0" into your string. If it contains "123.1", put "12310" into your string. I suggest you name variables that hold these "whole number of hundredths of a mile" with a suffix that indicates the unit, say "cMi" for centi-miles. In metric, of course, pad it out to three decimal places and use metres.

n1md4:
At least now I know what beast I'm dealing with. I now need an elegant way to concatenate them together.

Like this. No need for strings:

  long total = 0;

  if (dataFile) {
    while (dataFile.available()) {
      x = dataFile.read();
      if ( x >= '0' && x <= '9' ) {
        Serial.print("x: ");
        Serial.println(x);
        y = x - '0';
        Serial.print("y: ");
        Serial.println(y);

        total = total * 10 + y;
        Serial.print("total: ");
        Serial.println(total);
      }
    }
  }

This example doesn't exactly do what you want, but the principles used are what you need to learn.

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

void loop() {
 char buffer[15];
 int charsRead;
 long answer;

  while (Serial.available() > 0) {
    charsRead = Serial.readBytesUntil('\n', buffer, sizeof(buffer) - 1);
    buffer[charsRead] = '\0';
    answer = strtol(buffer, '\0', 10);
    Serial.println(answer);
  }
}

Your code reads the characters one at a time rather than collecting them together and then converting them into a long. This example uses the Serial object, but is similar to reading data from an SD card.

Thanks for all the help. I will try these suggestions later and report back.