Ma perché tutte queste cose complicate? Se hai variabili globali, sono globali, quindi non serve passarle alle funzioni perché le "vedono" tutte!
Tantomeno mettere tante variabili "volatile", dentro ad una ISR devi "lavorare" il meno possibile, non metterti a fare cose complicate perché nel frattempo il resto del codice è "congelato" (non sei mica in un ambiente multitask!).
Per genericità ed estensibilità. Ossia perché se in futuro vorrai fare "altro" dentro alla ISR e/o fare "altro" come consenguenza dell'evento, ne hai sempre la possibilità.
Appunto, "al momento" è la parola chiave.
Se fai un programma che sei certo che resterà sempre quello e non dovrai mai aggiungerci o modificare funzionalità va benissimo pure il delay(). Sta a te decidere lo stile di programmazione.![]()
Lo stato START non è "una tantum" come la setup(), ma ci "torna" quando ha finito di fare "il resto delle cose", quindi è utile, eccome. Sicuramente la prima cosa che devi fare quando hai questioni come questa è "disegnare" il tuo diagramma degli stati e vedere quindi come implementarlo. Puoi iniziare come ti ho accennato semplicmente scrivendolo, oppure (meglio) graficamente dove disegni ogni stato come un cerchio con all'interno un nome (il simbolo che userai nelle #define) dello stato, sotto al quale metterai gli "eventi" (ossia "se succede questo") ed un arco che rappresente la transizione (ossia "cosa fare quando si verifica l'evento") e che porta allo stato di destinazione (che può ossere un altro oppure lo stesso). Se dal disegno vedi che allo START non torni mai, allora quella parte la metti nel setup() e parti dal primo stato che la macchina deve avere.
Poi "traduci" tutto in commenti, che alla fine "traduci" aggiungendo sotto ad ogni commento il relativo codice (o, meglio, nel loop() quindi nel while metti solo le funzioni, ognuna delle quali deve fare una sola tipologia di "qualcosa", come ti ha consigliato anche Maurotec).
Detto questo, quindi, se tu hai bisogno solamente di 2 stati, ossia alla partenza del codice devi subito salvare, poi attendere l'interrupt, e quando si verifica, salvare e tornare ad attendere, non hai neanche bisogno di macchine a stati finiti (o MSF) a meno che, come dicevo, non prevedi in futuro di estendere le funzionalità e prevedere comportamenti diversi, in tal caso può valerne la pena strutturare il codice fin d'ora con gli stati.
Quindi prendendo spunto dal tuo attuale codice, se non hai necessità di una MSF puoi anche fare una cosa del genere. Nota come sono diventati setup() e loop() ;-), poi ti ho messo un po' di commenti per evidenziare le mie modifiche, leggili con attenzione (e comunque il codice NON l'ho provato, sta a te farlo!):
#include <DHT.h>
#include <DHT_U.h>
#include <ADS1115_WE.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "RTClib.h"
#include <avr/wdt.h>
#define DHTTYPE DHT22 // DHT 22
#define I2C_ADDRESS 0x48 //definisce indirizzo ADC
#define S_START 0 //stato di inizializazione
#define S_WAIT 1 //stato di attesa interupt
#define S_SAVE 2 //stato di salvataggio dati
// --- EDIT
// I simboli e costanti sempre in maiuscolo!
// Poi ti consiglio di abbreviare un poco, come per gli stati
// che hanno prefisso "S_" per i pin usa "P_"
#define P_TEMP 7 //definisce pin per il sensore di temoperatura
#define P_INT 2 //definisce pin per Allarme RTC DS3231
#define P_CS 5 //Definisce il pin per il P_CS
#define P_RELAY 3 //definisce il pin per relè malfunzionamento
// Il flag dell'ISR
volatile isISR = false;
// --- END
float voltage0 = 0;
float voltage1 = 0;
float voltage2 = 0;
float voltage3 = 0;
DHT dht(P_TEMP, DHTTYPE); //inizializza oggetto DHT
RTC_DS3231 rtc; //inizializza oggetto RTC
ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS); //inizializza ADC
// --- EDIT
// Visto che la variabile la usi in un paio di punti, mettila globale
DateTime now;
// --- END
void setup() {
Serial.begin(9600); //avvia la seriale a 9600
delay(500); // attende la stabilizazione della seriale
// --- EDIT
// Funzioni per "snellire" il codice
InitSD();
InitRTC();
InitDHT();
InitADC();
// --- END
pinMode(P_RELAY, OUTPUT);//imposto l'uscita del relay come OUTPUT
pinMode(P_INT, INPUT_PULLUP);//imposto l'uscita del Interupt ccome INPUT + Pullup
//wdt_enable(WDTO_8S); //avvia WDT 8 seocndi
// --- EDIT
// Se questo lo fai solo una volta, lo metti qui!
digitalWrite(P_RELAY, HIGH); //eccito il relè/chiudo il contatto NO
// Anche questo va (anche) qui, altrimenti non parte nulla
StartISR();
// Mio dubbio: non dovresti settare l'allarme anche qui?
ImpostaAllarme();
// --- END
}
void loop() {
if (isISR) {
now = rtc.now(); // Aggiorna data e ora attuali
StopISR();
Leggi();
Scrivi();
ImpostaAlarm();
StartISR();
}
}
void Scrivi() {
// noInterrupts(); //disabilita gli interupts per evitare strane cose durante la scrittura
DateTime now = rtc.now(); //carica in now data e ora attuali
File dataFile;
dataFile = SD.open("datalog.txt", FILE_WRITE); //apre file csv
dataFile.print(now.day(), DEC);
dataFile.print('/');
dataFile.print(now.month(), DEC);
dataFile.print('/');
dataFile.print(now.year(), DEC);
dataFile.print(" ");
dataFile.print(now.hour(), DEC);
dataFile.print(':');
dataFile.print(now.minute(), DEC);
dataFile.print(':');
dataFile.print(now.second(), DEC);
dataFile.print(" ");
dataFile.print(voltage0);
dataFile.print(" ");
dataFile.print(voltage1);
dataFile.print(" ");
dataFile.print(voltage2);
dataFile.print(" ");
dataFile.println(voltage3);
dataFile.close(); //chiude file CSV
// interrupts(); //riabilita gli interrupts per il normale funzionamento
Serial.println("scrittura file completata");
}
float readChannel(ADS1115_MUX channel) {
float voltage = 0.0;
adc.setCompareChannels(channel);
adc.startSingleMeasurement();
while (adc.isBusy()) {}
voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
return voltage;
}
void AlarmISR() {
isISR = true;
}
// --- EDIT
// Nuove funzioni
void InitSD() {
while (!SD.begin(P_CS)) //attende che la SD si inizializzi
{
Serial.println("Impossibile trovare SD, Ritento tra 5 secondi");
Serial.flush();
delay(5000);
}
Serial.println("SD Inizializata");
}
void InitRTC() {
while (!rtc.begin()) //aspetta che RTC Risponda
{
Serial.println(("Impossibile trovare DS3231, Ritento tra 5 secondi"));
Serial.flush();
delay(5000);
}
rtc.disable32K(); // disabilita uscita 32K
rtc.writeSqwPinMode(DS3231_OFF); //disattiva uscita onda quadra
rtc.clearAlarm(1);
rtc.disableAlarm(1);
rtc.disable32K(); // disabilita uscita 32K
rtc.writeSqwPinMode(DS3231_OFF);
Serial.println("RTC OK");
}
void InitDHT() {
dht.begin(); //inizializza DHT22
delay(100);
Serial.println("DHT22 OK");
}
void InitADC() {
Wire.begin();
while (!adc.init()) //aspetta che ADC Risponda
{
Serial.println(("Impossibile inizializare ADC, Ritento tra 5 secondi"));
Serial.flush();
delay(5000);
}
Serial.println("ADC Inizializato");
adc.setVoltageRange_mV(ADS1115_RANGE_6144); //setta lettura +/- 6.114v
adc.setMeasureMode(ADS1115_SINGLE); //single shot
}
void StartISR() {
isISR = false;
attachInterrupt(digitalPinToInterrupt(P_INT), AlarmISR, FALLING);
}
void StopISR() {
detachInterrupt(digitalPinToInterrupt(P_INT));
}
void Leggi() {
Serial.print("Sono in Leggi");
voltage0 = readChannel(ADS1115_COMP_0_GND); //Avvia lettura canale 0
voltage1 = readChannel(ADS1115_COMP_1_GND); //Avvia lettura canale 1
voltage2 = readChannel(ADS1115_COMP_2_GND); //Avvia lettura canale 2
voltage3 = readChannel(ADS1115_COMP_3_GND); //Avvia lettura canale 3
Serial.print("letture ADC ok");
}
void ImpostaAllarme() {
// Perché calcoli due volte l'ora successiva, una per setAlarm1 l'altra per la Serial?
// Impostala una volta e poi la usi.
// Tra l'altro non so neanche se come l'avevi impostata tu avrebbe funzionato, perché
// forse quando "now.minute()+1" vale 60 la conversione avvenga correttamente:
//DateTime next = (DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute() + 1, 0));
// Io per aggiungere un minuto farei così, prova a vedere se funziona:
DateTime next = DateTime(now.get() + 60);
rtc.setAlarm1(next, DS3231_A1_Hour);
Serial.print("Imposta allarme alle ");
Serial.print(next.hour());
Serial.print(":");
Serial.println(next.minute());
}
// --- END