Go Down

Topic: Guardar datos complejos en la SD [INFO] (Read 2286 times) 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 :)).

salvewis

se podria hacer lo mismo pero introduciendole tu los datos de id y los de nombre? por puerto serie,sin tener k estar ya prefijados?
gracias

noter

Sí se puede. Lo único es que los datos (la estructura guardada) deben tener la misma longitud. Por ello no se puede hacer con punteros; es decir, no puedes poner en tu estructura algo así:

struct datos {
unsigned long id;
char *nombre;
};

porque nombre no son datos, sino un puntero hacia unos datos. Sí podrías hacerlo así:

struct datos {
unsigned long id;
char nombre[50]; //la longitud máxima que podríamos introducir es de 50 caracteres.
}

Pero creo que mejor volvemos a tu post, y explicas un poco más exactamente qué quieres hacer.

ahernandovalbuena

#9
Feb 02, 2015, 10:16 pm Last Edit: Feb 03, 2015, 07:03 am by ahernandovalbuena
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()
{
 
}


Ya si queremos rizar el rizo podríamos usar estructuras dinámicas para dar un respiro a la limitada memoria de nuestros controladores:

Code: [Select]
typedef struct s {
     int id; 
     char nombre[11];
     float temperatura;   
     struct s *siguiente;
 } elemento;

elemento *c;

elemento *nuevo_ elemento(void)  {
    elemento *q;
    q = (elemento *)malloc(sizeof(elemento));
    if(!q) {
        Serial. Println ("No se ha reservado memoria para el nuevo nodo");
    return q;
 }

void loop () {
    elemento *q; //puntero a un nodo
    q = NULL; //lista vacía
    //Reserva dinámica de mem
    q = nuevo_elemento(); //q apunta al nodo recién creado
    q->sig = NULL; //fin de lista
 
     // Aquí operaciones de escritura
 
     //Liberación de memoria reservada
     free(q);
 }
Alvaro Hernando Valbuena

surbyte

Si analizas que quien pregunta tiene problemas para enviar un dato por el puerto serie y le respondes con punturos por dios!!! imagina donde va a esconderse?

Excelente tu aporte ahernandovalbuena pero fuera de lugar para este caso.
A mi me gusta tu sugerencia pero en otro contexto.

De todas formas para todo esto yo prefiero usar EDB Extended Data base Library. Todo hecho para EEPROM interna o externa o para SD.

ahernandovalbuena

#11
Feb 03, 2015, 06:54 am Last Edit: Feb 03, 2015, 07:02 am by ahernandovalbuena
Si analizas que quien pregunta tiene problemas para enviar un dato por el puerto serie y le respondes con punturos por dios!!! imagina donde va a esconderse?

Excelente tu aporte ahernandovalbuena pero fuera de lugar para este caso.
A mi me gusta tu sugerencia pero en otro contexto.

EDITO post anterior haciendo cita al primero para evitar estas confusiones.

De todas formas para todo esto yo prefiero usar EDB Extended Data base Library. Todo hecho para EEPROM interna o externa o para SD.
Entendí que el post era de carácter informativo y solo busque aportar mayor precisión,  stry si alguien se confundió con mis palabras.

De la misma manera, mirare la librería que dices pero algo hay  claro, el uso de punteros en este caso es fundamental.

Un saludo.
Alvaro Hernando Valbuena

surbyte

#12
Feb 03, 2015, 11:20 am Last Edit: Feb 03, 2015, 11:21 am by surbyte
No te lo discuto ahernandovalbuena y me parece excelente tu aporte.
Solo mira esto y entenderás mi respuesta

Quote
se podria hacer lo mismo pero introduciendole tu los datos de id y los de nombre? por puerto serie,sin tener k estar ya prefijados?

ahernandovalbuena

#13
Feb 03, 2015, 05:04 pm Last Edit: Feb 03, 2015, 05:06 pm by ahernandovalbuena
se podria hacer lo mismo pero introduciendole tu los datos de id y los de nombre? por puerto serie,sin tener k estar ya prefijados?
gracias

Buenas tardes,

Para la duda que me comentas se me ocurre la posibilidad de hacer una lectura del puerto serial, introduciendo los datos una vez reconozcamos la palabra clave INSERT:

Code: [Select]

#define TOTAL 20

char Texto[TOTAL];
char Caracter=-1;
unisgned int Indice= 0;


char Comparador(char* CadenaThis) {
    while (Serial.available() > 0) {
        if(Indice< TOTAL - 1) {
            Caracter= Serial.read();
            Texto[Indice] = Caracter;
            Indice++;
            Texto[Indice] = '\0';
        }
    }

    if (strcmp(Texto,CadenaThis)  == 0) {
        for (int i=0;i<TOTAL - 1;i++) {
            Texto[i]=0;
        }
        Indice=0;
        return(0);
    } else {
        return(1);
    }
}

void loop() {
    if (Comparador("INSERT")==0) {
        //Volveriamos a leer del serial pero en este caso el nombre o la cadena.

    }
}


No sé, lo he escrito un poco rápido y a lo mejor no tiene mucho sentido. Habría que darle una pensada.

Un saludo!


Alvaro Hernando Valbuena

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy