En este tutorial aprenderás cómo usar alarmas con el Real-Time-Clock (RTC) DS3231 y un Arduino.
El módulo DS3231 es un reloj externo. Tiene su propia batería y mantiene la hora y fecha actuales incluso cuando la alimentación principal está apagada. Además, cuenta con dos registros de alarma que puedes configurar para activar alarmas en tiempos y fechas específicas.
Un Real-Time-Clock es especialmente útil cuando quieres que el Arduino entre en modo de suspensión por períodos prolongados. La duración máxima de sueño para un Arduino Uno es de 8 segundos. Pero con un RTC puedes tener duraciones de sueño arbitrariamente largas, lo cual es ideal para extender la vida de la batería en registradores de datos, por ejemplo.
Partes necesarias
Para este proyecto necesitarás un módulo DS3231 RTC y un Arduino. Yo usé un Arduino Uno, pero cualquier otro Arduino también funcionará. Finalmente, en uno de los ejemplos de código de este tutorial, uso un zumbador pasivo para sonar una alarma.

Módulo DS3231 RTC

Arduino Uno

Cable USB para Arduino UNO

Zumbador pasivo

Juego de cables Dupont

Protoboard
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.
Conceptos básicos del módulo DS3231 RTC
El módulo DS3231 RTC es un reloj en tiempo real preciso basado en el DS3231 Chip. Además del DS3231, cuenta con una AT24C32 EEPROM y un soporte para una batería CR2032.
Gracias a su batería, el DS3231 mantiene la hora exacta incluso si el Arduino pierde energía, se reinicia o entra en modo de suspensión. La imagen a continuación muestra el frente y la parte trasera de un módulo típico DS3231 RTC:

Si necesitas más detalles sobre el módulo DS3231 RTC, echa un vistazo a nuestro Arduino and RTC Module DS3231 tutorial.
Pinout del módulo DS3231 RTC
El pinout del módulo DS3231 RTC es el siguiente: VCC y GND son para la alimentación, con un voltaje de suministro de 3.3–5.5V. SCL y SDA son los pines para la interfaz I2C. Los pines 32K y SQW son salidas para la señal de reloj de 32kHz y una señal de onda cuadrada programable. La salida SQW también puede configurarse como salida de señal de interrupción, que usaremos más adelante.

Aunque el módulo DS3231 RTC funciona con 5V, deberías usar 3.3V como alimentación si tienes una batería CR2032 insertada. El módulo tiene un circuito de carga muy básico que dañará la batería si lo alimentas con 5V. Para más información lee el Arduino and RTC Module DS3231 tutorial y el Battery charging circuit of DS3231 module artículo.
Conexión del módulo DS3231 RTC al Arduino
Conectar el módulo DS3231 RTC a un Arduino es sencillo. Primero, conecta los pines SCL y SDA del módulo DS3231 RTC a los pines correspondientes en la placa Arduino como se muestra abajo. Luego, conecta tierra a GND y 3.3V a VCC del módulo DS3231 RTC y listo.

Recuerda alimentar el módulo DS3231 RTC con 3.3V, de lo contrario la batería CR2032 insertada podría dañarse.
Código de prueba simple para el módulo DS3231 RTC
Deberías usar una librería de software para comunicarte con el módulo DS3231 RTC. Hay varias, pero aquí uso la Arduino-DS3231 librería de Korneliusz Jarzębski. Para instalarla, ve a github repo y haz clic en el botón verde «Code». Luego selecciona «Download Zip» en el menú como se muestra a continuación:

Esto descargará un archivo llamado «Arduino-DS3231-dev.zip» en tu computadora. Para instalar esta librería ZIP sigue los pasos habituales. Haz clic en Sketch -> Add .ZIP Library y luego selecciona el archivo Arduino-DS3231-dev.zip que acabas de descargar.

Con la librería instalada, puedes probar el funcionamiento del módulo DS3231 RTC. El siguiente ejemplo de código simple establece la hora y fecha en el módulo RTC al momento de compilación del sketch y luego imprime la hora y fecha en un bucle:
#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);
}
El código comienza incluyendo la librería Wire.h para comunicación I2C y la librería DS3231.h para comunicarse con el DS3231 RTC.
Luego, creamos una instancia de la clase DS3231. En la función setup() inicializamos el RTC y establecemos la fecha y hora actuales usando rtc.setDateTime(). Las macros __DATE__ y __TIME__ insertan automáticamente la fecha y hora en que se compiló el sketch. Ten en cuenta que la hora no será actual cuando el Arduino se reinicie después de compilar y subir el código.
En la función loop, recuperamos la fecha y hora del RTC usando RTCDateTime dt = rtc.getDateTime(), formateamos la fecha y hora como una cadena y la imprimimos en el Monitor Serial con Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt)). El formato especificado aquí es day-month-year hours:minutes:seconds. Consulta el DS3231_dateformat.ino file de la librería Arduino-DS3231 para más ejemplos de formatos.
Finalmente, introducimos un retardo de 1 segundo, para evitar que el Monitor Serial se inunde de mensajes.
Ejemplo de salida
Si subes el código y abres el Monitor Serial, deberías ver la fecha y hora impresas así:

Después de asegurarnos de que el DS3231 está correctamente conectado y funciona, hablemos de la función de alarma del DS3231.
Registros de alarmas del DS3231
El módulo DS3231 RTC tiene dos registros de alarma: Alarma 1 y Alarma 2. Estas dos alarmas pueden activar eventos basados en condiciones de tiempo específicas. La siguiente tabla muestra las configuraciones de bits para estas condiciones:

Como puedes ver, la Alarma 1 ofrece más opciones de configuración. Puedes configurarla para que se active según segundos, minutos, horas y día/fecha. La Alarma 2 solo permite configurar minutos, horas y día/fecha. Además, la Alarma 1 puede configurarse para activarse una vez por minuto, diariamente o semanalmente, mientras que la Alarma 2 solo puede activarse una vez o cada minuto.
Sin embargo, ninguna de las dos alarmas permite activar una alarma periódicamente si no coincide con una unidad base. Por ejemplo, puedes activar una alarma cada minuto, o en el décimo segundo de cada minuto, pero no puedes activar una alarma cada 10 segundos usando los registros de alarma. Pero te mostraré cómo sortear esta limitación.
Ejemplos de código para alarmas con DS3231
En esta sección discutiremos varios ejemplos de código para activar o usar alarmas.
Activar eventos sin usar los registros de alarma del DS3231
Comencemos con un ejemplo que en realidad no usa el registro de alarma. En su lugar, hacemos una consulta continua al DS3231 y verificamos su hora y fecha. Si cumple ciertas condiciones especificadas, ejecutamos una acción. El siguiente ejemplo de código imprime la hora actual cada 10 segundos:
#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 línea crítica es la siguiente, donde verificamos si los segundos son divisibles por 10 usando el operador modulo (%):
if (dt.second % 10 == 0)
Si es así, imprimimos la hora. Ten en cuenta que debes añadir un retardo de al menos 1 segundo (delay(1000)), de lo contrario la hora se imprimirá varias veces, ya que el bucle se ejecuta más rápido que 1 segundo.
La ventaja de activar alarmas de esta manera es que no estás limitado a las condiciones de activación que soportan los registros de alarma. Por ejemplo, si quieres imprimir la hora cada 10 segundos pero solo los fines de semana (dt.dayOfWeek >= 6), puedes escribir la siguiente condición:
if ((dt.dayOfWeek >= 6) && (dt.second % 10 == 0)) {
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
La clase RTCDateTime struct de la librería Arduino-DS3231 tiene los siguientes miembros de tiempo y fecha que puedes usar al implementar tu propia condición de activación:
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;
};
Aunque esto te da libertad total, implementar las condiciones de activación correctamente puede ser complicado y no puedes usar interrupciones de esta forma. Más sobre interrupciones más adelante.
En la siguiente sección, te mostraré cómo usar los registros de alarma para activar eventos. Las condiciones son más limitadas, pero el código será más corto y robusto.
Activar eventos usando los registros de alarma del DS3231
El siguiente ejemplo de código imprime la hora actual del RTC cada hora usando la Alarma 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));
}
}
Hay dos líneas nuevas de código. Primero, configuramos la condición de activación para la Alarma 2 en la función setup mediante
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M);
Esta condición se activa cuando el minuto es igual a 0 (DS3231_MATCH_M), es decir, una vez cada hora.
Segundo, verificamos si la condición se ha activado para la Alarma 2 en la función loop mediante
if (rtc.isAlarm2())
Esto es más corto y robusto, comparado con verificar la condición nosotros mismos. Por ejemplo, tendrías que escribir el siguiente código para activar cada minuto si verificas los campos de hora y fecha tú mismo:
if ((rtc.minute==0) & (rec.second==0)) {
RTCDateTime dt = rtc.getDateTime();
Serial.println(rtc.dateFormat("H:i:s", dt));
delay(1000);
}
Usando la función isAlarm2() la condición se simplifica y ya no necesitamos delay(), ya que isAlarm2() borra la alarma una vez que se ha activado. Puedes desactivar este comportamiento llamando a isAlarm2(false) en su lugar.
Coincidencia de hora y fecha
Las condiciones de activación se definen especificando qué partes de la hora y fecha deben coincidir con valores dados. Por ejemplo, si quieres activar una alarma cada lunes (=1) a las 10:45:30, configurarías la siguiente condición de alarma:
setAlarm1(1, 10, 45, 30, DS3231_MATCH_DY_H_M_S);
Ten en cuenta que solo la Alarma 1 soporta la coincidencia de segundos. El enum a continuación muestra la constante matching con sus máscaras de bits soportadas por la librería Arduino-DS3231 para la Alarma 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;
y aquí están las de la Alarma 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;
Puedes encontrar más ejemplos sobre cómo coincidir ciertos tiempos y fechas en el archivo de ejemplo DS3231_alarm.ino de la librería Arduino-DS3231.
Independientemente de si usas isAlarm1(), isAlarm2() o comparas los campos de fecha y hora tú mismo (por ejemplo, dt.second==10), siempre implica una consulta continua en la función loop para detectar si se ha activado una alarma.
Dependiendo de cuánto trabajo hagas en la función loop y cuánto tiempo tome, esto puede retrasar la detección de una alarma. Por ejemplo, si el código en loop tarda 30 segundos en ejecutarse pero quieres reaccionar a una alarma en menos de un segundo, eso no será posible.
Las interrupciones son una solución a este problema y serán el tema de la siguiente sección.
Habilitar salida de interrupción
El DS3231 soporta interrupciones en el pin SQW. Por defecto, este pin emite una señal de onda cuadrada (SQW) con una frecuencia configurable, pero puede configurarse para enviar una señal de interrupción si se activa una alarma.
Para probar esto, conecta un LED con una resistencia limitadora de corriente de unos 220Ω al pin SQW del DS3231 como se muestra a continuación:

El siguiente código configura la Alarma 1 para activarse cada décimo de segundo (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 línea importante en este código es
rtc.enableOutput(false);
que indica al DS3231 que desactive la salida de la señal de onda cuadrada en el pin SQW y envíe una señal de interrupción en su lugar. Sin esta línea, verás el LED parpadeando con una frecuencia constante de 1 Hz.
El código imprime la hora cada segundo pero no verifica el disparo en la función loop. Si ejecutas este código y observas el Monitor Serial y el LED, verás que el LED se apaga (y permanece apagado) una vez que se alcanza el décimo de segundo. ¡Así que las interrupciones se señalan con SQW en LOW!
El LED permanece apagado porque no borramos la alarma. Si quieres que el LED se apague, digamos, 5 segundos (delay(5000)) cada 10 segundos, podrías cambiar la función loop así:
void loop() {
if (rtc.isAlarm1(false)) {
delay(5000);
rtc.clearAlarm1();
}
}
El código verifica la alarma sin borrarla. Si la alarma está activada, espera 1 segundo, durante el cual el LED está apagado, y luego borra la alarma, lo que limpia la señal de interrupción en SQW y vuelve a encender el LED.
Despertar del sueño usando interrupciones del DS3231
El caso de uso más común para las interrupciones es despertar un Arduino de un sueño profundo a intervalos regulares para realizar algunas acciones como registrar datos. Por ejemplo, podrías querer medir la temperatura ambiente cada 30 minutos pero que el Arduino duerma entre mediciones para conservar la batería.
Para esto, retiramos el LED conectado al pin SQW y en su lugar conectamos el pin SQW del DS3231 al pin 2 del Arduino como se muestra a continuación:

Ten en cuenta que debes usar un pin del Arduino que soporte interrupciones. En el caso del Arduino Uno, solo los pines 2 y 3 pueden usarse para interrupciones.
Con la conexión de interrupción en su lugar, ahora podemos despertar el Arduino del sueño profundo cuando se active una alarma. El siguiente ejemplo de código pone el Arduino en sueño profundo, lo despierta cada minuto y luego imprime la hora:
#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
}
Desglosemos el código en sus componentes para entenderlo mejor.
Inclusiones
Comenzamos incluyendo las librerías necesarias para nuestro proyecto: Wire.h para comunicación I2C, DS3231.h para la interfaz con el DS3231 RTC, y avr/sleep.h para gestionar los modos de sueño.
#include "Wire.h" #include "DS3231.h" #include "avr/sleep.h"
Constantes y variables
Luego definimos una constante para el pin de interrupción y creamos una instancia de la clase DS3231 para interactuar con el RTC.
const int intPin = 2; DS3231 rtc;
Función de despertar
La función wakeUp() está definida pero permanece vacía. Es requerida por la función attachInterrupt() y no puede ser nula. Esta función se llamará cuando se active la interrupción.
void wakeUp() { }
Podrías hacer cosas simples allí, como incrementar una variable, pero no lo necesitamos ni usaremos. Sin embargo, ten cuidado, no deberías hacer nada complejo relacionado con interrupciones, lo que incluye Serial.print()!
Función setup
En la función setup() inicializamos la comunicación serial, configuramos el pin de interrupción y configuramos el 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);
}
También establecemos la hora y fecha del RTC, configuramos la Alarma 1 para que se active cada minuto (segundo = 0) y cambiamos la salida (SQW) para enviar interrupciones.
Función loop
En la función loop() adjuntamos una interrupción al pin de interrupción, permitiendo que el microcontrolador se despierte cuando el pin 2 pase a LOW. Imprimimos «sleeping…» en el Monitor Serial y luego ponemos el Arduino en modo de suspensión.
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
}
Si ocurre una interrupción (pin 2 pasa a LOW), nos despertamos (justo después de la línea con sleep_cpu()). Primero, deshabilitamos la interrupción para evitar que se active de nuevo inmediatamente. También borramos la alarma para que el pin de interrupción SQW del DS3231 vuelva a HIGH.
Luego imprimimos la hora actual y esperamos un poco para que el Monitor Serial termine de imprimir. Sin el retardo, la salida en el Monitor Serial podría truncarse.
Ejemplo de salida
Si subes este código deberías ver la siguiente salida en tu Monitor Serial:

Ten en cuenta que el Arduino entra en suspensión, se despierta cada minuto e imprime la hora.
No tuvimos que verificar si se activó una alarma usando isAlarm1(), ya que sabemos que una interrupción solo se dispara cuando se activa la Alarma 1 configurada. Sin embargo, si configuras Alarma 1 y Alarma 2, lo cual puedes hacer, entonces puedes identificar cuál se activó verificando isAlarm1() y isAlarm2().
Te mostraré un ejemplo en la siguiente sección, donde también conectamos un zumbador para señalar que se ha activado una alarma.
Distinguir alarmas y emitir sonido
Comenzamos conectando un zumbador activo buzzer al pin 3 del Arduino. Cualquier otro pin que soporte PWM también funcionaría.

Ten en cuenta que para este ejemplo retiré el cable de señal de interrupción del pin SQW, ya que no voy a usar interrupciones aquí. Pero podrías hacerlo.
El siguiente código hace que el Arduino emita una alarma cada minuto y cada hora usando el zumbador:
#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);
}
}
En la función setup definimos las dos alarmas. La Alarma 1 coincide con que los segundos sean cero, lo que significa que se activará cada minuto. La Alarma 2 coincide con que los minutos sean cero, lo que significa que se activará cada hora:
void setup() {
...
rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); // every minute
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); // every hour
}
En la función loop verificamos qué alarma se activó y emitimos un sonido diferente tone:
void loop() {
if (rtc.isAlarm1() && !rtc.isAlarm2()) {
tone(buzzerPin, 1000, 200);
}
if (rtc.isAlarm2()) {
tone(buzzerPin, 2000, 200);
}
}
Como al inicio de cada hora los minutos y segundos son cero, ambas alarmas se activan. Pero queremos reproducir solo el sonido de la Alarma 2 en la hora en punto. Por lo tanto, solo emitimos el sonido de la Alarma 1 si la Alarma 2 no está activada (rtc.isAlarm1() && !rtc.isAlarm2()).
También podrías implementar esto de forma más elegante como:
if (rtc.isAlarm1()) {
tone(buzzerPin, rtc.isAlarm2() ? 2000 : 1000, 200);
}
Y eso es todo. Espero que ahora sepas mucho más sobre la función de alarma del DS3231.
Conclusiones
En este tutorial aprendiste cómo usar alarmas con el Real-Time-Clock (RTC) DS3231 y un Arduino.
No hablamos sobre cómo configurar la hora y fecha del RTC. Simplemente usamos la hora de compilación para inicializar el RTC, lo cual está bien mientras no reinicies el Arduino. Si quieres configurar o ajustar la hora después de un reinicio o mientras el Arduino está funcionando, echa un vistazo al Arduino and RTC Module DS3231 tutorial, donde usamos botones para hacerlo. Ahí también puedes aprender cómo mostrar la hora en una pantalla LCD.
Si quieres aprender un poco más sobre los diferentes modos de sueño, revisa el How Do I Wake Up My Arduino From Sleep Mode? tutorial. Sin embargo, para proyectos alimentados por batería, quizás te convenga más un ESP32.
Además, dado que un ESP32 soporta Wi-Fi, sincronizar el reloj RTC con un internet time server es fácil y elimina la necesidad de botones para ajustar la hora. También resuelve el problema del horario de verano. Echa un vistazo al Real-Time-Clock DS3231 with ESP32 tutorial.
Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.
¡Feliz bricolaje ; )

