In questo tutorial imparerai come usare le sveglie con il Real-Time-Clock (RTC) DS3231 e un Arduino.
Il modulo DS3231 è un orologio esterno. Ha una propria batteria e tiene traccia dell’ora e della data corrente anche quando l’alimentazione principale è spenta. Inoltre dispone di due registri di allarme che puoi impostare per attivare sveglie a orari e date specifici.
Un Real-Time-Clock è particolarmente utile quando vuoi far dormire l’Arduino per lunghi periodi. La durata massima di sleep per un Arduino Uno è di 8 secondi. Ma con un RTC puoi avere durate di sleep arbitrariamente lunghe, ottimo per estendere la durata della batteria di data logger, per esempio.
Componenti necessari
Per questo progetto ti serviranno un modulo DS3231 RTC e un Arduino. Io ho usato un Arduino Uno ma qualsiasi altro Arduino andrà bene. Infine, in uno degli esempi di codice di questo tutorial, uso un buzzer passivo per suonare un allarme.

Modulo DS3231 RTC

Arduino Uno

Cavo USB per Arduino UNO

Buzzer passivo

Set di fili Dupont

Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Nozioni di base sul modulo DS3231 RTC
Il modulo DS3231 RTC è un Real-Time-Clock preciso basato su DS3231 Chip. Oltre al DS3231 ha una AT24C32 EEPROM e un alloggiamento per una CR2032 batteria.
Grazie alla batteria il DS3231 mantiene l’ora precisa anche se l’Arduino perde alimentazione, si riavvia o va in modalità sleep. L’immagine sotto mostra il fronte e il retro di un tipico modulo DS3231 RTC:

Se ti servono più dettagli sul modulo DS3231 RTC dai un’occhiata al nostro Arduino and RTC Module DS3231 tutorial.
Pinout del modulo DS3231 RTC
Il pinout del modulo DS3231 RTC è il seguente: VCC e GND sono per l’alimentazione, con una tensione di alimentazione da 3.3 a 5.5V. SCL e SDA sono i pin per l’interfaccia I2C. 32K e SQW sono uscite per il segnale di clock a 32kHz e un segnale a onda quadra programmabile. L’uscita SQW può anche essere configurata come uscita di segnale di interrupt, che useremo più avanti.

Anche se il modulo DS3231 RTC funziona a 5V, dovresti usare 3.3V come alimentazione se hai inserito una batteria CR2032. Il modulo ha un circuito di carica molto semplice che danneggerebbe la batteria se alimentato a 5V. Per maggiori informazioni leggi il Arduino and RTC Module DS3231 tutorial e l’Battery charging circuit of DS3231 module articolo.
Collegare il modulo DS3231 RTC all’Arduino
Collegare il modulo DS3231 RTC a un Arduino è semplice. Prima collega i pin SCL e SDA del modulo DS3231 RTC ai corrispondenti pin sull’Arduino come mostrato sotto. Poi collega il GND a massa e il 3.3V a VCC del modulo DS3231 RTC e il gioco è fatto.

Ricorda di alimentare il modulo DS3231 RTC a 3.3V, altrimenti la batteria CR2032 inserita potrebbe danneggiarsi.
Codice di test semplice per modulo DS3231 RTC
Dovresti usare una libreria software per comunicare con il modulo DS3231 RTC. Ce ne sono diverse, ma qui uso la libreria Arduino-DS3231 di Korneliusz Jarzębski. Per installarla vai su github repo e clicca il pulsante verde “Code”. Poi seleziona “Download Zip” dal menu come mostrato sotto:

Questo scaricherà un file chiamato “Arduino-DS3231-dev.zip” sul tuo computer. Per installare questa libreria ZIP segui i passaggi usuali. Clicca su Sketch -> Add .ZIP Library, poi seleziona il file Arduino-DS3231-dev.zip appena scaricato.

Con la libreria installata, puoi testare il funzionamento del modulo DS3231 RTC. Il seguente esempio di codice semplice imposta l’ora e la data nel modulo RTC al momento di compilazione dello sketch e poi stampa ora e data in un ciclo:
#include "Wire.h"
#include "DS3231.h"
DS3231 rtc;
void setup() {
Serial.begin(9600);
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
}
void loop() {
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt));
delay(1000);
}
Il codice inizia includendo la libreria Wire.h per la comunicazione I2C e la libreria DS3231.h per comunicare con il DS3231 RTC.
Poi creiamo un’istanza della classe DS3231. Nella funzione setup() inizializziamo l’RTC e impostiamo la data e ora correnti usando rtc.setDateTime(). Le macro __DATE__ e __TIME__ inseriscono automaticamente la data e l’ora in cui lo sketch è stato compilato. Nota che l’ora non sarà aggiornata se l’Arduino viene riavviato dopo la compilazione e il caricamento del codice.
Nella funzione loop recuperiamo data e ora dall’RTC usando RTCDateTime dt = rtc.getDateTime(), formattiamo data e ora come stringa e la stampiamo sul Serial Monitor con Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt)). Il formato specificato qui è day-month-year hours:minutes:seconds. Vedi il DS3231_dateformat.ino file della libreria Arduino-DS3231 per altri esempi di formattazione.
Infine, introduciamo un ritardo di 1 secondo, per evitare che il Serial Monitor venga inondato di messaggi.
Esempio di output
Se carichi il codice e apri il Serial Monitor, dovresti vedere la data e l’ora stampate così:

Dopo aver verificato che il DS3231 è cablato correttamente e funziona, parliamo della funzione di allarme del DS3231.
Registri di allarme del DS3231
Il modulo DS3231 RTC ha due registri di allarme: Allarme 1 e Allarme 2. Questi due allarmi possono attivare eventi basati su condizioni temporali specifiche. La tabella seguente mostra le impostazioni dei bit per queste condizioni:

Come vedi, l’Allarme 1 offre più opzioni di configurazione. Puoi impostarlo per attivarsi in base a secondi, minuti, ore e giorno/data. L’Allarme 2 permette solo di impostare minuti, ore e giorno/data. Inoltre l’Allarme 1 può essere configurato per attivarsi ogni minuto, giornalmente o settimanalmente, mentre l’Allarme 2 può attivarsi solo una volta o ogni minuto.
Tuttavia, nessuno dei due allarmi permette di attivare un allarme periodico se non corrisponde a un’unità base. Per esempio, puoi attivare un allarme ogni minuto, o al decimo secondo di ogni minuto, ma non puoi attivare un allarme ogni 10 secondi usando i registri di allarme. Però ti mostrerò come aggirare questa limitazione.
Esempi di codice per allarmi con DS3231
In questa sezione discuteremo vari esempi di codice per attivare o usare gli allarmi.
Attivare eventi senza usare i registri di allarme DS3231
Iniziamo con un esempio che in realtà non usa il registro di allarme. Invece interroghiamo continuamente il DS3231 e controlliamo ora e data. Se soddisfano certe condizioni, eseguiamo un’azione. Il seguente esempio stampa l’ora corrente ogni 10 secondi:
#include "Wire.h"
#include "DS3231.h"
DS3231 rtc;
void setup() {
Serial.begin(9600);
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
}
void loop() {
RTCDateTime dt = rtc.getDateTime();
if (dt.second % 10 == 0) {
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
}
La linea critica è la seguente, dove controlliamo se i secondi sono divisibili per 10 usando l’operatore modulo (%):
if (dt.second % 10 == 0)
Se è così stampiamo l’ora. Nota che devi aggiungere un ritardo di almeno 1 secondo (delay(1000)), altrimenti l’ora viene stampata più volte, dato che il loop gira più veloce di 1 secondo.
Il vantaggio di attivare allarmi in questo modo è che non sei limitato alle condizioni di attivazione supportate dai registri di allarme. Per esempio, se vuoi stampare l’ora ogni 10 secondi ma solo nei weekend (dt.dayOfWeek >= 6), puoi scrivere la seguente condizione:
if ((dt.dayOfWeek >= 6) && (dt.second % 10 == 0)) {
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
La classe RTCDateTime struct della libreria Arduino-DS3231 ha i seguenti membri per data e ora che puoi usare per implementare la tua condizione di attivazione:
struct RTCDateTime {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t dayOfWeek;
uint32_t unixtime;
};
Anche se questo ti dà completa libertà, implementare correttamente le condizioni di attivazione può essere complicato e non puoi usare gli interrupt in questo modo. Ne parleremo più avanti.
Nella prossima sezione ti mostro come usare i registri di allarme per attivare eventi. Le condizioni sono più limitate ma il codice sarà più breve e robusto.
Attivare eventi usando i registri di allarme DS3231
Il seguente esempio stampa l’ora corrente dell’RTC ogni ora usando l’Allarme 2:
#include "Wire.h"
#include "DS3231.h"
DS3231 rtc;
void setup() {
Serial.begin(9600);
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M);
}
void loop() {
if (rtc.isAlarm2()) { // also clears alarm
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
}
}
Ci sono due nuove righe di codice. Prima impostiamo la condizione di attivazione per l’Allarme 2 nella funzione setup tramite
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M);
Questa condizione scatta quando i minuti sono 0 (DS3231_MATCH_M), cioè una volta ogni ora.
Poi controlliamo se la condizione è stata attivata per l’Allarme 2 nella funzione loop tramite
if (rtc.isAlarm2())
Questo è più breve e robusto rispetto a controllare la condizione da soli. Per esempio, dovresti scrivere questo codice per attivare ogni minuto, se controlli tu stesso i campi di ora e data:
if ((rtc.minute==0) & (rec.second==0)) {
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
Usando la funzione isAlarm2() la condizione diventa più semplice e non serve più il delay(), dato che isAlarm2() cancella l’allarme una volta attivato. Puoi disattivare questo comportamento chiamando isAlarm2(false) invece.
Corrispondenza di ora e data
Le condizioni di attivazione si definiscono specificando quali parti di ora e data devono corrispondere a valori dati. Per esempio, se vuoi attivare un allarme ogni lunedì (=1) alle 10:45:30 imposteresti questa condizione di allarme:
setAlarm1(1, 10, 45, 30, DS3231_MATCH_DY_H_M_S);
Nota che solo l’Allarme 1 supporta la corrispondenza dei secondi. L’enum sotto mostra le costanti matching con le loro maschere bit supportate dalla libreria Arduino-DS3231 per l’Allarme 1:
typedef enum {
DS3231_EVERY_SECOND = 0b00001111,
DS3231_MATCH_S = 0b00001110,
DS3231_MATCH_M_S = 0b00001100,
DS3231_MATCH_H_M_S = 0b00001000,
DS3231_MATCH_DT_H_M_S = 0b00000000,
DS3231_MATCH_DY_H_M_S = 0b00010000
} DS3231_alarm1_t;
e qui ci sono quelle per l’Allarme 2:
typedef enum {
DS3231_EVERY_MINUTE = 0b00001110,
DS3231_MATCH_M = 0b00001100,
DS3231_MATCH_H_M = 0b00001000,
DS3231_MATCH_DT_H_M = 0b00000000,
DS3231_MATCH_DY_H_M = 0b00010000
} DS3231_alarm2_t;
Puoi trovare altri esempi su come abbinare orari e date nel file di esempio DS3231_alarm.ino della libreria Arduino-DS3231.
Indipendentemente dal fatto che usi isAlarm1(), isAlarm2() o confronti tu stesso i campi di data e ora (es. dt.second==10), serve sempre un polling continuo nella funzione loop per rilevare se un allarme è stato attivato.
A seconda di quanto lavoro fai nella funzione loop e quanto tempo impiega, questo può ritardare la rilevazione di un allarme. Per esempio, se il codice nel loop impiega 30 secondi ma vuoi reagire a un allarme entro un secondo, non sarà possibile.
Gli interrupt sono una soluzione a questo problema e saranno l’argomento della prossima sezione.
Abilitare l’uscita interrupt
Il DS3231 supporta interrupt sul pin SQW. Di default questo pin emette un segnale a onda quadra (SQW) con frequenza configurabile ma può essere impostato per inviare un segnale di interrupt se un allarme scatta.
Per provarlo, collega un LED con una resistenza di limitazione corrente di circa 220Ω al pin SQW del DS3231 come mostrato sotto:

Il seguente codice imposta l’Allarme 1 per scattare ogni decimo di secondo (setAlarm1(0, 0, 0, 10, DS3231_MATCH_S)):
#include "Wire.h"
#include "DS3231.h"
DS3231 rtc;
void setup() {
Serial.begin(9600);
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
rtc.setAlarm1(0, 0, 0, 10, DS3231_MATCH_S);
rtc.enableOutput(false);
}
void loop() {
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
La linea importante in questo codice è
rtc.enableOutput(false);
che dice al DS3231 di disabilitare l’uscita del segnale a onda quadra sul pin SQW e di inviare invece un segnale di interrupt. Senza questa linea vedresti il LED lampeggiare a frequenza costante di 1 Hz.
Il codice stampa l’ora ogni secondo ma non controlla il trigger nella funzione loop. Se esegui questo codice e guardi il Serial Monitor e il LED vedrai che il LED si spegne (e resta spento) una volta raggiunto il decimo di secondo. Quindi, gli interrupt sono segnalati da SQW che va LOW!
Il LED resta spento perché non cancelliamo l’allarme. Se vuoi che il LED si spenga per, diciamo, 5 secondi (delay(5000)), ogni 10 secondi, potresti modificare la funzione loop così:
void loop() {
if (rtc.isAlarm1(false)) {
delay(5000);
rtc.clearAlarm1();
}
}
Il codice controlla l’allarme senza cancellarlo. Se l’allarme scatta aspetta 1 secondo, durante il quale il LED è spento, poi cancella l’allarme, che azzera il segnale di interrupt su SQW e riaccende il LED.
Risveglio dal sleep usando gli interrupt del DS3231
L’uso più comune degli interrupt è risvegliare un Arduino dal deep sleep a intervalli regolari per eseguire azioni come il data logging. Per esempio, potresti voler misurare la temperatura ambiente ogni 30 minuti ma far dormire l’Arduino nel frattempo per risparmiare batteria.
Per fare questo rimuoviamo il LED collegato al pin SQW e invece colleghiamo il pin SQW del DS3231 al pin 2 dell’Arduino come mostrato sotto:

Nota che devi usare un pin Arduino che supporta gli interrupt. Nel caso di Arduino Uno solo i pin 2 e 3 possono essere usati per interrupt.
Con il collegamento interrupt pronto, ora possiamo risvegliare l’Arduino dal deep sleep quando scatta un allarme. Il seguente esempio mette l’Arduino in deep sleep, lo risveglia ogni minuto e poi stampa l’ora:
#include "Wire.h"
#include "DS3231.h"
#include "avr/sleep.h"
const int intPin = 2;
DS3231 rtc;
void wakeUp() {
}
void setup() {
Serial.begin(9600);
pinMode(intPin, INPUT);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S);
rtc.enableOutput(false);
}
void loop() {
attachInterrupt(digitalPinToInterrupt(intPin), wakeUp, LOW);
Serial.println("sleeping...");
delay(100); // finish printing
sleep_cpu(); // <= go to sleep
// waking up here when interrupt detected
detachInterrupt(digitalPinToInterrupt(intPin));
rtc.clearAlarm1();
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(100); // finish printing
}
Scomponiamo il codice nei suoi componenti per capirlo meglio.
Inclusioni
Iniziamo includendo le librerie necessarie per il progetto: Wire.h per la comunicazione I2C, DS3231.h per interfacciarsi con il DS3231 RTC, e avr/sleep.h per gestire le modalità di sleep.
#include "Wire.h" #include "DS3231.h" #include "avr/sleep.h"
Costanti e variabili
Poi definiamo una costante per il pin interrupt e creiamo un’istanza della classe DS3231 per interagire con l’RTC.
const int intPin = 2; DS3231 rtc;
Funzione di risveglio
La funzione wakeUp() è definita ma rimane vuota. È richiesta dalla funzione attachInterrupt() e non può essere nulla. Questa funzione sarà chiamata quando l’interrupt scatta.
void wakeUp() { }
Potresti fare cose semplici lì, come incrementare una variabile, ma non ne abbiamo bisogno e non la useremo. Attenzione però, non dovresti fare nulla di complesso lì relativo agli interrupt, incluso Serial.print()!
Funzione setup
Nella funzione setup() inizializziamo la comunicazione seriale, configuriamo il pin interrupt e impostiamo il Arduino sleep mode.
void setup() {
Serial.begin(9600);
pinMode(intPin, INPUT);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S);
rtc.enableOutput(false);
}
Impostiamo anche ora e data dell’RTC, configuriamo l’Allarme 1 per scattare ogni minuto (secondo = 0) e impostiamo l’uscita (SQW) per inviare interrupt.
Funzione loop
Nella funzione loop() colleghiamo un interrupt al pin interrupt, permettendo al microcontrollore di risvegliarsi quando il pin 2 va LOW. Stampiamo “sleeping…” sul Serial Monitor e poi mettiamo l’Arduino in modalità sleep.
void loop() {
attachInterrupt(digitalPinToInterrupt(intPin), wakeUp, LOW);
Serial.println("sleeping...");
delay(100); // finish printing
sleep_cpu();
detachInterrupt(digitalPinToInterrupt(intPin));
rtc.clearAlarm1();
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(100); // finish printing
}
Se scatta un interrupt (pin 2 va LOW), ci risvegliamo (subito dopo la linea con sleep_cpu()). Prima disabilitiamo l’interrupt per evitare che scatti di nuovo subito. Cancelliamo anche l’allarme per riportare il pin interrupt SQW del DS3231 a HIGH.
Poi stampiamo l’ora corrente e aspettiamo un po’ per permettere al Serial Monitor di completare la stampa. Senza il delay l’output potrebbe essere troncato.
Esempio di output
Se carichi questo codice dovresti vedere il seguente output sul Serial Monitor:

Nota che l’Arduino va in sleep, si risveglia ogni minuto e stampa l’ora.
Non abbiamo dovuto controllare se un allarme è scattato usando isAlarm1(), dato che sappiamo che un interrupt scatta solo quando l’Allarme 1 impostato viene attivato. Tuttavia, se imposti Allarme 1 e Allarme 2, cosa possibile, puoi identificare quale è scattato controllando isAlarm1() e isAlarm2().
Ti mostro un esempio nella sezione seguente, dove colleghiamo anche un buzzer per segnalare che un allarme è scattato.
Distinguere gli allarmi e suonare il buzzer
Iniziamo collegando un buzzer attivo al pin 3 dell’Arduino. Qualsiasi altro pin che supporta PWM andrebbe bene.

Nota che per questo esempio ho rimosso il filo del segnale interrupt da SQW, dato che non userò gli interrupt qui. Ma potresti farlo.
Il seguente codice fa suonare un allarme all’Arduino ogni minuto e ogni ora usando il buzzer:
#include "Wire.h"
#include "DS3231.h"
const int buzzerPin = 3;
DS3231 rtc;
void setup() {
rtc.begin();
rtc.setDateTime(__DATE__, __TIME__);
rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); // every minute
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); // every hour
}
void loop() {
if (rtc.isAlarm1() && !rtc.isAlarm2()) {
tone(buzzerPin, 1000, 200);
}
if (rtc.isAlarm2()) {
tone(buzzerPin, 2000, 200);
}
}
Nella funzione setup definiamo i due allarmi. L’Allarme 1 scatta quando i secondi sono zero, quindi ogni minuto. L’Allarme 2 scatta quando i minuti sono zero, quindi ogni ora:
void setup() {
...
rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); // every minute
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); // every hour
}
Nella funzione loop controlliamo quale allarme è scattato e suoniamo un tone diverso:
void loop() {
if (rtc.isAlarm1() && !rtc.isAlarm2()) {
tone(buzzerPin, 1000, 200);
}
if (rtc.isAlarm2()) {
tone(buzzerPin, 2000, 200);
}
}
Poiché all’inizio di ogni ora minuti e secondi sono zero, entrambi gli allarmi scattano. Ma vogliamo suonare solo il suono dell’Allarme 2 all’inizio dell’ora. Quindi suoniamo solo l’Allarme 1 se l’Allarme 2 non è scattato (rtc.isAlarm1() && !rtc.isAlarm2()).
Potresti anche implementarlo in modo più elegante così:
if (rtc.isAlarm1()) {
tone(buzzerPin, rtc.isAlarm2() ? 2000 : 1000, 200);
}
E questo è tutto. Spero che ora tu sappia molto di più sulla funzione di allarme del DS3231.
Conclusioni
In questo tutorial hai imparato come usare gli allarmi con il Real-Time-Clock (RTC) DS3231 e un Arduino.
Non abbiamo parlato di come impostare ora e data dell’RTC. Abbiamo semplicemente preso l’ora di compilazione per inizializzare l’RTC, cosa che va bene finché non riavvii l’Arduino. Se vuoi impostare o regolare l’ora dopo un reset o mentre l’Arduino è in funzione dai un’occhiata al Arduino and RTC Module DS3231 tutorial, dove usiamo pulsanti per farlo. Lì puoi anche imparare come visualizzare l’ora su un LCD.
Se vuoi saperne di più sulle diverse modalità di sleep, dai un’occhiata al How Do I Wake Up My Arduino From Sleep Mode? tutorial. Tuttavia, per progetti alimentati a batteria potresti preferire un ESP32.
Inoltre, dato che un ESP32 supporta il Wi-Fi, sincronizzare l’orologio RTC con un internet time server è facile e elimina la necessità di pulsanti per regolare l’ora. Risolve anche il problema dell’ora legale. Dai un’occhiata al Real-Time-Clock DS3231 with ESP32 tutorial.
Se hai domande sentiti libero di lasciarle nella sezione commenti.
Buon divertimento con il tinkering ; )

