Buongiorno a tutti, sto sviluppando una stazione remota che legge sensori e scatta ed invia immagini. Dato che va a batteria ho il problema di gestire i consumi e quello che ho fatto è lowpower.deepsleep + ds3231 che risveglia su 2 allarmi Alarm1 al minuto 15 e 45 tutte le ore (solo sensori) ed alarm 2 scatto foto ed invio solo alle 10:30 e 14:30 ogni giorno. Il problema che però riscontro è il risveglio che o non avviene oppure trascina uno dei due allarmi. questo è il codice per chi può darmi qualche dritta in merito.
Grazie per vs il contributo
#include <Wire.h>
#include <ArduinoLowPower.h>
#include "log.h"
#include "times_utils.h"
#include "sensors.h"
#include "display.h"
#include "camera.h"
#include "thingspeak_utils.h"
#include "common.h"
#define DS3231_INT_PIN A2
volatile bool wokeUp = false;
// ================== DEBUG ==================
#define DEBUG true
#define debugPrint(...) if (DEBUG) Serial.print(__VA_ARGS__)
#define debugPrintln(...) if (DEBUG) Serial.println(__VA_ARGS__)
// aggiungere debug mode anche per deepsleep
// ================== Watchdog ==================
#define WATCHDOG_TIMEOUT (60UL * 60UL * 1000UL) // 60 minuti = 3.600.000 ms era >> 240000UL
unsigned long lastLoopTime = 0;
unsigned long lastWatchdogCheck = 0;
void checkWatchdog() {
// 🔹 Controlla se è trascorso troppo tempo dall’ultimo aggiornamento del watchdog
// - 'millis()' restituisce i millisecondi trascorsi dall’avvio della scheda
// - 'lastWatchdogCheck' è l’ultimo momento in cui il loop è stato eseguito
// - Se la differenza supera la soglia WATCHDOG_TIMEOUT, si considera un blocco
if (millis() - lastWatchdogCheck > WATCHDOG_TIMEOUT) {
// 🔹 Legge l’orario corrente dal modulo RTC, applicando l’ora legale
// (serve per loggare nel file l’istante del reset)
DateTime now = applyDST(rtc.now());
// 🔹 Crea una stringa descrittiva del tipo:
// "Watchdog timeout: reset a 2025-11-03 17:09:53"
char msg[64];
snprintf(msg, sizeof(msg),
"Watchdog timeout: reset a %04d-%02d-%02d %02d:%02d:%02d",
now.year(), now.month(), now.day(),
now.hour(), now.minute(), now.second());
// 🔹 Stampa il messaggio sulla seriale per debug
debugPrintln(msg);
// 🔹 Scrive il messaggio nel file di log su SD (tramite logMessage)
// → utile per capire, dopo il riavvio, che il reset è stato causato dal watchdog
logMessage(msg);
// 🔹 Piccolo ritardo per permettere il completamento della scrittura su SD
// (senza questo, c’è il rischio che la MCU si resetti prima di finire il log)
// 🔸 Attendo 1 secondo prima del reset per assicurare:
// - completamento scrittura log su SD
// - flush dei buffer
// - rilascio bus SPI/I2C
delay(1000);
// 🔹 Esegue un reset hardware del microcontrollore
// - NVIC_SystemReset() è una chiamata diretta al vettore di reset ARM Cortex-M
// - Riavvia completamente la scheda come se fosse premuto il pulsante RESET
NVIC_SystemReset();
}
}
// ================== LED lampeggio ==================
void lampeggiaLED(int times, int onTime = 100, int offTime = 100) {
for (int i = 0; i < times; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(onTime);
digitalWrite(LED_BUILTIN, LOW);
if (i < times - 1) delay(offTime); // No delay dopo l'ultimo
}
}
// ================== ISR ==================
void wakeUp() { wokeUp = true; }
// ================== Funzione di reset ==================
void resetBoard(const char *reason = "Richiesta di reset manuale da seriale") {
delay(100);
Wire.begin();
DateTime now = getSafeNow();
if (now.year() < 2000 || now.year() > 2100) {
if (rtc.lostPower()) {
debugPrintln("RTC ha perso potenza, reimposto data/ora compilazione");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
now = rtc.now();
}
now = applyDST(now);
char msg[64];
snprintf(msg, sizeof(msg), "%s alle %02d:%02d:%02d",
reason, now.hour(), now.minute(), now.second());
debugPrintln(msg);
delay(500);
NVIC_SystemReset();
}
// ================== Setup ==================
void setup() {
pinMode(DS3231_INT_PIN, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
lampeggiaLED(5, 1000, 1000);
digitalWrite(LED_BUILTIN, LOW); // spengo il led base in caso rimanga acceso
#if DEBUG
Serial.begin(9600);
unsigned long startSerial = millis(); //Salva il tempo attuale (in millisecondi dall’avvio) per calcolare un timeout.
while (!Serial && millis() - startSerial < 2000); // Il ciclo resta in attesa al massimo 2000 ms (=2 secondi). Se dopo 2s la Serial non è disponibile, prosegue comunque.
// ➡️ Questo evita che lo sketch resti bloccato se il monitor seriale non è collegato.
debugPrintln("=== Avvio sistema meteo remoto ===");
debugPrintln("Comandi: SCATTA / EINK / INVIO-IOT / RESET");
#endif
Wire.begin(); // Inizializza il bus I²C (necessario per sensori come SHT30, BME280, DS3231, ecc.).
//-->> nei file .h e.cpp ✅ Inclusione (#include <Wire.h>) → sì, in tutti i file che usano l’I²C.
pinMode(EPD_CS, OUTPUT);
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(EPD_CS, HIGH);
digitalWrite(SD_CS_PIN, HIGH);
sensorsInit(); // Inizializzo i sensori
// Avvia prima SD
initCommonPins(); // Definizione dell'uso dei pins come prima cosa
selectSD(); // Disattiva il display e attiva la SD
bool sdOK = sd.begin(SD_CONFIG); // Inizializza SD
releaseSD(); // Dsiattiva SD
if (!sdOK) {
debugPrintln("⚠️ SD non trovata!");
} else {
debugPrintln("✅ SD inizializzata correttamente");
}
// Solo dopo aver diattivato la SD
selectEPD(); // Attivo il display
display.begin(); // Inizializzo il dispaly
releaseEPD(); // Disattiva il display
if (!rtc.begin()) { // Inizializzo il modulo RTC
debugPrintln("❌ RTC non trovato! Controlla collegamenti I2C");
while (1);
}
// Inizializzo la CAM
if (!cameraInit()) debugPrintln("⚠️ Camera non inizializzata! Scatto foto 📸 e upload dati 📝 potrebbero non funzionare.");
// Disattiva uscita onda quadra, pulisce eventuali allarmi pendenti
rtc.disable32K(); // uscita che non serve tenere attiva quindi la disabilitiamo
rtc.writeSqwPinMode(DS3231_OFF); // INT/SQW come interrupt
rtc.clearAlarm(1); //cancella l’eventuale stato di attivazione di Alarm1
rtc.clearAlarm(2); //cancella l’eventuale stato di attivazione di Alarm2
//DateTime now = getSafeNow(); // applico l'ora legale calcolata in time_utils.h e .cpp
DateTime now = rtc.now();
// ================== Calcolo prossimo Alarm1 (Lettura sensori + invio su thingspeak + aggiorna Eink) ==================
// Alarm1: eventi periodici ogni mezz'ora, sempre al minuto 15 o 45.
// Esempio: 08:15, 08:45, 09:15, 09:45, ecc. tutta la giornata
DateTime nextAlarm1 = nextAlarm1Time(now);
rtc.setAlarm1(nextAlarm1, DS3231_A1_Minute);
/* Qui usi la modalità DS3231_A1_Minute, che confronta fino ai minuti: “L’allarme scatterà ogni ora quando i minuti e i secondi coincidono.”
In pratica: Scatterà ogni ora, allo stesso minuto e secondo di nextAlarm1. Non considera l’ora! (quindi se metti nextAlarm1 = 08:15, scatta alle 08:15, 09:15, 10:15, ecc.) */
// ================== Calcolo prossimo Alarm2 (Lettura sensori + scatto foto + invio + aggiorna Eink)==================
// Alarm2: eventi a orari fissi della giornata.
// Orari configurati: 08:20, 11:45, 15:00, 22:30
DateTime nextAlarm2 = nextAlarm2Time(now);
rtc.setAlarm2(nextAlarm2, DS3231_A2_Minute);
LowPower.attachInterruptWakeup(DS3231_INT_PIN, wakeUp, FALLING); /// SPOSTARE NELLA GESTIONE DELLE COMMON
debugPrint("Prossimo Alarm1 (💻 DATI): "); debugPrintln(nextAlarm1.timestamp());
debugPrint("Prossimo Alarm2 (📸 FOTO): "); debugPrintln(nextAlarm2.timestamp());
lastLoopTime = millis();
}
// ================== LOOP ==================
void loop() {
checkWatchdog(); // == spento in fase di debug
lastLoopTime = millis();
// ===== Controllo comandi da Serial =====
// se apri il Monitor Serial a 9600 e scrivi SCATTA\n
if (Serial && Serial.available()) {
// Se la Serial è attiva e ci sono dati ricevuti dal PC/monitor seriale
String cmd = Serial.readStringUntil('\n'); // Legge la stringa inviata fino al carattere di newline ('\n')
cmd.trim(); // Rimuove spazi bianchi e caratteri di ritorno a capo all'inizio/fine
if (cmd.equalsIgnoreCase("SCATTA")) {
delay(100); // piccolo ritardo per stabilizzare SPI/Serial
DateTime now = getSafeNow();
SensorData d = sensorsRead();
if (!cameraInit()) {
debugPrintln("SCATTA: cameraInit fallita");
} else {
delay(150); // garantisce che la camera sia pronta
if (takeAndUpload()) {
debugPrintln("SCATTA: foto scattata e inviata");
displayUpdate(d, now, "Foto scattata e inviata");
} else {
debugPrintln("SCATTA: errore scatto/upload");
}
}
}
else if (cmd.equalsIgnoreCase("EINK")) {
debugPrintln("EINK: aggiornamento manuale display");
SensorData d = sensorsRead();
DateTime now = getSafeNow();
displayUpdate(d, now, "Aggiornamento manuale E-Ink");
}
else if (cmd.equalsIgnoreCase("INVIO-IOT")) {
debugPrintln("INVIO-IOT: invio manuale su ThingSpeak");
SensorData d = sensorsRead();
sensorsPrint(d);
uploadToThingSpeak(d);
DateTime now = applyDST(rtc.now());
displayUpdate(d, now, "Dati inviati manualmente");
logMessage("INVIO-IOT manuale completato");
}
else if (cmd.equalsIgnoreCase("RESET")) {
debugPrintln("Confermi reset? Digita SI per procedere.");
while (!Serial.available());
String confirm = Serial.readStringUntil('\n');
confirm.trim();
if (confirm.equalsIgnoreCase("SI")) {
resetBoard("Reset manuale confermato");
} else {
debugPrintln("Reset annullato.");
}
}
else {
debugPrintln("Comando non riconosciuto. Usa SCATTA / EINK / INVIO-IOT / RESET.");
}
delay(2000);
}
if (wokeUp) {
wokeUp = false;
delay(100); //stabilizza
Wire.begin(); //<-- RE-INIZIALIZZA IL BUS I2C! Al risveglio, il bus è in uno stato indefinito
DateTime now = applyDST(rtc.now()); //DateTime now = rtc.now(); // DateTime now = getSafeNow();
debugPrint("Sveglio alle: "); debugPrintln(now.timestamp());
// ⏲️ // ===== Alarm1 =====
if (rtc.alarmFired(1)) {
debugPrintln(">>> Alarm1 (💻 DATI): lettura sensori + invio a ThingSpeak");
lampeggiaLED(5, 500, 500);
SensorData d = sensorsRead();
sensorsPrint(d);
gsmConnect(); // aggiunto qui collegamento GSM
uploadToThingSpeak(d); //attenzione i pin schermo bloccano lo scketch e gli allarmi occhio alla connesione doppia gsm con invio cam
delay(150);
gsmDisconnect();
delay(1000);
releaseSD(); // Dsiattiva SD
//displayUpdate(d, now, "Dati inviati"); //funzione non usata per stampa su eink che ha refresh di 180000ms -> occhio che blocca INT PIN a LOW
selectEPD(); // Attivo il display
releaseEPD(); // Disattiva il display
char msg1[64];
snprintf(msg1, sizeof(msg1),
"Alarm1: dati inviati alle %02d:%02d:%02d",
now.hour(), now.minute(), now.second());
logMessage(msg1);
delay(100);
rtc.clearAlarm(1); // clearAllRtcAlarms(rtc); // ✅ invece di rtc.clearAlarm(1)
rtc.clearAlarm(2); // ✅ pulisci anche A2F se rimasto attivo
rtc.writeSqwPinMode(DS3231_OFF); // 🔧 Rilascia INT dopo gestione allarme
bool a1 = rtc.alarmFired(1);
bool a2 = rtc.alarmFired(2);
debugPrint("Dopo clear: A1F="); debugPrint(a1);
debugPrint(" A2F="); debugPrintln(a2);
DateTime nextAlarm1 = nextAlarm1Time(now); // ✅ Imposta prossimo Alarm1
//rtc.setAlarm1(nextAlarm1, DS3231_A1_Minute);
rtc.setAlarm1(DateTime(0, 0, 0, nextAlarm1.hour(), nextAlarm1.minute(), 0), DS3231_A1_Minute); // dichiarazione target più precisa e completa
debugPrint("Prossimo Alarm1 (💻 DATI) - Invio Dati ambientali: "); debugPrintln(nextAlarm1.timestamp());
debugPrint("INT pin dopo set: ");
debugPrintln(digitalRead(DS3231_INT_PIN) == LOW ? "LOW ⚠️" : "HIGH ✅");
// --- ⚠️ Controllo automatico INT stuck LOW ---
unsigned long startCheck = millis();
while (digitalRead(DS3231_INT_PIN) == LOW && millis() - startCheck < 2000) {
debugPrintln("INT ancora LOW... attendo rilascio");
delay(100);
}
if (digitalRead(DS3231_INT_PIN) == LOW) {
debugPrintln("⚠️ INT bloccato LOW dopo Alarm1 → reset RTC comm.");
Wire.end();
delay(100);
Wire.begin();
rtc.clearAlarm(1);
rtc.clearAlarm(2);
rtc.writeSqwPinMode(DS3231_OFF);
}
debugPrintln(" === Debug di ricontrollo ===");
debugPrint("A1F="); debugPrint(rtc.alarmFired(1));
debugPrint(" A2F="); debugPrintln(rtc.alarmFired(2));
}
// ⏲️ // ===== Alarm2 =====
if (rtc.alarmFired(2)) {
debugPrintln(">>> Alarm2 (FOTO): lettura sensori + scatto foto 📷 + upload 📤");
lampeggiaLED(10, 100, 100);
SensorData d = sensorsRead();
sensorsPrint(d);
// Nessun invio su thingspeak per evitare un doppio gsmconnect
if (takeAndUpload()) {
//displayUpdate(d, now, "Foto scattata e inviata");
logMessage("Alarm2: foto scattata e inviata");
} else {
//displayUpdate(d, now, "Errore foto/upload");
logMessage("Alarm2: errore foto/upload");
}
delay(1000); // lascia stabilizzare INT prima di gestire l’allarme
// --- ⚙️ Reset del flag e rilascio INT ---
rtc.clearAlarm(2); // Cancella flag A2F (appena scattato)
rtc.clearAlarm(1); // Cancella anche A1F nel dubbio
rtc.writeSqwPinMode(DS3231_OFF); // 🔧 Rilascia il pin INT (HIGH)
bool a1 = rtc.alarmFired(1);
bool a2 = rtc.alarmFired(2);
debugPrint("Dopo clear: A1F="); debugPrint(a1);
debugPrint(" A2F="); debugPrintln(a2);
DateTime nextAlarm2 = nextAlarm2Time(now);
rtc.setAlarm2(DateTime(0, 0, 0, nextAlarm2.hour(), nextAlarm2.minute(), 0), DS3231_A2_Minute);
debugPrint("Prossimo Alarm2 impostato a: ");
debugPrintln(nextAlarm2.timestamp());
debugPrint("Prossimo Alarm2 - Invio foto 📷: "); debugPrintln(nextAlarm2.timestamp());
// --- ⚠️ Controllo automatico INT stuck LOW ---
unsigned long startCheck = millis();
while (digitalRead(DS3231_INT_PIN) == LOW && millis() - startCheck < 2000) {
debugPrintln("INT ancora LOW... attendo rilascio");
delay(100);
}
if (digitalRead(DS3231_INT_PIN) == LOW) {
debugPrintln("⚠️ INT bloccato LOW dopo Alarm2 → reset RTC comm.");
Wire.end();
delay(100);
Wire.begin();
rtc.clearAlarm(1);
rtc.clearAlarm(2);
rtc.writeSqwPinMode(DS3231_OFF);
}
// 🔍 verifica stato INT
pinMode(DS3231_INT_PIN, INPUT_PULLUP);
for (int i = 0; i < 5 && digitalRead(DS3231_INT_PIN) == LOW; i++) {
debugPrintln("INT ancora LOW, ritento...");
delay(100);
}
debugPrint("INT pin dopo reset: ");
debugPrintln(digitalRead(DS3231_INT_PIN) == LOW ? "LOW ⚠️" : "HIGH ✅");
}
}
delay(20);
LowPower.sleep(); // attiva in produzione