Go Down

Topic: [Risolto] Lettura e scrittura di un record, come struttura dati, da e su SD  (Read 541 times) previous topic - next topic

Federico66

Basta usare un cast e la struct viene vista come un array di byte. E' una cosa normale che fai su PC in C ma anche su Arduino lo fai tranquillamente.  
https://www.programiz.com/c-programming/c-file-input-output#example-write
Infatti stavo studiando e ho trovato che si può fare qualcosa del genere:

Code: [Select]

struct datastore {
  char a[a];
  unsigned int b;
  double c;
};

//SCRIVE
struct datastore data1;
data1.a = "abcd";
data1.b = 1;
data1.c = 1.0;
File dataFile1= SD.open("datalog.dat", FILE_WRITE);
dataFile1.write((const uint8_t *)&data1, sizeof(data1));

//READ
struct datastore data2;
File dataFile2 = SD.open("datalog.dat", FILE_READ);
dataFile2.read((uint8_t *)&data2, sizeof(data2));

//data2: contiene l'intera struttura (record)



Per me non è facilissimo capire questo:
Code: [Select]

(const uint8_t *)&data1

ma, se ho ben capito, è il puntatore all'indirizzo di data1 convertito in byte, quindi ritorna data1 (struttura) come un array di byte.
Corretto?
Spero di non aver detto una cavolata, ma mi piace capire quello che scrivo, altrimenti non imparo :-)


TIA
F


"La logica vi porterà da A a B. L'immaginazione vi porterà dappertutto." A. Einstein

Standardoil

Secondo me è l'indirizzo di data1 convertito in puntatore a byte
Prima legge di Nelson (che sono io): Se vuoi il mio aiuto dimostrami almeno che hai letto il nostro "aiutateCi ad aiutarVi"

Non bado a studenti, che copino altrove

Tu hai problema-Io ti domando-Tu non mi rispondi: vuol dire che non ti serve più

torn24

Non ho capito subito ma sicuramente è fuorviante  :)   uint8_t , un intero senza segno a 8 bit, equivale a byte. Comunque è fuorviante, almeno per chi non ha una conoscenza approfondita del linguaggio C.

nid69ita

#18
May 06, 2019, 05:24 pm Last Edit: May 06, 2019, 05:28 pm by nid69ita
 Passaggio di parametri per indirizzo.  Che gli passo una struttura, una semplice variabile o un array poco importa.
E' una cosa normale per il C.  Poi un puntatore è un puntatore, il cast in questo caso è per farlo "digerire" al controllo del compilatore che fa sul parametro.  :)


& ritorna indirizzo di data, è un puntatore di tipo datastore, quindi lo convertiamo (cast) a puntatore di tipo const uint_8.   Il cast fa capire al compilatore che sappiamo qul che facciamo, che siamo consci del fatto che passiamo un puntatore ad un qualcosa che vogliamo sia trattato a byte (o uint_8).
my name is IGOR, not AIGOR

Federico66

Passaggio di parametri per indirizzo.  Che gli passo una struttura, una semplice variabile o un array poco importa.
E' una cosa normale per il C.  Poi un puntatore è un puntatore, il cast in questo caso è per farlo "digerire" al controllo del compilatore che fa sul parametro.  :)

& ritorna indirizzo di data, è un puntatore di tipo datastore, quindi lo convertiamo (cast) a puntatore di tipo const uint_8.   Il cast fa capire al compilatore che sappiamo qul che facciamo, che siamo consci del fatto che passiamo un puntatore ad un qualcosa che vogliamo sia trattato a byte (o uint_8).
Ti ringrazio, anche oggi ho imparato qualcosa di nuovo :-)

F

PS
Stasera provo e se funziona posto l'esempio per OP, che nel frattempo si è perso :-)
"La logica vi porterà da A a B. L'immaginazione vi porterà dappertutto." A. Einstein

Federico66

#20
May 07, 2019, 12:19 pm Last Edit: May 07, 2019, 12:21 pm by Federico66
Eccomi qua.
Al momento sono molto intenzionato ad imparare, e per imparare bisogna avere qualcosa da realizzare, quindi ho preso la palla al balzo (questo post) e ho cercato di scrivere una procedura per gestire un file di dati strutturati su SD (scrittura, lettura, ricerca).
Questo mi ha dato modo di lavorare molto con le stringhe C (ho cercato di evitare la classe String), che per me sono un vero cruccio (abitualmente lavoro con C#, VB.Net).

Posto qui le due versioni (file binario, file di testo), con la speranza che qualcuno esperto possa verificare il codice ed eventualmente indicarmi come migliorarlo/ottimizzarlo.
Naturalmente questo aiuterebbe anche l'OP :D

Ho pensato di non aprire un'altro topic, in quanto strettamente legato al post originale, se non è prassi, ditemelo.


TIA
Federico




Versione file binario:
Code: [Select]

#include <SD.h>
#define pinSD 10

char fileName[12] = "data.dat";

//Definisce la struttura (record)
struct dataStore {
  char a[10];
  unsigned int b;
  double c;
};

//Variabile per il record cercato
struct dataStore data;

//Variabile di appoggio per un record
struct dataStore dataRow;

//Stringa di appoggio per la stampa
char dataLine[50];

//Stringa da cercare nel db
char stringaDaCercare[10] = "D6";

void setup() {
  Serial.begin(9600);
  Serial.println(__FILE__);

  //Verifica la presenda della card
  if (!SD.begin(pinSD)) {
    Serial.println("SD card failed!");
    while (1);
  }
  Serial.println("SD card ready");

  //Se il file esiste lo elimina
  if (SD.exists(fileName)) {
    sprintf(dataLine, "Delete file: %s", fileName);
    Serial.println(dataLine);
    SD.remove(fileName);
  }

  //Apre il file per la scrittura
  File fileW = SD.open(fileName, FILE_WRITE);
  if (fileW) {
    sprintf(dataLine, "Write file: %s", fileName);
    Serial.println(dataLine);   
    //Numero massimo di record da scrivere
    int maxSize = 10;
    for (int i = 0; i < maxSize; i++) {
      //Valorizza il record
      sprintf(dataRow.a, "%s%d", "D", i);
      dataRow.b = i;
      dataRow.c = (double)i / 2.0;
      //Scrive il record su file
      fileW.write((const byte *)&dataRow, sizeof(dataRow));

      //Scrive il record sulla seriale
      //Nota: sprintf ha problemi con i double/float, ho trovato due alternative
      // 1: dtostrf per la conversione in stringa e poi sprintf
      char c[5];
      dtostrf(dataRow.c, 2, 2, c);
      sprintf(dataLine, "%s;%d;%s", dataRow.a, dataRow.b, c);
      // 2: conversione del double in due interi (parte intera+decimale)
      //sprintf(dataLine, "%s;%d;%d.%02d", data.a, data.b, (int)data.c, (int)(data.c*100)%100);
      Serial.println(dataLine);
    }
    fileW.close();
  }
  else {
    sprintf(dataLine, "ERROR Write file: %s", fileName);
    Serial.println(dataLine);       
  }

  //Valorizza il primo capo con un valore di errore 
  strncpy(data.a, "NULL", 10);
  //Apre il file per la lettura
  File fileR = SD.open(fileName, FILE_READ);
  if (fileR) {
    sprintf(dataLine, "Read file: %s", fileName);
    Serial.println(dataLine);     
    //Cicla tutti i bytes leggendo un record alla volta
    while (fileR.available()) {
      //Legge un record (legge tanti bytes quanti ne servono per riepire il record)
      fileR.read((byte *)&dataRow, sizeof(dataRow));
      //Se il primo campo è uguale a stringaDaCercare (quello che cerco), setta data ed esce
      if (strncmp(dataRow.a, stringaDaCercare, 10) == 0) {
        data = dataRow;
        break;
      }
    }
    fileR.close();
  }
  else {
    sprintf(dataLine, "ERROR Write file: %s", fileName);
    Serial.println(dataLine);       
  }

  //Se il primo campo è diverso da "NULL" stampo il record trovato
  if (strncmp(data.a, "NULL", 10) != 0) {
    char c[5];
    dtostrf(data.c, 2, 2, c);
    sprintf(dataLine, "Trovato: %s;%d;%s", data.a, data.b, c);
    Serial.println(dataLine);
  } else {
    Serial.println("Non trovato"); 
  }


}

void loop() {
// vuoto
}



Versione file testo:
Code: [Select]

#include <SD.h>
#define pinSD 10

char fileName[12] = "data.txt";

struct dataStore {
  char a[10];
  unsigned int b;
  double c;
};

//Variabile per il record cercato
struct dataStore data;

//Variabile di appoggio per un record
struct dataStore dataRow;

//Stringa di appoggio
char dataLine[50];

//Stringa da cercare nel db
char stringaDaCercare[10] = "S8";

void setup() {
  Serial.begin(9600);
  Serial.println(__FILE__);

  //Verifica la presenda della card
  if (!SD.begin(pinSD)) {
    Serial.println("SD card failed!");
    while (1);
  }
  Serial.println("SD card ready");

  //Se il file esiste lo elimina
  if (SD.exists(fileName)) {
    sprintf(dataLine, "Delete file: %s", fileName);
    Serial.println(dataLine);
    SD.remove(fileName);
  }

  //Apre il file per la scrittura
  File fileW = SD.open(fileName, FILE_WRITE);
  if (fileW) {
    sprintf(dataLine, "Write file: %s", fileName);
    Serial.println(dataLine);
    //Numero massimo di record da scrivere
    int maxSize = 10;
    for (int i = 0; i < maxSize; i++) {
      //Valorizza il record
      sprintf(dataRow.a, "%s%d", "S", i);
      dataRow.b = i;
      dataRow.c = (double)i / 2.0;

      //Crea la stringa delimitata
      //Nota: sprintf ha problemi con i double/float, ho trovato due alternative
      // 1: dtostrf per la conversione in stringa e poi sprintf
      char c[5];
      dtostrf(dataRow.c, 2, 2, c);
      sprintf(dataLine, "%s;%d;%s", dataRow.a, dataRow.b, c);
      // 2: conversione del double in due interi (parte intera+decimale)
      //sprintf(dataLine, "%s;%d;%d.%02d", data.a, data.b, (int)data.c, (int)(data.c*100)%100);

      //Scrive il record su file
      fileW.println(dataLine);

      //Scrive il record sulla seriale
      Serial.println(dataLine);
    }
    fileW.close();
  }
  else {
    sprintf(dataLine, "ERROR Write file: %s", fileName);
    Serial.println(dataLine);
  }

  //Valorizza il primo capo con un valore di errore
  strncpy(data.a, "NULL", 10);
  //Apre il file per la lettura
  File fileR = SD.open(fileName, FILE_READ);
  if (fileR) {
    sprintf(dataLine, "Read file: %s", fileName);
    Serial.println(dataLine);
    //Cicla il file leggendo una riga alla volta (fino a \n, fine riga)
    while (fileR.available()) {
      String line = fileR.readStringUntil('\n');
      //Converte la stringa (riga) in un char array
      line.toCharArray(dataLine, line.length());
      //Estrae il primo campo
      char * strtokIndx;
      strtokIndx = strtok(dataLine, ";");
      strcpy(dataRow.a, strtokIndx);
      //Se il primo campo è uguale a stringaDaCercare (quello che cerco), setta data ed esce
      if (strncmp(dataRow.a, stringaDaCercare, 10) == 0) {
        strncpy(data.a, dataRow.a, 10);
        strtokIndx = strtok(NULL, ";");
        data.b = atoi(strtokIndx);
        strtokIndx = strtok(NULL, ";");
        data.c = atof(strtokIndx);
        break;
      }
    }
    fileR.close();
  }
  else {
    sprintf(dataLine, "ERROR Read file: %s", fileName);
    Serial.println(dataLine);
  }

  //Se il primo campo è diverso da "NULL" stampo il record trovato
  if (strncmp(data.a, "NULL", 10) != 0) {
    char c[5];
    dtostrf(data.c, 2, 2, c);
    sprintf(dataLine, "Trovato: %s;%d;%s", data.a, data.b, c);
    Serial.println(dataLine);
  } else {
    Serial.println("Non trovato");
  }

}

void loop() {
//VUOTO
}
"La logica vi porterà da A a B. L'immaginazione vi porterà dappertutto." A. Einstein

crc57

Scusate il ritardo, non sono sparito ;D .
Voglio ringraziare tutti quanti ed ognuno singolarmente per il contributo dato al post.
Mi fa piacere che sia stato utile a molti.
Un ringraziamento particolare a Federico66 il cui codice funziona perfettamente, sia in scrittura che in lettura.
Il LF che mettevo a fine record era senzaltro un errore usando
Code: [Select]
dbFile.readBytesUntil(LF, &Record, 8); ma dovendo essere parte di una esercitazione didattica, volevo mostrare il file creato ai ragazzi nella maniera più ordinata possibile, tant'è che l'ho reintrodotto nella struttura come Filler.
Ho modificato anche un po' la struttura
Devo ammettere che i puntatori sono #potentementecomplicatiepericolosi
Con questo Vs. contributo il mio progetto ha avuto una svolta molto positiva.
Grazie a tutti.

Code: [Select]

struct ABX { // Struttura del record di database
char DBTipo; // A=amministratore / U=utente / X=revocato
unsigned long DbCard; // ID impronta
byte DbImp; // ID Card RFID
char DbNome[20]; // Nominativo utente
char Filler; // filler
} Record;



Code: [Select]

/* prova di scrittura */
Record.DBTipo = 'A';
Record.DbCard = 3134562064;
Record.DbImp = 1;
copyC(Admin, Record.DbNome, 20);
Record.Filler = LF;
dbFile = SD.open(NomeDb, FILE_WRITE);
dbFile.write((const uint8_t *)&Record, sizeof(Record));
dbFile.flush();



Code: [Select]

/* prova di lettura */
dbFile.read((uint8_t *)&Record, sizeof(Record));
Serial.print(Record.DBTipo);
Serial.print(F("-"));
Serial.print(Record.DbCard);
Serial.print(F("-"));
Serial.print(Record.DbImp);
Serial.print(F("-"));
Serial.print(Record.DbNome);
Serial.print(Record.Filler);

Federico66

Mi fa piacere che sia stato utile a molti.
A me sicuramente, quindi grazie a te, fino a qualche giorno fa per me il C era solo la terza lettera dell'alfabeto :D

Federico

PS
non mi è chiara la differenza uint8_t e byte, visto che comunque indicano il byte, ma visto che nel reference di Arduino uint8_t non viene segnalato, alla fine ho usato byte
"La logica vi porterà da A a B. L'immaginazione vi porterà dappertutto." A. Einstein

nid69ita

#23
May 07, 2019, 03:50 pm Last Edit: May 07, 2019, 03:51 pm by nid69ita
A me sicuramente, quindi grazie a te, fino a qualche giorno fa per me il C era solo la terza lettera dell'alfabeto :D

Federico

PS
non mi è chiara la differenza uint8_t e byte, visto che comunque indicano il byte, ma visto che nel reference di Arduino uint8_t non viene segnalato, alla fine ho usato byte
Sono delle definizioni di tipo per evitare ambiguità
uint_8  è sicuro.  sta per unsigned intero a 8 bit, anche un byte lo è (su tutte le architetture)
ma ad esempio     uint_16   è unsigned intero a 16 bit (2 byte).   NON è detto si la stessa cosa con int in quanto la dimensione in byte di un int dipende dall'architettura, può essere 2 o 4 byte.
my name is IGOR, not AIGOR

Federico66

Sono delle definizioni di tipo per evitare ambiguità
uint_8  è sicuro.  sta per unsigned intero a 8 bit, anche un byte lo è (su tutte le architetture)
ma ad esempio     uint_16   è unsigned intero a 16 bit (2 byte).   NON è detto si la stessa cosa con int in quanto la dimensione in byte di un int dipende dall'architettura, può essere 2 o 4 byte.
Chiarissimo come sempre, grazie
"La logica vi porterà da A a B. L'immaginazione vi porterà dappertutto." A. Einstein

crc57

Chiarissimo come sempre, grazie
A riprova di quello che dice nid69ita a proposito di uint16_t, in Arduino UNO è di 2 byte quindi l'ID della card RFID l'ho dovuta definire unsigned long che invece è 4 byte anche su Arduino UNO

Go Up