Correo: [email protected] - Teléfono: +56979962128

Esta es una guía de inicio que muestra cómo conectar el módulo de reloj de tiempo real DS3231 con la placa Raspberry Pi Pico programada con MicroPython: aprenda a configurar y obtener la hora, configurar alarmas y obtener lecturas de temperatura.

 
Raspberry Pi Pico con DS3231 RTC (reloj en tiempo real): Obtener la hora y configurar alarmas (MicroPython)
 

El módulo RTC DS3231 es un excelente módulo para un cronometraje preciso, también le permite configurar alarmas, emitir ondas cuadradas con diferentes frecuencias y obtener lecturas de temperatura.

Introducción a los módulos de reloj de tiempo real (RTC)

Los módulos RTC, como el DS3231 y el DS1307, cuentan con su propio reloj en miniatura para controlar la hora. Suelen incluir un soporte para conectar una batería y así seguir funcionando incluso si la Raspberry Pi Pico se reinicia o se queda sin energía.

 
Módulos RTC DS3231 y RTC DS1307
 

El DS3231 y el DS1307 son dos de las opciones más populares para usar con microcontroladores. Ambos son compatibles con Raspberry Pi Pico y se comunican mediante el protocolo I2C. El DS3231 es más preciso porque proporciona resultados con compensación de temperatura. Además, también se pueden configurar alarmas externas con el DS3231, lo cual puede resultar muy útil.

Presentamos el módulo RTC DS3231

La siguiente imagen muestra el módulo RTC DS3231. Utiliza un oscilador de cristal con compensación de temperatura (TCXO) de 32 kHz para registrar el tiempo con precisión (es resistente a los cambios de temperatura). Por ello, también permite obtener datos de temperatura.

 
Módulo RTC DS3231
   

Además de realizar un seguimiento preciso de la fecha y la hora, también tiene memoria incorporada para almacenar hasta dos alarmas y puede emitir ondas cuadradas a diferentes frecuencias: 1 Hz, 4 kHz, 8 kHz y 32 kHz.

La comunicación con el módulo RTC se realiza mediante el protocolo de comunicación I2C. Normalmente, se encuentra en la dirección 0x68.

Este módulo también incluye una EEPROM 24C32 de 32 bytes que permite almacenar cualquier dato no volátil. Puede comunicarse con esta memoria EEPROM mediante I2C, direccionando la dirección correcta (0x57).

Portapilas DS3231

El DS3231 viene con un portapilas para conectar una pila y mantener una hora precisa. En caso de un corte de energía, puede seguir registrando la hora con precisión y mantener todas las alarmas

Debe 

usar una batería LIR2032, que es recargable

 . No use una CR2032 (no recargable).

 
Portapilas RTC DS3231
Módulo RTC DS3231 con batería de respaldo
 

Si desea utilizar una batería CR2032, que no es recargable, debe desconectar el circuito de carga de la batería desoldando y quitando la resistencia (etiquetada como R4 en mi módulo) al lado del diodo.

 
RTC DS3231 Retire la resistencia para batería no recargable
   

Alarmas del DS3231

El DS3231 puede almacenar hasta dos alarmas: 

alarma 1

 y 

alarma 2.

 Estas alarmas se pueden configurar para que se activen en función de una hora o fecha específica. Cuando se activa una alarma, el pin SQW del módulo emite una señal BAJA

Puedes detectar esta señal con la Raspberry Pi Pico y 

activar interrupciones

 . Esta función es extremadamente útil para tareas periódicas, automatización basada en tiempo y alertas puntuales (ya que puedes borrar una alarma después de que se active).

Dirección I2C del módulo RTC DS3231

Por defecto, la dirección del RTC DS3231 es 

0x68

 y la de la EEPROM conectada al módulo es 0x57. Puede ejecutar un 

script de escáner I2C para verificar las direcciones

 .

Distribución de pines del módulo RTC DS3231

La siguiente tabla describe rápidamente la distribución de pines del módulo RTC DS3231.

32 mil Salida del oscilador de 32 kHz: se puede usar como referencia de reloj
SQW Salida de onda cuadrada/interrupción
SCL Pin SCL para I2C
Adventista del Séptimo Día Pin SDA para I2C
VCC Proporciona alimentación al módulo (3.3 V o 5 V)
Tierra Tierra
 

Conexión del módulo RTC DS3231 a la Raspberry Pi Pico

 
Cableado del módulo DS3231 a la Raspberry Pi Pico
 

Aquí hay una lista de las piezas necesarias para este tutorial:

   

Conecte el DS3231 a la Raspberry Pi Pico. Puede usar la siguiente tabla como referencia. Usamos los pines I2C predeterminados.

Módulo RTC DS3231 Frambuesa Pico
SQW GPIO 3(o cualquier otro pin digital)
SCL GPIO 5
Adventista del Séptimo Día GPIO 4
VCC 3 contra 3
Tierra Tierra
   

Trabajando con el RTC

El uso de un módulo RTC en sus proyectos siempre requiere dos pasos importantes.

  1. Configuración de la hora actual : puede hacerlo manualmente insertando la hora actual (o una hora diferente deseada) en el código; la hora local del sistema; u obtener la hora de un servidor NTP.
  2. Retención de la hora: para garantizar que el RTC mantenga la hora correcta, incluso si se corta la alimentación, debe estar conectado a una batería. Los módulos RTC incluyen un soporte para batería, generalmente para una pila de botón. 
 

Módulo RTC MicroPython DS3231

Existen diferentes bibliotecas con distintas funciones que facilitan la comunicación con el módulo RTC DS3231. Usaremos una versión ligeramente modificada del módulo 

urtc.py

desarrollado por Adafruit.

Siga los siguientes pasos para instalar el módulo requerido.

Descargue y cargue el  urtc.py

  1. Haga clic aquí para descargar el  código  urtc.py ;
  2. Copie el código a un archivo en Thonny IDE;
  3. Vaya a  Archivo > Guardar como… y seleccione Raspberry Pi Pico;   
  4. Guarde el archivo con el nombre  urtc.py (no cambie el nombre). 
 
# Forked and adapted from https://github.com/adafruit/Adafruit-uRTC/tree/master
import collections
import time
DateTimeTuple = collections.namedtuple("DateTimeTuple", ["year", "month",
    "day", "weekday", "hour", "minute", "second", "millisecond"])
def datetime_tuple(year=None, month=None, day=None, weekday=None, hour=None,
                   minute=None, second=None, millisecond=None):
    return DateTimeTuple(year, month, day, weekday, hour, minute,
                         second, millisecond)
def _bcd2bin(value):
    return (value or 0) - 6 * ((value or 0) >> 4)
def _bin2bcd(value):
    return (value or 0) + 6 * ((value or 0) // 10)
def tuple2seconds(datetime):
    return time.mktime((datetime.year, datetime.month, datetime.day,
        datetime.hour, datetime.minute, datetime.second, datetime.weekday, 0))
def seconds2tuple(seconds):
    (year, month, day, hour, minute,
     second, weekday, _yday) = time.localtime(seconds)
    return DateTimeTuple(year, month, day, weekday, hour, minute, second, 0)
class _BaseRTC:
    _SWAP_DAY_WEEKDAY = False
    def __init__(self, i2c, address=0x68):
        self.i2c = i2c
        self.address = addressdef _register(self, register, buffer=None):
        if buffer is None:
            return self.i2c.readfrom_mem(self.address, register, 1)[0]
        self.i2c.writeto_mem(self.address, register, buffer)
    def _flag(self, register, mask, value=None):
        data = self._register(register)
        if value is None:
            return bool(data & mask)
        if value:
            data |= mask
        else:
            data &= ~mask
        self._register(register, bytearray((data,)))
    def datetime(self, datetime=None):
        if datetime is None:
            buffer = self.i2c.readfrom_mem(self.address,
                                           self._DATETIME_REGISTER, 7)
            if self._SWAP_DAY_WEEKDAY:
                day = buffer[3]
                weekday = buffer[4]
            else:
                day = buffer[4]
                weekday = buffer[3]
            return datetime_tuple(
                year=_bcd2bin(buffer[6]) + 2000,
                month=_bcd2bin(buffer[5]),
                day=_bcd2bin(day),
                weekday=_bcd2bin(weekday),
                hour=_bcd2bin(buffer[2]),
                minute=_bcd2bin(buffer[1]),
                second=_bcd2bin(buffer[0]),
            )
        datetime = datetime_tuple(*datetime)
        buffer = bytearray(7)
        buffer[0] = _bin2bcd(datetime.second)
        buffer[1] = _bin2bcd(datetime.minute)
        buffer[2] = _bin2bcd(datetime.hour)
        if self._SWAP_DAY_WEEKDAY:
            buffer[4] = _bin2bcd(datetime.weekday)
            buffer[3] = _bin2bcd(datetime.day)
        else:
            buffer[3] = _bin2bcd(datetime.weekday)
            buffer[4] = _bin2bcd(datetime.day)
        buffer[5] = _bin2bcd(datetime.month)
        buffer[6] = _bin2bcd(datetime.year - 2000)
        self._register(self._DATETIME_REGISTER, buffer)
class DS1307(_BaseRTC):
    _NVRAM_REGISTER = 0x08
    _DATETIME_REGISTER = 0x00
    _SQUARE_WAVE_REGISTER = 0x07
    def stop(self, value=None):
        return self._flag(0x00, 0b10000000, value)
    def memory(self, address, buffer=None):
        if buffer is not None and address + len(buffer) > 56:
            raise ValueError("address out of range")
        return self._register(self._NVRAM_REGISTER + address, buffer)
class DS3231(_BaseRTC):
    _CONTROL_REGISTER = 0x0e
    _STATUS_REGISTER = 0x0f
    _DATETIME_REGISTER = 0x00
    _TEMPERATURE_MSB_REGISTER = 0x11
    _TEMPERATURE_LSB_REGISTER = 0x12
    _ALARM_REGISTERS = (0x08, 0x0b)
    _SQUARE_WAVE_REGISTER = 0x0e
    def lost_power(self):
        return self._flag(self._STATUS_REGISTER, 0b10000000)
    def alarm(self, value=None, alarm=0):
        return self._flag(self._STATUS_REGISTER,
                          0b00000011 & (1 << alarm), value)
    def interrupt(self, alarm=0):
        return self._flag(self._CONTROL_REGISTER,
                          0b00000100 | (1 << alarm), 1)
    def no_interrupt(self):
        return self._flag(self._CONTROL_REGISTER, 0b00000011, 0)
    def stop(self, value=None):
        return self._flag(self._CONTROL_REGISTER, 0b10000000, value)
    def datetime(self, datetime=None):
        if datetime is not None:
            status = self._register(self._STATUS_REGISTER) & 0b01111111
            self._register(self._STATUS_REGISTER, bytearray((status,)))
        return super().datetime(datetime)
    def alarm_time(self, datetime=None, alarm=0):
        if datetime is None:
            buffer = self.i2c.readfrom_mem(self.address,
                                           self._ALARM_REGISTERS[alarm], 3)
            day = None
            weekday = None
            second = None
            if buffer[2] & 0b10000000:
                pass
            elif buffer[2] & 0b01000000:
                day = _bcd2bin(buffer[2] & 0x3f)
            else:
                weekday = _bcd2bin(buffer[2] & 0x3f)
            minute = (_bcd2bin(buffer[0] & 0x7f)
                      if not buffer[0] & 0x80 else None)
            hour = (_bcd2bin(buffer[1] & 0x7f)
                    if not buffer[1] & 0x80 else None)
            if alarm == 0:
                # handle seconds
                buffer = self.i2c.readfrom_mem(
                    self.address, self._ALARM_REGISTERS[alarm] - 1, 1)
                second = (_bcd2bin(buffer[0] & 0x7f)
                          if not buffer[0] & 0x80 else None)
            return datetime_tuple(
                day=day,
                weekday=weekday,
                hour=hour,
                minute=minute,
                second=second,
            )
        datetime = datetime_tuple(*datetime)
        buffer = bytearray(3)
        buffer[0] = (_bin2bcd(datetime.minute)
                     if datetime.minute is not None else 0x80)
        buffer[1] = (_bin2bcd(datetime.hour)
                     if datetime.hour is not None else 0x80)
        if datetime.day is not None:
            if datetime.weekday is not None:
                raise ValueError("can't specify both day and weekday")
            buffer[2] = _bin2bcd(datetime.day)
        elif datetime.weekday is not None:
            buffer[2] = _bin2bcd(datetime.weekday) | 0b01000000
        else:
            buffer[2] = 0x80
        self._register(self._ALARM_REGISTERS[alarm], buffer)
        if alarm == 0:
            # handle seconds
            buffer = bytearray([_bin2bcd(datetime.second)
                                if datetime.second is not None else 0x80])
            self._register(self._ALARM_REGISTERS[alarm] - 1, buffer)
    def get_temperature(self):
        """
        Reads the temperature from the DS3231's temperature registers.
        Returns the temperature as a float in Celsius.
        """
        msb = self._register(self._TEMPERATURE_MSB_REGISTER)  # 0x11
        lsb = self._register(self._TEMPERATURE_LSB_REGISTER)  # 0x12
        if msb is None or lsb is None:
            print("Error: Register read returned None")
            return None
        temp = msb + ((lsb >> 6) * 0.25)
        if msb & 0x80:
            temp -= 256
        return tempclass PCF8523(_BaseRTC):
    _CONTROL1_REGISTER = 0x00
    _CONTROL2_REGISTER = 0x01
    _CONTROL3_REGISTER = 0x02
    _DATETIME_REGISTER = 0x03
    _ALARM_REGISTER = 0x0a
    _SQUARE_WAVE_REGISTER = 0x0f
    _SWAP_DAY_WEEKDAY = True
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.init()
    def init(self):
        # Enable battery switchover and low-battery detection.
        self._flag(self._CONTROL3_REGISTER, 0b11100000, False)
    def reset(self):
        self._flag(self._CONTROL1_REGISTER, 0x58, True)
        self.init()
    def lost_power(self, value=None):
        return self._flag(self._CONTROL3_REGISTER, 0b00010000, value)
    def stop(self, value=None):
        return self._flag(self._CONTROL1_REGISTER, 0b00010000, value)
    def battery_low(self):
        return self._flag(self._CONTROL3_REGISTER, 0b00000100)
    def alarm(self, value=None):
        return self._flag(self._CONTROL2_REGISTER, 0b00001000, value)
    def datetime(self, datetime=None):
        if datetime is not None:
            self.lost_power(False) # clear the battery switchover flag
        return super().datetime(datetime)
    def alarm_time(self, datetime=None):
        if datetime is None:
            buffer = self.i2c.readfrom_mem(self.address,
                                           self._ALARM_REGISTER, 4)
            return datetime_tuple(
                weekday=_bcd2bin(buffer[3] &
                                 0x7f) if not buffer[3] & 0x80 else None,
                day=_bcd2bin(buffer[2] &
                             0x7f) if not buffer[2] & 0x80 else None,
                hour=_bcd2bin(buffer[1] &
                              0x7f) if not buffer[1] & 0x80 else None,
                minute=_bcd2bin(buffer[0] &
                                0x7f) if not buffer[0] & 0x80 else None,
            )
        datetime = datetime_tuple(*datetime)
        buffer = bytearray(4)
        buffer[0] = (_bin2bcd(datetime.minute)
                     if datetime.minute is not None else 0x80)
        buffer[1] = (_bin2bcd(datetime.hour)
                     if datetime.hour is not None else 0x80)
        buffer[2] = (_bin2bcd(datetime.day)
                     if datetime.day is not None else 0x80)
        buffer[3] = (_bin2bcd(datetime.weekday) | 0b01000000
                     if datetime.weekday is not None else 0x80)
        self._register(self._ALARM_REGISTER, buffer)
 
 

Con el módulo cargado en la placa, ahora puede usar las funcionalidades de la biblioteca en su código para interactuar con el módulo RTC DS3231

 

DS3231 RTC con Raspberry Pi Pico: Configurar y obtener la hora

 
RPi Pico DS3231 Establecer y obtener la hora
 

El siguiente código sincroniza la hora RTC con la hora del sistema y obtiene la fecha, hora y temperatura actuales cada segundo.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-ds3231-rtc-micropython/
import time
import urtc
from machine import I2C, Pin
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
# Initialize RTC (connected to I2C)
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)
# Set the current time using a specified time tuple
# Time tuple: (year, month, day, day of week, hour, minute, seconds, milliseconds)
#initial_time = (2024, 1, 30, 1, 12, 30, 0, 0)
# Or get the local time from the system
initial_time_tuple = time.localtime()  # tuple (microPython)
initial_time_seconds = time.mktime(initial_time_tuple)  # local time in seconds
# Convert to tuple compatible with the library
initial_time = urtc.seconds2tuple(initial_time_seconds)
# Sync the RTC
rtc.datetime(initial_time)
while True:
    current_datetime = rtc.datetime()
    temperature = rtc.get_temperature()
    # Display time details
    print('Current date and time:')
    print('Year:', current_datetime.year)
    print('Month:', current_datetime.month)
    print('Day:', current_datetime.day)
    print('Hour:', current_datetime.hour)
    print('Minute:', current_datetime.minute)
    print('Second:', current_datetime.second)
    print('Day of the Week:', days_of_week[current_datetime.weekday])
    print(f"Current temperature: {temperature}°C")    
    # Format the date and time
    formatted_datetime = (
        f"{days_of_week[current_datetime.weekday]}, "
        f"{current_datetime.year:04d}-{current_datetime.month:02d}-{current_datetime.day:02d} "
        f"{current_datetime.hour:02d}:{current_datetime.minute:02d}:{current_datetime.second:02d} "
    )
    print(f"Current date and time: {formatted_datetime}")
    print(" n");
    time.sleep(1)
 
 

Cómo funciona el código

Echemos un vistazo rápido a las partes relevantes de este código.

Importar bibliotecas

Primero, necesitas importar el

urtc

módulo que has cargado previamente y que contiene las funciones para interactuar con el RTC. También necesitas importar el

Pin

y

I2C

clases para establecer una comunicación I2C con el módulo

import time 
import urtc 
from machine import I2C, Pin
 

Inicializar el módulo RTC

Luego, inicialice la comunicación I2C y cree un objeto llamado

rtc

para referirse a nuestro módulo RTC DS3231.

# Initialize RTC (connected to I2C) 
i2c = I2C(0, scl=Pin(5), sda=Pin(4)) 
rtc = urtc.DS3231(i2c)

Sincronizar la hora

Para sincronizar la hora del RTC, debe usar el método

datetime()

en

objeto rtc y pasar como argumento una tupla de tiempo con el siguiente formato:

(year, month, day, day of week, hour, minute, seconds, milliseconds)
  Nota:

 esta tupla es diferente de la utilizada por el módulo Micropyhton time

Podemos establecer la hora manualmente en una fecha y hora definidas de la siguiente manera:

#initial_time = (2024, 1, 30, 1, 12, 30, 0, 0)
 

O bien, podemos sincronizar el RTC con la hora local del sistema. Sincronizaremos la hora con la hora local del sistema.

Primero, obtenemos la tupla de tiempo de la hora local usando

time.localtime()

.

 
initial_time_tuple = time.localtime() # tuple (microPython)
 

La tupla devuelta es diferente de la utilizada por el módulo urtc.

 

Entonces, primero lo convertimos a segundos usando

time.mktime()

.

initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds



Y finalmente, lo convertimos en una tupla que sea compatible con la biblioteca usando la función seconds2tuple() de la biblioteca urtc que acepta como argumento el número de segundos desde la época de la hora local

initial_time = urtc.seconds2tuple(initial_time_seconds)

Por fin podemos pasar nuestra variable initial_time

que contiene la hora local en una tupla compatible con el módulo

urtc

 a la función

datetime()

de la siguiente manera.

 
# Convert to tuple compatible with the library
initial_time = urtc.seconds2tuple(initial_time_seconds)

# Sync the RTC
rtc.datetime(initial_time)
Nota:

 Después de configurar la hora por primera vez, solo es necesario volver a configurarla si el reloj de tiempo real pierde energía (debe tener una batería conectada para evitarlo). Puede usar la función lost_power() en el objeto rtc para comprobar si ha perdido energía. Esta función devuelve Verdadero o Falso según corresponda.

Obtener la hora

Después de estas líneas de código, el módulo RTC se sincroniza con tu hora local y puedes simplemente llamarlo rtc.datetime()para obtener la hora actual del RTC. Esto devuelve un objeto con todos los elementos de hora

Para obtener e imprimir cada elemento de tiempo, podemos hacer lo siguiente:

 
current_datetime = rtc.datetime()
print('Current date and time:')
print('Year:', current_datetime.year)
print('Month:', current_datetime.month)
print('Day:', current_datetime.day)
print('Hour:', current_datetime.hour)
print('Minute:', current_datetime.minute)
print('Second:', current_datetime.second)
print('Day of the Week:', days_of_week[current_datetime.weekday])
 

También podemos obtener la temperatura actual del módulo llamando al metodo get

_temperature()

en el objeto rtc

 
temperature = rtc.get_temperature()

Los datos de temperatura vienen en grados Celsius

 
print(f"Current temperature: {temperature}°C")
 

Imprimimos la hora y la temperatura actual cada segundo.

 
time.sleep(1)
 

Ejecutar el código

Ejecute este código anterior para sincronizar la hora del reloj en tiempo real con la hora local y obtener la hora y la temperatura actuales

Ejecutar un script de MicroPython en el IDE de Thonny

A partir de ahora, si el RTC tiene una batería conectada, mantendrá la hora sincronizada con tu hora local. Así que ya no necesitas sincronizarlo y puedes simplemente llamar rtc.datetime() para obtener la hora actual.

Raspberry Pi Pico con DS3231 con MicroPython: Obtener fecha, hora y temperatura

DS3231 con Raspberry Pi Pico: Configuración de alarmas

 

El módulo RTC DS3231 permite configurar hasta dos alarmas: alarma 1 y alarma 2. Al llegar a la hora de la alarma, el módulo cambia el estado del pin SQW de ALTO a BAJO (es activo bajo). Podemos comprobar el cambio en ese pin y realizar una acción específica cuando se dispara la alarma.

 
Raspberry Pi Pico DS3231 - Configuración de alarmas
 

Notas importantes sobre las alarmas:

  • El RTC le permite guardar hasta dos alarmas;
  • sólo puedes tener una alarma activa a la vez;
  • después de que se activa una alarma, debes borrar su bandera para evitar que se active y bloquee la Pi Pico;
  • Debes desactivar una alarma antes de activar la otra.
 

DS3231 – Configuración de las alarmas

El siguiente ejemplo muestra cómo configurar dos alarmas. Se enciende un LED cuando se activa la alarma. Para probar este ejemplo, añada un LED con una resistencia de 220 ohmios al circuito. Conecte el LED a GPIO 2.

 
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-ds3231-rtc-micropython/

import time
import urtc
from machine import Pin, I2C

# Pin setup for SQW pin and LED
CLOCK_INTERRUPT_PIN = 3  # Adjust to your specific GPIO pin for SQW
LED_PIN = 2  # Adjust to your specific GPIO pin for the LED

# Initialize RTC (connected to I2C)
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)

# Setup GPIO for SQW and LED
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
led_pin = Pin(LED_PIN, Pin.OUT)
led_pin.off()

# Alarm times (year, month, day, weekday, hour, minute, second, millisecond)
alarm1_time = urtc.datetime_tuple(2025, 01, 04, None, 12, 32, 00, 0)  # Alarm 1 uses full datetime
alarm2_time = urtc.datetime_tuple(2025, 01, 04, None, 12, 35, 00, 0)   # Alarm 2 uses day, hour, minute, weekday

# Print the current time from the RTC
def print_current_time():
    now = rtc.datetime()
    formatted_time = f"{now.year}-{now.month:02}-{now.day:02}{now.hour:02}:{now.minute:02}:{now.second:02}"
    print(f"Current time: {formatted_time}")


#Callback for handling alarm interrupt.
def on_alarm(pin):
    print("Interrupt detected.")
    
    if rtc.alarm(alarm=0):  # Check if Alarm 1 triggered
        print("Alarm 1 triggered.")
        rtc.alarm(False, alarm=0)  # Clear Alarm 1 flag
        led_pin.value(not led_pin.value())  # Toggle LED for Alarm 1
    
    if rtc.alarm(alarm=1):  # Check if Alarm 2 triggered
        print("Alarm 2 triggered.")
        rtc.alarm(False, alarm=1)  # Clear Alarm 2 flag
        led_pin.value(not led_pin.value())  # Toggle LED for Alarm 2

# Setup alarms on the DS3231
def setup_alarms():
    # Clear any existing alarms
    rtc.alarm(False, 0)
    rtc.alarm(False, 1)
    rtc.no_interrupt()

    # Set the desired alarm times
    rtc.alarm_time(alarm1_time, 0)  # Alarm 1
    rtc.alarm_time(alarm2_time, 1)  # Alarm 2

    # Enable interrupts for the alarms
    rtc.interrupt(0)
    rtc.interrupt(1)
    print("Alarms set successfully.")


# Attach the interrupt callback
sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)

try:
    # Sync time to compile time if RTC lost power
    if rtc.lost_power():
        initial_time_tuple = time.localtime()  # tuple (MicroPython)
        initial_time_seconds = time.mktime(initial_time_tuple)  # local time in seconds
        initial_time = urtc.seconds2tuple(initial_time_seconds)  # Convert to tuple compatible with the library
        rtc.datetime(initial_time)  # Sync the RTC

    print("RTC initialized. Setting alarms...")
    setup_alarms()

    while True:
        print_current_time()
        time.sleep(5)
except KeyboardInterrupt:
    print("Exiting program.")
 
 

¿Cómo funciona el código?

Echemos un vistazo a las partes relevantes del código para configurar las alarmas.

Definición de los pines

Primero, debe definir el GPIO que está conectado al pin SQW. Este es el pin que cambiará de estado cuando se activen las alarmas. Elegimos conectarlo a GPIO 3, pero puede elegir cualquier otro pin siempre que las conexiones del circuito coincidan

CLOCK_INTERRUPT_PIN = 3 # Adjust to your specific GPIO pin for SQW

Configuramos el pin SQW como una entrada con una resistencia pull-up interna (porque es un pin activo bajo).

 
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
 

Horas de alarma

Luego, creamos dos variables de tipo

datetime_tuple

Para guardar los tiempos de alarma en variables de la siguiente manera:

# Alarm times (year, month, day, weekday, hour, minute, second, millisecond)
alarm1_time = urtc.datetime_tuple(2025, 01, 04, None, 12, 32, 00, 0) # Alarm 1 uses full datetime
alarm2_time = urtc.datetime_tuple(2025, 01, 04, None, 12, 35, 00, 0) # Alarm 2 uses day, hour, minute, weekday
 

Ajuste las alarmas a las horas deseadas.

Configuración del pin SQW como interrupción

Luego, configuramos el pin SQW como una interrupción llamando al metodo

irq()

sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)
  El método irq() acepta los siguientes argumentos:
  • manejador: esta es una función que se llamará cuando se detecte una interrupción, en este caso la función on_alarm() .
  • disparador: esto define el modo de disparo. Hay 3 condiciones diferentes. En nuestro caso, usaremos IRQ_FALLING para activar la interrupción cada vez que el pin pase de ALTO a BAJO.
 

Función on_alarm

Esta función

on_alarm

 verifica qué alarma se activó y la desactiva para borrar su indicador. Se llamará cuando se dispare una alarma.

 
def on_alarm(pin):
La siguiente línea comprueba si se activó la alarma 1. Esto nos permite desactivarla inmediatamente después de que se active y ejecutar una tarea específica a una hora determinada.
if rtc.alarm(alarm=0): # Check if Alarm 1 triggered
   print("Alarm 1 triggered.")
   rtc.alarm(False, alarm=0) # Clear Alarm 1 flag
   led_pin.value(not led_pin.value()) # Toggle LED for Alarm 1
Esta línea borra el indicador alarm1 (para restablecerlo a su estado inicial).
rtc.alarm(False, alarm=0) # Clear Alarm 1 flag
 

Luego, alternamos el estado del LED.

led_pin.value(not led_pin.value()) # Toggle LED for Alarm 1

Actuamos de forma similar para la alarma2.

if rtc.alarm(alarm=1): # Check if Alarm 2 triggered
print("Alarm 2 triggered.")
rtc.alarm(False, alarm=1) # Clear Alarm 2 flag
led_pin.value(not led_pin.value()) # Toggle LED for Alarm 2
 

Configuración de las alarmas

Creamos una función llamada setup_alarms()para configurar las alarmas.

Para configurar las alarmas, primero debemos borrar cualquier alarma existente de la siguiente manera

def setup_alarms():
# Clear any existing alarms
rtc.alarm(False, 0)
rtc.alarm(False, 1)
rtc.no_interrupt()
 




Luego, podemos configurar las horas de alarma llamando el método

alarm_time() en

el objeto

rtc

. Pasa como argumentos la hora de la alarma (una variable de tipo

datetime_tuple

) y el número de alarma (

0

=alarm1;

1

=alarm2).

 



# Set the desired alarm times
rtc.alarm_time(alarm1_time, 0) # Alarm 1
rtc.alarm_time(alarm2_time, 1) # Alarm 2
Habilitamos la interrupción para las alarmas, de modo que el pin SQW se active cuando se dispara una alarma
rtc.interrupt(0)
rtc.interrupt(1)
Entonces tenemos una declaración try que ajustará la hora del RTC si es necesario (si ha perdido energía) y configurará las alarmas
try:
# Sync time to compile time if RTC lost power
if rtc.lost_power():
initial_time_tuple = time.localtime() # tuple (MicroPython)
initial_time_seconds = time.mktime(initial_time_tuple) # local time in seconds
initial_time = urtc.seconds2tuple(initial_time_seconds) # Convert to tuple compatible with the library
rtc.datetime(initial_time) # Sync the RTC

print("RTC initialized. Setting alarms...")
setup_alarms()
 

Luego, imprimiremos constantemente la hora actual cada cinco segundos.

 
while True:
print_current_time()
time.sleep(5)
La detección de las alarmas se realiza en segundo plano porque hemos configurado una interrupción en el pin SQW.
 

Prueba de las alarmas

Configura las alarmas a una hora cercana para que puedas verlas en acción. Luego, ejecuta el código

Ejecute el script MicrppPython Thonny IDE
 

La hora actual se imprimirá en la carcasa cada cinco segundos. Cuando se active la alarma, aparecerá un mensaje en la carcasa y el LED se encenderá.

 
DS3231 RTC con Raspberry Pi Pico MicroPython: activación de alarmas
   

DS3231 – Configuración de alarmas periódicas

Para configurar alarmas periódicas, podemos ajustar la hora de la alarma cada vez que se active. Por ejemplo, imagine que desea activar una alarma cada 10 minutos. Para ello:

  • leer la hora actual cuando se activa la alarma;
  • configurar una nueva alarma agregando 600 segundos (10 minutos) a la hora actual;
  • después de 10 minutos se activará la alarma;
  • Repita los pasos anteriores.

Eso es exactamente lo que implementaremos en el siguiente ejemplo.


# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-ds3231-rtc-micropython/

from machine import Pin, I2C
import time
import urtc

# Pin setup for SQW pin and LED
CLOCK_INTERRUPT_PIN = 3  # Adjust to your specific GPIO pin for SQW
LED_PIN = 2  # Adjust to your specific GPIO pin for the LED

# Initialize RTC (connected to I2C)
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
rtc = urtc.DS3231(i2c)

# Setup GPIO for SQW and LED
sqw_pin = Pin(CLOCK_INTERRUPT_PIN, Pin.IN, Pin.PULL_UP)
led_pin = Pin(LED_PIN, Pin.OUT)
led_pin.off()

# Add minutes to a datetime tuple and return a new datetime tuple
def add_minutes_to_time(dt, minutes):
    timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, 0, 0))
    new_timestamp = timestamp + (minutes * 60)
    new_time = time.localtime(new_timestamp)
    return urtc.datetime_tuple(new_time[0], new_time[1], new_time[2], None, new_time[3], new_time[4], new_time[5], 0)

# Print the current time from the RTC
def print_current_time():
    now = rtc.datetime()
    formatted_time = f"{now.year}-{now.month:02}-{now.day:02} {now.hour:02}:{now.minute:02}:{now.second:02}"
    print(f"Current time: {formatted_time}")

# Callback for handling alarm interrupt
def on_alarm(pin):
    print("Alarm triggered! Toggling the LED.")
    led_pin.value(not led_pin.value())  # Toggle the LED

    # Clear the alarm flag and schedule the next alarm
    if rtc.alarm(alarm=0):  # Check if Alarm 0 triggered
        print("Clearing Alarm 0 flag.")
        rtc.alarm(False, alarm=0)  # Clear alarm flag

        # Schedule the alarm to repeat 10 minutes from now
        now = rtc.datetime()
        next_alarm_time = add_minutes_to_time(now, 10)  # Add 10 minutes
        rtc.alarm_time(next_alarm_time, alarm=0)  # Set new alarm
        rtc.interrupt(0)  # Ensure Alarm 0 interrupt is enabled
        print(f"Next alarm set for: {next_alarm_time}")


def setup_alarm():
    # Clear any existing alarms
    rtc.alarm(False, 0)
    rtc.no_interrupt()

    # Get the current time and set the first alarm 1 minute from now
    now = rtc.datetime()
    first_alarm_time = add_minutes_to_time(now, 1)  # Set first alarm for 1 minute from now
    rtc.alarm_time(first_alarm_time, alarm=0)  # Alarm 0

    # Enable the interrupt for Alarm 0
    rtc.interrupt(0)

    print(f"Alarm set for: {first_alarm_time}")

# Attach the interrupt callback
sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)

try:
    # Sync time to compile time if RTC lost power
    if rtc.lost_power():
        initial_time_tuple = time.localtime()  # tuple (MicroPython)
        initial_time_seconds = time.mktime(initial_time_tuple)  # local time in seconds
        initial_time = urtc.seconds2tuple(initial_time_seconds)  # Convert to tuple compatible with the library
        rtc.datetime(initial_time)  # Sync the RTC

    print("RTC initialized. Setting up the periodic alarm...")
    setup_alarm()

    while True:
        print_current_time()
        time.sleep(5)
except KeyboardInterrupt:
    print("Exiting program.")
 

En este ejemplo, creamos una función llamada

add_minutes_to_time()

que añadirá minutos a la hora actual.

# Add minutes to a datetime tuple and return a new datetime tuple
def add_minutes_to_time(dt, minutes):
timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, 0, 0))
new_timestamp = timestamp + (minutes * 60)
new_time = time.localtime(new_timestamp)
return urtc.datetime_tuple(new_time[0], new_time[1], new_time[2], None, new_time[3], new_time[4], new_time[5], 0)
 

Esta función acepta como argumentos una tupla datetime

(como la hora actual) y los minutos que desea agregar a la hora actual para configurar una nueva alarma en el futuro. Esta función devuelve una nueva tupla datetime

 con la nueva hora de alarma.

Cuando se activa una alarma, la función on_alarma()

 se ejecutará. Primero, alternará el estado del LED integrado.

# Callback for handling alarm interrupt
def on_alarm(pin):
print("Alarm triggered! Toggling the LED.")
led_pin.value(not led_pin.value()) # Toggle the LED
Luego se desactivará (borrará la bandera de la alarma 1).
if rtc.alarm(alarm=0): # Check if Alarm 0 triggered
   print("Clearing Alarm 0 flag.")
   rtc.alarm(False, alarm=0) # Clear alarm flag
  Después, se configurará una nueva hora de alarma añadiendo un número predefinido de minutos a la hora actual. En este ejemplo, configuramos una alarma cada 10 minutos.



 

# Schedule the alarm to repeat 10 minutes from now
now = rtc.datetime()
next_alarm_time = add_minutes_to_time(now, 10) # Add 10 minutes
rtc.alarm_time(next_alarm_time, alarm=0) # Set new alarm
rtc.interrupt(0) # Ensure Alarm 0 interrupt is enabled
print(f"Next alarm set for: {next_alarm_time}")

Puede cambiar esto fácilmente ajustándolo al tiempo deseado en la siguiente línea.

next_alarm_time = add_minutes_to_time(now, 10) # Add 10 minutes
 

Prueba de las alarmas periódicas

Configura las alarmas a una hora cercana para que puedas verlas en acción. Luego, ejecuta el código Ejecutar un script de MicroPython en el IDE de Thonny
 

La primera alarma se activará después de un minuto. Las siguientes alarmas se activarán cada 10 minutos. Cuando se activa una alarma, también se activa el estado del LED.

 
Raspberry Pi Pico con DS3231: Configuración de alarmas periódicas
 

Terminando

En este tutorial, aprendiste a usar el módulo de reloj en tiempo real DS3231 con la Raspberry Pi Pico programada con MicroPython para obtener la fecha y hora actuales y configurar alarmas. Este módulo puede ser muy útil en proyectos de registro de datos, automatización, tareas periódicas y muchas otras aplicaciones.













Preguntas frecuentes

¿Qué es un módulo RTC y por qué lo necesito?

Es un reloj de tiempo real: un módulo con su propio reloj en miniatura para llevar la hora. Módulos como el DS3231 y el DS1307 incluyen soporte para una batería, así que siguen funcionando aunque la Raspberry Pi Pico se reinicie o se apague.

¿Qué ventajas tiene el DS3231 sobre otros RTC?

Es un módulo de cronometraje preciso que además permite configurar alarmas, emitir ondas cuadradas de distintas frecuencias y obtener lecturas de temperatura. Hace bastante más que solo dar la hora.

¿Se puede leer la temperatura con el DS3231?

Sí. Entre sus funciones está entregar lecturas de temperatura, algo que usa internamente para compensar la deriva del cristal y que también queda disponible para el proyecto.

¿Cómo se conecta el DS3231 a la Raspberry Pi Pico?

Por el bus I2C, igual que la mayoría de los módulos de este tipo, y se programa desde MicroPython. La guía cubre configurar la hora, obtenerla, programar alarmas y leer la temperatura.

Aprende esto en un curso

Si prefieres verlo en vivo y con alguien que resuelva tus dudas, estos cursos presenciales de MCI Educación cubren el tema de este tutorial: