[Risolto] Lettura e scrittura di un record, come struttura dati, da e su SD

Salve a tutti, vi sarei grato se mi poteste aiutare a risolvere un problema di lettura da una memoria SD.
Vorrei realizzare un sistema di identificazione RFID e/o impronta digitali memorizzando su una SD alcune informazioni che ho ordinato in una struttura dati.

File dbFile;                             // Classe File

struct StruRec {                      // Struttura del record di database
 char       DBTipo;                   // A=amministratore / U=utente / X=revocato
 uint16_t DbImp;                   // ID impronta
 uint32_t DbCard;                  // ID Card RFID
 char       DbNome[20];          // Nominativo utente
}
Record;

Per quanto riguarda la scrittura non ho problemi, scrivo i vari campi con delle semplici dbFile.write(Campo1) ecc. e dopo aver scritto tutti i campi eseguo dbFile.write(LF) come fine record.

dbFile.write('U');
dbFile.write(IdImp);
dbFile.write(IdRfid);
dbFile.write(NomUte);
dbFile.write(LF);
dbFile.flush();

Il problema è come popolare, in fase di lettura, la struttura dati?
I records non sono molti quindi anche una lettura sequenziale non dovrebbe creare problemi di prestazioni.
Avrei voluto fare una codifica tipo questa:

void LettMicro() {
   dbFile.seek(0);
   while (dbFile.available()) {
      int size = dbFile.readBytesUntil(LF, &Record, 8);

    }
    return;
}

ma naturalmente non funziona.

Grazie a tutti

attacco io, con alcune domande
ma la scrittura funziona bene bene?
intendo dire, hai provato a scrivere, chiudere il file, spegnere, portare la scheda SD sul PC e vedere se ha scritto giusto?
perchè io avrei dei dubbi, primo
e comunque, al di la dei miei dubbi, sei tu che devi essere sicuro che la scrittura vada, secondo

Di questo progetto sono ancora nella fase di scrittura del codice, di assemblare l'hardware è ancora presto, ma ho appena finito un braccio robotico con 6 servomotori in cui utilizzo

 int size = dbFile.readBytesUntil(LF, Record.SerPos, MaxMot);

per la lettura delle posizioni precedentemente memorizzate dove Record.SerPos è un array di 6 byte dove ho memorizzato ciclicamente gli angoli dei 6 servomotori.
In fase di lettura della SD il braccio robotico riesegue le movenze che avevo eseguito con i comandi manuali e memorizzato nella SD.
In quel caso però il record (la struttura) era costituito da un solo array ed ha funzionato perfettamente.

struct StruRec { // Struttura del record di database
 byte SerPos[MaxMot];
}
Record;

In questo caso la struttura è costituita da elementi di tipo diverso.

In verità avevo provato anche la libreria di database ma non sono riuscito a far funzionare neppure gli esempi, mi scriveva sulla SD sempre lo stesso record.
Sarebbe stata ottima ;D

ma allora il tuo problema è la scrittura, non la lettura...

A me sembra che la funzione File.write(data) accetti come argomenty byte, char o char *

Quindi

dbFile.write(IdImp);

e

dbFile.write(IdRfid);

probabilmente non scriveranno correttamente IdImp e IdRfid nel file.

Prova

dbFile.write((byte *)&IdImp, sizeof(IdImp));

e

dbFile.write((byte *)&IdRfid, sizeof(IdRfid));

Inoltre, con questa istruzione

      int size = dbFile.readBytesUntil(LF, &Record, 8);

leggi al massimo 8 byte, ma sizeof(Record) > 8, quindi devi usare

      int size = dbFile.readBytesUntil(LF, &Record, sizeof(Record));

Infatti gli avevo ben chiesto se si ritrovava con quello che aveva scritto...

Grazie Standardoil per le dritte, faccio un po' di prove e ti faccio sapere.

Premesso che il C lo trovo leggermente ostico, ma credo ci sia un po' di confusione, e vi prego di correggermi se sbaglio.
L'OP usa una struttura, ma poi cerca di scrivere su file i dati non strutturati ed inserendo un LF per indicare il fine record, il che naturalmente (IMHO) comporta non poche complicazioni in fase di lettura.
Ho come l'impressione che ci sia confusione anche sulla modalità di scrittura, binaria/testuale.

Al momento non saprei tradurlo in C, ma nel caso di scrittura binaria, ipotizzo qualcosa del genere (probabilmente va effettuata una conversione della variabile data)

//Scrittura
StruRec data;
dbFile.write(data, sizeof(data));

//Lettura
dbFile.read(data, sizeof(data));

in questo modo, data contiene l'intero record, data.DBTipo e così via.

Ripeto, probabilmente ho scritto una castronata in C, ma mi interessa capire se la logica è corretta.

In alternativa, si potrebbe scrivere un file di testo con separatore, ma anche il quel caso non è necessario usare un LF, se si una dbFile.println("a;b;c;d")

TIA
Federico

@federico, direi di si.
Un file testo ha righe a dimensione diversa, quindi un fine linea fa capire quando finisce una riga.
Un file binario ci scrivi dei byte e quindi, ad esempio, una struct a dimensione fissa. Quindi se lo struct è di 100 byte ogni 100 byte c'e' un record.
Ovviamente se il file contiene dati solo binari non è da usare un separatore tipo nuova riga e inoltre il file non è ben leggibile con un editor. Se contiene stringhe almeno una parte è leggibile, ma le variabili numeriche sono memorizzare come in memoria RAM/SRAM quindi non facili da leggere (un long sono 4 byte quindi spezzato nelle 4 parti).
Un "database" di solito è binario e non testuale.

@Federico66 quello che dici il linguaggio C lo permette di fare su file, con la libreria standar e su un computer.

Adesso la classe FILE non fa parte della libreria standar del C, mette a disposizione dei metodi che non permettono di salvare struct su SD.
Per cui quello che hanno fatto o cercano di fare, è perché l'unica cosa consentita dai metodi forniti.
Da quello che ho letto il massimo che si può fare con FILE.write, è scrivere un array di char.
Secondo me non è neanche indispensabile organizzare i dati in struct, visto che poi dovrai leggere e scrivere i singoli campi.

@nid69ita: scusa, ma nelle mie premesse ho dimenticato di dire per lavoro progetto e sviluppo sistemi di gestione dati (da quasi 30 anni!) :slight_smile:
I miei dubbi sono solo sulle modalità di scrittura/lettura dei dati strutturati da parte dell'OP, visto che, non avendo esperienza di C, (senza studiare), non saprei come scrivere (se si puo', ma penso di si) una struct su file binario.

nid69ita:
Un file testo ha righe a dimensione diversa, quindi un fine linea fa capire quando finisce una riga.

Certo, ma se i dati li scrivo in formato testo usando println, il fine riga viene già aggiunto, quindi non serve aggiungere un LF, se invece li scrivo con write, allora ho una dimensione fissa per i bytes, quindi quando li leggo, mi serve solo sapere quanti bytes devo leggere per avere un record, e quindi il LF non serve comunque.

In definitiva non capisco l'utilità di queste righe, in quanto penso siano inutili

dbFile.write(LF);
int size = dbFile.readBytesUntil(LF, &Record, 8);

Sbaglio?

F

PS
Sono fresco di Arduino, ma l'argomento mi ha incuriosito. Nei giorni scorsi mi è arrivato un modulo SD, quindi studierò :slight_smile:

Boh, io sono dell'idea che per un neofita ogni cosa vale per semplificare il lavoro
Quindi voto per scrivere su file di testo, pur se la struttura permetterebbe usare file binari
Dopo scritto di va su un pc per avere conferma
Solo dopo si fa la lettura, che essendo di testo forse è un pochino più semplice
Magari dopo butto giù un abbozzo, ma non sarà tanto presto, stasera ho traffico. ..

torn24:
@Federico66 quello che dici il linguaggio C lo permette di fare su file, con la libreria standar e su un computer.
Per cui quello che hanno fatto o cercano di fare, è perché l'unica cosa consentita dai metodi forniti.
Da quello che ho letto il massimo che si può fare con FILE.write, è scrivere un array di char.
Secondo me non è neanche indispensabile organizzare i dati in struct, visto che poi dovrai leggere e scrivere i singoli campi.

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.

Che il comando a disposizione sia lo standard fwrite o la write della lib, sempre un puntatore a qualcosa devi dargli e quanti byte scrivere
fwrite(&num, sizeof(struct threeNum), 1, fptr);

In realta' il modo usato da crc57 per scrivere il record funziona a 2 condizioni

  1. che il compilatore non inserisca nella struttura byte di padding Data structure alignment - Wikipedia

  2. che i dati della struttura non contengano al loro interno il carattere usato come terminatore (LF)

Mentre la 1) probabilmente e' verificata per un processore a 8 bit, la 2) e' piu' problematica perche' la struttura contiene 2 interi (per un totale di 6 bytes) che possono avere qualunque valore (assumiamo che il campo nome utente non contenga LF).

Se almeno un byte di uno dei due interi e' uguale al codice ASCII del LF, la lettura del record si fermera' nel punto sbagliato.

Tra parentesi, anche la scrittura del campo nome utente potrebbe non funzionare, perche' write(char *) mi pare che non scriva il terminatore di stringa (lo '\0' finale), e quindi in fase di lettura non si sapra' quanti caratteri sono validi dei 20 che costituiscono il campo.

Io personalmente avrei usato questo codice per scrivere il record

Record.DBTipo = 'U';
Record.DbImp = IdImp;
Record.DbCard = IdRfid;

if (strlen(NomUte) < 20)
	strcpy(Record.DbNome,NomUte);
else
	// gestire l'errore di stringa troppo lunga
	
dbFile.write((byte *)&Record, sizeof(Record));
dbFile.flush();

e per la lettura, invece di readBytesUntil

dbFile.read(&Record,sizeof(Record));

nid69ita:
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.
C Files I/O: Opening, Reading, Writing and Closing a file

Infatti stavo studiando e ho trovato che si può fare qualcosa del genere:

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:

(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 :slight_smile:

TIA
F

Secondo me è l'indirizzo di data1 convertito in puntatore a byte

Non ho capito subito ma sicuramente è fuorviante :slight_smile: 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.

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. :slight_smile:

& 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).

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. :slight_smile:

& 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 :slight_smile:

F

PS
Stasera provo e se funziona posto l'esempio per OP, che nel frattempo si è perso :slight_smile: