[Risolto] Gestire errore di scrittura su micro SD.

Ciao a tutti

ho "creato" il solito datalogger con arduino nano, per rilevare temperatura e umidità uso un sensore DHT22, rtc DS1307 per data e ora e una fotoresistenza per la luminosità, i dati vengono presi e scritti su SD ogni 30 minuti senza nessun problema

mi è capitato di leggere i dati e questi si erano fermati da un paio di giorni (purtroppo il progetto non è sempre visibile)

per il momento ho installato un led rosso nel caso in cui la scrittura non vada a buon fine, nel caso in cui questo si accende devo resettare il micro, ma non mi sembra una soluzione valida

la mia domanda è:

è possibile reinizializzare la scheda SD in automatico senza dover resettare il tutto??

grazie per qualsiasi consiglio

Io mi domando un altra cosa: come mai una scrittura può non andare a buon fine? E come lo si vede?
Domando questo perché, da quel poco che uso le SD per qualche progettino, so che la scheda si inizializza una volta, in setup() e se va bene allora va bene sempre. Di conseguenza credo che se qualcosa non va bene vuol dire che c'é qualche problema hardware (la scheda che non tocca, fili staccati ecc), quibdi dubito che si possa operare da software.
Comunque credo che un modo di reinizializzare una SD esista, e che se c'é é scritto nel reference della libreria SD

1 Like

grazie per una risposta rapida.

ho riletto il reference come consigliato, ma non ho trovato nulla di utile (per quanto possa valere ho anche sbirciato all'interno dei vari file .h e .c ma non sono in grado di capirli al 100% (forse arrivo al 3%))

comunque se ti mettono a disposizione un diciamo "allarme" di mancata apertura/scrittura del file forse un modo dovrebbe esserci per riprovare, a dire il vero non ho riprovato a riscrivere dopo l'errore

ci provo e aspetto che succeda qualcosa

grazie

Se c'è un errore hardware (dell'interfaccia e/o della scheda SD) c'è poco da cercare di inizializzare.

Se l'errore è software, allora dipende dal tipo di errore.

Può darsi che la SD abbia il filesystem danneggiato, ma anche in questo caso non puoi fare nulla se non estrarla, formattarla, e rimetterla dentro.

Oppure, la cosa più probabile forse, hai riempito lo spazio disponibile oppure hai creato un file troppo grande per il file system (es. un file da 4Gbyte). In entrambi i casi è necessario pensare a dividere i dati in più file ed una procedura di pulziia che cancelli i file più vecchi di un certo tempo quando lo spazio disponibile scende al di sotto di un minimo (es. 5%).

come faccio a sapere il tipo di errore nel caso sia sw?? posso solo intercettarlo come mancata apertura o mancata scrittura del file, devo mettere altri led?? perchè altre soluzioni non mi vengono in mente

non credo sia un problema hw, in quanto una volta riavviato il tutto a distanza di giorni funziona perfettamente ovvero continua a scrivere accodando i dati sullo stesso file come da programma, il "peso" del file è di circa 50 Kb quindi nulla di che e lo spazio disponibile è di circa 1 Gb

Metti sorgente e schema, che ci si da un occhio

Concordo con gli altri: se una scrittura fallisce, è per un problema "grave", che non si risolverà semplicemente con un nuovo tentativo, altrimenti avrebbe funzionato in prima istanza.

Non c'è un OS con altri processi che girano (come su un PC) e che possono lockare il file o altri problemi del genere. Ogni errore è hardware (fili staccati o SD passata a miglior vita) o "logico" (del filesystem).

Ad esempio, il mio logger GPS in condizioni normali non dà mai errori, se li dà è colpa del filesystem, di solito. Purtroppo la FAT si corrompe con una facilità estrema.

r69ts:
come faccio a sapere il tipo di errore nel caso sia sw?? posso solo intercettarlo come mancata apertura o mancata scrittura del file, devo mettere altri led?? perchè altre soluzioni non mi vengono in mente

Come ha detto anche Standardoil, posta il tuo codice (non uso il termine "sketch", che non esiste in nessun altro ambito di programmazione, scusami.. :wink: ), una descrizione di cosa succede quando capita l'errore, lo schema dei collegamenti, altrimenti non è semplice poterti aiutare o almeno consigliare.
Ad esempio sei riuscito a capire se dà errore nella open? O nel write? O nella close? Se possibile, puoi lasciare collegato un PC con monitor seriale e scrivere info di debug con Serial.print() per capire dove e quando esattamente accade il problema?
Se invece non puoi perché magari è installato in un posto non accessibile, perché in questo caso non scrivi nella EEPROM di Arduino (così resta memorizzato anche al reset) la condizione dell'ultimo errore ad es. data ed ora ed un qualche codice per indicare dove è avvenuto?

Per iniziare però potresti intanto provare a cambiare microSD (magari è la scheda difettosa?)? E comunque facendo resettare Arduino da programma quando avviene questo evento, per capire se in questo modo si possa recuperare il blocco (seppure siano comunque da investigare le cause).
Per farlo puoi provare così, collega un pin, es. il 12, al pin RESET di Arduino e fai una cosa del genere:

#define RESETPIN 12
void setup() {  
  digitalWrite(RESETPIN, HIGH);
  pinMode(RESETPIN, OUTPUT);     
  Serial.begin(9600);
  ... qui tutto il resto
}

void loop() {
  ..
  if (condizione errore) {
    digitalWrite(RESETPIN, LOW);
  }

Penso che possa funzionare.

SukkoPera:
Ad esempio, il mio logger GPS in condizioni normali non dà mai errori, se li dà è colpa del filesystem, di solito. Purtroppo la FAT si corrompe con una facilità estrema.

Si, oppure è la microSD che è difettosa (es. magari quando si scalda?).
Ma sempre fermo restando che se vediamo il codice possiamo cercare di dare altri consigli.

intanto ringrazio per tutti i consigli avuti

purtroppo lo schema non esiste (ovviamente) fatto in "ambiente" di prova e quindi definitivo, casomai allego una foto se ci riesco

non mandatemi al quel paese per come è stato scritto (non è il mio lavoro principale e sono un principiante)

di seguito il codice funzionante:

#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à

int scrittura_ko=8;  //led per avvertire mancata scrittura su file
int luce=A0;    // pin analogico per lettura luminosità A0
int chipSelect = 10;  // cs per scrittura SD
bool scritto=false;  // flag per scrittura eseguita
bool inizio=false;  // faccio iniziare periodo in qualsiasi momento
int periodo=0;      // inizio lettura dati e scrittura su SD
char nomefile[15];  // array nome 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

  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;
    inizio=true;
  }
  if (inizio==false && now.minute() >30 ){       // aggiorno periodo a qualsiasi ora metto in funzione il programma
    periodo=0;
    inizio=true;
  } 


  if (scritto==false && now.minute() == periodo ){      //verifico condizione per scrivere su file
    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 

      dataFile.println(dati());                        // aggiungo al file i dati richiamati dalla funzione dati()
      dataFile.close();                            // chiudo il file
      //  Serial.println(nomefile);                    //scrivo su seriale il nome file
      Serial.println(dati());                      // 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);
    }
  }
}

String 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

  /* Serial.println("scritto da funzione");
   Serial.println(buf);    
   */
  return buf;  
}

docdoc:
Per farlo puoi provare così, collega un pin, es. il 12, al pin RESET di Arduino e fai una cosa del genere:
...omissis...
Penso che possa funzionare.

Non è una buona pratica secondo Atmel

You should not try to use another pin of the AVR to pull the external RESET line. The pins of the AVR are tristated halfway through the minimum reset time, this releases the RESET line and hence nothing happens.

Mi pare di ricordare che sul sito di Atmel consigliavano l'uso del watchdog per resettare, prima però sarebbe meglio tentare di capire se l'errore è software e porvi rimedio, il softreset deve essere l'ultima spiaggia, lo dico soprattutto per l'OP che è inesperto (a detta sua) che potrebbe essere tentato dal lato oscuro della programmazione :slight_smile:
Comunque in un mio progetto ho inserito il reset via watchdog per resettare da remoto casomai mi servisse reinizializzare il tutto e il metodo funziona, anche se in fase operativa non mi è mai servito :slight_smile:

Complimenti, ben indentato, ha la sua importanza
Unico vero bacherozzo che vedo è l'uso degli oggetti stringa, io farei lo sforzo di sostituirli con stringhe di C
Quindi a te rimane acceso il led, che significa mancata apertura file...
Una formattata alla sd, possibilmente cambiarla, ma magari trovare la maniera di vedere la variabile nomefile non sarebbe male
Hai visto mai che ci rimane dentro un bacherozzo...

docdoc:
... Per farlo puoi provare così, collega un pin, es. il 12, al pin RESET di Arduino e fai una cosa del genere:

Come ha già detto "fabpolli" è assolutamnete da NON fare e contrario a tutte le specifiche di Atmel.

Basta fare un po' di ricerche su questo forum per trovare discussioni in cui, con Astrobeed, se ne è ampiamente parlato e sono state date tutte le spiegazioni (... che coinvolgono i "tempi" del reset).

In ogni caso ... come detto mille volte ... a voi v'ha rovinato Windows ed il suo CTRL-ALT-DEL :smiling_imp: !!! I problemi si risolvono, NON si fanno certe schifezze !!!

Guglielmo

Però c'è un problema:
Tu non spegni mai il led, quindi non sappiamo se dopo una prima scrittura cliccata ne sono invece avvenute di buone oppure se il programma si è impiantato o cosa...
Hai il Pc sempre connesso?

grazie a tutti

per quanto riguarda il reset automaizzato andrò in cerca di informazioni al riguardo anche se la soluzione di docdoc non mi dispiaceva affatto.

il bacherozzo non riesco a capirlo, mi sembra di passare tutto su array "riempiti" tramite sprintf (uso di oggetti stringa?? perdona la mia ignoranza)
stringhe di C?? lavorare con array di char?? ogni consiglio è benvenuto al riguardo (forse ho un po di casino in mente)

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

E quindi Debug difficile
Usi una Uno?

r69ts:
il bacherozzo non riesco a capirlo, mi sembra di passare tutto su array "riempiti" tramite sprintf (uso di oggetti stringa?? perdona la mia ignoranza)

Standardoil si riferisce al tipo della funzione dati

String dati() {

Che appunto usa la classe String.
Per farti tornare il valore su un array di char hai svariate possibilità, ad esempio passare l'array come parametro per riferimento, far tornare il suo puntatore dalla funzione ecc.. La cosa più semplice è definirlo globale come hai fatto per gli altri e trasformare la funzione in modo che sia di tipo void

r69ts:
grazie a tutti

per quanto riguarda il reset automaizzato andrò in cerca di informazioni al riguardo anche se la soluzione di docdoc non mi dispiaceva affatto.

Quella soluzione non funziona
Una soluzione percorribile (anche se è bene risolvere il problema e non aggiraglo così come ti ho già indicato io e @gbp01) può essere questa

#include <avr/wdt.h>

nel setup per sicurezza

wdt_disable();

Quando vuoi resettare usi

wdt_enable(WDTO_8S);
for(;;){}

Tu (lo OP) vuoi leggere i dati solo al minuto 0 e al minuto30 di ogni ora?
Stasera arrivò a casa tardi, e dal furbofono è dura scrivere
Però appena posso ti do due dritte, oltre a quella di fabpolli, che è ottima
Si tratta di capire se è il nome file o la SD a dar problemi

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