Errore Scrittura SD e Reset automatico

Ciao a tutti
Vorrei esporvi un problema che non riesco a risolvere.
Sto costruendo un anemometro Datalogger che dovrà costantemente monitorare la velocità del vento, e dovrà farlo per svariati mesi consecutivamente.

Purtroppo però lo fa solo per pochi minuti. Legge la velocità, scrive sul display e fa il log correttamente su scheda SD. Purtroppo però dopo qualche minuto, va in errore durante la scrittura sulla SD. Ogni 2 secondi (così ho impostato il logging), quando tenta la scrittura l'errore continua per una decina di tentativi circa. Poi l'intero sistema si resetta, fa di nuovo il setup e ricomincia a lavorare correttamente come da programma.

e poi ricomincia il ciclo di errori sd e reset....

Ho riformattato la scheda (in fat32), ho eseguito il test fisico dei settori sulla SD, ma è tutto ok
vi allego il sorgente ed uno dei log

le righe vuote è dove lui tenta più volte la scrittura, e poi c'è un buco di circa un minuto dove lui si "riavvia"

Arduino UNO clone, Arduino IDE 2.3.2 da Flatpack su Linux Manjaro

Spero di non aver scordato nulla

#include <SoftwareSerial.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>


#define RXPin     6
#define TXPin     7
#define RTS       5    // RS485 Direction control (RE/DE of MAX485 - pins 2 e 3)
#define SQWinput  3    // Il Pin di Arduino che legge lo SQW dall'orologio
#define Interval  2    // ogni quanti cicli di SQW dell'orologio DS3231 devo leggere il vento e scriverlo su SD
                       // Interval 2 significa 2 interrupts quindi 2 secondi

SoftwareSerial RS485Serial(RXPin, TXPin);
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS3231 rtc;

volatile int cicli = 0; // in questa variabile metteremo il numero di cicli di SQW; conta quante volte scatta l'interrupt
byte Anemometer_buf[7]; //array dove verrà memorizzata tutta la catena di dati dall'anemometro
byte Anemometer_request[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A}; // inquiry frame

volatile float Anemometer = 0;
DateTime now;
File logFile;

String mese[12] = {"Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"};

void setup() {

  /*
  Serial.begin(9600);
  Serial.println("Seriale Inizializzata");
  */

  lcd.begin(16,2);
  lcd.clear();
  lcd.backlight();


  pinMode(RTS, OUTPUT); 
  delay(300);
   // initializing the rtc
  if(!rtc.begin()) {
      lcd.print("Couldn't find RTC!");
      while (1) delay(500);
  } 
  
  lcd.clear();

  if (rtc.lostPower()) {
      rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
      lcd.print("LostPower");
  }

  lcd.clear();

  rtc.writeSqwPinMode(DS3231_SquareWave1Hz);
  pinMode(SQWinput, INPUT);
  digitalWrite(SQWinput, HIGH);
  
  lcd.clear();

  // Start the Modbus serial Port, for anemometer
  RS485Serial.begin(4800);   
  delay(500);

  while(!SD.begin()) {
    lcd.print("Errore SD");
    delay(500);
    lcd.clear();
  }

  //lcd.clear();

  attachInterrupt(digitalPinToInterrupt(SQWinput), clockInterrupt, FALLING);

}

void loop() {

 if (cicli == Interval) {
    cicli = 0;
    
    String data = "";
    String ora = "";
    digitalWrite(RTS, HIGH);  //pone il pin RTS alto, cioè indica all'RS485 che sto per trasmettere
    RS485Serial.write(Anemometer_request, sizeof(Anemometer_request));
    RS485Serial.flush();
    
    
    digitalWrite(RTS, LOW);  //pone il pin RTS basso, cioè indica all'RS485 che voglio ricevere
    RS485Serial.readBytes(Anemometer_buf, 7);
    
    Anemometer = Anemometer_buf[4]/10.0;
    
  
  
    now = rtc.now();
    char *res = malloc(5);
    sprintf(res, "%02d", now.day());
    data = res;
    sprintf(res, "%02d", now.month());
    data = data + "/" + res;
    sprintf(res, "%04d", now.year());
    data = data + "/" + res;
    sprintf(res, "%02d", now.hour());
    ora = res;
    sprintf(res, "%02d", now.minute());
    ora = ora + ":" + res;
    sprintf(res, "%02d", now.second());
    ora = ora + ":" + res;
    lcd.setCursor(0,0);
    lcd.print (pad2(now.day()) + " " + mese[(now.month()-1)] + " " + ora);
    lcd.setCursor(0,1);
    lcd.print (Anemometer*3.6);
    lcd.print (" km/h");

    String fileName = ""; // qui metto il nome del file cui vado a scrivere la riga
    String log = ""; //qui metto la stringa della riga che vado a scrivere
  
    fileName = String(now.year()) + "-" + pad2(now.month()) + ".log";
   
    log = pad2(now.day()) + ";" + ora + ";" + String(Anemometer);
      
    logFile = SD.open(fileName, FILE_WRITE);

    if (logFile) {
      logFile.println(log);
      logFile.close();
    } else {
      lcd.clear();
      lcd.print("Errore Scrittura");
    }
  }
}


void clockInterrupt() {
  //questa è la routine di interrupt
  //Serial.println("interrupt " + String(cicli));
  cicli++;
  //Serial.println("cicli = " + String(cicli));
}
 
String pad2(int n) {
  String ret = "";
  if (n<10) ret = "0" + String(n);
  else ret = String(n);
  return ret;
}

File 2024-04.LOG

03;21:33:38;3.80
03;21:33:40;3.80
03;21:33:42;3.60
03;21:33:44;3.80
03;21:33:46;3.80
03;21:33:48;4.10
03;21:33:50;3.80
03;21:33:52;3.60
03;21:33:54;3.30
03;21:33:56;3.40
03;21:33:58;3.20
03;21:34:00;3.60
03;21:34:02;3.60
03;21:34:04;3.80
03;21:34:06;3.90
03;21:34:08;4.20
03;21:34:10;4.20
03;21:34:12;4.10
03;21:34:14;4.20
03;21:34:16;4.60
03;21:34:18;5.10
03;21:34:20;5.10
03;21:34:22;5.10
03;21:34:24;5.10
03;21:34:26;5.00
03;21:34:28;5.20
03;21:34:30;5.00
03;21:34:32;5.10
03;21:34:34;5.10
03;21:34:36;5.10
03;21:34:38;5.10
03;21:34:40;5.00
03;21:34:42;5.10
03;21:34:44;5.20
03;21:34:46;5.10




;5.20
03;21:35:33;5.10
03;21:35:35;5.00
03;21:35:37;5.00
03;21:35:39;5.00
03;21:35:41;5.00
03;21:35:43;5.10
03;21:35:45;5.00
03;21:35:47;4.90
03;21:35:49;4.80
03;21:35:51;4.70
03;21:35:53;4.80
03;21:35:55;4.50
03;21:35:57;4.90
03;21:35:59;3.20
03;21:36:01;2.00
03;21:36:03;1.60
03;21:36:05;1.30
03;21:36:07;2.90
03;21:36:09;3.60
03;21:36:11;3.80
03;21:36:13;3.80
03;21:36:15;4.00
03;21:36:17;4.30
03;21:36:19;4.10
03;21:36:21;4.10
03;21:36:23;4.20
03;21:36:25;4.30
03;21:36:27;4.40
03;21:36:29;4.30
03;21:36:31;4.30
03;21:36:33;4.00
03;21:36:35;3.90
03;21:36:37;4.30
03;21:36:39;4.10
03;21:36:41;4.40




;3.90
03;21:37:28;3.60
03;21:37:30;3.60
03;21:37:32;3.70
03;21:37:34;3.90
03;21:37:36;3.70
03;21:37:38;3.80
03;21:37:40;3.90
03;21:37:42;3.70
03;21:37:44;3.40
03;21:37:46;3.50
03;21:37:48;3.50
03;21:37:50;3.60
03;21:37:52;3.30
03;21:37:54;3.40
03;21:37:56;3.50
03;21:37:58;3.40
03;21:38:00;3.40
03;21:38:02;3.80
03;21:38:04;3.80
03;21:38:06;3.60
03;21:38:08;3.70
03;21:38:10;4.00
03;21:38:12;4.00
03;21:38:14;4.10
03;21:38:16;4.40
03;21:38:18;4.50
03;21:38:20;4.30
03;21:38:22;4.20
03;21:38:24;4.00
03;21:38:26;3.90
03;21:38:28;3.90
03;21:38:30;3.90
03;21:38:32;3.70
03;21:38:34;3.80
03;21:38:36;3.80




;3.80
03;21:39:23;3.80
03;21:39:25;4.00
03;21:39:27;3.90
03;21:39:29;4.00
03;21:39:31;3.90
03;21:39:33;3.80
03;21:39:35;4.00
03;21:39:37;4.20
03;21:39:39;4.00
03;21:39:41;3.90
03;21:39:43;4.00
03;21:39:45;3.90
03;21:39:47;4.00
03;21:39:49;4.30
03;21:39:51;4.20
03;21:39:53;4.10
03;21:39:55;4.00
03;21:39:57;4.30
03;21:39:59;4.00
03;21:40:01;3.70
03;21:40:03;3.60
03;21:40:05;3.80
03;21:40:07;3.80
03;21:40:09;3.60
03;21:40:11;3.70
03;21:40:13;3.80
03;21:40:15;3.80
03;21:40:17;4.00
03;21:40:19;4.00
03;21:40:21;4.30
03;21:40:23;4.20
03;21:40:25;3.80
03;21:40:27;3.80
03;21:40:29;3.90
03;21:40:31;3.90




;3.80
03;21:41:18;4.30
03;21:41:20;4.20
03;21:41:22;4.20
03;21:41:24;4.30
03;21:41:26;4.20
03;21:41:28;4.10
03;21:41:30;3.80
03;21:41:32;4.00
03;21:41:34;3.90
03;21:41:36;4.10
03;21:41:38;4.00
03;21:41:40;3.50
03;21:41:42;2.70
03;21:41:44;2.50
03;21:41:46;2.50

Molto semplicemente ci sono troppi oggetti string

Proprio per essere chiari: se usi una volta un oggetto string lo hai usata una volta di troppo

Ci sono varie maniere per evitarle, basta solo pensarci un po'

Per il resto il programma non è male

Prosegui che sei bravo

1 Like

@C1P8 questa ti è sfuggita vero, altro che String.

Come e da dove ti è venuta l'idea di usare malloc?
Se allochi dinamicamente 5 byte ad ogni ciclo di loop prima poi la memoria finisce. Ad ogni malloc segue sempre un free(res).

Ciao.

1 Like

Vero, ma visto che dal log della SD ha fatto 155 cicli prima di crashare, a 5 byte per ciclo sarebbero solo 775 byte. Il problema secondo me è nell'accoppiata di questa malloc() e l'uso delle "String"! Ad esempio ha fatto tutte quelle righe con sprintf() per formattare data ed ora (tralasciamo per ora il malloc()) per poi costruirci due String (di cui poi una "data" totalmente inutile perché mai utilizzata), poi ha usato una funzione "pad2()" per fare semplicemente il pad di mese o giorno (che in realtà ha più correttamente fatto prima con sprintf()) ed anche qui altra String temporanea!

Insomma, un po' di confusione o un "minestrone" di pezzi di codice.
Per riassumere, togliere tutte le String è cosa buona e giusta, unita all'evitare il malloc() anche questo inutile, basta definire una variabile globale stringa (non "String") di dimensioni adeguate per poterla usare in ogni caso di conversione da numeri a stringa.

Quindi al nostro amico proporrei queste modifiche:

...
// NO! 
//String mese[12] = {"Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"};
char mese[][12] = {"Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"};
// Nuova variabile buffer per le conversioni in stringa
char buf[20];
...
    // non servono più
    //String data = "";
    //String ora = "";
...
    now = rtc.now();
    // Evitare...
    //char *res = malloc(5);
   // commentare tutte le successive righe per "data" e "ora"

    lcd.setCursor(0,0);

    //lcd.print (pad2(now.day()) + " " + mese[(now.month()-1)] + " " + ora);
    sprintf(buf, "%02d %s %02d:%02d:%02d", now.day(), mese[(now.month()-1)], now.hour(), now.minute(), now.second());
    lcd.print (buf);

...   ed infine:
    //String fileName = ""; // qui metto il nome del file cui vado a scrivere la riga
    //String log = ""; //qui metto la stringa della riga che vado a scrivere
    //fileName = String(now.year()) + "-" + pad2(now.month()) + ".log";
    //log = pad2(now.day()) + ";" + ora + ";" + String(Anemometer);
    //logFile = SD.open(fileName, FILE_WRITE);
    sprintf(buf, "%d-%02d.log", now.year(), now.month());
    logFile = SD.open(buf, FILE_WRITE);
    if (logFile) {
      // Non potendo usare sprintf per i float, dato che il valore iniziale è un 
      // byte usiamo questo workaround...
      sprintf(buf, "%02d;%02d;%d.%d",  now.day(), now.month(), 
          Anemometer_buf[4]/10, Anemometer_buf[4]-10*(Anemometer_buf[4]/10));
      logFile.println(buf);
      logFile.close();
    } else {
      lcd.clear();
      lcd.print("Errore Scrittura");
    }
1 Like

Fantastico!!
Posso quasi dire che sono stati più utili questi posts che tutti i tutorial che ho seguito. Ovviamente karma a tutti

Iniziamo:

Ottimo! So che il C non le gestisce nativamente percui anche a me sembrava una forzatura, ma non sapevo fino a questo punto. Mi hai dato una conferma, grazie. Mi documenterò anche in questa direzione.

Grazie, detto da esperti dà sempre la sua bella soddisfazione!

:rofl: :rofl: scusa, ma mi fa ride 'sta cosa

Ho seguito un po' di tutorial su yt, in questo caso parliamo di Paolo Aliverti, video #233 (6:45) su Arduino.
Il malloc() ad intuito sa anche a me di instruzione potente ma pericolosa; essendo però il mio primo progetto, non mi sono soffermato ad interpretare troppo la cosa. Ma lo farò sicuramente, anche grazie a voi.

Ok, ottimo! non pensavo che dopo malloc() occorresse necessariamente un disalloc() perchè pensavo che all'uscita del loop() la variabile res venisse distrutta, invece da come ho capito, ogni ciclo aggiunge un'allocazione. Molto istruttivo!

Continuo:

:rofl: Giusto, è esattamente questo!
E pensa che per poterlo pubblicare l'ho ridotto a circa la metà togliendo via un bel po' di codice tipo Serial.print() per imparare come si comporta il sistema ed altre tonnellate di variabili/oggetti vari non più in uso o ottimizzati. Rischiavo davvero il BAN se lo avessi pubblicato così

Cavolo, sinceramente non lo avevo riflettuto! Ancora grazie! Ottime dritte

Ancora grazie per il codice e tutto l'aiuto. Davvero sono state nozioni importanti per me

Scusate, un'altra cosa; forse è OT, ma in qualche modo inerente. Ho alcuni libri sul c/c++ quasi tutti inutili ad eccezione di uno di 976 pagine che non è male e che pagai nel '99 la bellezza di 92.000 Lire (per chi non conosce le lire, più o meno 100€ di ora).
Spiega le basi, ma non insegna quasi nulla di come ottimizzare il codice, e/o scrivere in maniera efficiente o cose simili. Diciamo che mi occorrono nozioni sulla programmazione avanzata e più intelligente. Nessuno dei libri che ho lo fa.
Prima di spendere cifre così importanti vorrei andare sul sicuro chiedendo a voi se conoscete qualche libro anche costoso e in inglese che assolva a questo compito.

Oppure magari creare un topic adatto o simili dove si possano condividere consigli e risorse del genere tipo siti/video/libri che sono risultati particolarmente buoni.
Sbircerò sul forum intanto che è veramente vasto, magari qualcuno ci ha già pensato

Ma io mi sono meravigliato del fatto che nonostante i disturbi della vista di cui soffro non mi sia sfuggito quel malloc(5), quindi vuole dire che ancora non sono da buttare. :smiley:

Visto il video (l'avevo già visto ma non ricordavo), ora tu osservalo meglio il video, vedrai che quel comando si trova fuori da ogni funzione. Il compilatore non genera una chiamata alla funzione malloc() ma invece risolve l'allocazione al momento della compilazione ne più ne meno come:

char res[5]; // globale auto-inizializzata a zero

Si res viene distrutta è perdi il valore del puntatore ed malloc te ne restituisce un altro valido e così via fino a quando non fallisce.
res si trova nella porzione di memoria detta stack.

Se eseguita, la chiamata a funzione malloc(), alloca durante l'esecuzione memoria nella porzione detta heap. Se l'allocazione ha successo viene restituito un puntatore valido, diversamente viene restituito nullptr.
L'allocazione dinamica ha costo in cicli cpu. La classe String e altre usano internamente malloc o calloc o altre chiamate simili, il rilascio della memoria allocata avviene tramite la funzione free(puntatore) gestito sempre internamente. Questo tipo di allocazione porta facilmente alla frammentazione della memoria heap, quando ciò accade, se malloc(10) non trova 10 byte consecutivamente liberi fallisce restituendo nullptr.

if (res == nullptr) {
    Serial.println("allocazione fallita.");
}

Puoi verificare il successo o fallimento di malloc con il codice precedente.

PS: se proprio vuoi fare esperimenti con malloc e company fallo pure, ma considera che l'uso pratico è molto raro. Decidi tu se dedicargli tempo già adesso, io ti consiglio di usare il tempo per studiare ciò che viene usato spesso, lasciando malloc per ultimo.

Ciao.

1 Like

Nessuno dei libri che ho letto spiega i dettagli. In ogni caso devi cercare qualcosa che abbia a che fare con embedded C.

Ciao.

1 Like

I libri si conoscono dal titolo

Comunque io ho imparato dal K&R, come molti qui

E poi ho migliorato leggendo molto ma molto tanto qui

Ci sono alcuni, qui, dai quali si può imparare tanto leggendo i loro post

1 Like

Tranne eventuali regole generali e/o convenzioni comuni utili (come ad es. indentare bene, usare maiuscole per le costanti, raggruppare il codice in funzioni logicamente omogenee, eccetera) è difficile che un libro possa fare questo perché le cose di cui parli sono strettamente legate all'infrastruttura nella quale si sta operando, dalla presenza (o meno) di un sistema operativo, di framework a disposizione, dell'hardware, e la cosa è ancora più specifica se si parla di microcontrollori come Arduino.

Considera poi che l'"ottimizzazione" e/o scrivere in maniera "efficiente" sono influenzate non solo dalle precedenti considerazioni ma anche dallo stile del programmatore (che deriva dall'esperienza) e dagli eventuali requisiti (es. se il codice debba rispettare determinati tempi di risposta, se debba controllare più elementi contemporaneamente o in sequenza, eccetera).

Se può consolarti, tutto questo significa anche che, a parità di hardware e requisiti, due programmatori possono affrontare la cosa con approcci diversi (in base alla propria esperienza e forma mentale), quindi anche se il funzionamento sarà quasi identico il codice a volte potrà essere anche quasi totalmente differente.

Per cui non preoccuparti, per saper scrivere codice più ottimizzato, o funzionale ti basta fare pratica ed accumulare esperienza.

E questo però è sbagliato
Sono gli analisti che affrontano il problema
Mica i programmatori

A parità di analisi dallo stesso analista due programmatori hanno poco da inventare
Se poi devono seguire le linee guide aziendali anche meno

Invece se si va allo sbaraglio senza analisi e con scelte estemporanee per ogni differente programmatore c'è si una differente strada, ma per il baratro

Ma che da buttare... tu almeno hai la scusa dei disturbi alla vista, io faccio spesso distrazioni pesanti..
Queta è di ieri pomeriggio: ho preso una multa per aver dimenticato il disco orario. E fin qui, può capitare... Ma non quando la macchina era parcheggiata proprio sotto il cartello... e non solo: il cartello con l'obbligo di disco l'avevo messo io 2 settimane fa!

E che voglio di più? Semplice e linare

Grazie per il link, messo subito nei segnalibri.
Il resto che spieghi su malloc per me è ancora oscuro, ma ho uno spunto su cui documentarmi

Certo, lo farò, ma come appunto dici, lo userò molto poco. Lì è stato più un copiare dai tutorial

ok fatto qualche ricerca, c'è parecchio materiale

Ancora Grazie

Grazie del consiglio! L'ho cercato, è piùttosto costoso, ma se dite che è ottimo, sono soldi ben spesi

Ah non lo metto in dubbio; considerando solo questo thread c'è carne di qualità al fuoco

Sono più o meno d'accordo con entrambi, dipende da molte cose; ad esempio potrebbe capitare che analista e programmatore siano la stessa persona. Ma questo è un dibattito spinoso

Allora:

Davvero Fantastastico!!

Ho fatto girare il sistema per poco più di 3 ore e qundi ho tolto la SD. Non c'è alcuna interruzione nè altro.
E' per - fet - to!!!

Ora devo andarmi a rivedere la parte software del pc che interpreta i dati e capire come cambiare l'indirizzo del segnavento modbus, possibilmente senza bestemmie (eh già, i progettisti gli hanno assegnato lo stesso indirizzo dell'anemometro...). Ma questi sarebbero altri topic

Ancora grazie a tutti per le info preziosissime!

Ovviamente l'analista fa il suo lavoro di analisi dei problemi (ossia dei requisiti utente) e prepara la progettazione, ma non fa il programmatore, nel senso che non si occupa direttamente della realizzazione. Io sono analista e sono stato anche programmatore ovviamente (tuttora programmo anche per hobby...), non so quale sia la tua esperienza in questa materia, ma io come analista non vado a controllare i programmatori e non conosco nessun analista che vada a stabilire come debba essere fatto il codice (tranne per le regole comuni o aziendali, soprattutto quando si lavora in team, quindi con criteri di leggibilità oltre che di standard definiti e documentazione). Ho gestito team di svariati programmatori quindi scusami ma so di cosa parlo.

Quello a cui mi riferisco comunque è più semplice, ossia dai un requisito di una certa complessità media (quindi non solo fare una somma, per capirci, ma implementare un algoritmo ad esempio) a tre programmatori diversi ed è altamente probabile che il codice mostri tre approcci più o meno differenti. E non per qualche "capriccio" ma soprattutto perché la "forma mentis" d ognuno porta spesso a soluzioni (leggi implementazioni) diverse. Per dire, anche solo implementare un algoritmo di sorting di un array o bilanciamento di un albero (b-tree) sono realizzabili con codici anche quasi totalmente differenti.

Comunque questo è un discorso teorico/accademico, possiamo (dobbiamo) tralasciare la cosa, almeno in questo thread. :wink:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.