Problem saving data to SD

Hi,
I try to save the data acquired by different sensors into an ascii file (named data.log) at a microSD card (This one: http://www.libelium.com/tienda/catalog/product_info.php?cPath=21&products_id=66), by the use of Filelogger library.

I tried to use the example included with the filelogger library with a very simple example that measure the inner temperature and voltage of my arduino duemillanove without any extra component. In my real project i want to save more data from temperature, humidity, pressure sensor, etc. But in this way is more simple.
The problem is that the code does not compile. I am sure that the solution is quite simple, but i don´t know how to solve it…

Here is the code:

#include <FileLogger.h>
#define MEM_PW 8

long readTemp() {
  long result;
  // Read temperature sensor against 1.1V reference
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
  delay(20); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (result - 125) * 1075;
  return result;
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

void write_data(float msg[]){
  unsigned int length = (strlen(msg)+1);
  buffer[length];
  for(int i=0; i<length;i++)
    buffer[i] = msg[i];
  FileLogger::append("data.log", buffer, length);
}

void setup() {
  pinMode(MEM_PW, OUTPUT);
  digitalWrite(MEM_PW, HIGH);
  Serial.begin(9600);
  Serial.println("Arduino inner temperature and inner voltage");
}

void loop() {
  Serial.print(readTemp(), DEC); // read the inner temperature of arduino
  Serial.print(" x10^-4 degC   ");
  Serial.print(readVcc(), DEC); // read the voltage
  Serial.println("  mVolts");
  buffer[0]= (readTemp()); // save the data at the microSD
  buffer[1]= (readTemp());
  writedata(buffer);
  delay(1000);
}

and this is the error showed during compilation:

In function ‘void write_data(float*)’:
error: cannot convert ‘float*’ to ‘const char*’ for argument ‘1’ to ‘size_t strlen(const char*)’ In function ‘void loop()’:

I think that the problem is related to two things. First that i should establish the number of components in the array, what is the number of values that i want to save in the same line of the data.log file, one per sensor… And the other topic is that i should establish the length of each value in the array. I am not sure if this is true and neither how should i do that.

Could you help me?
Thanks!

ok, i made some small changes in the code.

I deleted the writedata procedure and i wrote the procedures in the void loop().

This is how it looks like now:

#include <FileLogger.h>
#define MEM_PW 8

long readTemp() {
  long result;
  // Read temperature sensor against 1.1V reference
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
  delay(20); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (result - 125) * 1075;
  return result;
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  pinMode(MEM_PW, OUTPUT);
  digitalWrite(MEM_PW, HIGH);
  Serial.begin(9600);
  Serial.println("Arduino inner temperature and inner voltage");
}

void loop() {
  Serial.print(readTemp(), DEC); // read the inner temperature of arduino
  Serial.print(" x10^-4 degC   ");
  Serial.print(readVcc(), DEC); // read the voltage
  Serial.println("  mVolts");
  char data[1];
  unsigned int length = (strlen(data[1])+1);
  data[0]=(readTemp());
  data[1]=(readVcc());
  FileLogger::append("data.log", data, length);
  delay(1000);
}

Now, the error compiling is:

In function ‘void loop()’:
error: invalid conversion from ‘char’ to 'const char*

I tried changing the type of array called data, form chart to float (because the resulting values that i want to store are that…).
This is the line of code:

float data[1];

but then, the error is very similar:

In function ‘void loop()’:
error: cannot convert ‘float’ to ‘const char*’ for argument ‘1’ to 'size_t strlen(const char*)

So i decided to assign a value to the length of each value stored in the array to 8 digits (in the example, temperature and voltage should be smaller numbers…).
This is the line code:

unsigned int length = (8);

and this is the new error:

In function ‘void loop()’:
error: cannot convert ‘float*’ to ‘byte*’ for argument ‘2’ to ‘int FileLogger::append(const char*, byte*, long unsigned int)’

Reading this error message and the FileLogger .h and .ccp files, looks like if the data array should be only byte type of data.
It means that i should convert the reading from the sensors (in this case “readTemp()” and “readVcc()”) to byte. I don´t know if it is possible and how to do it. does it means to convert to string?
I am lost in this point. I really appreciate any idea and help.

Thanks!

Hi again,

I am in the way…

Since the FileLogger library require a byte array, i changed the definition of the array to:

byte data[1];

and the length of each value of the array to 20 in that way:

unsigned int length = (20);

So the void loop() code looks like that right now:

void loop() {
  byte data[1];
  unsigned int length = (20);
  data[0]=(readTemp());
  data[1]=(readVcc());
  FileLogger::append("data.log", data, length);
  Serial.print(readTemp(), DEC); // read the inner temperature of arduino
  Serial.print(" x10^-4 degC   ");
  Serial.print(readVcc(), DEC); // read the voltage
  Serial.println("  mVolts");
  delay(1000);
}

And it compiles!! and it also save things into the SD!!
However, there are also bad news… it not store the data…
what i have when i open the file into the wordpad, is this:

6L 8 % data.log x L 6L 8 % data.log x L 6L 8 % data.log x L 6L 8 % data.log x L

and so on for a very long long line. It looks like if each value generate this sequence. In fact, the spaces seems to be tabs and not spaces…
So, the code is not correct!! but i am in the way.

As even, any comment and help is really welcome!

P.D. Sorry, this posts looks like my own blog!

You declared data to be an array of length 1. Then, you tried to put more data into the array than would fit.

You didn't include the code for readTemp and readVcc, so I can't tell if they return bytes, or larger values.

When you told FileLogger to append 20 bytes, it tried, but you didn't supply 20 bytes. The array you supplied was only one byte long, so it grabbed some random data to fill the extra space.

Change data like this:

byte data[20];

Thanks PaulS,

yeah, i included the complete code in posts #1 and #2. I included only the last changes in post #3 to made the post shorter.

Yes, you are right, there was an error on the number of members in the array. It is fixed!

readTemp() return a value such as 285000 meanwhile readVcc() returns a value such as 4940. how many bytes are this?? (sorry. probably is a very stupid question but i really don´t have idea...).

I will change the code and try again... Thanks for the light!

Hi again, here is the void loop code that i have right now:

void loop() {
  byte data[2];
  float temp=readTemp();
  float Vcc=readVcc();
  data[0]=(temp);
  data[1]=(Vcc);
  unsigned long length = sizeof(data[2]);
  FileLogger::append("data.log", data, length);
}

I made automatic the calculation of the size of the data array.
I declare correctly the size of the array.

It compiles, but the stored result is this:

5

5
5
5

555555555

So, may be i should convert the sequence of data into a long string, with “,” in between the different data (in this case, temp and Vcc). Any idea about how to do it?
Thanks!

In your earlier post, you declared readTemp and readVcc as returning longs. Longs are 4 bytes.

Setting a variable of one type (byte) to the value of another variable of a different type can result in loss of data, if the types are not the same size.

Longs and bytes are not the same size, so there is data loss when you stuff temp and Vcc into data[0] and data[1].

Finally, the sizeof function returns the size of the specified argument. You specified the third member of a two member array as the argument. The sizeof function figured out that the argument was of type byte, so it returned the size of a byte, in bytes, or 1.

So, the FileLogger logged one byte.

If you are going to use sizeof, the argument is the whole array, not one member of the array.

In any case, I think you are going to get better results if you convert the values to strings, and save them. The sprintf function will do that.

char message[20]; sprintf(message,"%l,%l", temp, Vcc);

Then, use strlen to get the length of the string, and pass message and the length to FileLogger::append.

Great PaulS!

Thanks so much for all this help. And also for the information about the bytes.

I made some changes in the code, but now i have problems in compilation.

Firstly, i changed the temp and Vcc to long type for don´t loss information.

Later, i included your ideas in the code, but i have a problem... the FileLogger procedure require a byte sequence, so i converted the message to an array, following the same example than in FileLogger example. But it have problems to know the size of the message...

Here is the code:

void loop() {
  long temp=readTemp();
  long Vcc=readVcc();
  char message[20];
  sprintf(message,"%l,%l", temp, Vcc);
  unsigned long length = strlen(message);
  byte data[]=message;
  FileLogger::append("data.log", data, length);
}

The error in compilation is this:

In function 'void loop()': error: initializer fails to determine size of 'data'

as i said, if i remove the declaration of the array and call to the message in the FileLogger procedure, the error is this:

In function 'void loop()': error: invalid conversion from 'char*' to 'byte*'

Thanks!

The char and byte types are the same size. The FileLogger::append function says it wants a byte array, but what it means is that it wants a byte-sized array.

You have a byte-sized array, message. You simply need to tell the function that. You do that with a cast:

FileLogger::append("data.log", (byte *)message, length);

The (byte *) part is called a cast. It tells the compiler to treat message as though it was a byte array.

There are implicit and explicit casts. This code uses an implicit cast:

long temp;
int i = 14;

temp = i;

The temp variable expects a long value, but you can supply an int, and the compiler will convert the value to a long, implicitly, before storing it.

Any time you try to store a smaller value type in a larger value type, an implicit cast is performed, as long as the types are the same (int, float, etc.).

When the types are not the same, but the sizes are, an explicit cast is required.

When the types are not the same, and the sizes are not the same, an explicit cast may result in loss of data, if the destination is smaller than the source.

Great lesson PaulS, i am learning a lot!

I made the changes that you proposed. Now it compiles, but it produce a sequence of symbols without sense…

This is the code:

void loop() {
  long temp=readTemp();
  long Vcc=readVcc();
  char message[20];
  sprintf(message,"%l,%l", temp, Vcc);
  unsigned long length = strlen(message);
  FileLogger::append("data2.log", (byte*)message, length);
 delay(1000);
}

and this what it write in the SD card, repeated in a long sequence:

@ <[ch8221]

=l
ÿ
jå[ch8226]

Thanks for your interest, help, efforts and patience!

Add a Serial.println call after the sprintf function, to verify that message contains what it's supposed to, please. Let me know what it prints.

i did, and just only enters seems to be passed to the serial, because the vertical scroll increase each second, but not characters appear on the screen...

So rare, isn´t it?

UPDATE: at the end of the void loop() i also included some lines to print temp and Vcc, and they appear! So the problem is not related to the procedures but sending the results to the SD.

What does your loop code look like, now?

The sprintf statement should have "%ld, %ld", not "%l, %l".

Great!!!! Congratulations!!!! it works!!!!

Yes, it was the problem... since i didn´t know whatt the "gramatic" of this expressions i couldn´t detect it. Good job!

Now i have other couple of question related to this... but much more easy!

it save all the data one near the other in a long long line. So i what to have very couple of data in a single line. In the example of FileLogger it is solver writing \r\n at the end of the string. In the sentence that you builded, it not run in this way:

sprintf(message,"%ld,%ld", temp, Vcc, "\r\n");

Is there any other "magic" combination of characters such as "%ld" to do it? Any idea?

And, finally, if i increase the number of sensors, i suposse that i also should change (increasing) the value of char message[20], is it right?

Thanks so much for your efforts. I will prepare a clean code to post here for future readers, although it must be consider your contribution! Thanks one more time for your great help and effort!

Thanks PaulS!

The format part of the sprintf argument list is the part between the "". If you want the message array to contain a CR\LF, add \n at the end:

sprintf(message,"%ld,%ld\n", temp, Vcc);

If you want to write more data into message, message must be longer.

As for the format "magic", see this: http://www.cppreference.com/wiki/c/io/printf

Thanks so much PaulS,

It works perfectly now.

The link is great and looks very useful to learn more about C.

Now i post here the final code as i have it working. May be it could be useful to somebody else in the future.

#include <FileLogger.h>
#define MEM_PW 8

long readTemp() {
  long result;
  // Read temperature sensor against 1.1V reference
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
  delay(20); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (result - 125) * 1075;
  return result;
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  pinMode(MEM_PW, OUTPUT);
  digitalWrite(MEM_PW, HIGH);
  Serial.begin(9600);
  Serial.println("Arduino inner temperature and inner voltage");
}

void loop() {
  // To read the inner temperature and voltage
  long temp=readTemp();
  long Vcc=readVcc();
  // To prepare and to save the data tostore in the microSD
  char message[20];
  sprintf(message,"%ld, %ld\n", temp, Vcc);
  unsigned long length = strlen(message);
  FileLogger::append("data.log", (byte*)message, length);
  // Now i show the result in serial
  Serial.print(temp, DEC); // show the inner temperature of arduino
  Serial.print(" x10^-4 degC   ");
  Serial.print(Vcc, DEC); // show the voltage
  Serial.println("  mVolts");
  // Wait 1 second until to take a new measurement
  delay(1000);
}

You could apply it to any other measurement acquired by temperature sensors, accelerometers, or whatever… just only pay attention to the expression to be save, and the length of the message to be stored in the SD, as you could read in the previous post and the great explanations by PaulS.

Thanks PaulS. Enjoy everyone!