[Risolto] Gestire errore di scrittura su micro SD.

r69ts:
purtroppo come docdoc ipotizzava il progetto vive lontano dal pc, quindi quando il led è acceso vuol dire che si è piantata la scrittura e quindi devo premere il reset di arduino

Come ti hanno fatto notare, la classe String va evitata come la morte. Tanto più se come in questo caso ne fai un valore di ritorno di una funzione che viene richiamata continuamente nel loop. Ed io credo/temo che il blocco non sia della scrittura su SD ma proprio dell'intero programma...

Usa una variabile globale (fuori da setup() e loop() per intenderci) di tipo char* e appoggia lì la stringa da scrivere usando una funzione , es. "leggiDati()". Non è particolarmente "elegante" ma funziona, ed in queste piccole MCU risparmiare memoria e cicli è sempre utile.. :wink:

D'altronde già hai una "buf[60]" per cui potresti per esempio fare semplicemente questo, portando buf[] come globale:

char buf[80];
...
    if (dataFile) {                                      // se tutto a posto 
      leggiDati();
      dataFile.println(buf);                        // aggiungo al file i dati richiamati dalla funzione dati()
      dataFile.close();                            // chiudo il file
      Serial.println(buf);                      // scrivo su seriale i dati aggiunti
      scritto=true;                                //porto a vero la condizione scritto
...
void scriviDati() {
  myDHT22.readData();         //aggiorno dati temperatura e umidità
  DateTime now = rtc.now();  // aggiorno orologio

  int x = map(analogRead(luce),0,1024,100,0);  //aggiorno e scalo valore luminosità

  sprintf(buf,"%02d/%02d/%02d; %02d:%02d ; %d; %hi.%01hi; %i.%01i",now.year(),now.month(),now.day(),
  now.hour(),now.minute(),x,myDHT22.getTemperatureCInt()/10, abs(myDHT22.getTemperatureCInt()%10),
  myDHT22.getHumidityInt()/10, myDHT22.getHumidityInt()%10);           // formatto i dati da scrivere e li inserisco in buf
}

fabpolli:
(anche se è bene risolvere il problema e non aggiraglo così come ti ho già indicato io e @gbp01)

Beh, io ho dato una soluzione "al volo" ma ho anche scritto: "seppure siano comunque da investigare le cause :wink:

Non credo che il programma si impianti, almeno non li, all'apertura del file fallisce, ma poi, nel ramo else, accende il led, quindi almeno alla else ci arriva.
Quindi io voto per un problema sulla SD, che sia hw, oppure sw o magari solo sbagliato il filename
Sarebbe magari utile far "lampeggiare" il led, che ci indicherebbe programma ancora in servizio
Oppure trovare la maniera di vedere il nome file, collegando il pc ad una softserial, per non resettare arduino
Ancora, spegnere il led se un tentativo successivo va a buon fine
Comunque certamente cambiare sd con una formattata di fresco
E aggiungo: provare a fare scritture frequenti,adessocon una scrittura ogni 30 minuti è dura aspettare l'errore

C'é una vosa che non ho capito:
Anche se il progetto non prevede computer non funziona come si vorrebbe. Quinfi cosa impedisce di fare prove in un circuito simile attaccato al pc, fare li tutto il debug che si vuole, e poi a codice completo e funzionante testare il circuito vero e proprio?
In questo modo si vede anche se il probkema é hw o sw (se rimane é hw)

Standardoil:
Non credo che il programma si impianti, almeno non li, all'apertura del file fallisce, ma poi, nel ramo else, accende il led, quindi almeno alla else ci arriva.

Non è detto, perché se è la chiamata a dati() che restituisce la famigerata String, è possibile che non riesca più a chiamare la funzione perché il processore si è "impantanato" con la gestione della memoria per le tante chiamate a oggetti String che poi dovrebbe buttare essendo il valore di ritorno della funzione (e dato che non c'è un GC, ad ogni chiamata temo vada ad allocare quei 60 byte ad invocazione...).

Non escludo che possa essere un problema della SD o del circuito di gestione, però questa mi pare la più probabile.

Silente:
cosa impedisce di fare prove in un circuito simile attaccato al pc, fare li tutto il debug che si vuole, e poi a codice completo e funzionante testare il circuito vero e proprio?

Me lo chiedevo anche io (e l'ho chiesto all'OP) ma nel listato vedo che legge e scrive dalla seriale per cui non penso possa farlo, a meno di non modificare almeno quella parte.

la scheda in uso è una arduino nano

le scritture vengono fatte ogni 30 minuti quindi anche le letture vengono fatte ogni 30 minuti e mi sembra di aver letto da qualche parte che "tutto" quello che sta all'interno delle funzione in qusto caso dati(), una volta usciti dalla funzione la memoria viene liberata (in materia di risorse occupate)
quindi secondo me (da profano e ignorante) credevo fosse + "economico" usare le funzioni e chiamarle solo al momento del bisogno piuttosto che lavorarle ad ogni ciclo.

ora mi sono accorto della chiamata quasi simultanea della funazione dati() la prima per la scrittura del dato e la seconda 2 righe sotto che mi serviva per il debug

comunque tanto per fare il punto della situazione provvederò a inserire delle sequenze di lampeggio al led (per ora non sono capace di lavorare in eeprom) in modo tale da avere diversi lampeggi a seconda di dove il programma si ferma (in inizializzazione, apertura, scrittura o chiusura file)

solo adesso rileggendo il programma mi sono reso conto che riscrivere la variabile nomefile ad ogni ciclo sia una stupidaggine grande come un palazzo, quindi l'ho spostata all'interno del if per scrivere nel file

da così

void loop () {

  DateTime now = rtc.now();    //aggiorno orologio

  sprintf(nomefile,"%02d%02d.txt",now.year(),now.month());   // creo nome file da anno e mese forniti da orologio
 
  if (inizio==false && now.minute() <30 ){       // aggiorno periodo a qualsiasi ora metto in funzione il programma 
    periodo=30;
.........

a così

  void loop () {

  DateTime now = rtc.now();    //aggiorno orologio

 .......

  if (scritto==false && now.minute() == periodo ){      //verifico condizione per scrivere su file
    sprintf(nomefile,"%02d%02d.txt",now.year(),now.month());   // creo nome file da anno e mese forniti da orologio
    File dataFile = SD.open(nomefile , FILE_WRITE);    // apro in scrittura il file nome file creato con sprintf
    // if the file is available, write to it:              // se non esiste lo crea in automatico
.....

Usare le funzioni è corretto, così come usare gli array di char che come dici tu all'uscita della funzione il loro "spazio" viene liberato dalla memoria.
Invece la classe String del c++ che usi come tipo della funzione dati su un microcontrollore può generare svariati problemi e risultati poco prevedibili in quanto, contrariamente a ciò che avviene ad esempio su PC, non esisste un garbage collecor che va a "pulire" la memoria da oggetti non più utilizzati portanto a volte a blocchi e/o risultati non facilmente debuggabili.
Quindi segui il consiglio che ti abbiamo fornito ovvero trasforma la funzione dati da String a void, porta la definizione del vettore buf e poi richiami la funzione e usi direttamente buf, ovvero quello che ti ha suggerito pari pari @docdoc.
Il fatto di ricalcolare il nome del file ad ogni ciclo di loop effettivamente era uno spreco di risorse ma non è quello a farti piantare il tutto, comunque ben vengano le ottimizzazioni quindi potresti anche pensare di farlo una volta al mese! con una cosa del genere:

byte mesePrecedente = 13;
...
void loop()
{
 ..
  if (scritto==false && now.minute() == periodo ){      //verifico condizione per scrivere su file
    if(mesePrecedente != now.month())
    {
       sprintf(nomefile,"%02d%02d.txt",now.year(),now.month());
       mesePrecedente = now.month();
    }
}

r69ts:
"tutto" quello che sta all'interno delle funzione in qusto caso dati(), una volta usciti dalla funzione la memoria viene liberata (in materia di risorse occupate)

Si ma creare e distruggere oggetti, soprattutto gli String, sono cose "pesanti" come gestione perché rischiano una frammentazione della RAM, per cui avrai magari metà RAM ancora disponibile ma nessun segmento da almeno 60 byte contigui, ma che, per le altre variabili "statiche", continuerà a funzionare. Arduino non ha un GC (Garbage Collector) "intelligente" come altri linguaggi di alto livello.

comunque tanto per fare il punto della situazione provvederò a inserire delle sequenze di lampeggio al led

No, come PRIMA cosa elimina quella funzione che ritorna String e sostituiscila come ti ho suggerito, questo ti permette di escludere la gestione della RAM. Se in quel modo funziona, era questo il problema.

solo adesso rileggendo il programma mi sono reso conto che riscrivere la variabile nomefile ad ogni ciclo sia una stupidaggine grande come un palazzo, quindi l'ho spostata all'interno del if per scrivere nel file

Questa cosa invece non ha grossi problemi perché non è una "String", ma un array statico di 15 byte che vai a riscrivere. D'accordo che riscriverlo ad ogni loop è assolutamente inutile quindi hai fatto bene, però non può causare problemi.

Visto che stiamo parlando di ottimizzare il codice cambia tutti i tipi della variabili int in byte per tutti quei valori che non possono assumere valori negativi e non maggiori di 255, risparmi preziona memoria della MCU, magari qui non ti servirà granché ma è proedeutico ad un uso corretto dei tipi e per cosa più "pesanti" può far la differenza

Torno a dire. Il programma secondo me non si impianta, altrimenti non accenderebbe il led.
Se accende il led ha eseguito il ramo else
Quindi non ha aperto il file
E questo lo può fare solo per:
SD non inizializzata
Filesystem compromesso
Filename inadeguato
SD guasta
Sono sicuro che ricadiamo in uno di questi 4 casi

E aggiungo:
A file non aperto non chiama la funzione dati()
quindi la dati(), per usando la classe string, non essendo stata chiamata certamente non ha provocato l'anomalia

Standardoil:
Torno a dire. Il programma secondo me non si impianta, altrimenti non accenderebbe il led.

Tu ragioni troppo da windowsiano... :slight_smile:

Non dico che sia per forza come affermo, però non è che il programma o gira o si ferma del tutto, qui parliamo di piccoli processori e pezzettini di RAM, quale parte di "potrebbe funzionare una parte ma non funzionare la chiamata a dati()" non era chiara? :smiley:

Per cui alla saturazione della RAM il sistema può iniziare a "fare cose strane" ossia non previste, fino anche a piantarsi del tutto, ma io non sono proprio certo che la causa del malfunzionamento sia proprio la scrittura sulla SD e non altro.

Per questo, ripeto, intanto leviamo quella robaccia-bacherozzo della classe String che neanche serve a nulla, evitiamo il valore di ritorno così "strano" e vediamo che succede.

Certamente però continuo anche io a non capire come mai non sia possibile lasciare Arduino collegato ad un PC per un paio di giorni e loggare quello che scrive sulla seriale... Questo sicuramente darebbe indizi molto più precisi ed interessanti sulle possibili cause.

Il mio pinguino sì è appena offeso, ti tocca di pagargli un ghiacciolo :slight_smile:
comunque c'è del vero in quello che dici

Per lo OP
Hai per caso un secondo arduino?
Che si potrebbe usare per il debug
Altrimenti diventa difficile...

ok ok

fatto la revisione del codice come consigliato sostituendo int con byte, char buf[] globale, tolto ogni riferimento alla classe String (spero che sprintf non ne faccia parte, altrimenti amen), la funzione è diventata void, messo serial.print ad ogni possibile fallimento di apertura, scrittura, lettura file

di seguito il codice rivisto:

#include <Wire.h>    // libreria per lettura su seriale Wire
#include "RTClib.h"  // libreria per lettura orologio 
#include <SD.h>      // libreria per uso SD
#include <DHT22.h>    // libreria per sensore temperatura e umidità

#define DHT22_PIN 9    // pin ingresso dati sensore temperatura e umidità
/*
 ** MOSI - pin 11      // pin usati per SD
 ** MISO - pin 12      // pin usati per SD
 ** CLK - pin 13      // pin usati per SD
 ** CS - pin 10        // pin usati per SD
 */

RTC_DS1307 rtc;     //inizializzazione orologio
DHT22 myDHT22(DHT22_PIN);  //inizializzazione sensore temperatura/umidità

byte scrittura_ko=8;     //led per avvertire mancata scrittura su file
byte luce=A0;            // pin analogico per lettura luminosità A0
byte chipSelect = 10;    // cs per scrittura SD
bool scritto=false;     // flag per scrittura eseguita
bool inizio=false;      // faccio iniziare periodo in qualsiasi momento
byte periodo=0;          // inizio lettura dati e scrittura su SD
char nomefile[15];      // array nome file
char buf[80];           //array dati da inserire nel file 
File myFile;            // dichiarazione file

void setup () {
  Serial.begin(9600);    // inizializzo seriale
  delay(2000);      

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }
  Serial.print("Initializing SD card...");
  pinMode(10, OUTPUT);      // pin cs per SD
  pinMode(8,OUTPUT);      // pin x led mancata scrittura su sd

  if (!SD.begin(chipSelect))
  {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");


}

void loop () {

  DateTime now = rtc.now();    //aggiorno orologio

  if (inizio==false && now.second() <30 ){       // aggiorno periodo a qualsiasi ora metto in funzione il programma 
    periodo=30;
    inizio=true;
  }
  if (inizio==false && now.second() >30 ){       // aggiorno periodo a qualsiasi ora metto in funzione il programma
    periodo=0;
    inizio=true;
  } 


  if (scritto==false && now.second() == periodo ){      //verifico condizione per scrivere su file
    sprintf(nomefile,"%02d%02d.txt",now.year(),now.month());   // creo nome file da anno e mese forniti da orologio
    File dataFile = SD.open(nomefile , FILE_WRITE);    // apro in scrittura il file nome file creato con sprintf
    // if the file is available, write to it:              // se non esiste lo crea in automatico
    if (dataFile) {                                      // se tutto a posto 
      dati();
      dataFile.println(buf);                        // aggiungo al file i dati richiamati dalla funzione dati()
      dataFile.close();                            // chiudo il file
      Serial.println("scritto da scrittura file");//scrivo su seriale il nome file
      Serial.println(buf);                      // scrivo su seriale i dati aggiunti
      scritto=true;                                //porto a vero la condizione scritto
      //Serial.println(scritto);
    }  

    else {
      Serial.print("errore apertura file: " );
      Serial.print(nomefile);                        
      Serial.println("  mancata scrittura dati" );
      digitalWrite(scrittura_ko, HIGH);


    }
  }
  if (periodo == 0 && scritto==true ){        //condizione per aumentare il periodo di 30 minuti
    periodo=30;
    scritto=false;
  }
  if (periodo==30 && scritto==true){          //condizione per aumentare il periodo di 30 minuti
    periodo=0;
    scritto=false;
  }


  if (Serial.read()=='a'){          // comando letto su seriale per aprire in lettura il file creato
    myFile = SD.open(nomefile);    
    if (myFile) {
      Serial.println();
      // read from the file until there's nothing else in it:
      while (myFile.available()) {    //condizione per leggere tutto il contenuto del file
      Serial.write(myFile.read());  // scrivo su monitor seriale il contenuto del file
      }
      myFile.close();        // chiudo il file
    } 
    else {
      Serial.print("errore apertura file: " );
      Serial.println(nomefile);
    }
  }
}

void dati() {              // funzione per raccogliere dati da scrivere su file

  myDHT22.readData();         //aggiorno dati temperatura e umidità
  DateTime now = rtc.now();  // aggiorno orologio

  int x = map(analogRead(luce),0,1024,100,0);  //aggiorno e scalo valore luminosità
  // char buf[60];              // inizializzo array dati da scrivere

  sprintf(buf,"%02d/%02d/%02d; %02d:%02d ; %d; %hi.%01hi; %i.%01i",now.year(),now.month(),now.day(),
  now.hour(),now.minute(),x,myDHT22.getTemperatureCInt()/10, abs(myDHT22.getTemperatureCInt()%10),
  myDHT22.getHumidityInt()/10, myDHT22.getHumidityInt()%10);
  // formatto i dati da scrivere e li inserisco in buf (globale)

   Serial.println("scritto da funzione");
   Serial.println(buf);    

}

questo il risultato di serial.print

Initializing SD card...card initialized.
scritto da funzione
2018/07/12; 16:05 ; 59; 24.1; 56.0
scritto da scrittura file
2018/07/12; 16:05 ; 59; 24.1; 56.0
scritto da funzione
2018/07/12; 16:05 ; 59; 24.1; 56.0
scritto da scrittura file
2018/07/12; 16:05 ; 59; 24.1; 56.0
scritto da funzione
2018/07/12; 16:06 ; 59; 24.1; 56.0
scritto da scrittura file
2018/07/12; 16:06 ; 59; 24.1; 56.0
scritto da funzione
2018/07/12; 16:06 ; 58; 24.1; 56.1
scritto da scrittura file
2018/07/12; 16:06 ; 58; 24.1; 56.1
scritto da funzione
2018/07/12; 16:07 ; 54; 24.1; 56.1
scritto da scrittura file
2018/07/12; 16:07 ; 54; 24.1; 56.1
scritto da funzione
2018/07/12; 16:07 ; 56; 24.1; 56.1
scritto da scrittura file
2018/07/12; 16:07 ; 56; 24.1; 56.1
scritto da funzione
2018/07/12; 16:08 ; 58; 24.1; 56.1
scritto da scrittura file
2018/07/12; 16:08 ; 58; 24.1; 56.1

per il momento è tutto

Bravo

aggiornamento della situazione:

il datalogger sta girando da circa 20 ore con rilevamento e scritture ogni 30 secondi, facendo un rapido calcolo ha fatto circa 2500 scritture e non ha dato nessun problema, considerando che a me servono 2 scritture ora, vuol dire che è stato simulato un periodo di circa 2 mesi di scritture normali

lo lascio correre ancora per un paio d'ore e poi se non si verifica niente di anomalo chiudo il 3d con risolto

nel frattempo ringrazio tutti coloro che mi hanno aiutato a capire dove sbagliavo (classe string) e modi alternativi e compatibili per il reset sw tramite wd (anche se non mi è molto chiaro quel ciclo for senza argomenti)

grazie

r69ts:
il reset sw tramite wd (anche se non mi è molto chiaro quel ciclo for senza argomenti)

Un for vuoto è un ciclo che non termina mai .... e quindi, passato il tempo impostato, scatta il watchdog.

Ripeto che i problemi vanno RISOLTI, non aggirati con questi trucchetti ... ::slight_smile:

Guglielmo

r69ts:
nel frattempo ringrazio tutti coloro che mi hanno aiutato a capire dove sbagliavo (classe string) e modi alternativi e compatibili per il reset sw tramite wd (anche se non mi è molto chiaro quel ciclo for senza argomenti)

Il ciclo for senza argomenti di fatto blocca l'esecuzione del programma in quel punto, potresti scrivrla anche come

while(1){}
while(true){}

e in tanti altri modi.
La sostanza è che impostando il watchdog solitamente all'inizio del loop si usa wdt_reset() per resettare il watchdog e non farlo scattare in caso di blocco anomalo del programma non si passa più da quell'istruzione e il wd resetta tutto. Con quel for stai simulando un "blocco anomalo" del programma e dopo il tempo impostato (da mio esempio 8 secondi) il watchdog resetta tutto.
Nel tuo caso attivando il watchdog in caso d'errore e non come canonicamente si fa al termine del setup potresti anche omettere il for e far girare il programma che non avendo il wdt_reset() non resetterebbe il watchdog e dopo il tempo impostato resetterebbe la MCU, ma onde evitare comportamenti ancora più imprevedibili è bene fermarsi in un punto preciso.
Resto dell'idea che il watchdog debba essere usato solo per quel che è nato ovvero gestire errori imprevisti e non questa classe di casistiche per le quali è bene trovare l'errore e risolvere. Opterei per memorizzare in EEPROM data e ora dell'errore di apertura file, eventualmente il numero di occorrenze con cui capita l'errore per tentare di capire se il problema c'è ancora e tentare di risolverlo.
Sono anche dell'opioniche che Standardoil non ha torto ovvero l'errore lo hai se si danneggia il filesystem, il file, ecc.

eccoci qua come promesso

riverificato e niente di anomalo nessun fallimento in apertura o scrittura

ancora grazie per l'aiuto ricevuto e karma++ a tutti i partecipanti

alla prossima