L’idea nasce da un disguido

Tornare a casa e scoprire che, in nostra assenza, un guasto ha causato un blackout può trasformarsi in un problema serio: il freezer che si scongela, l’acquario senza filtro, l’impianto di allarme che si scarica. È esattamente quello che è successo a noi, e da lì è nata l’idea di costruirci un dispositivo capace di avvisarci via SMS, con un breve ritardo rispetto all’evento (più avanti spieghiamo da cosa dipende), anche mentre siamo fuori casa.

In questo progetto usiamo di nuovo il nostro modulo GSM preferito, il SIM800L. Per i dettagli sul suo funzionamento generale, sui collegamenti base e sui comandi AT più comuni, rimandiamo alla nostra guida dedicata: Guida completa al modulo GSM SIM800L.

Aggiornamento: questa è la versione 2 del progetto. Rispetto alla prima pubblicazione abbiamo corretto un errore nello schema di lettura del sensore (segnalato da alcuni lettori nei commenti — grazie!), aggiunto un orologio in tempo reale (RTC), la gestione a basso consumo con sleep mode, un fusibile di protezione in ingresso, un level shifter per la seriale del modulo GSM, un display OLED che mostra lo stato del dispositivo a colpo d’occhio e il firmware completo, prima assente. Lungo il percorso abbiamo scoperto e corretto anche due errori nostri, il risveglio dallo sleep e la lettura della tensione di batteria: li raccontiamo per intero nelle sezioni dedicate.

Perché farlo da soli

Esistono già in commercio sistemi di monitoraggio remoto e prese “smart” con notifica di mancanza rete. Costano meno tempo, ma tipicamente richiedono un abbonamento cloud, un’app proprietaria e una connessione Wi-Fi sempre attiva (che va giù insieme alla rete elettrica, in molti casi). Costruirselo da soli costa qualche serata in più, ma restituisce tre cose che una soluzione chiusa non dà: capire davvero come funziona, poterlo modificare in ogni sua parte (soglie, numeri di telefono, logica di allarme) e, non ultimo, il piacere di essersi risolti il problema con le proprie mani.

Come funziona un dispositivo di questo tipo

Un sistema di rilevamento blackout via SMS, in generale, deve occuparsi di quattro cose:

  1. Rilevare l’interruzione: restare costantemente collegato alla rete e accorgersi quando la tensione scompare.
  2. Avvisare: inviare un SMS con l’informazione dell’evento, con data e ora precise.
  3. Segnalare il ripristino: avvisare quando la corrente torna, indicando per quanto tempo è mancata.
  4. Restare acceso durante il blackout: per farlo serve una batteria tampone che si ricarica quando la rete è presente.

Esistono ovviamente soluzioni commerciali già pronte, ma la soddisfazione di costruirsene una da zero, capirne ogni componente e poterla personalizzare liberamente vale decisamente la pena.

Nella nostra versione aggiungiamo un quinto elemento, non indispensabile ma molto comodo: un piccolo display OLED che mostra, senza bisogno di collegare un computer, se la rete è presente, quanta carica ha la batteria e se il modulo GSM è agganciato alla rete cellulare.

L’hardware

Arduino Nano

Il cuore del sistema. Compatto, economico, con un numero di pin più che sufficiente per questo progetto. Usiamo la versione “commerciale” basata su ATMEGA328P, priva delle protezioni delle schede originali: va maneggiata con cura in fase di prototipazione. Utilizza il convertitore USB-seriale CH340G, che richiede l’installazione di driver dedicati.

Modulo BMS USB 5V per batteria LiPo (TC4056A)

Si occupa sia della ricarica della batteria sia della distribuzione dell’alimentazione a tutto il sistema. Riceve 5V/2A da un comune alimentatore per smartphone e, tramite il fotoaccoppiatore, permette anche di “leggere” la presenza di tensione di rete. Include protezioni da sovraccarico e cortocircuito.

Fusibile di protezione in ingresso (novità v2)

Prima del BMS abbiamo aggiunto un fusibile PTC autoripristinante da 1,1A. A differenza di un fusibile tradizionale, non va sostituito dopo un intervento: si “riarma” automaticamente una volta rimossa la causa del sovraccorrente. È una protezione economica ma importante per evitare danni al BMS e alla batteria in caso di problemi sull’alimentatore o di un cortocircuito accidentale in fase di montaggio.

Step-up MT3608

Il modulo BMS restituisce sempre la tensione della batteria (3.7V), anche quando la rete è presente: non ha uno step-up integrato. Il modulo MT3608 converte questa tensione in 5V stabili per alimentare Arduino. In uscita aggiungiamo un condensatore elettrolitico da 100µF per stabilizzare ulteriormente la tensione.

Il trimmer va tarato prima di collegare Arduino. L’uscita va regolata a 5,0V misurati con un multimetro, prima di collegare qualunque altra cosa, perché non è detto che il modulo arrivi già regolato su quel valore. Il nostro non lo era, e ce ne siamo accorti nel modo meno piacevole: da una lettura della batteria che non tornava. La storia completa è nella sezione dedicata, più avanti.

Fotoaccoppiatore PC817

Il componente più delicato del progetto. Contiene un LED e un fototransistor nello stesso package e permette di trasferire un segnale tra due circuiti senza contatto elettrico diretto, isolando fisicamente la parte a bassa tensione da eventuali disturbi lato alimentazione.

Batteria LiPo 3.7V

Alimenta il sistema durante il blackout. Va sempre gestita tramite un BMS con protezioni da sovraccarico/cortocircuito: le celle LiPo, se maltrattate, possono danneggiarsi o incendiarsi.

Modulo GSM SIM800L

Si occupa dell’invio effettivo degli SMS. In trasmissione può assorbire picchi fino a 2A: va alimentato direttamente dalla batteria (mai dal pin 5V di Arduino) e protetto con un condensatore da almeno 2200µF il più vicino possibile ai suoi pin di alimentazione. Le sue linee seriali TX/RX lavorano a 3.3V, mentre Arduino Nano è a 5V: il paragrafo successivo spiega come le colleghiamo. Per l’alimentazione, i collegamenti base e i comandi AT essenziali, consulta la nostra guida dedicata: Guida completa al modulo GSM SIM800L.

Due precisazioni sul nostro montaggio. La prima: lo sketch parla con il modulo a 9600 baud. Se il vostro SIM800L era stato lasciato configurato a una velocità diversa (il nostro vecchio test usava 57600 baud) non risponderà ai comandi, e il display mostrerà SIM800L: ASSENTE. La seconda: i pin DTR e RST del modulo non sono collegati ad Arduino, quindi non esiste uno sleep hardware del SIM800L, che resta sempre acceso e agganciato alla rete.

Modulo level shifter logico 3.3V–5V (novità v2)

Il SIM800L lavora a livelli logici 3.3V, mentre Arduino Nano è a 5V: le due linee seriali (TX e RX) vanno quindi adattate. Nella primissima bozza di questo progetto avevamo pensato a un semplice partitore resistivo sulla sola linea RX (quella che riceve dati a 5V da Arduino), lasciando la linea TX collegata direttamente. Funziona, ma è un compromesso: il partitore restituisce un livello “attenuato” invece di un vero 3.3V pulito, ed è comunque asimmetrico (una linea protetta, l’altra no).

Avendo in laboratorio un modulo level shifter logico bidirezionale (i diffusi moduli a 4 canali basati sul MOSFET BSS138 ne bastano 2), abbiamo preferito usare quello per entrambe le linee, TX e RX: restituisce livelli netti (0V/3.3V) invece di una tensione intermedia, ed è un componente pensato esattamente per questo scopo, con margini di sicurezza migliori rispetto a due resistori messi insieme all’occorrenza. Il lato HV (alta tensione) si collega alla rail 5V di Arduino, il lato LV (bassa tensione) al pin 3V3 di Arduino Nano (il regolatore di bordo eroga margine più che sufficiente per un chip di traduzione di livello), con il GND in comune a tutto il sistema.

A livello di firmware non cambia nulla: i pin D7/D8 e la comunicazione seriale software restano esattamente gli stessi, perché il level shifter è trasparente al codice — traduce solo la tensione, non il protocollo.

RTC DS3231 (novità v2)

Arduino non ha un proprio orologio persistente: se lo alimentiamo da zero non sa che ora è, e dopo un blackout prolungato perderebbe comunque il conteggio se basato solo su millis(). Il modulo DS3231, collegato via bus I2C, è un orologio hardware dedicato con una precisione molto elevata (tipicamente entro pochi secondi al mese) e una piccola batteria tampone CR2032 che gli permette di continuare a contare il tempo anche a sistema spento. Grazie a questo modulo, ogni SMS riporta ora data e ora reali dell’evento, e la durata del blackout viene calcolata con precisione. Come vedremo nella sezione software, l’ora viene anche riallineata a quella del PC a ogni nuovo caricamento dello sketch.

Display OLED SSD1306 128×64 (novità v2)

Un dispositivo che aspetta pazientemente un blackout ha un difetto: da fuori non si capisce se sta funzionando. Per questo abbiamo aggiunto un piccolo display OLED da 0,96″, con controller SSD1306, che mostra a colpo d’occhio cosa sta succedendo.

Si collega sullo stesso bus I2C del DS3231 (SDA su A4, SCL su A5, alimentazione e GND): l’indirizzo tipico del display è 0x3C, quello del DS3231 è 0x68, quindi nessun conflitto. Lo sketch verifica la presenza del display sul bus all’avvio: se manca, il monitor blackout funziona lo stesso, perché la protezione non deve dipendere da uno schermo.

Lo schermo ha 8 righe da 8 pixel, con 16 caratteri per riga. Ecco cosa mostra:

19/09/2026 16:52      data e ora (hh:mm)
Rete elettrica:
ONLINE                stato della rete (caratteri doppi) / OFFLINE
Batteria: 3.63V       lettura di A0 con il partitore
Arduino: SLEEP        ATTIVO al risveglio, SLEEP appena prima di dormire
SIM800L: ATTIVO       ATTIVO se risponde ai comandi AT, altrimenti ASSENTE
GSM: CONNESSO         da AT+CREG?: CONNESSO / RICERCA... / NO RETE / --

Qualche precisazione, perché alcune righe si prestano a equivoci. Lo stato della rete è quello confermato dal debounce, non la lettura istantanea del pin. La scritta Arduino: ATTIVO compare solo nei momenti in cui il micro è sveglio: durante lo sleep il display continua a mostrare da solo l’ultima immagine, quindi lo sketch scrive SLEEP un istante prima di addormentarsi e ATTIVO appena si sveglia. Se il SIM800L non è collegato, il display mostra ASSENTE e GSM: --. Ogni riga viene riempita di spazi e riscritta senza cancellare lo schermo, quindi non c’è sfarfallio.

Il display acceso sul prototipo: una sola occhiata per sapere se rete, batteria e modulo GSM sono a posto.

Il display acceso sul prototipo: una sola occhiata per sapere se rete, batteria e modulo GSM sono a posto.

Perché U8g2 in modalità U8x8. La libreria più diffusa per questi display, Adafruit_SSD1306, tiene in RAM un buffer dell’intero schermo: 1 KB. L’ATmega328P ha 2 KB di RAM in tutto e, con lo sketch v2 di partenza, ne restavano liberi circa 1 100 byte: il buffer non ci sta. U8g2 in modalità U8x8 scrive solo testo, direttamente sul display, senza alcun buffer. In più tutti i testi fissi restano in flash grazie alla macro F().

Il prezzo da pagare. Un OLED sempre acceso assorbe, in stima, circa 10–15 mA: un ordine di grandezza da confermare con una misura effettiva. Non è poco per un dispositivo alimentato a batteria. Il contrasto si regola con la costante OLED_CONTRASTO (più basso significa meno consumo e meno usura del pannello) e chi cerca la massima autonomia può semplicemente non montare il display.

📷 Il nostro prototipo

Il prototipo reale, montato su basetta millefori prima della sistemazione definitiva nel case. Non è perfetto — ed è giusto che si veda.

Il prototipo reale, montato su basetta millefori prima della sistemazione definitiva nel case. Non è perfetto — ed è giusto che si veda.

Distinta base (BOM)

Ecco l’elenco completo dei componenti necessari per realizzare il progetto, inclusi i valori dei componenti passivi discussi nello schema elettrico.

Nota: in qualità di Affiliato Amazon si riceve un guadagno dagli acquisti idonei effettuati tramite questi link, senza alcun costo aggiuntivo per chi acquista.

#ComponenteSpecificheQtàNoteLink Amazon
1Arduino NanoATmega328P, clone CH340G1Ricorda i driver CH340GLink Amazon
2Modulo BMS TC4056A USBRicarica LiPo 1S, protezioni OCP/OVP1Ingresso USB-C o micro-USBLink Amazon
3Step-up MT3608DC-DC boost regolabile1Tarare l’uscita a 5,0V misurati con il tester (trimmer), prima di collegare ArduinoLink Amazon
4Fotoaccoppiatore PC817Optoisolatore LED + fototransistor1Link Amazon
5Batteria LiPo 3.7VCapacità in base all’autonomia desiderata (min. 1500mAh consigliati)1Con connettore JST compatibile col BMSLink Amazon
6Modulo GSM SIM800LCon antenna1Verificare copertura 2G dell’operatore nella propria zonaLink Amazon
7Modulo RTC DS3231Con batteria tampone CR2032 inclusa1Bus I2CLink Amazon
8Display OLED 0,96″ SSD1306128×64 pixel, interfaccia I2C1Indirizzo tipico 0x3C; stesso bus I2C del DS3231. Facoltativo: il firmware funziona anche senzaLink Amazon
9Fusibile PTC autoripristinante1,1A (o fusibile rapido tradizionale + porta fusibile)1Sull’ingresso 5V, prima del BMS
10Resistore R1330 Ω, 1/4W1Serie al LED del PC817
11Resistore R3100 kΩ, 1/4W1Filtro RC su D3
12Condensatore C3100 nF ceramico1Filtro RC su D3
13Condensatore C12200 µF, 10V, elettrolitico low-ESR1Vicino ai pin di alimentazione del SIM800LLink
14Condensatore C2100 nF ceramico1Decoupling SIM800L
15Condensatore C4100 µF elettrolitico1Filtro uscita step-up
16Resistori R_alto / R_basso10 kΩ, 1/4W (x2)2Partitore monitor batteria su A0
17Modulo level shifter logico bidirezionale3.3V–5V, tipo BSS138, min. 2 canali (i moduli comuni da 4 canali vanno benissimo)1Sostituisce il partitore RX; usato su TX e RX del SIM800LLink Amazon
18Alimentatore 5V / 2ATipo caricabatteria smartphone, USB-A o USB-C1
19SIM prepagata solo voce/SMSCon PIN disabilitato1Verificare credito residuo periodicamente–
20Basetta millefori o PCB prototipazione1
21Contenitore/case per elettronicaDimensioni in base all’ingombro finale1
22Cavetteria e connettori Dupontq.b.

Motivazioni delle scelte hardware principali

Perché Arduino Nano e non ESP32. L’ESP32 offrirebbe Wi-Fi/Bluetooth integrati e potenzialmente notifiche anche via internet oltre che via SMS. Per questo specifico progetto, però, abbiamo preferito l’ATmega328P dell’Arduino Nano: la gestione del deep sleep è più semplice e ben documentata (registri Watchdog diretti, senza le complessità del deep sleep di ESP32), il consumo in power-down è estremamente prevedibile, e la logica a 5V si interfaccia più linearmente con moduli come il PC817 senza dover ragionare sui livelli a 3.3V tipici di ESP32. Chi preferisce comunque partire da ESP32 troverà spunti nella FAQ dedicata più avanti.

Perché SIM800L e non un modulo LTE/NB-IoT. Il SIM800L resta una scelta economica e ben documentata, con una community molto ampia alle spalle. Il limite più concreto è la dismissione progressiva della rete 2G da parte di alcuni operatori: va verificata la copertura 2G locale prima di partire con il progetto. Un modulo LTE Cat-M1/NB-IoT sarebbe più a prova di futuro, ma ha un costo e una complessità di programmazione superiori — un possibile sviluppo futuro più che una scelta di base.

Perché un fotoaccoppiatore e non un semplice partitore resistivo. Un partitore resistivo collegato direttamente alla tensione di rete raddrizzata funzionerebbe, ma metterebbe in comunicazione elettrica diretta la parte a bassa tensione (Arduino) con quella collegata alla rete. Il PC817 introduce un isolamento galvanico reale tra i due mondi: un requisito di sicurezza che riteniamo non negoziabile, anche in un progetto hobbistico.

Perché un level shifter dedicato e non un partitore resistivo sulla linea RX. Un partitore funziona, e nella bozza iniziale del progetto era la soluzione prevista: due resistori (10kΩ/20kΩ) bastano a riportare i 5V di Arduino a un livello che il SIM800L legge come HIGH. Il problema è che resta un compromesso: il livello in uscita non è un vero 3.3V ma un valore intermedio calcolato per stare sopra soglia, ed è comunque una soluzione asimmetrica, visto che la linea TX (dal SIM800L verso Arduino) restava collegata direttamente. Avendo a disposizione un modulo level shifter bidirezionale, lo abbiamo preferito su entrambe le linee: restituisce livelli logici netti (0V/3.3V e 0V/5V) invece di un compromesso resistivo, con un margine di sicurezza maggiore per il modulo GSM.

Perché un RTC esterno e non l’ora di rete del SIM800L. Il SIM800L può in teoria recuperare l’ora dalla rete GSM, ma richiede una sincronizzazione dipendente dalla copertura e dall’operatore, non sempre affidabile. Un RTC dedicato come il DS3231 costa poco, non dipende dalla rete cellulare ed è preciso indipendentemente da cosa succede al modulo GSM in quel momento — un requisito importante per un dispositivo che deve restare affidabile proprio quando le altre infrastrutture (elettrica, di rete) sono in difficoltà.

Perché un display, nonostante il consumo. Un dispositivo che protegge la casa mentre siamo via ha comunque bisogno di essere controllato ogni tanto: sapere a colpo d’occhio che la rete è presente, che la batteria è carica e che il modulo GSM è agganciato vale, per noi, più dei pochi milliampere in più. Se però l’autonomia in blackout è la priorità assoluta, il display è la prima cosa da togliere o da attenuare: il firmware è scritto per funzionare identico anche senza.

Lo schema elettrico: la parte che avevamo sbagliato

Nella prima versione di questo articolo diversi lettori (grazie Francesco e Mario!) ci hanno segnalato dei dubbi sul collegamento del PC817 al pin D3 di Arduino. Avevano ragione: la configurazione originale produceva sul pin digitale circa 450mV in presenza di rete — un valore che l’ATmega328P non legge come HIGH, ma come LOW (la soglia minima per HIGH a 5V logici è circa 3V). Il segnale era troppo debole per essere interpretato in modo affidabile.

La correzione, più semplice e più robusta della versione originale, sfrutta il fototransistor del PC817 in configurazione open-collector con la resistenza di pull-up interna di Arduino (INPUT_PULLUP):

  • Rete presente → LED del PC817 acceso → fototransistor in conduzione → D3 tirato a GND (LOW netto)
  • Blackout → fototransistor interdetto → pull-up interna porta D3 a 5V (HIGH netto)

A questo, nella v2, abbiamo aggiunto un piccolo filtro RC hardware (R3 100kΩ + C3 100nF) tra l’uscita del PC817 e il pin D3, con una costante di tempo di circa 10ms: smorza eventuali disturbi elettrici veloci prima ancora che arrivino ad Arduino, alleggerendo il compito del debounce software.

Schema elettrico completo (v2)

Schema elettrico completo del progetto, con tutti i valori dei componenti passivi (resistori e condensatori), realizzato con EasyEDA

Schema elettrico completo del progetto, con tutti i valori dei componenti passivi (resistori e condensatori), realizzato con EasyEDA.

Nello schema trovate:

  • posizione e valore di ogni resistore: R1 = 330Ω (limita la corrente del LED del PC817), R3 = 100kΩ (filtro RC su D3), R_alto e R_basso = 10kΩ (partitore di lettura della batteria su A0)
  • posizione e valore di ogni condensatore: C1 = 2200µF e C2 = 100nF (decoupling del SIM800L), C3 = 100nF (filtro RC su D3), C4 = 100µF (filtro all’uscita dello step-up)
  • il modulo RTC DS3231 e il display OLED SSD1306 sullo stesso bus I2C (SDA su A4, SCL su A5)
  • il modulo level shifter logico 3.3V–5V sulle linee TX/RX del SIM800L (D7/D8), con il lato HV a 5V e il lato LV sul pin 3V3 di Arduino (al posto del partitore R5/R6 inizialmente previsto)
  • il punto di alimentazione diretta del SIM800L dalla batteria, con C1 e C2 vicino ai suoi pin

Descrizione del funzionamento

  1. Misurazione: essendo un sistema alimentato a 5V, non serve un vero sensore di tensione: basta sapere se è presente o meno.
  2. Isolamento e conversione: il fotoaccoppiatore PC817 converte la presenza/assenza di tensione di rete in un segnale digitale, mantenendo la separazione fisica dei circuiti.
  3. Filtraggio: un filtro RC hardware (R3 + C3) elimina i disturbi più veloci prima ancora che il segnale arrivi ad Arduino.
  4. Debounce software basato su RTC: il nuovo stato deve rimanere stabile per alcuni secondi consecutivi, verificati tramite l’orologio RTC (non tramite millis(), che si ferma durante lo sleep — vedi sezione dedicata), prima di essere considerato un evento reale.
  5. Display: appena lo stato è confermato, il display OLED si aggiorna, prima ancora dell’invio degli SMS (che dura diversi secondi).
  6. Allarme: in caso di conferma, il microcontrollore invia un SMS tramite il modulo GSM, con data/ora precise.

Quanto ritardo c’è tra il blackout e l’SMS

L’SMS non parte nell’istante in cui manca la corrente, e non è un difetto: è il prezzo di un sistema che non si fa ingannare da un’interruzione di pochi istanti. Il ritardo ha tre origini, e nel video del collaudo è ben visibile.

  1. Il LED del PC817 non si spegne subito. Il LED del fotoaccoppiatore è alimentato dalla linea a 5V dell’alimentatore, attraverso R1. Quando la rete manca, quella tensione non cade di colpo: alimentatore e ingresso del BMS contengono tipicamente dei condensatori che la mantengono ancora per un po’, e il LED resta acceso finché la corrente non scende sotto la soglia di conduzione. Il filtro RC (R3 e C3) aggiunge solo circa 10ms. Ritardo misurato: INSERISCI-RITARDO-LED-MISURATO.
  2. Il debounce software e i risvegli del Watchdog. Quando D3 cambia stato, il Pin Change sveglia subito Arduino, ma al primo risveglio lo sketch si limita a registrare l’istante del cambio: il nuovo stato deve resistere per almeno DEBOUNCE_SEC (8 secondi) prima di essere considerato un evento reale, ed è questo che evita falsi allarmi per una micro-interruzione. La verifica successiva, però, avviene solo al risveglio seguente, e durante lo sleep quello che di norma sveglia Arduino è il Watchdog, che scatta ogni circa 8 secondi indipendentemente dal Pin Change. Se al primo controllo non sono ancora passati 8 secondi, si aspetta il colpo successivo: la conferma arriva quindi tra circa 8 e 16 secondi dopo il cambio di stato del pin (stima ricavata dal codice, non una misura). Lo stesso vale per l’SMS di ripristino.
  3. L’invio degli SMS. I messaggi partono uno dopo l’altro, con le pause di inviaSMS() e i 2 secondi tra un numero e l’altro, più il tempo di consegna della rete GSM. Il secondo destinatario riceve quindi l’SMS alcuni secondi dopo il primo.

Il ritardo complessivo è la somma di queste tre voci. Il compromesso tra rapidità e resistenza alle micro-interruzioni si regola con DEBOUNCE_SEC.

Sleep mode e gestione a interrupt (novità v2)

Uno dei limiti che avevamo segnalato nella prima versione è che step-up e Arduino restano sempre accesi, anche a rete presente. Non è un difetto risolvibile con un relè, come qualcuno ci ha proposto nei commenti: la bobina dovrebbe comunque restare sempre eccitata per “sentire” il momento esatto dell’interruzione, spostando il consumo invece di eliminarlo.

La soluzione che abbiamo implementato nella v2 agisce lato firmware:

  • Arduino resta normalmente in sleep mode profondo (SLEEP_MODE_PWR_DOWN), lo stato a più basso consumo disponibile.
  • Si risveglia in tre casi:
    1. Pin Change Interrupt sul pin D3 (PCINT19), a ogni cambio di stato della rete, in entrambe le direzioni: il micro esce subito dallo sleep, senza attendere il Watchdog. La conferma dell’evento, però, richiede più tempo (vedi «Quanto ritardo c’è tra il blackout e l’SMS» nella sezione precedente).
    2. Watchdog Timer, che lo risveglia comunque ogni circa 8 secondi, per i controlli periodici (SMS in arrivo, stato del modulo GSM, tensione batteria).
    3. Attività sulla linea RX del SIM800L (D7), gestita dalla libreria SoftwareSerial.

Va precisato che il modulo SIM800L resta comunque sempre acceso e connesso alla rete GSM, indipendentemente dallo sleep di Arduino: lo sleep agisce solo sul microcontrollore. E se il display OLED è montato, assorbe (in stima) circa 10–15 mA, molto più di un ATmega328P in power-down: lo sleep serve a non sprecare corrente, non a fare miracoli sul bilancio complessivo.

Una conseguenza tecnica di questa scelta: in SLEEP_MODE_PWR_DOWN la funzione millis() si ferma, perché si arrestano anche i timer interni. Per questo, nella v2, tutte le temporizzazioni “lunghe” (debounce, durata del blackout) sono calcolate tramite l’RTC DS3231, che continua a funzionare indipendentemente dallo stato di sleep del microcontrollore.

Per lo stesso motivo, gli SMS in arrivo non vengono più intercettati “al volo” (che richiederebbe la CPU sempre attiva), ma restano in memoria sulla SIM e vengono letti a polling ad ogni risveglio con il comando AT+CMGL="REC UNREAD" — compatibile al 100% con lo sleep, con un ritardo massimo di risposta di circa 8 secondi.

Perché non INT1: un errore nostro, scoperto in fase di verifica

Nella prima stesura della v2 usavamo attachInterrupt() su D3, che sull’Arduino Nano è INT1, in modalità CHANGE. Sulla carta tutto tornava. Nella pratica c’è un dettaglio del datasheet dell’ATmega328P che sfugge facilmente: in SLEEP_MODE_PWR_DOWN gli interrupt esterni INT0 e INT1 funzionano solo a livello LOW, perché il rilevamento dei fronti richiede il clock di I/O, che in power-down è fermo. Il risultato era che il cambio di stato della rete non svegliava il micro: a rilevarlo ci pensava il Watchdog, con un ritardo fino a circa 8 secondi rispetto a quanto avevamo promesso.

L’interrupt a livello LOW non era una strada percorribile. Con la rete presente D3 è a LOW, quindi l’interrupt scatterebbe di continuo, e non coprirebbe comunque il passaggio a HIGH, cioè proprio il blackout. La soluzione è il Pin Change Interrupt, che funziona in power-down su entrambi i fronti: abbiamo abilitato PCINT19 su D3 (gruppo PCINT2).

C’è anche un vicolo cieco che vale la pena raccontare. Il passo naturale era definire una nostra ISR(PCINT2_vect), ma lo sketch non compila: SoftwareSerial definisce già i vettori PCINT0, PCINT1 e PCINT2, e il linker segnala multiple definition of __vector_5 (verificato). Lo sketch quindi si limita ad abilitare il Pin Change su D3 e lascia scattare il vettore di SoftwareSerial, che con D3 non fa nulla di dannoso: per uscire dallo sleep basta che l’interrupt scatti. Il lavoro vero lo fa loop(), che ora dorme all’inizio e, al risveglio, esegue sempre i controlli.

[Da verificare prima della pubblicazione] Il risveglio con Pin Change è compilato ma non ancora provato sul prototipo: il risultato va aggiunto alla sezione sul collaudo.

L’attesa di 4 secondi che annullava lo sleep

Un secondo problema riguardava il polling degli SMS. La funzione controllaSMSNonLetti() aspettava sempre 3 secondi la risposta a AT+CMGL e 1 secondo quella a AT+CMGD, anche quando la risposta arrivava subito: l’Arduino restava sveglio circa 4 secondi ogni 8 e lo sleep serviva a poco.

Ora la lettura delle risposte del SIM800L termina appena la risposta è completa, cioè quando finisce con OK o ERROR e il modulo ha smesso di trasmettere da 100 ms; il timeout resta solo come limite massimo. Se non arriva nulla, nemmeno l’eco del comando, si rinuncia dopo 500 ms. E se il modulo non risponde, il ciclo salta anche la cancellazione degli SMS.

Misurare la batteria senza fidarsi dei 5V (novità v2)

Il firmware controlla ogni tanto la tensione della batteria, per avvisarci via SMS se il sistema di allarme rischia di restare senza carica, e la mostra sul display. Lo fa con un partitore 10kΩ/10kΩ collegato al pin A0, che porta all’ingresso analogico metà della tensione di batteria. Sembra la parte più banale del progetto. È stata, invece, la seconda occasione in cui un numero non tornava.

Il punto di partenza. Lo sketch convertiva la lettura di A0 assumendo che il riferimento dell’ADC fosse esattamente 5,0V (VREF = 5.0), cioè la tensione di alimentazione dell’Arduino. Un primo confronto con il tester mostrava uno scarto piccolo: 3,63V misurati contro 3,70V letti dallo sketch, circa il 2% in più. Per compensare avevamo aggiunto un fattore di correzione (0.981, cioè 3,63 / 3,70) e un offset.

Il numero che non tornava. Li abbiamo poi tolti, perché sul livello della batteria c’era un problema che la correzione non spiegava. Senza correzioni, il display leggeva 4,52V contro i 3,6V del tester: circa il 25% in più, troppo per essere un semplice errore di taratura.

La diagnosi, con il tester. Abbiamo misurato la tensione sul pin 5V del Nano:

Alimentazione di ArduinoTensione misurata sul pin 5V
USB del PC4,75V
MT3608, collegato alla batteria4,13V

Il “5V” non era 5V. E se il riferimento dell’ADC è più basso di quanto lo sketch presume, la tensione della batteria viene sovrastimata di un fattore 5,0 / Vcc: con Vcc a 4,13V sarebbe circa il 21%, dello stesso ordine del 25% che vedevamo. Il trimmer del MT3608 non era tarato come previsto dal progetto: lo abbiamo regolato a 5,0V misurati.

La correzione nel firmware. Regolare il trimmer sistemava l’hardware, ma nel firmware restava l’assunzione sbagliata. Il valore di Vcc, quindi, non viene più assunto: viene eliminato dal calcolo. A ogni lettura lo sketch misura anche il riferimento interno da 1,1V dell’ATmega328P (il bandgap), usando lo stesso riferimento AVcc. Le due letture dipendono entrambe da Vcc, ma il loro rapporto no:

V_A0 = 1,1 × lettura_A0 / lettura_bandgap
V_batteria = V_A0 × (R_alto + R_basso) / R_basso

Il ragionamento è questo: l’ADC restituisce tensione / Vcc × 1023. Per A0 otteniamo V_A0 / Vcc × 1023, per il bandgap 1,1 / Vcc × 1023. Nel rapporto tra le due, Vcc e 1023 si semplificano e resta V_A0 / 1,1. Con un calcolo di verifica, la stessa batteria (3,60V) dà lo stesso risultato con Vcc a 5,0V, a 4,75V e a 4,13V. Ogni misura è la media di 8 letture, scartando la prima dopo ogni cambio di canale.

L’alternativa scartata. analogReference(INTERNAL) usa direttamente l’1,1V come riferimento dell’ADC, ma con il nostro partitore 10kΩ/10kΩ il pin A0 arriva a circa 2,1V a batteria carica e l’ADC si bloccherebbe a fondo scala. Servirebbe un partitore diverso (per esempio 33kΩ/10kΩ): una modifica hardware che abbiamo evitato scegliendo la misura tramite bandgap. Non esiste un riferimento interno a 3,3V.

Il limite e la taratura. L’1,1V nominale può scostarsi fino a circa il 10% da chip a chip. Il nostro primo confronto, con il MT3608 a 5,0V e il riferimento nominale, dava 4,26V sul display contro 4,16V del tester: lo scarto passava dal 25% a circa il 2,4%, dentro il campo di tolleranza del bandgap e dei resistori del partitore. Per chi vuole affinare, il valore si regola nella costante VREF_INTERNO:

  1. misurare la batteria con il tester e leggere il valore mostrato dal display;
  2. calcolare VREF_INTERNO = 1,1 × V_tester / V_mostrata;
  3. riportare il risultato nello sketch e ricaricarlo.

Nel nostro caso: 1,1 × 4,16 / 4,26 = 1,074. Con questo valore la lettura del display è risultata corretta.

Il software

Il firmware completo (monitor_blackout.ino) include ora, rispetto alla prima versione:

  • Sleep mode + Pin Change Interrupt + Watchdog come descritto sopra
  • RTC DS3231 per timestamp reali negli SMS e calcolo preciso della durata del blackout, con riallineamento dell’ora a ogni nuovo upload dello sketch
  • Debounce basato su RTC, robusto anche con il microcontrollore che dorme la maggior parte del tempo
  • Display OLED con data e ora, stato della rete, tensione di batteria, stato di Arduino, del SIM800L e della sua connessione alla rete GSM
  • Comando SMS “STATO”: inviando un SMS con questo testo si riceve una risposta con stato della rete, tensione di batteria e orario
  • Controllo periodico della batteria, con lettura indipendente dalla tensione di alimentazione di Arduino e SMS di avviso in caso di tensione sotto soglia (per sapere se il sistema di allarme stesso rischia di rimanere senza carica)
  • Lettura delle risposte del SIM800L senza attese fisse, per tenere Arduino sveglio il minimo indispensabile

Richiede due librerie, entrambe installabili dal Library Manager dell’IDE Arduino: RTClib di Adafruit e U8g2 di olikraus. Lo sketch compila senza avvisi per Arduino Nano. Ecco quanto pesa:

Versione del firmwareFlashRAM globaleRAM libera
Firmware v2 di partenza15 786 B (51%)945 B (46%)1 103 B
Firmware attuale (con display OLED, stato GSM e lettura batteria tramite bandgap)21 488 B (69%)1 205 B (58%)843 B

Di seguito il dettaglio di ogni funzione, con lo snippet corrispondente, identico al file sorgente. Il file completo resta comunque disponibile come sketch scaricabile (monitor_blackout.ino): Link al file completo del progetto.

Librerie, configurazione e costanti

Librerie incluse, pin utilizzati, numeri di telefono, soglie di debounce e parametri per la lettura della batteria. È la sezione da personalizzare per prima, in particolare le due costanti NUMERO_ALLARME_1 e NUMERO_ALLARME_2. Da guardare con attenzione anche VREF_INTERNO (la taratura della lettura di batteria, spiegata nella sezione dedicata), OLED_CONTRASTO (per ridurre il consumo del display) e RTC_RITARDO_UPLOAD_SEC (la stima dei secondi tra compilazione e avvio dello sketch).



#include <SoftwareSerial.h>
#include <Wire.h>
#include <EEPROM.h>
#include <RTClib.h>
#include <U8x8lib.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

// ---------------- CONFIGURAZIONE ----------------

const uint8_t PIN_RETE = 3; // PCINT19 (gruppo PCINT2) su Arduino Nano/Uno
const uint8_t SIM800_RX = 7; // <- TX del SIM800L (via level shifter)
const uint8_t SIM800_TX = 8; // -> RX del SIM800L (via level shifter)
const uint8_t PIN_BATTERIA = A0;

// Numeri di telefono a cui inviare gli SMS di allarme (formato internazionale)
const char* NUMERO_ALLARME_1 = "+39XXXXXXXXXX";
const char* NUMERO_ALLARME_2 = "+39YYYYYYYYYY";

// Invia un SMS di conferma all'accensione del dispositivo
const bool SMS_ALL_AVVIO = true;

// Debounce: la rete deve rimanere nel nuovo stato per almeno questo
// tempo (secondi) prima di essere considerata un evento confermato.
const uint16_t DEBOUNCE_SEC = 8;

// Ogni quanti risvegli da Watchdog (~8s l'uno) controllare la batteria.
// 40 risvegli x 8s = ~5 minuti.
const uint16_t BATTERIA_OGNI_N_WAKE = 40;

const float R_ALTO = 10000.0;
const float R_BASSO = 10000.0;
// Riferimento interno (bandgap) dell'ATmega328P usato per misurare la batteria.
// Il valore nominale e' 1.1V ma da chip a chip puo' scostarsi fino a circa
// il 10%: se lo sketch e il tester non concordano, si regola questo valore
// (VREF_INTERNO = 1.1 * V_tester / V_mostrata). La tensione di Arduino
// (Vcc) invece non serve piu': viene misurata a ogni lettura.
// Taratura attuale: con 1.1 lo sketch leggeva 4.26V contro 4.16V del tester,
// quindi 1.1 * 4.16 / 4.26 = 1.074.
const float VREF_INTERNO = 1.074;
const uint8_t LETTURE_ADC = 8; // letture mediate per ogni misura, riduce il rumore
const float SOGLIA_BATTERIA_BASSA = 3.5; // volt
const float ISTERESI_BATTERIA = 0.15; // volt, evita SMS ripetuti

// Display OLED SSD1306 128x64 (I2C)
const uint8_t OLED_INDIRIZZO = 0x3C; // 0x3D se il modulo ha il ponticello spostato
const uint8_t OLED_CONTRASTO = 64; // 0-255: piu' basso = meno consumo e meno usura

// Aggiornamento RTC ad ogni upload: secondi che passano tra la compilazione
// e l'avvio dello sketch (upload + reset), sommati all'ora di compilazione.
// E' una stima: l'errore residuo e' dell'ordine di qualche decina di secondi.
const uint8_t RTC_RITARDO_UPLOAD_SEC = 10;
const int EEPROM_INDIRIZZO_COMPILAZIONE = 0; // occupa 4 byte (uint32_t)

// Valore di statoGSM quando il SIM800L non risponde (gli altri valori sono
// quelli restituiti da AT+CREG?: 0 non registrato, 1 registrato, 2 in
// ricerca, 3 negato, 5 registrato in roaming)
const uint8_t STATO_GSM_NESSUNA_RISPOSTA = 255;

Oggetti globali e variabili di stato

Gli oggetti che parlano con i moduli (SoftwareSerial per il SIM800L, RTC, display) e le variabili che tengono traccia dello stato del sistema. Solo flagWdtInterrupt è volatile, perché viene scritta da una routine di interrupt: rispetto alla versione precedente non esiste più un flag per il pin di rete, per il motivo spiegato nella sezione sullo sleep.

SoftwareSerial sim800(SIM800_RX, SIM800_TX);
RTC_DS3231 rtc;
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE);

// ---------------- VARIABILI DI STATO ----------------

volatile bool flagWdtInterrupt = false;

bool statoReteGrezzo = true;       // ultima lettura istantanea del pin
bool statoReteConfermato = true;   // true = rete presente (assunzione iniziale)
bool cambioInCorso = false;

DateTime timestampPrimoCambio;
DateTime timestampInizioBlackout;

uint16_t contatoreWake = 0;
bool batteriaBassaSegnalata = false;
bool oledPresente = false;
bool sim800Attivo = false;         // true = il SIM800L risponde ai comandi AT
uint8_t statoGSM = STATO_GSM_NESSUNA_RISPOSTA;  // registrazione alla rete GSM (AT+CREG?)

setup()

All’avvio: inizializza le seriali, il display (per primo, così può mostrare “Avvio in corso”), l’RTC con l’eventuale riallineamento all’ora di compilazione, il modulo SIM800L e il suo stato GSM. Poi legge lo stato iniziale della rete e abilita il Pin Change Interrupt su D3 scrivendo direttamente nei registri: azzera il flag eventualmente già pendente, abilita il pin nella maschera e abilita il relativo gruppo. Non definisce nessuna ISR, per il vicolo cieco con SoftwareSerial raccontato prima. In chiusura configura il Watchdog, disegna il display e, se abilitato, invia un SMS di conferma avvio.

void setup() {
  Serial.begin(9600);
  sim800.begin(9600);
  Wire.begin();

  pinMode(PIN_RETE, INPUT_PULLUP);

  inizializzaOLED();

  if (!rtc.begin()) {
    Serial.println(F("Errore: RTC DS3231 non rilevato."));
  } else {
    sincronizzaRtcAlNuovoUpload();
  }

  inizializzaSIM800();
  aggiornaStatoGSM();

  statoReteGrezzo = (digitalRead(PIN_RETE) == LOW);
  statoReteConfermato = statoReteGrezzo;

  // Pin Change Interrupt su D3: sveglia il micro da PWR_DOWN ad ogni cambio di
  // stato (sia blackout che ritorno rete). NON si definisce l'ISR: il vettore
  // PCINT2_vect e' gia' definito da SoftwareSerial (un secondo ISR darebbe
  // errore di linker per definizione multipla) e basta che l'interrupt scatti
  // per riportare il micro fuori dallo sleep. Il lavoro vero lo fa loop().
  PCIFR |= _BV(digitalPinToPCICRbit(PIN_RETE));
  *digitalPinToPCMSK(PIN_RETE) |= _BV(digitalPinToPCMSKbit(PIN_RETE));
  *digitalPinToPCICR(PIN_RETE) |= _BV(digitalPinToPCICRbit(PIN_RETE));
  impostaWatchdog();

  aggiornaDisplay();

  if (SMS_ALL_AVVIO) {
    DateTime ora = rtc.now();
    String msg = "Sistema monitor blackout avviato il ";
    msg += formattaData(ora) + ". Rete: ";
    msg += statoReteConfermato ? "presente." : "ASSENTE.";
    inviaSMSATutti(msg);
  }
}

Watchdog Timer: ISR(WDT_vect) e impostaWatchdog()

La routine di interrupt del Watchdog è volutamente minimale: si limita a impostare un flag booleano, senza fare nulla di “pesante” (niente comunicazione seriale, niente calcoli), come vuole la buona pratica per le Interrupt Service Routine. Sarà il loop() a occuparsi del lavoro vero e proprio, una volta risvegliato. impostaWatchdog() configura il Watchdog Timer al periodo massimo disponibile in singolo colpo (~8 secondi) e in modalità “solo interrupt”, cioè senza reset del microcontrollore.

ISR(WDT_vect) {
  flagWdtInterrupt = true;
}

void impostaWatchdog() {
  cli();
  wdt_reset();
  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  // ~8 secondi, modalita' solo interrupt (nessun reset del micro)
  WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);
  sei();
}

vaiInSleep()

Mette il microcontrollore nello stato di consumo più basso disponibile (SLEEP_MODE_PWR_DOWN). L’esecuzione si ferma sulla riga sleep_cpu() e riprende solo al verificarsi di uno degli interrupt configurati: il cambio di stato della rete (Pin Change), la scadenza del Watchdog o l’attività sulla linea RX del SIM800L.

void vaiInSleep() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();      // il micro si ferma qui fino al prossimo interrupt
  sleep_disable();
}

loop()

Il ciclo principale dorme all’inizio: scrive SLEEP sul display, si addormenta, e al risveglio scrive ATTIVO ed esegue sempre la stessa sequenza. Prima gestisce la rete, poi controlla gli SMS non letti e aggiorna lo stato GSM; il controllo della batteria scatta solo se il risveglio è dovuto al Watchdog e ogni BATTERIA_OGNI_N_WAKE risvegli, così un cambio di stato della rete non fa avanzare il contatore. Infine ridisegna il display.

void loop() {
  // Il display OLED resta acceso anche durante lo sleep: lo stato di Arduino
  // va scritto PRIMA di addormentarsi e riscritto appena svegli.
  oledStatoArduino(false);

  // Si esce dallo sleep per: cambio di stato su D3 (Pin Change), Watchdog
  // (~8s) o attivita' sulla linea RX del SIM800L.
  vaiInSleep();

  oledStatoArduino(true);

  bool risveglioWdt = flagWdtInterrupt;
  flagWdtInterrupt = false;
  if (risveglioWdt) contatoreWake++;

  gestisciRete();
  controllaSMSNonLetti();
  aggiornaStatoGSM();

  if (risveglioWdt && contatoreWake % BATTERIA_OGNI_N_WAKE == 0) {
    controllaBatteria();
  }

  aggiornaDisplay();
}

sincronizzaRtcAlNuovoUpload()

Porta l’orologio all’ora di compilazione dello sketch ogni volta che questo viene caricato, così l’RTC parte già con l’ora del PC senza doverla impostare a mano. Per distinguere un upload da un semplice riavvio confronta l’ora di compilazione con un marcatore salvato in EEPROM. Un rtc.adjust() incondizionato sarebbe sbagliato: a ogni riavvio (per esempio dopo un blackout in cui l’Arduino si resetta) l’ora tornerebbe indietro a quella dell’ultimo upload. L’RTC viene reimpostato anche quando il DS3231 segnala di aver perso l’alimentazione (lostPower(), per esempio con la CR2032 scarica o assente).

Due limiti da conoscere. L’errore residuo è di 10–30 secondi, perché RTC_RITARDO_UPLOAD_SEC (10 s) è solo una stima del tempo tra compilazione e avvio. Inoltre, se l’IDE non ricompila lo sketch, __TIME__ resta quello della compilazione precedente: per forzare l’aggiornamento basta modificare una riga.

// Porta l'RTC all'ora di compilazione quando lo sketch e' stato appena
// caricato. Il riconoscimento e' fatto con un marcatore (l'ora di
// compilazione) salvato in EEPROM: se coincide, questo e' un semplice
// riavvio e l'RTC NON va toccato. Altrimenti, dopo ogni riavvio (per esempio
// dopo un blackout) l'ora tornerebbe indietro a quella dell'ultimo upload.
// Se l'IDE non ricompila lo sketch, __TIME__ resta quello della compilazione
// precedente: per forzare l'aggiornamento basta modificare lo sketch.
void sincronizzaRtcAlNuovoUpload() {
  DateTime compilazione(F(__DATE__), F(__TIME__));
  uint32_t marcatoreCompilazione = compilazione.unixtime();

  uint32_t marcatoreSalvato;
  EEPROM.get(EEPROM_INDIRIZZO_COMPILAZIONE, marcatoreSalvato);

  // rtc.lostPower(): batteria tampone del modulo scarica/assente
  if (marcatoreSalvato != marcatoreCompilazione || rtc.lostPower()) {
    rtc.adjust(compilazione + TimeSpan(RTC_RITARDO_UPLOAD_SEC));
    EEPROM.put(EEPROM_INDIRIZZO_COMPILAZIONE, marcatoreCompilazione);
  }
}

gestisciRete(): debounce basato su RTC

Il cuore della logica di rilevamento. Legge lo stato istantaneo del pin, lo confronta con l’ultimo stato confermato e, se diverso, avvia (o prosegue) una finestra di debounce cronometrata tramite l’RTC. Solo se il nuovo stato persiste per almeno DEBOUNCE_SEC secondi viene considerato un evento reale: a quel punto il display si aggiorna subito, prima dell’invio degli SMS di allarme o di ripristino (quest’ultimo con la durata calcolata come differenza tra due DateTime).

void gestisciRete() {
  bool letturaAttuale = (digitalRead(PIN_RETE) == LOW); // true = rete presente
  DateTime ora = rtc.now();

  if (letturaAttuale == statoReteConfermato) {
    // stato coerente con quello confermato: nessun cambiamento in corso
    cambioInCorso = false;
    statoReteGrezzo = letturaAttuale;
    return;
  }

  if (!cambioInCorso || letturaAttuale != statoReteGrezzo) {
    // primo rilevamento del cambiamento (o "rimbalzo" nel frattempo)
    cambioInCorso = true;
    statoReteGrezzo = letturaAttuale;
    timestampPrimoCambio = ora;
    return;
  }

  // il nuovo stato persiste: verifichiamo da quanto tempo
  TimeSpan trascorso = ora - timestampPrimoCambio;
  if (trascorso.totalseconds() >= DEBOUNCE_SEC) {
    statoReteConfermato = letturaAttuale;
    cambioInCorso = false;

    // Il display si aggiorna subito, prima dell'invio degli SMS (che dura
    // diversi secondi).
    aggiornaDisplay();

    if (!statoReteConfermato) {
      // INIZIO BLACKOUT
      timestampInizioBlackout = ora;
      String msg = "ATTENZIONE: blackout rilevato alle ";
      msg += formattaData(ora);
      inviaSMSATutti(msg);
    } else {
      // RIPRISTINO
      TimeSpan durata = ora - timestampInizioBlackout;
      long minuti = durata.totalseconds() / 60;
      String msg = "Rete ripristinata alle " + formattaData(ora);
      msg += ". Durata blackout: circa " + String(minuti) + " minuti.";
      inviaSMSATutti(msg);
    }
  }
}

Piccola funzione di supporto usata per formattare le DateTime dell’RTC in un testo leggibile da inserire negli SMS e sul display:

// Formatta una DateTime dell'RTC in un testo leggibile per gli SMS
String formattaData(const DateTime& d) {
  char buf[20];
  snprintf(buf, sizeof(buf), "%02d/%02d/%04d %02d:%02d",
           d.day(), d.month(), d.year(), d.hour(), d.minute());
  return String(buf);
}

Display: oledRiga() e inizializzaOLED()

oledRiga() scrive una riga di testo riempiendola di spazi fino a 16 caratteri, così non serve cancellare lo schermo tra un aggiornamento e l’altro e non c’è sfarfallio. Esiste in due versioni: una per il testo costruito a runtime, in RAM, e una per il testo fisso F("..."), che resta in flash e non occupa RAM. inizializzaOLED() non si fida di begin(), che non verifica se il display è collegato: lo cerca lui sul bus I2C, e se non lo trova esce senza errori, lasciando il resto del firmware funzionante.

// Scrive una riga di testo (caratteri 8x8 pixel, 16 per riga, 8 righe)
// riempiendola di spazi: cosi' non serve cancellare lo schermo tra un
// aggiornamento e l'altro e non c'e' sfarfallio.
// Versione per testo in RAM (buffer costruiti a runtime)...
void oledRiga(uint8_t riga, const char* testo) {
  char buf[17];
  snprintf(buf, sizeof(buf), "%-16s", testo);
  oled.drawString(0, riga, buf);
}

// ...e per testo fisso F("..."), che resta in flash e non occupa RAM
void oledRiga(uint8_t riga, const __FlashStringHelper* testo) {
  char buf[17];
  strncpy_P(buf, (PGM_P)testo, sizeof(buf) - 1);
  buf[sizeof(buf) - 1] = '\0';
  oledRiga(riga, buf);
}

void inizializzaOLED() {
  // begin() non verifica che il display sia collegato: lo cerchiamo noi sul bus.
  Wire.beginTransmission(OLED_INDIRIZZO);
  oledPresente = (Wire.endTransmission() == 0);
  if (!oledPresente) return;

  oled.setI2CAddress(OLED_INDIRIZZO << 1);  // U8x8 vuole l'indirizzo a 8 bit
  oled.begin();
  oled.setContrast(OLED_CONTRASTO);
  oled.setFont(u8x8_font_chroma48medium8_r);
  oledRiga(3, F("Avvio in corso"));
}

Display: oledStatoArduino() e aggiornaDisplay()

oledStatoArduino() gestisce la riga “Arduino: ATTIVO/SLEEP”, che va scritta prima di addormentarsi e riscritta appena svegli, perché l’SSD1306 mostra da solo l’ultima immagine anche durante lo sleep. aggiornaDisplay() disegna l’intero schermo: data e ora (solo hh:mm, perché lo schermo si aggiorna ad ogni risveglio, circa ogni 8 secondi, e i secondi non avrebbero senso), stato della rete in doppia dimensione, tensione di batteria, stato del SIM800L e registrazione alla rete GSM.

// Stato di Arduino (riga 5). Quando il display e' aggiornato Arduino e'
// sveglio per definizione: "SLEEP" e' quello che si legge durante lo sleep,
// perche' l'SSD1306 continua a mostrare l'ultima immagine da solo.
void oledStatoArduino(bool attivo) {
  if (!oledPresente) return;
  oledRiga(5, attivo ? F("Arduino: ATTIVO") : F("Arduino: SLEEP"));
}

// Disegna l'intero schermo (8 righe da 8 pixel):
//   0     data e ora (hh:mm: lo schermo si aggiorna ad ogni risveglio, circa
//         ogni 8 secondi, quindi i secondi non avrebbero senso)
//   1-3   stato della rete monitorata dal PC817 (ONLINE/OFFLINE, doppia dimensione)
//   4     tensione di batteria
//   5     stato di Arduino
//   6     SIM800L: ATTIVO (risponde ai comandi AT) o ASSENTE (non risponde:
//         non collegato, spento o velocita' seriale diversa)
//   7     SIM800L: connessione alla rete GSM
void aggiornaDisplay() {
  if (!oledPresente) return;

  DateTime ora = rtc.now();
  char buf[17];
  char tensione[8];

  oledRiga(0, formattaData(ora).c_str());

  oledRiga(1, F("Rete elettrica:"));
  oled.draw2x2String(0, 2, statoReteConfermato ? "ONLINE " : "OFFLINE");

  dtostrf(leggiTensioneBatteria(), 4, 2, tensione);
  snprintf(buf, sizeof(buf), "Batteria: %sV", tensione);
  oledRiga(4, buf);

  oledStatoArduino(true);

  oledRiga(6, sim800Attivo ? F("SIM800L: ATTIVO") : F("SIM800L: ASSENTE"));

  switch (statoGSM) {
    case 1:                       // registrata sulla rete di casa
    case 5:                       // registrata in roaming
      oledRiga(7, F("GSM: CONNESSO"));
      break;
    case 2:
      oledRiga(7, F("GSM: RICERCA..."));
      break;
    case STATO_GSM_NESSUNA_RISPOSTA:
      oledRiga(7, F("GSM: --"));
      break;
    default:                      // 0 = non registrata, 3 = negata
      oledRiga(7, F("GSM: NO RETE"));
      break;
  }
}

inizializzaSIM800() e inviaComandoAT()

All’avvio del modulo GSM viene verificato che risponda (il risultato del primo AT alimenta lo stato ATTIVO/ASSENTE del display), impostata la modalità SMS testuale e configurata la ricezione (AT+CNMI=2,0,0,0,0) in modo che i messaggi in arrivo restino salvati in memoria SIM, pronti per essere letti a polling dal firmware, invece di essere inoltrati “al volo” sulla seriale (incompatibile con lo sleep, come spiegato sopra). inviaComandoAT() restituisce true se il modulo ha risposto OK.

void inizializzaSIM800() {
  sim800Attivo = inviaComandoAT("AT", 1000);
  inviaComandoAT("AT+CMGF=1", 1000);  // modalita' SMS testo
  // Gli SMS restano in memoria SIM: li leggeremo a polling (compatibile
  // con lo sleep, a differenza della modalita' URC "al volo").
  inviaComandoAT("AT+CNMI=2,0,0,0,0", 1000);
}

// Invia un comando AT e ne mostra la risposta sul monitor seriale.
// Restituisce true se il modulo ha risposto "OK".
bool inviaComandoAT(const char* comando, unsigned long attesaMs) {
  sim800.println(comando);
  bool ok = false;
  char precedente = 0;
  unsigned long inizio = millis();
  while (millis() - inizio < attesaMs) {
    if (sim800.available()) {
      char c = sim800.read();
      Serial.write(c);
      if (precedente == 'O' && c == 'K') ok = true;
      precedente = c;
    }
  }
  return ok;
}

leggiRegistrazioneGSM() e aggiornaStatoGSM()

Chiedono al modulo se è registrato sulla rete GSM (AT+CREG?) e restituiscono il campo stat della risposta +CREG: <n>,<stat>, oppure un valore speciale se il modulo non risponde. La lettura si ferma appena arriva OK, senza aspettare il timeout. Se il modulo non risponde ai comandi AT, aggiornaStatoGSM() non insiste.

// Chiede al modulo se e' registrato sulla rete GSM (AT+CREG?) e restituisce
// il campo "stat" della risposta "+CREG: <n>,<stat>", oppure
// STATO_GSM_NESSUNA_RISPOSTA se il modulo non risponde. Si ferma appena
// arriva "OK", senza aspettare il timeout.
uint8_t leggiRegistrazioneGSM() {
  char buf[48];
  uint8_t n = 0;
  buf[0] = '\0';

  sim800.println(F("AT+CREG?"));
  unsigned long inizio = millis();
  while (millis() - inizio < 1000 && n < sizeof(buf) - 1) {
    if (sim800.available()) {
      buf[n++] = sim800.read();
      buf[n] = '\0';
      if (strstr(buf, "OK") != NULL) break;
    }
  }

  const char* p = strstr(buf, "+CREG:");
  if (p == NULL) return STATO_GSM_NESSUNA_RISPOSTA;
  p = strchr(p, ',');
  if (p == NULL) return STATO_GSM_NESSUNA_RISPOSTA;
  return (uint8_t)atoi(p + 1);
}

void aggiornaStatoGSM() {
  // Se il modulo non risponde ai comandi AT e' inutile insistere
  statoGSM = sim800Attivo ? leggiRegistrazioneGSM() : STATO_GSM_NESSUNA_RISPOSTA;
}

inviaSMSATutti() e inviaSMS()

Gestiscono l’invio effettivo degli SMS. Il sistema è configurato per avvisare sempre due numeri di telefono (NUMERO_ALLARME_1 e NUMERO_ALLARME_2), con una breve pausa tra un invio e l’altro per non sovraccaricare il modulo GSM. L’invio vero e proprio segue la sequenza di comandi AT standard del SIM800L, terminata dal carattere Ctrl+Z (codice ASCII 26).

void inviaSMSATutti(const String& testo) {
  inviaSMS(NUMERO_ALLARME_1, testo);
  delay(2000); // pausa tra un invio e l'altro
  inviaSMS(NUMERO_ALLARME_2, testo);
}

void inviaSMS(const char* numero, const String& testo) {
  sim800.print("AT+CMGS=\"");
  sim800.print(numero);
  sim800.println("\"");
  delay(500);
  sim800.print(testo);
  delay(500);
  sim800.write(26); // Ctrl+Z
  delay(3000);
  while (sim800.available()) sim800.read();
}

controllaSMSNonLetti(), leggiRispostaSIM800(), rispostaCompleta() e rispondiConStato()

Ad ogni risveglio, invece di attendere un messaggio in arrivo in tempo reale, il firmware interroga attivamente la SIM chiedendo i messaggi non letti (AT+CMGL="REC UNREAD"). Se la risposta termina con OK il modulo è vivo; se tra i messaggi compare il testo “STATO”, risponde con un riepilogo (stato rete, tensione batteria, orario); infine ripulisce la memoria della SIM per evitare che si riempia. leggiRispostaSIM800() è la funzione che ha eliminato le attese fisse: torna appena la risposta è completa, e il timeout resta solo come limite massimo.

void controllaSMSNonLetti() {
  sim800.println(F("AT+CMGL=\"REC UNREAD\""));
  String risposta = leggiRispostaSIM800(3000);

  // Se il modulo e' vivo la risposta termina sempre con "OK"
  sim800Attivo = (risposta.indexOf("OK") != -1);
  if (!sim800Attivo) return;  // niente da leggere ne' da cancellare

  if (risposta.indexOf("STATO") != -1) {
    rispondiConStato();
  }

  // Pulisce la memoria SIM (letti e non letti) per evitare di riempirla
  sim800.println(F("AT+CMGD=1,4"));
  leggiRispostaSIM800(1000);
}
// Legge la risposta del SIM800L e torna appena e' completa, senza aspettare
// il timeout (che resta come limite massimo). E' completa quando termina con
// "OK" o "ERROR" e il modulo ha smesso di trasmettere. Se non arriva nulla,
// nemmeno l'eco del comando, si rinuncia dopo ATTESA_PRIMA_RISPOSTA_MS.
String leggiRispostaSIM800(unsigned long timeoutMs) {
  const unsigned long ATTESA_PRIMA_RISPOSTA_MS = 500;
  const unsigned long PAUSA_FINE_RISPOSTA_MS = 100;

  String buffer = "";
  unsigned long inizio = millis();
  unsigned long ultimoCarattere = inizio;
  while (millis() - inizio < timeoutMs) {
    while (sim800.available()) {
      buffer += (char)sim800.read();
      ultimoCarattere = millis();
    }
    if (buffer.length() == 0) {
      if (millis() - inizio >= ATTESA_PRIMA_RISPOSTA_MS) break;
    } else if (millis() - ultimoCarattere >= PAUSA_FINE_RISPOSTA_MS &&
               rispostaCompleta(buffer)) {
      break;
    }
  }
  return buffer;
}

bool rispostaCompleta(const String& s) {
  const char* c = s.c_str();
  size_t n = s.length();
  return (n >= 4 && strcmp(c + n - 4, "OK\r\n") == 0) ||
         (n >= 7 && strcmp(c + n - 7, "ERROR\r\n") == 0);
}
void rispondiConStato() {
  DateTime ora = rtc.now();
  String msg = "Stato sistema (" + formattaData(ora) + "):\n";
  msg += statoReteConfermato ? "Rete: presente\n" : "Rete: ASSENTE\n";
  msg += "Batteria: " + String(leggiTensioneBatteria(), 2) + "V";
  inviaSMSATutti(msg);
}

controllaBatteria()

Ogni BATTERIA_OGNI_N_WAKE risvegli da Watchdog (circa ogni 5 minuti, con il Watchdog a 8 secondi), il firmware legge la tensione di batteria e invia un SMS di avviso se scende sotto soglia — con un margine di isteresi per evitare di inviare SMS ripetuti a ridosso della soglia.

void controllaBatteria() {
  float v = leggiTensioneBatteria();

  if (v < SOGLIA_BATTERIA_BASSA && !batteriaBassaSegnalata) {
    batteriaBassaSegnalata = true;
    DateTime ora = rtc.now();
    String msg = "Attenzione: batteria del sistema di monitoraggio scarica (";
    msg += String(v, 2) + "V) - " + formattaData(ora);
    inviaSMSATutti(msg);
  } else if (v > (SOGLIA_BATTERIA_BASSA + ISTERESI_BATTERIA)) {
    batteriaBassaSegnalata = false;
  }
}

leggiAdcBatteria(), leggiAdcBandgap() e leggiTensioneBatteria()

Le tre funzioni che implementano la lettura descritta nella sezione dedicata. Le prime due fanno la media di più letture: una del pin A0, una del riferimento interno da 1,1V, entrambe con AVcc come riferimento dell’ADC (dopo aver scartato la prima, perché il segnale ha bisogno di stabilizzarsi). leggiTensioneBatteria() le combina in modo che la Vcc si semplifichi, e applica il rapporto del partitore.

// Lettura media di A0 (con AVcc come riferimento dell'ADC)
float leggiAdcBatteria() {
  analogRead(PIN_BATTERIA);  // la prima lettura dopo un cambio di canale si scarta
  uint32_t somma = 0;
  for (uint8_t i = 0; i < LETTURE_ADC; i++) {
    somma += analogRead(PIN_BATTERIA);
  }
  return somma / (float)LETTURE_ADC;
}

// Lettura media del riferimento interno da 1.1V, sempre con AVcc come
// riferimento dell'ADC: piu' Vcc e' bassa, piu' il valore letto e' alto
// (5.0V -> circa 225, 4.0V -> circa 281).
float leggiAdcBandgap() {
  // AVcc come riferimento, canale interno 1.1V (MUX3..0 = 1110).
  // analogRead() reimposta ADMUX ad ogni chiamata, quindi non serve ripristinarlo.
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2);  // il bandgap impiega circa 1 ms a stabilizzarsi dopo la selezione

  uint32_t somma = 0;
  for (uint8_t i = 0; i <= LETTURE_ADC; i++) {
    ADCSRA |= _BV(ADSC);
    while (ADCSRA & _BV(ADSC)) { }
    if (i > 0) somma += ADC;  // la prima conversione si scarta
  }
  return somma / (float)LETTURE_ADC;
}

// Tensione di batteria in volt. Le due letture usano lo stesso riferimento
// (AVcc), quindi la Vcc si semplifica:
//   V_A0 = 1.1 * lettura_A0 / lettura_bandgap
// e il risultato non dipende da quanto vale l'alimentazione di Arduino.
float leggiTensioneBatteria() {
  float lettBatteria = leggiAdcBatteria();
  float lettBandgap = leggiAdcBandgap();
  float vA0 = VREF_INTERNO * lettBatteria / lettBandgap;
  return vA0 * (R_ALTO + R_BASSO) / R_BASSO;
}

Possibili sviluppi futuri

  • Doppio SIM800L o modulo NB-IoT per ridondanza in zone con copertura GSM debole.
  • Sensore di temperatura (es. DS18B20) per usi come il monitoraggio di frigoriferi/freezer, oltre alla semplice mancanza rete.
  • Log su SD card degli eventi, per un’analisi storica delle interruzioni.
  • Sleep hardware del SIM800L, collegando i pin DTR e RST oggi non usati, per ridurre il consumo del modulo che è la voce più pesante del bilancio.
  • Display che si spegne da solo dopo un certo tempo, o su richiesta, per recuperare i 10–15 mA (in stima) che assorbe sempre acceso.

Disclaimer

Questo articolo ha scopo divulgativo e hobbistico. Il progetto descritto prevede il collegamento, seppur tramite isolamento galvanico (fotoaccoppiatore), a un circuito derivato dalla rete elettrica a 230V, oltre alla gestione di batterie LiPo: entrambi elementi che, se maneggiati in modo scorretto, possono comportare rischi per cose e persone (scosse elettriche, cortocircuiti, incendi).

La realizzazione del progetto è a totale rischio e responsabilità di chi la effettua. L’autore e officinasottocasa.it non si assumono alcuna responsabilità per danni diretti o indiretti a persone, animali o cose derivanti dalla costruzione, installazione o utilizzo di quanto descritto in questo articolo, né garantiscono l’idoneità del progetto per uno specifico scopo, incluso l’uso in contesti professionali o commerciali. Si raccomanda di operare solo se in possesso delle competenze tecniche necessarie e, in caso di dubbio, di rivolgersi a un elettricista qualificato.

Per le condizioni complete relative all’utilizzo dei contenuti di questo sito si rimanda alla pagina Termini e Condizioni di utilizzo.

Una nota per chi vuole usarlo in ambito professionale

Se l’obiettivo è proteggere, ad esempio, la catena del freddo in un’attività commerciale, è importante ricordare che questo resta un progetto DIY hobbistico. Applicazioni con requisiti normativi stringenti richiedono generalmente soluzioni certificate: meglio valutare con attenzione se un sistema fai-da-te sia davvero adatto al contesto d’uso.

Il collaudo reale

Uno schema che funziona sulla carta e un circuito che funziona davvero sono due cose diverse. Ecco cosa abbiamo verificato materialmente sul nostro prototipo, prima di considerare il progetto concluso.

🎥 Il test del blackout simulato

Guarda il video: stacchiamo la spina e cronometriamo l’arrivo dell’SMS

Abbiamo staccato fisicamente la spina dell’alimentatore e cronometrato il tempo di risposta reale del sistema, dalla disconnessione all’arrivo dell’SMS sul telefono.

📱 L’SMS ricevuto davvero

Mostra immagine

Chiusura

Quello che portate a casa da questo articolo non è solo un progetto da replicare: è anche un esempio di come un dettaglio tecnico apparentemente marginale — una soglia logica sbagliata di poche centinaia di millivolt — possa passare inosservato finché qualcuno non lo mette davvero alla prova. E non è stato l’unico. Un interrupt che sulla carta svegliava il micro e in power-down no, un “5V” che erano 4,13V: sono tutti dettagli che, a prima vista, sembravano a posto. È il motivo per cui continuiamo a chiedervi di segnalarci errori e dubbi nei commenti: ci hanno già permesso di migliorare sensibilmente questo progetto, e siamo certi che potranno farlo ancora.

Vi è piaciuto questo articolo? Condividete la vostra opinione nei commenti, segnalateci errori o miglioramenti e, se avete un progetto in mente ma non sapete da dove iniziare, contattateci: potrebbe diventare il prossimo articolo del blog.

Sostieni Officina Sottocasa

Articoli come questo — schema corretto, firmware completo e commentato, ore passate a misurare davvero i valori invece di stimarli — richiedono tempo, e restano gratuiti per chiunque voglia costruirsi il proprio progetto. Se il nostro lavoro vi è stato utile e volete aiutarci a continuare a produrre contenuti tecnici approfonditi e gratuiti, potete sostenerci su Tipeee: Sostieni Officina Sottocasa su Tipeee. Ogni contributo, anche piccolo, fa la differenza.

FAQ

Quanto dura la batteria durante un blackout?

Dipende dalla capacità della batteria scelta e dai consumi reali del sistema: il SIM800L (sempre acceso, con picchi durante l’invio degli SMS), il display OLED se montato (in stima circa 10–15 mA) e l’Arduino, che in sleep consuma pochissimo. Non abbiamo ancora un’autonomia misurata da dichiarare: la troverete nella sezione sul collaudo. Per allungarla si può abbassare OLED_CONTRASTO o non montare il display, e va comunque sempre verificata sul proprio sistema con un test pratico.

Il SIM800L funziona con qualsiasi SIM?

Serve una SIM che supporti la rete 2G/GSM. Alcuni operatori in Italia stanno progressivamente dismettendo il 2G a favore di 4G/5G: prima di procedere è bene verificare la copertura 2G del proprio operatore nella propria zona.

Posso usare un ESP32 al posto di Arduino Nano?

Sì, è possibile, con il vantaggio di avere Wi-Fi/Bluetooth integrati e, volendo, la possibilità di inviare notifiche anche via internet oltre che via SMS. Richiede però di rivedere la gestione dell’alimentazione e del deep sleep, che su ESP32 funziona in modo diverso rispetto ad ATmega328P.

Come faccio a sapere se un SMS è stato inviato correttamente?

Il modulo SIM800L risponde ai comandi AT con un codice di stato che si può leggere via seriale durante lo sviluppo (monitor seriale dell’IDE Arduino). Per un uso più avanzato si può implementare un controllo dell’esito dell’invio nel firmware.

Il dispositivo funziona anche se il router/modem di casa va giù?

Sì: a differenza di soluzioni basate su Wi-Fi o su app che richiedono una connessione internet attiva in casa, questo sistema comunica tramite rete cellulare GSM, indipendente dalla connessione internet domestica.

A quanti numeri di telefono vengono inviati gli SMS di allarme?

Il sistema, così configurato, avvisa due numeri di telefono ad ogni evento (blackout, ripristino, batteria scarica, risposta al comando “STATO”): NUMERO_ALLARME_1 e NUMERO_ALLARME_2 nel firmware. Per aggiungerne altri basta introdurre nuove costanti seguendo lo stesso schema e richiamare inviaSMS() anche per i nuovi numeri all’interno di inviaSMSATutti().

Come imposto l’ora sull’RTC DS3231?

Non serve impostarla a mano: a ogni nuovo caricamento dello sketch il firmware riallinea l’RTC all’ora di compilazione (più una stima di 10 secondi per compilazione e upload), con un errore residuo di 10–30 secondi. Un semplice riavvio, per esempio dopo un blackout, non tocca l’ora: la mantiene la batteria CR2032 integrata nel modulo. Se l’IDE non ricompila lo sketch, basta modificare una riga per forzare l’aggiornamento.

Come faccio a tarare la lettura della tensione di batteria?

Misurate la batteria con un tester e confrontate il valore con quello mostrato dal display. Poi calcolate VREF_INTERNO = 1,1 × V_tester / V_mostrata, riportate il risultato nella costante omonima dello sketch e ricaricatelo. Il riferimento interno da 1,1V può scostarsi fino a circa il 10% da chip a chip, quindi la taratura va rifatta per ogni esemplare.

È sicuro lasciare una batteria LiPo sempre in carica?

Sì, a patto di usare un BMS con le protezioni adeguate (come il TC4056A impiegato in questo progetto), che gestisce correttamente la carica di mantenimento evitando sovraccarichi.

Serve una SIM con piano dati o basta voce/SMS?

Per questo progetto basta una SIM che supporti l’invio di SMS: non è necessario un piano dati.

Categories: Elettronica

6 Comments

Salvatore · 28/09/2025 at 10:52

Salve Alessandro. Ho cercato in rete qualcosa che corrisponde al tuo progetto, ed ho trovato appunto questo progetto, adesso lo ho quasi finito di costruire ma mi sono accorto che non ce lo sketch, mi potresti dire per favore dove trovarlo/scaricarlo? Mi sono cimentato subito alla costruzione dell hardware ma non ho guardato se c era anche il programma,comunque te ne sarei molto grato! Tanti saluti e buon proseguimento!!

    Alessandro · 25/09/2026 at 21:55

    Ciao Salvatore. Abbiamo aggiornato il progetto e l’intero articolo con importanti novità e ora è disponibile tutto il software

Mario · 30/07/2025 at 18:44

Salve Alessandro, inizio con il precisare che non ho studiato informatica ne elettronica e che è solo un hobby!
Mi chiamo Mario, ho 61 anni e di “mestiere” faccio il musicista!
Chiarito di essere il classico “praticone”, inizio!
Una mia amica, ha il problema dei possibili black out nel suo negozio di generi alimentari quindi con congelatori e frigoriferi accesi h24.
Partendo dal presupposto che, costruire in proprio, se funziona, da più soddisfazioni, ho iniziato a cercare sul web un sistema che riuscisse a capire
l’avvenuto black out e tramite un controllo, potesse fare una classica telefonata, non SMS, ad una serie di numeri per avvertire del problema e mi sono imbattuto nel tuo schema.

https://www.officinasottocasa.it/come-monitorare-blackout-della-rete-elettrica-con-arduino-e-un-sim800l/

Sono in attesa che arrivi tutto il materiale ma ho preferito acquistare come modulo GSM, un SIM 900A.
Ora, non ho trovato nella pagina linkata, lo skech, quindi, mi chiedevo se fosse possibile poterlo recuperare in qualche modo.
Poi, leggendo lo schema, mi sono venuti dei dubbi amletici!!!!
Il primo, è già stato sollevato e mi riferisco ad R1 e al GND.
Altra cosa. Il modulo TC4056A, fa uscire sempre e a prescindere dallo stato, tensione dal pin 3 che quindi tiene sempre acceso lo step UP e di conseguenza, il modulo Arduino, giusto?
Mi chiedo, se ci fosse un modo per evitare di alimentare sempre questi moduli e farli accendere solo al momento del Black out. Il più semplice sarebbe un relè ma che dovrebbe avere la bobina sempre sotto tensione per commutare solo nel momento del Black Out e alimentare i moduli tramite l’out di TC4056A, ma ho paura a tenere sotto tensione la bobina!!!
Ancora, mi sembra di capire, che il circuito funzioni con un segnale che tramite l’optoisolatore CYPC817, manda verso D3 quando V1 non è alimentato, giusto?
Scusa per i mie quesiti magari un po stupidi e ringraziandoti anticipatamente,
cordialmente
Mario
P.S. Naturalmente non è nelle mie intenzioni il commercializzare nulla, ma solo la soddisfazione di riuscire a risolvere il problema alla mia amica!
Grazie!!!

    Alessandro · 22/09/2025 at 22:07

    Ciao Mario, andiamo con ordine.
    In primis, il negozio della tua amica vende generi alimentari e deve garantire la catena del freddo e la corretta conservazione. Credo che debba rispettare specifiche norme di legge, quindi è possibile che una soluzione DIY non sia quella più appropriata per la tua amica.
    Ti confermo che in questo caso lo step-up e Arduino sono sempre accesi. Effettivamente con un relè si potrebbe accendere tutto solo all’occorrenza. Non ho una risposta al momento ma ci si può ragionare, anche con il contributo dei nostri lettori. Certo, qualcosa sotto tensione ci sarà sempre. Ma si accettano consigli.
    In quanto all’ultimo punto hai capito bene: il cuore del progetto è l’optoaccoppiatore che di fatto invia il segnala per attivare la chiamata.
    Relativamente alla commercializzazione, niente paura, non è il nostro business. In commercio ci sono già soluzioni pronte all’uso domestiche/industriali certificate. Qualunque allarme casalingo, anche quelli da pochi euro venduti su amazon, rilevano l’assenza rete e chiamano i numeri di telefono memorizzati…

Francesco · 22/03/2025 at 09:40

Ciao Alessandro.
Progetto interessante e ben esposto.
Un piccolo dubbio: come fa a funzionare se il pin D3 di arduino è sempre a massa?
Credo che ci sia stata una piccola svista nel disegno dello schematico. Il pin D3 dovrebbe andare direttamente sul pin 3 del PC817 e da li R1 andare a massa.
Chi non ha il fotoaccoppiatore può utilizzare direttamente un transistor opportunamente polarizzato.
E’ così?

    Alessandro · 09/04/2025 at 21:57

    Ciao Francesco, grazie per la segnalazione. Sto verificando lo schema per capire se c’è un refuso. Anche se, come vedi nell’articolo, la simulazione funziona con quella configurazione.
    Per quanto riguarda l’utilizzo di un transistor opportunamente polarizzato al posto del fotoaccoppiatore direi che è possibile. In fondo il fotoaccoppiatore è costituito da un fototransistor. Io ho optato per un fotoaccoppiatore per il vantaggio della separazione dei circuiti. Ho trovato un articolo sul forum arduino in cui si parla esattamente della sostituzione di un optoisolatore con un transistor: https://forum.arduino.cc/t/transistor-o-fotoaccoppiatori/50638/4.
    Torna a trovarci presto e contattaci se ha idee su cui possiamo dare un contributo.
    Grazie.

Lascia un commento

Avatar placeholder

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *