Raspberry Pi Pico: DS3231 RTC (часы реального времени) — получение времени и настройка будильников (MicroPython)

Это руководство по началу работы, которое показывает, как подключить модуль часов реального времени DS3231 к плате Raspberry Pi Pico, программируемой на MicroPython: узнайте, как устанавливать и получать время, настраивать будильники и получать показания температуры.

Raspberry Pi Pico с DS3231 RTC (часы реального времени) — получение времени и настройка будильников (MicroPython)

Модуль DS3231 RTC — это отличный модуль для точного хронометража, он также позволяет настраивать будильники, генерировать прямоугольные волны с различными частотами и получать показания температуры.

В этом руководстве мы рассмотрим следующие темы:

Предварительные требования — прошивка MicroPython

Для выполнения этого руководства вам необходима прошивка MicroPython, установленная на плату Raspberry Pi Pico. Вам также понадобится IDE для написания и загрузки кода на плату.

Рекомендуемая IDE для MicroPython на Raspberry Pi Pico — Thonny IDE. Следуйте следующему руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.

Знакомство с модулями часов реального времени (RTC)

Модули RTC, такие как DS3231 и DS1307, имеют внутри собственные миниатюрные часы для самостоятельного отслеживания времени. Обычно они поставляются с держателем батареи для подключения батарейки, чтобы они продолжали работать, даже если Raspberry Pi Pico перезагрузится или потеряет питание.

Модули RTC DS3231 и RTC DS1307

DS3231 и DS1307 — одни из самых популярных вариантов для использования с микроконтроллерами. Оба совместимы с Raspberry Pi Pico и взаимодействуют по протоколу I2C. DS3231 более точен, потому что выдаёт результаты с температурной компенсацией. Кроме того, с DS3231 также можно настраивать внешние будильники, что может быть чрезвычайно полезно.

Знакомство с модулем DS3231 RTC

На следующем изображении показан модуль DS3231 RTC. Он использует кварцевый генератор 32 кГц с температурной компенсацией (TCXO) для точного отслеживания времени (устойчив к изменениям температуры). Благодаря этому он также позволяет получать данные о температуре.

Модуль RTC DS3231

Помимо точного отслеживания даты и времени, он также имеет встроенную память для хранения до двух будильников и может генерировать прямоугольные волны на различных частотах: 1 Гц, 4 кГц, 8 кГц и 32 кГц.

Связь с модулем RTC осуществляется по протоколу I2C. Обычно его адрес — 0x68.

Этот модуль также поставляется с EEPROM 24C32 на 32 байта, которую вы можете использовать для хранения любых энергонезависимых данных. Вы можете общаться с этой EEPROM-памятью по I2C, обращаясь к нужному адресу (0x57).

Держатель батареи DS3231

DS3231 поставляется с держателем батареи для подключения батарейки, обеспечивающей точное хронометрирование. В случае отключения питания он по-прежнему может точно отслеживать время и сохранять все будильники.

Вы должны использовать аккумулятор LIR2032, который является перезаряжаемым. Не используйте CR2032 (неперезаряжаемый).

Держатель батареи RTC DS3231 Модуль DS3231 RTC с резервным питанием от батареи

Если вы хотите использовать батарейку CR2032, которая является неперезаряжаемой, вам необходимо отключить цепь зарядки батареи, отпаяв и удалив резистор (обозначенный R4 на моём модуле) рядом с диодом.

RTC DS3231 — удаление резистора для неперезаряжаемой батареи

Будильники DS3231

DS3231 может хранить до двух будильников: будильник 1 и будильник 2. Эти будильники можно настроить на срабатывание по определённому времени и/или дате. Когда будильник срабатывает, вывод SQW модуля выдаёт сигнал LOW.

Вы можете обнаружить этот сигнал с помощью Raspberry Pi Pico и вызвать прерывание. Эта функция чрезвычайно полезна для периодических задач, автоматизации на основе времени, а также одноразовых оповещений (потому что вы можете сбросить будильник после его срабатывания).

I2C-адрес модуля DS3231 RTC

По умолчанию адрес DS3231 RTC — 0x68, а EEPROM, подключённая к модулю, — 0x57. Вы можете запустить скетч I2C-сканера, чтобы проверить адреса.

Распиновка модуля DS3231 RTC

В следующей таблице кратко описана распиновка модуля DS3231 RTC.

Вывод

Описание

32K

Выход генератора 32 кГц — может использоваться как опорный тактовый сигнал

SQW

Выход прямоугольной волны / прерывания

SCL

Вывод SCL для I2C

SDA

Вывод SDA для I2C

VCC

Питание модуля (3.3 В или 5 В)

GND

GND

Подключение модуля DS3231 RTC к Raspberry Pi Pico

Подключение модуля DS3231 к Raspberry Pi Pico

Вот список деталей, необходимых для этого руководства:

  • Плата Raspberry Pi Pico

  • Модуль DS3231 RTC

  • Светодиод

  • Резистор 220 Ом

  • Перемычки (провода)

  • Макетная плата

Подключите DS3231 к Raspberry Pi Pico. Вы можете использовать следующую таблицу в качестве справки. Мы используем стандартные выводы I2C.

Модуль DS3231 RTC

Raspberry Pi Pico

SQW

GPIO 3 (или любой другой цифровой вывод)

SCL

GPIO 5

SDA

GPIO 4

VCC

3V3

GND

GND

Работа с RTC

Использование модуля RTC в ваших проектах всегда требует двух важных шагов.

  1. Установка текущего времени: вы можете сделать это вручную, вставив текущее время (или другое желаемое время) в код; использовать локальное время системы; или получить время с NTP-сервера.

  2. Сохранение времени: чтобы убедиться, что RTC сохраняет правильное время, даже при потере питания, к нему необходимо подключить батарейку. Модули RTC поставляются с держателем батареи, обычно для батарейки-таблетки.

Модуль DS3231 RTC для MicroPython

Существуют различные библиотеки с различными функциями, которые упрощают взаимодействие с модулем DS3231 RTC. Мы будем использовать слегка модифицированную версию модуля urtc.py, разработанного Adafruit.

Следуйте следующим шагам для установки необходимого модуля.

Загрузка и установка urtc.py

  1. Нажмите здесь, чтобы загрузить код urtc.py;

  2. Скопируйте код в файл в Thonny IDE;

  3. Перейдите в File > Save as… и выберите Raspberry Pi Pico;

  4. Сохраните файл с именем urtc.py (не меняйте имя).

# 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 = address

    def _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 temp

class 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)

После загрузки модуля на плату вы можете использовать функциональность библиотеки в вашем коде для взаимодействия с модулем DS3231 RTC.

DS3231 RTC с Raspberry Pi Pico: установка и получение времени

RPi Pico DS3231 — установка и получение времени

Следующий код синхронизирует время RTC с системным временем и получает текущую дату, время и температуру каждую секунду.

# 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)

Как работает код

Давайте кратко рассмотрим основные части этого кода.

Импорт библиотек

Сначала вам нужно импортировать модуль urtc, который вы загрузили ранее и который содержит функции для взаимодействия с RTC. Вам также нужно импортировать классы Pin и I2C для установления I2C-соединения с модулем.

import time
import urtc
from machine import I2C, Pin

Инициализация модуля RTC

Затем инициализируйте I2C-соединение и создайте объект с именем rtc для обращения к нашему модулю DS3231 RTC.

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

Синхронизация времени

Для синхронизации времени RTC вы должны использовать метод datetime() объекта rtc и передать в качестве аргумента кортеж времени следующего формата:

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

Примечание: этот кортеж отличается от того, который используется модулем time в MicroPython.

Мы можем установить время вручную на определённую дату и время следующим образом:

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

Или мы можем синхронизировать RTC с локальным временем системы. Мы будем синхронизировать время с локальным временем системы.

Сначала мы получаем кортеж локального времени с помощью time.localtime().

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

Возвращаемый кортеж отличается от того, который используется модулем urtc.

Поэтому сначала мы преобразуем его в секунды с помощью time.mktime().

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

И наконец, мы преобразуем его в кортеж, совместимый с библиотекой, используя функцию seconds2tuple() из библиотеки urtc, которая принимает в качестве аргумента количество секунд с начала эпохи для локального времени.

initial_time = urtc.seconds2tuple(initial_time_seconds)

Наконец, мы можем передать нашу переменную initial_time, содержащую локальное время в кортеже, совместимом с модулем urtc, в функцию datetime() следующим образом.

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

# Sync the RTC
rtc.datetime(initial_time)

Примечание: После установки времени в первый раз нам нужно устанавливать его снова только в том случае, если RTC потеряет питание (для предотвращения этого следует подключить батарейку). Вы можете использовать функцию lost_power() объекта rtc, чтобы проверить, терял ли он питание. Эта функция возвращает True или False соответственно.

Получение времени

После этих строк кода модуль RTC синхронизирован с вашим локальным временем, и вы можете просто вызвать rtc.datetime(), чтобы получить текущее время от RTC. Это возвращает объект со всеми элементами времени.

Чтобы получить и вывести каждый элемент времени, мы можем сделать следующее:

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])

Мы также можем получить текущую температуру с модуля, вызвав метод get_temperature() объекта rtc.

temperature = rtc.get_temperature()

Данные о температуре приходят в градусах Цельсия.

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

Мы выводим время и текущую температуру каждую секунду.

time.sleep(1)

Запуск кода

Запустите предыдущий код, чтобы синхронизировать время RTC с локальным временем и получить текущее время и температуру.

Запуск MicroPython-скрипта в Thonny IDE

С этого момента, если к RTC подключена батарейка, он будет сохранять время, синхронизированное с вашим локальным временем. Поэтому вам больше не нужно синхронизировать его, и вы можете просто вызвать rtc.datetime(), чтобы получить текущее время.

Raspberry Pi Pico с DS3231 на MicroPython — получение даты, времени и температуры

DS3231 с Raspberry Pi Pico — настройка будильников

Модуль DS3231 RTC позволяет настроить до двух будильников: будильник 1 и будильник 2. Когда время достигает времени будильника, модуль изменяет состояние вывода SQW с HIGH на LOW (активный низкий уровень). Мы можем проверить изменение на этом выводе и выполнить определённую задачу при срабатывании будильника.

Raspberry Pi Pico DS3231 — настройка будильников

Важные замечания о будильниках:

  • RTC позволяет сохранить до двух будильников;

  • одновременно может быть активен только один будильник;

  • после срабатывания будильника вы должны сбросить его флаг, чтобы избежать повторного срабатывания и сбоя Pico;

  • вы должны деактивировать один будильник перед активацией другого.

DS3231 — настройка будильников

Следующий пример показывает, как настроить два будильника. Он переключает светодиод при срабатывании будильника. Для тестирования этого примера добавьте светодиод с резистором 220 Ом к схеме. Подключите светодиод к 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.")

Как работает код

Давайте рассмотрим основные части кода для настройки будильников.

Определение выводов

Сначала вам нужно определить GPIO, который подключён к выводу SQW. Это вывод, который изменит состояние при срабатывании будильника. Мы выбрали подключение к GPIO 3, но вы можете выбрать любой другой вывод, если схема подключения соответствует.

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

Мы устанавливаем вывод SQW как вход с внутренним подтягивающим резистором (поскольку это вывод с активным низким уровнем).

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

Время будильников

Затем мы создаём две переменные типа datetime_tuple для сохранения времени будильников в переменные следующим образом:

# 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

Настройте будильники на нужное вам время.

Настройка вывода SQW как прерывания

Затем мы устанавливаем вывод SQW как прерывание, вызывая метод irq().

sqw_pin.irq(trigger=Pin.IRQ_FALLING, handler=on_alarm)

Метод irq() принимает следующие аргументы:

  • handler: это функция, которая будет вызвана при обнаружении прерывания, в нашем случае — функция on_alarm().

  • trigger: определяет режим срабатывания. Существует 3 различных условия. В нашем случае мы используем IRQ_FALLING для срабатывания прерывания всякий раз, когда вывод переходит из HIGH в LOW.

Функция on_alarm

Функция on_alarm проверяет, какой будильник сработал, и деактивирует этот будильник, чтобы сбросить его флаг. Эта функция будет вызвана при срабатывании будильника.

def on_alarm(pin):

Следующая строка проверяет, сработал ли будильник 1. Это позволяет нам деактивировать его сразу после срабатывания, а также выполнить определённую задачу в определённое время.

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

Эта строка сбрасывает флаг будильника 1 (возвращает его в исходное состояние).

rtc.alarm(False, alarm=0)  # Clear Alarm 1 flag

Затем мы переключаем состояние светодиода.

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

Аналогичные действия выполняются для будильника 2.

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() для настройки будильников.

Для настройки будильников сначала мы должны сбросить все предыдущие существующие будильники следующим образом.

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

Затем мы можем установить время будильников, вызвав метод alarm_time() объекта rtc. Передайте в качестве аргументов время будильника (переменная типа datetime_tuple) и номер будильника (0=будильник 1; 1=будильник 2).

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

Мы включаем прерывание для будильников, чтобы вывод SQW срабатывал при активации будильника.

rtc.interrupt(0)
rtc.interrupt(1)

Затем у нас есть оператор try, который при необходимости корректирует время RTC (если он потерял питание) и настраивает будильники.

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)

Обнаружение будильников выполняется в фоновом режиме, потому что мы настроили прерывание на выводе SQW.

Тестирование будильников

Установите будильники на ближайшее время, чтобы увидеть их в действии. Затем запустите код.

Запуск MicroPython-скрипта в Thonny IDE

Текущее время будет выводиться в консоль каждые пять секунд. Когда будильник сработает, вы получите сообщение в консоли и светодиод переключится.

DS3231 RTC с Raspberry Pi Pico на MicroPython — срабатывание будильников

DS3231 — настройка периодических будильников

Для настройки периодических будильников мы можем корректировать время будильника каждый раз, когда он срабатывает. Например, представьте, что вы хотите срабатывание будильника каждые 10 минут. Для этого:

  • считываем текущее время при срабатывании будильника;

  • устанавливаем новый будильник, добавив 600 секунд (10 минут) к текущему времени;

  • через 10 минут будильник сработает;

  • повторяем предыдущие шаги.

Именно это мы реализуем в следующем примере.

# 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.")

В этом примере мы создаём функцию с именем add_minutes_to_time(), которая добавляет минуты к текущему времени.

# 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)

Эта функция принимает в качестве аргументов кортеж datetime (например, текущее время) и количество минут, которые вы хотите добавить к текущему времени для настройки нового будильника в будущем. Эта функция возвращает новый кортеж datetime с новым временем будильника.

Когда будильник срабатывает, выполняется функция on_alarm(). Сначала она переключает состояние встроенного светодиода.

# 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

Затем она деактивирует (сбрасывает флаг будильника 1).

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

После этого она устанавливает новое время будильника, добавляя заранее определённое количество минут к текущему времени. В этом примере мы настраиваем будильник каждые 10 минут.

# 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}")

Вы можете легко изменить это, настроив нужное время в следующей строке.

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

Тестирование периодических будильников

Установите будильники на ближайшее время, чтобы увидеть их в действии. Затем запустите код.

Запуск MicroPython-скрипта в Thonny IDE

Первый будильник сработает через одну минуту. Следующие будильники будут срабатывать каждые 10 минут. При срабатывании будильника мы также переключаем состояние светодиода.

Raspberry Pi Pico с DS3231 — настройка периодических будильников

Заключение

В этом руководстве вы узнали, как использовать модуль часов реального времени DS3231 с Raspberry Pi Pico, программируемым на MicroPython, для получения текущей даты и времени и настройки будильников. Этот модуль может быть очень полезен в проектах логирования данных, автоматизации, периодических задачах и многих других приложениях.

Надеемся, что это краткое руководство оказалось полезным. У нас есть руководства для других датчиков и модулей, которые могут быть вам полезны: