Realizzare un file ciclico di tipo CSV sulla scheda SD con Arduino

Buongiorno, sono riuscito a aprire un file, nominarlo ed impiegarlo per fare un data logging dei dati letti dal mio arduino uno impiegando una datalogger shield. Dopo poco il mio file diventa ingestibile. Come posso fare per sovrascrivere il file raggiunto un certo numero di righe? Mi spiego: Non voglio cancellare il vecchio file e aprirne un'altro, vorrei banalmente sovrascrivere sullo stesso file a partire dalla prima riga.

Se avessi banalmente 4 righe come limite, scriverei:

Dato 1


Dato1 Dato2


Dato1 Dato2 Dato3


Dato1 Dato2 Dato3 Dato4


Dato5 Dato2 Dato3 Dato4


Dato5 Dato6 Dato3 Dato4

E via dicendo... credo che più chiaro di così si muoia Il problema è che aprendo i dati con EXCEL dopo poco non li gestisce più. Per me poi per evitare anche la saturazione della scheda SD la soluzione che suggerisco è ottimale.

Mi sapete dire come si punta ad una riga di un CSV e ci si scrive e "ri" scrive sopra? Grazie. Il resto se gli esempi forniti girano è facile! :-) Buona giornata!

Idea semplice da pensare, ma decisamente meno da implementare. Il mio consiglio è magari di usare due file: scrivi un po' di record in uno, poi passi all'altro, scrivi tot record e torni al primo, ecc. Potrebbe andare?

MadHatter1981: Buongiorno,

Ti invitiamo a presentarti (dicci quali conoscenze hai di elettronica e di programmazione) qui: Presentazioni e a leggere il regolamento se non lo hai già fatto: Regolamento Qui una serie di link utili, non inerenti al tuo problema: - qui una serie di schede by xxxPighi per i collegamenti elettronici vari: ABC - Arduino Basic Connections - qui le pinout delle varie schede by xxxPighi: Pinout - qui una serie di link [u]generali[/u] utili: Link Utili

sono solo io che non ho capito il problema ?

tu scrivi su un file in una SD connessa ad arduino uno, all'inizio funziona e poi ? cosa succede ?

In che senso poi apri il file con Excel ? estrai la SD dall'arduino e a metti nel PC ?

Buongiorno, grazie per le risposte. Visto che mi avete chiesto dei chiarimenti eccoli! :

A) Si può andare bene con due file! grazie! Senza offendere nessuno però voglio dire... "Funziona ma è una schifezza!" Mettiamo che il mio limite di righe è 1000 su Excel...(va da 65000 a 1000000 nella realtà in base alla versione) Avrò sempre almeno un file di 1000 righe completo da aprire ed usare certo, ma quelli contenuti in esso non sono gli ultimi 1000 dati. Per trovare gli ultimi 1000 dati devo fare un collage del file incompleto e di quello completo e prendere le ultime 1000 righe! Definireste il procedimento professionale? Hmmm.

Come suggerimento credo di aver bisogno del modo di scrivere su una particolare riga di un file. Così da poter dire ad arduino:

Scrivi sulla riga 1 (è sempre l'esempio delle 4 righe totali) ................riga 2 ................riga 3 ................riga 4 ................riga 1 ................riga 2 ................riga 3 ................riga 4 ................riga 1 ................riga 2 ........................

Qualcuno sa quali istruzioni si usano per puntare ad una riga di un file CSV?

B) Mi avete chiesto chiarimenti in merito al funzionamento del mio programma: Il mio programma funziona benone! Il file di log che produce si allunga in modo proporzionale al tempo di registrazione (cosa naturale ovviamente) Quando però voglio vedere ciò che ho campionato (ed a me interessa solo l'ultimo giorno di registrazione) mi trovo di fronte una mole tale di dati (sempre che la memoria non si sia saturata nel frattempo) che Excel non è in grado di aprirli più. Ovviamente per farlo tolgo la scheda da Arduino e la metto nel PC. Poi apro il file CSV con Excel e "vorrei" elaborare a "mano" i dati.

Ringrazio intanto tutti del sostegno in questo progetto, ma credo di aver bisogno di un puntatore a riga. Si può fare? Si deve fare diversamente? Attendo suggerimenti da persone più ferrate di me in materia.

In realtà ho trovato dei dati riguardo questo procedimento ma sono in C e riguardano il solo processo di lettura. Non conoscendo io altro che Arduino, non sono stato in grado di tradurlo e modificarlo.

Vi metto il link che ho si trovato ma che non mi è stato poi in effetti di aiuto perché troppo complicato per me da modificare: (spero non sia proibito in tal caso chiedo umilmente scusa e nel caso cancellatelo spero non sia necessario bannarmi per questa disattenzione)

http://www.dis.uniroma1.it/~liberato/struct/file/righe.shtml

Se qualcuno è più bravo di me magari riesce a cavarne qualcosa. Grazie a tutti dell'aiuto!

Scusate, forse non capisco io il problema ::) , ma leggendo quanto MadHatter1981 scrive al primo post, ovvero :

... scrivo riga 1, posizione 1 ... scrivo riga 2, posizione 2 ... scrivo riga 3, posizione 3 ... ... scrivo riga N, posizione N ... scrivo riga N+1, posizione 1 ... scrivo riga N+2, posizione 2 ... e così via

mi sembra facilmente risolvibile usando gli appositi metodi messi a disposizione dalla classe SD ... basta andare a studiarseli sul reference.

Esiste infatti sia il metodo seek() che permette di "riposizionarsi" a scrivere ad un determinato punto che, occorrendo, il metodo position() che permette di sapere "il punto" in cui ci si trova.

Sempre se ho ben capito, a MadHatter1981 basta contare le righe scritte e, ad ogni multiplo di N ... dare una SD.seek(0) ovvero riposizionare il puntatore del file a 0 e continuare a scrivere sovrascrivendo i dati già scritti.

Unica precauzione, per non avere poi casini sulle letture, è usare record di lunghezza fissa.

Guglielmo

Chiaro ora, Se si può leggere ad un determinato rigo si potrà anche scrivere. Sicuramente aiuta leggere il tuo listato, oppure ancor meglio allega una versione minimale che fa vedere come crei e scrivi il file

Grazie Guglielmo!!! Spero di riuscire ad imparare a farlo partendo dai riferimenti che mi hai mandato! Credo che sia proprio quello che non riuscivo a trovare. Sono poi molto felice perché vedo che molte altre persone come me hanno difficoltà nella gestione dei file su scheda SD. Ora vedo se riesco a farcela e nel caso posto uno sketch semplice che tutti possano usare con semplicità! Incrociamo le dita :-)))

È vincolante che i record abbiano lunghezza fissa, allora è banale, certo. Io davo per scontato di no, forse influenzato da una cosa che sto facendo ora ;).

Sukko Pera io per ora sono riuscito a realizzarmi una base per mettermi a studiare ed a fare prove (segue) e per me non è banale. Se ti va di modificarlo fino a farlo diventare ciclico mi saresti molto gentile. Grazie!

// Tentativo di file ciclico
#include <SPI.h>
#include <SD.h>

// Sulla Ethernet shield il CS pin è il 4. Fate attenzione al fatto che anche se non fosse impiegato come CS pin
// il CS pin deve comunque rimanere impostato come output. Anziché il 4 può essere il 10 su molte schede
// Arduino oppure il 53 come sul Mega. In caso contrario le funzioni della SD library non funzioneranno.
const int chipSelect = 4;
long N = 1; // Contatore progressivo della lettura

File dataFile;

void setup()
{
  // Accertatevi che il Chip Select pin sia settato come OUTPUT anche se non lo usate:
  pinMode(SS, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    // Se non c'è la scheda bloccati qui fino al reset
    // Si possono fare altre cose in questo spazio volendo
    while (1) ;
  }
  // APRIAMO IL FILE SU CUI VGLIAMO ANDARE A SCRIVERE!!!
//  dataFile = SD.open("datalog.txt", FILE_WRITE); // Se vogliamo un file di tipo TXT
  dataFile = SD.open("datalog.CSV", FILE_WRITE); // Se vogliamo un file di tipo CSV
  if (! dataFile) {
      // Se non riesci a trovare il file bloccati qui fino al reset
      // Si possono fare altre cose in questo spazio volendo
    while (1) ;
  }
}

void loop()
{
  // make a string for assembling the data to log:
  String dataString = "";

  // read three sensors and append to the string:
  
  dataFile.print("Lettura ADC-A0 N");
  dataFile.print(N);
  dataFile.print(": ");
  dataFile.println(analogRead(A0));
  N++; // Aggiorniamo il contatore
  dataFile.flush(); // Questa funzione è molto importante. Serve a consolidare "SALVARE" i dati scritti
  // Si può fare ad ogni scrittura a ad intervalli di un certo numero di scritture. Farlo meno spesso
  // rende l'attività di scrittura notevolmente più veloce. Il consolidamento dei dati infatti è una attività
  // molto onerosa dal punto di vista del tempo necessario a svolgerla. 
  // Un metodo facile è mettere il processo di lettura e scrittura in un ciclo for di N elementi. Ogni N scritture
  // salveremo i nostri dati una volta usciti dal ciclo.
  
  delay(500); // Questa è una zavorra temporale serve a rallentare tutto. Si può ridurre di entità, aumentare o togliere del tutto
}

Io te lo spiego concettualmente, ma le modifiche le fai tu ;).

Un file di per sé non ha il concetto di "riga", è solo una sequenza di N byte/caratteri, dove N è la dimensione del file.

Il concetto di "riga" lo crei tu, introducendo "ogni tanto" uno o più caratteri che rappresentano quello che si chiama "a capo". A seconda delle convenzioni della piattaforma che segui, questo può essere rappresentato da un CR (Carriage Return), da un LF (Line Feed), o dalla coppia CR+LF. Assumiamo di usare un semplice CR.

Ad ogni file che apri con SD.open() è associato un offset, che rappresenta il punto del file a cui avverrà la prossima lettura/scrittura. Quando apri in lettura, l'offset è impostato all'inizio del file, dato che vorrai leggerlo dall'inizio appunto. Quando apri in scrittura, l'offset è impostato alla attuale dimensione del file, in modo da potervi accodare dei dati.

La libreria SD mette a disposizione la funzione seek(), che ti permette di spostare l'offset dove ti pare. Nel tuo caso, una volta riempite tutte le righe, dovrai spostarlo alla riga che vuoi sovrascrivere. Ma dato che non esistono righe, ma solo offset, dobbiamo avere un modo di convertire il numero di riga in offset. Per fare questo [u]è fondamentale che le righe abbiano una lunghezza fissa[/u]: valuta le tue necessità e stabiliscila. Assumiamo ad esempio 79 caratteri, a cui ne va aggiunto uno per il CR. A questo punto è semplice capire che se vuoi sovrascrivere la riga N, ti basta fare, dopo la open():

dataFile.seek (N * 80);
dataFile.print (...);

(Sarebbe bene usare una costante per la lunghezza della riga, ma vabbeh.)

Fin qua ti è chiaro? C'è il problema aggiuntivo di capire quale è effettivamente la prossima riga da scrivere, che non è banale se vuoi far sì che, resettando Arduino, la scrittura prosegua dal punto corretto. Ti suggerirei per ora di non preoccuparti di questo e semplicemente di iniziare ad implementare la logica per scrivere i file da capo ad ogni accensione, poi vedremo di gestire anche questa cosa.

Questo è quanto se vuoi chiudere il file ad ogni scrittura, non so ogni quanto fai i campionamenti, per cui boh? Se puoi permetterti di tenerlo sempre aperto, allora il problema, come dice gbp, è ancora di più semplice soluzione: conta quante righe scrive, e arrivato al limite riposizionati all'inizio del file con una seek(0)...

Buon divertimento ;).

Ottima spiegazione sukko :)

Un altro problema vedo, visto che lui vuile solo gli ultimi mettiamo 100 valori, e visto che inizia a sovrascrivere daccapo arrivando a 100, mettiamo che inizia a sovrascrivere ed arriva a rigo 30. Quando inserisce la SD nel pc come fa a sapere excel che deve ordibare i dati dalla 30 andando in indietro ed arrivando a 0 continuare a leggere in indietro fino a 31 ?

Serve un ukteriore riferimento, tipo un carattere di ultima registrazione che viene scritto all'ultimo rigo e cancellato dal rigo precedente, oppure un riferimento temporale, il millis o ancor meglio un rtc

Suggerimenti ...

  1. NON userei la classe String, ma le vere stringhe del C ... con le quali è molto più semplice creare una struttura di lunghezza fissa.

  2. Aggiungerei in testa ai dati un contatore di riga "logica" ... che so io ... di 6 caratteri, così va da 000000 a 999999 e risolve il problema della sequenza temporale dato che sa esattamente quale riga va prima e quale dopo.

Guglielmo

Yep, Bell'idea,

Invece un carattere speciale per indicare l'ultima scrittura è utile comunque sul discorso di capire, dopo un reset, da dove reiniziare, perché il numero di riga logica la usi lato excel facilmente, mentre lato micro fai un seek e cerchi il carattere speciale

La mia idea originaria era la 2 di gbp, ma avrei affrontato questo discorso in un secondo momento. In ogni caso la presenza di un timestamp la davo per scontata per gli stessi motivi di cui sopra, ma forse sono un po’ maniacale :D.

Testato: Invece un carattere speciale per indicare l'ultima scrittura è utile comunque sul discorso di capire ......

Probabilmente non serve ... ... parti dall'inizio, leggi record a record e confronti il numero logico con il successivo, appena il seguente è minore del precedente sei all'ultimo che hai scritto ;)

Se non incontri mai un seguente minore, vuol dire che l'ultimo che leggi e anche fisicamente l'ultimo :)

Guglielmo

SukkoPera: In ogni caso la presenza di un timestamp la davo per scontata per gli stessi motivi di cui sopra

... ovviamente, invece di un numero progressivo, va benissimo anche un time-stamp, ma ... ti serve un RTC con batteria altrimenti, se spegni e riaccendi ... sei rovinato :D :D :D

Guglielmo

GRAZIE A TUTTI PROBLEMA RISOLTO!!! GIRA ED E’ SUFFICIENTEMENTE SEMPLICE DA ESSERE RIMANEGGIATO DAI NEOFITI COME ME!
GRAZIE DELL’AIUTO!!! Soprattutto a SukkoPera per il suo secondo commento! E’ stato determinante
Inserirò anziché la lettura dell’ADC la lettura del CLOCK esterno che ho sulla Datalogger shield.
Per ora è meglio lasciarlo così che altrimenti diventa troppo complicato da capire e modificare.

Per quanto mi riguarda la domanda è chiusa e risolta! grazie a tutti! :slight_smile:

// File ciclico impiegando la funzione seek (ha qualche difettuccio ma a me va bene anche così)

// LA FUNZIONE SEEK funziona così:

// dataFile.seek(posizione);

// Dove dataFile è il file object! lo abbiamo dichiarato dicendo:
// File dataFile;
// File che poi si chiama all'anagrafe: datalog.CSV... lo abbiamo battezzato così quando lo abbiamo creato o aperto:
// dataFile = SD.open("datalog.CSV", FILE_WRITE);
// posizione invece è il numero del carattere da cui vogliamo cominciare a scrivere.
// UN FILE in realtà è costituito da una sola riga di caratteri successivi: lo spazio è un carattere, L'accapo(/n) è un carattere etc...
// per trovare l'inizio di una particolare riga dobbiamo quindi calcolare che quel posto li era il 1492-esimo carattere e usare seek:
// dataFile.seek(1492);
// su quella riga evidentemente troveremo dati sulla scoperta dell'america! Hahhaha :-)

// A questo punto dobbiamo non contare le righe ma diciamo i caratteri. Il nostro file quindi avrà un limite di caratteri.
// Per poter trovare l'inizio di ogni riga poi le righe devono essere tutte SEMPRE!!! lunghe uguali!!!!

// Come facciamo? Purtroppo un ADC ci risponde 0 come 1023, e quindi occupiamo da 1 a 4 caratteri! Come si fa?
// Basta scrivere una riga di... che ne so? 30 spazi bianchi? e sovrascriverci. A quel punto sarà tutto bianco
// fuorché quello che ci abbiamo scritto dopo noi, ma più importante saranno sempre 30 caratteri! Non si scappa!

// Programma creato da Graziano Ullucci grazianoullucci@gmail.com il 4/11/2015
// L'uso di questo programma è consentito solo a coloro che pensano di avere pari dignità di chiunque altro
// e che credono che non esistono domande stupide solo domande a cui non si ha l'umiltà di rispondere

// Ringrazio il Forum di Arduino per aver fornito l'aiuto necessario a realizzarlo! Grazie a tutti!

#include <SPI.h>
#include <SD.h>

// Sulla Ethernet shield il CS pin è il 4. Fate attenzione al fatto che anche se non fosse impiegato come CS pin
// il CS pin deve comunque rimanere impostato come output. Anziché il 4 può essere il 10 su molte schede
// Arduino oppure il 53 come sul Mega. In caso contrario le funzioni della SD library non funzioneranno.
const int chipSelect = 4;
const int RigaLength = 30;  // E' il numero di caratteri che vogliamo che la riga contenga al massimo "ATTENZIONE! NON SUPERARE!!!";
// Non è sufficiente specificarlo qui! Dobbiamo modificare anche la riga del programma detta degli "SPAZI" che troverete dopo in accordo.
const long MaxRighe = 100;   // E' il numero di righe che vogliamo che il file contenga;
long Offset = 0;
long N = 0;    // Contatore progressivo della lettura
long Riga = 0; // Indice di riga

File dataFile;

void setup()
{
  // Accertatevi che il Chip Select pin sia settato come OUTPUT anche se non lo usate:
  pinMode(SS, OUTPUT);
  
  if (!SD.begin(chipSelect)) {
    // Se non c'è la scheda bloccati qui fino al reset
    // Si possono fare altre cose in questo spazio volendo
    while (1) ;
  }
  // APRIAMO IL FILE SU CUI VOGLIAMO ANDARE A SCRIVERE!!!
//  dataFile = SD.open("datalog.txt", FILE_WRITE); // Se vogliamo SCRIVERE "FILE_WRITE" su un file di tipo TXT altrimenti "FILE_READ" per LEGGERE 
  dataFile = SD.open("datalog.CSV", FILE_WRITE); // Se vogliamo SCRIVERE "FILE_WRITE" su un file di tipo CSV altrimenti "FILE_READ" per LEGGERE
  if (! dataFile) {
      // Se non riesci a trovare il file bloccati qui fino al reset
      // Si possono fare altre cose in questo spazio volendo
    while (1) ;
  }
}

void loop()
{
  Riga++;
  if (Riga >= MaxRighe){
  Riga=0;
  }
  dataFile.seek((RigaLength+1)*Riga); // Il "+1" c'è perché il println aggiunge /n alla fine della scrittura per andare a capo e dobbiamo contarlo!
  // Riga degli "SPAZI" dobbiamo creare una riga che contiene un numero pari a RigaLength di spazi
  dataFile.println("                              "); // Garantisce una riga di 30 elementi liberi più l'accapo "/n"
  dataFile.seek((RigaLength+1)*Riga);
  dataFile.print("Lettura ADC-A0 N");
  dataFile.print(N);
  dataFile.print(": ");
  dataFile.print(analogRead(A0));
  N++; // Aggiorniamo il contatore
  
  dataFile.flush(); // Questa funzione è molto importante. Serve a consolidare "SALVARE" i dati scritti
  // Si può fare ad ogni scrittura a ad intervalli di un certo numero di scritture. Farlo meno spesso
  // rende l'attività di scrittura notevolmente più veloce. Il consolidamento dei dati infatti è una attività
  // molto onerosa dal punto di vista del tempo nesessario a svolgerla. 
  // Un metodo facile è mettere il processo di lettura e scrittura in un ciclo for di N elementi. Ogni N scritture
  // salveremo i nostri dati una volta usciti dal ciclo.
  
  delay(100); // Questa è una zavorra temporale serve a rallentare tutto. Si può ridurre di entità, aumentare o togliere del tutto
}

gpb01: ... ovviamente, invece di un numero progressivo, va benissimo anche un time-stamp, ma ... ti serve un RTC con batteria altrimenti, se spegni e riaccendi ... sei rovinato :D :D :D

Sì, hai ragione, anche qua ho dato per scontata la presenza di un RTC, cosa decisamente non ovvia. Tra l'altro, pensandoci, in quello che sto facendo io il timestamp non c'è, lo aggiungo da PC quando leggo i dati :kissing:.

@MadHatter1981: Contento tu abbia risolto. Ci sarebbero ancora molte cose da sistemare, ma se sei soddisfatto del funzionamento del tuo sketch, ci accontentiamo anche noi :).

Grazie SukkoPera! Ora posso integrarlo nel mio progetto!