How to Obtain a FILE* pointer?

Hello:

I am reading a simple file "config.txt" inside the micro SD card (Arduino Ethernet rev3). The file contains some config parameters and i need to read it a save it into variables. For that task i want to use fscanf() function but the file open order fp.open("config.txt",FILE_READ) return to me a file descriptor (int).

I have been searching for solutions and i found the function fdopen() but it does not compile with the arduino libraries. Is there a way to obtain a file pointer from file descriptor with arduino libraries?

thank you.

What SD card library are you using? The included SD library in arduino IDE has plenty of sample code. The FILE is spelled File in arduino code.

Hello:

I am using the Arduino IDE 1.0.1 over Ubuntu Linux, the SD library is the default <SD.h> at libraries directory. I have read almost all examples about sd files and all of them work with a file descriptor (int). In arduino language file descriptors are File fp; for example, but i usually code in Ansi C or Posix C where the definition is FILE *fp; (pointer). I think that fdopen() function is from Posix C Api and arduino do not support it.

Thank you for all.

From sample code:

#include <SD.h>

File myFile;

void setup()
{
  Serial.begin(9600);
  Serial.print("Initializing SD card...");
  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin 
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output 
  // or the SD library functions will not work. 
   pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  myFile = SD.open("test.txt", FILE_WRITE);
  
  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
	// close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
  
  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");
    
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
    	Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
  } else {
  	// if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}

void loop()
{
	// nothing happens after setup
}

Although myFile is an object, not a pointer, you can easily use a pointer. Did I misunderstand you?

It's not an int or a pointer. It's an instance of a File class.

I suggest you read a line, and then do a scanf on it.

[quote author=Nick Gammon link=topic=178759.msg1325821#msg1325821 date=1374551270] I suggest you read a line, and then do a scanf on it. [/quote]

Yep, Arduino SD class has a File class that derives from Stream class which does not have the whole arsenal of fscanf etc. so the best is to read and do sscanf (not scanf, which only works on stdin, which is absent in arduino).

Hello:

You are rigth guys, after read your posts i guess that there is no way to obtain a FILE* pointer but your solution it is very interesting. I am going to read line to line and apply sscanf which requires a const char *line argument. For your information, the reading is like that:

The "config.txt" contains:

{bp25,1,0,25.2,20,1} {lm32,0,5,45.5,60,1} {bp25,2,7,10.2,20,0} etc...

I am going to obtain every line inside a char line[20];

then: sscanf(const char *line , "{%s,%d,%d,%f,%d,%d}" , sensor , num1 , num2 , fnum1 , num3 , num4);

It will be my strategy, how do you see it?

Greetings

txiku: Hello:

You are rigth guys, after read your posts i guess that there is no way to obtain a FILE* pointer but your solution it is very interesting. I am going to read line to line and apply sscanf which requires a const char *line argument. For your information, the reading is like that:

The "config.txt" contains:

{bp25,1,0,25.2,20,1} {lm32,0,5,45.5,60,1} {bp25,2,7,10.2,20,0} etc...

I am going to obtain every line inside a char line[20];

then: sscanf(const char *line , "{%s,%d,%d,%f,%d,%d}" , sensor , num1 , num2 , fnum1 , num3 , num4);

It will be my strategy, how do you see it?

You don't have an operating system, so you don't have the upper layer (FILE *, libstdc++ file routines, etc.) of the library.

Note, sscanf will consume a lot of your program space, and you use it and have complex code, you might not have enough space for your code. In addition, since you are using floating point, another large chunk of program code will be taken up with the floating point emulation routines.

Unfortunately using sscanf like you mention won't work for AVR based Arduinos. because in order to save code space, the scanf/printf library is compiled without floating point support. So using %f will not work. There are versions of the AVR libraries with floating point enabled for scanf/printf, but these are not default. I don't believe the IDE gives you an easy way to use these libraries (you basically have to go in and edit the boards.txt file to add the appropriate libraries).

Obviously in terms of reading the line, you need to make sure the array you hold the line is enough for the largest line and trailing null.

I would tend to write code that reads the line character by character and does the parsing inline, rather than building up a buffer, and using sscanf to parse it.

Hello MichaelMeissner:

You really explain me what i was looking for, now i understand the problematic. For parsing the data inline i guess that i have to use the String class of arduino because it has parseInt() and parseFloat() functions. I am going on with my code and thank you.

Thank for all members.

For parsing the data inline i guess that i have to use the String class of arduino because it has parseInt() and parseFloat() functions.

No, you don't. You can (and should) use a NULL terminated array of chars, also known as a string, and functions like strtok(), atoi(), and atof() to parse the string and convert the tokens to numeric values.

It is true, the functions in string.h are more powerful and i usually work with char line[]; variables. Thank you for the advice.

PaulS:

For parsing the data inline i guess that i have to use the String class of arduino because it has parseInt() and parseFloat() functions.

No, you don't. You can (and should) use a NULL terminated array of chars, also known as a string, and functions like strtok(), atoi(), and atof() to parse the string and convert the tokens to numeric values.

I would suggest instead of using strtok, use strchr since you are searching for a single character. Or use strtol and strtof which can return the location of the first non-digit character. Note, in some versions of the Arduino libraries, the floating point conversion functions are broken, but I don't recall the details.

struct sensor_type
{
    char name[10];
    int num1;
    int num2;
    float fnum1;
    int num3;
    int num4;
};

// Parse the sensor line, return true if it looks ok
bool
parse_sensor (struct sensor_type &sensor, char *buffer)
{
    char *start = buffer;
    char *end = strchr (',');
    size_t len;

    if (!end)
        return false;

    len = end - start;
    memcpy (sensor.name, start, len);
    sensor.name[len] = '\0';

    start = end + 1;                  // start at char after ','
    end = NULL;
    sensor.num1 = (int) strtol (start, &end, 0);

    if (!end || *end != ',')
        return false;

    start = end + 1;                  // start at char after ','
    end = NULL;
    sensor.num2 = (int) strtol (start, &end, 0);

    if (!end || *end != ',')
        return false;
 
    start = end + 1;                  // start at char after ','
    end = NULL;

    sensor.fnum1 = (float) strtof (start, &end);
  
    if (!end || *end != ',')
        return false;

    // and so on for the other 2 fields.
    return true;
}

MichaelMeissner: Note, in some versions of the Arduino libraries, the floating point conversion functions are broken, but I don't recall the details.

Support for printing floating point values via vprintf() and related functions is optional in the AVR library, and the Arduino IDE does not enable this option. I don't know whether it's possible to reconfigure the IDE to enable that option, but it doesn't seem to be supported by the 1.0.4 version which I use.

PeterH: Support for printing floating point values via vprintf() and related functions is optional in the AVR library, and the Arduino IDE does not enable this option. I don't know whether it's possible to reconfigure the IDE to enable that option, but it doesn't seem to be supported by the 1.0.4 version which I use.

Note, I wasn't clear. I wasn't talking about floating point support not being compiled in sprintf/sscanf libraries, but one of the lower level functions (strtod or dtostrf) that was incorrect. However, I don't remember the details. Hopefully it has been fixed in 1.0.5.

I don’t recall these strtod and dtostrf are broken in arduino IDE 1.0.x I’ve used them. Arduino IDE should include a copy of avr gcc library manual so the OP can read about these functions. They are easy to use.

If you just need the character functions, like fscanf(), you can easily implement your own files. All you need to do is provide the appropriate fput/fget() functions. Here’s a test I did to get “fprintf” to work on serial:

#include <stdio.h>
FILE* ser;

int fput(char c, FILE* f) {
  Serial.write( c);
  return 0;
}

int fget(FILE* f) {
  int i = Serial.read();
  if (i < 0) return _FDEV_ERR;
  return i;
}

void setup() {
  Serial.begin(9600);
  ser = fdevopen(fput, fget);
}

Hello:

Finally i have used strtok() function from string.h to solve my problem. I have read every line inside the file character by character and then i used strtok() to obtain all tokens. The code is like this:

File fp;
char c;
char buffer[50];
char delim[4] = "{,}";
char *res;

fp = open("config.txt",FILE_READ);
if(fp)
{
   for(j=0;j<18;j++)    //there are 18 lines inside config.txt
   {
      for(i=0;i<30;i++)    //max length line is about 25 caracters
      {
         c = fp.read();       //read character by character until '\n' or end of file
         if(c == '\n' || c == EOF)
         {
            buffer[i] = '\0';       //if line end is reach i write the null character
            break;
         }
         else
         {
            buffer[i] = c;       //if not i write the next character to buffer
         }
         res = strtok(buffer,delim);      
        strcpy(pt->name,res);             //store the name in struct
        res = strtok(NULL,delim);
        pt->num1 = atoi(res);             //store num1 in struct
        res = strtok(NULL,delim);
        pt->num2 = atoi(res)              //store num2 in struct
        ....
        ....
       }
    }
}

Thanks for all the support.

Can this thread be deleted? I had to read through 20ish replys before I figured out no one answered the question this thread asked. It is a bunch of random discussion having nothing to do with an answer. Hopefully deleting it will save someone else time in future.

ryanb9: Can this thread be deleted?

Of course it can.

But it won't be deleted for the false reasons you cite (every post is on-topic except your's and mine) nor for your failure at reading comprehension (the answer is in reply #7).