Go Down

Topic: Guardar datos complejos en la SD [INFO] (Read 1 time) previous topic - next topic

curro92

Aug 19, 2013, 07:40 am Last Edit: Aug 19, 2013, 08:10 am by curro92 Reason: 1
Hola,
como es un tema bastante recurrente, ahí va un ejemplo de cómo guardar y recuperar datos complejos en una SD, porque casi siempre los ejemplos se suelen reducir a datos en forma de texto.

La mejor opción para ello, a mi modo de ver,  es utilizar una estructura de datos, donde se pueden incluir toda clase de variables (no punteros).

Por ejemplo, si se define una estructura como esta
Code: [Select]

struct datos
{
 int id;  
 char nombre[11];
 float temperatura;
 float humedad;
} datos, datos2;


Se puede escribir y leer la estructura como un todo en la SD. Para probar, poner primero contenido en la estructura,

Code: [Select]

  datos.id = 33;
 datos.temperatura = 25.3;
 datos.humedad = 77.7;
 strcpy(datos.nombre, "lugar_A");


Para escibir, como la función write pide un buffer del tipo const uint8_t hay que hacer una conversión explícita

Code: [Select]

 myFile = SD.open("test.txt", FILE_WRITE);
 if(myFile)
 {
    Serial.println("abierto para escribir 'test.txt'");
    myFile.write((const uint8_t*)(&datos), sizeof(datos));
    myFile.close();
 }


Y para leer, en el ejemplo se utiliza datos2 para comprobar que los datos se cargan desde el fichero, aunque se podría haber usado datos

Code: [Select]
   
    myFile = SD.open("test.txt");
    if(myFile)
    {
       Serial.println("abierto para leer 'test.txt'");

       myFile.read(&datos2, sizeof(datos2));

       // y para mostrar por la consola
       Serial.print("id: ");
       Serial.println(datos2.id, DEC);
       Serial.print("nombre: ");
       Serial.println(datos2.nombre);
       
       Serial.print("temperatura: ");
       Serial.println(datos2.temperatura, DEC);  
       Serial.print("humedad: ");
       Serial.println(datos2.humedad, DEC);  
       
       myFile.close();
    }


Tan solo queda un detalle por arreglar: la salida por consola me da
Code: [Select]

temperatura: 25.2999992370
humedad: 77.6999969482



Ahí va el sketch entero por si alguien lo quiere probar

Code: [Select]



/*
Conexiones para un Arduino Uno:
uint8_t const SS_PIN   = 10;
uint8_t const MOSI_PIN = 11;
uint8_t const MISO_PIN = 12;
uint8_t const SCK_PIN  = 13;

*/

#include <SD.h>

File myFile;

int kont;

struct datos
{
 int id;
 float temperatura;
 float humedad;
 char nombre[11];
} datos, datos2;

void setup()
{
 kont = 0;
 Serial.begin(9600);
 Serial.print("Initializing SD card...");
 pinMode(10, OUTPUT);

 if (!SD.begin(10))
 {
   Serial.println("initialization failed!");
   return;
 }
 Serial.println("initialization done.");
 
 datos.id = 33;
 datos.temperatura = 25.3;
 datos.humedad = 77.7;
 strcpy(datos.nombre, "lugar_A");

 myFile = SD.open("test.txt", FILE_WRITE);
 if(myFile)
 {
    Serial.println("abierto para escribir 'test.txt'");
    myFile.write((const uint8_t*)(&datos), sizeof(datos));
    myFile.close();
     
    myFile = SD.open("test.txt");
    if(myFile)
    {
       Serial.println("abierto para leer 'test.txt'");

       myFile.read(&datos2, sizeof(datos2));
       Serial.print("id: ");
       Serial.println(datos2.id, DEC);
       Serial.print("nombre: ");
       Serial.println(datos2.nombre);
       
       Serial.print("temperatura: ");
       Serial.println(datos2.temperatura, DEC);  
       Serial.print("humedad: ");
       Serial.println(datos2.humedad, DEC);  
       
       myFile.close();
    }
    else
    {
        Serial.println("error read test.txt");
    }
 }
 else
 {
     // if the file didn't open, print an error:
     Serial.println("error FILE_WRITE test.txt");
 }
}


void loop()
{
 
}






Sergegsx

#2
Aug 30, 2013, 10:04 pm Last Edit: Aug 30, 2013, 10:06 pm by Sergegsx Reason: 1
enhorabuena por el post. muy util !

Podrias dar mas detalles de estas lineas=

Code: [Select]

myFile.write((const uint8_t*)(&datos), sizeof(datos));


1) datos se supone que es una STRUCTURA, y lo estas convirtiendo (cast) a un "unsigned integer 8 bits" ?
2) El "&" se usa para que el cast se realice sobre los elementos que integran la Structura, en lugar de tener que referirse a cada elemento por separado?
3) Sizeof, simplemente le dice cuantos elementos van a escribirse, es decir, los que hay dentro de la estructura?
por lo que realmente la funcion write, aquí es algo compleja y hace una lectura de todos los elementos de la estructura de uno en uno?

He entendido algo bien? puedes corregirme donde me equivoco?
muchas gracias

noter

#3
Aug 31, 2013, 12:45 am Last Edit: Aug 31, 2013, 12:49 am by noter Reason: 1

enhorabuena por el post. muy util !

Podrias dar mas detalles de estas lineas=

Code: [Select]

myFile.write((const uint8_t*)(&datos), sizeof(datos));


1) datos se supone que es una STRUCTURA, y lo estas convirtiendo (cast) a un "unsigned integer 8 bits" ?
2) El "&" se usa para que el cast se realice sobre los elementos que integran la Structura, en lugar de tener que referirse a cada elemento por separado?
3) Sizeof, simplemente le dice cuantos elementos van a escribirse, es decir, los que hay dentro de la estructura?
por lo que realmente la funcion write, aquí es algo compleja y hace una lectura de todos los elementos de la estructura de uno en uno?

He entendido algo bien? puedes corregirme donde me equivoco?
muchas gracias

Hola. Efectivamente datos es una estructura que contiene la información que deseamos guardar.
El & es el operador de indirección, y lo que hace es devolvernos un puntero a esa estructura, es decir, la dirección de memoria donde está guardada; osea que &datos es un puntero a nuestra struct de datos.
Como la función write necesita un (puntero a uint8) se realiza el casting a (uint8 *) para evitar un toque de atención del compilador. Así que lo que hace write es escribir (sizeof(datos)) bytes (uint8) a partir de la dirección de memoria donde está la estructura, sin importarle qué tipo de datos contiene.
Como añadido, señalar que este sistema es sencillo y efectivo, pero hay que tener en cuenta que nuestra struct no contenga punteros, porque copiará eso, el puntero y no el contenido (por ejemplo si tenemos un char *cadena dentro de nuestra estructura deberemos ocuparnos de copiar la cadena aparte).

curro92


Hola, Sergegsx
@noter lo ha explicado a la perfección.
Lo que no sé por qué es necesaria esa conversión explícita a uint8 ('unsigned int de 8 bites'), cuando se podría hacer lo mismo con un unsigned char o un byte, porque lo que se transmite cada vez por el puerto serie es eso, una serie de 8 bites.

Sergegsx

muchisimas gracias noter, me ha quedado clarisimo y creo que por fin he entendido al menos 1 uso practico de los punteros, hasta ahora nunca habia entendido para que usarlo.
mil gracias

curro92 gracias por el post, parece sencillo pero creo que seria buena idea meterlo en el playground si no lo esta ya.

noter



Hola, Sergegsx
@noter lo ha explicado a la perfección.
Lo que no sé por qué es necesaria esa conversión explícita a uint8 ('unsigned int de 8 bites'), cuando se podría hacer lo mismo con un unsigned char o un byte, porque lo que se transmite cada vez por el puerto serie es eso, una serie de 8 bites.


Creo que los tres datos son equivalentes, y de hecho también compila sin errores (byte *)&datos y (unsigned char*)&datos. A mí, personalmente, me gusta usar la notación byte, pero para otros tal vez sea más explícito el uint8 (para gustos los colores :)).

Go Up