Raspberry Pi Pico: DS3231 RTC (часы реального времени) — получение времени и настройка будильников (MicroPython)
Это руководство по началу работы, которое показывает, как подключить модуль часов реального времени DS3231 к плате Raspberry Pi Pico, программируемой на 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 перезагрузится или потеряет питание.
DS3231 и DS1307 — одни из самых популярных вариантов для использования с микроконтроллерами. Оба совместимы с Raspberry Pi Pico и взаимодействуют по протоколу I2C. DS3231 более точен, потому что выдаёт результаты с температурной компенсацией. Кроме того, с DS3231 также можно настраивать внешние будильники, что может быть чрезвычайно полезно.
Знакомство с модулем DS3231 RTC
На следующем изображении показан модуль DS3231 RTC. Он использует кварцевый генератор 32 кГц с температурной компенсацией (TCXO) для точного отслеживания времени (устойчив к изменениям температуры). Благодаря этому он также позволяет получать данные о температуре.
Помимо точного отслеживания даты и времени, он также имеет встроенную память для хранения до двух будильников и может генерировать прямоугольные волны на различных частотах: 1 Гц, 4 кГц, 8 кГц и 32 кГц.
Связь с модулем RTC осуществляется по протоколу I2C. Обычно его адрес — 0x68.
Этот модуль также поставляется с EEPROM 24C32 на 32 байта, которую вы можете использовать для хранения любых энергонезависимых данных. Вы можете общаться с этой EEPROM-памятью по I2C, обращаясь к нужному адресу (0x57).
Держатель батареи DS3231
DS3231 поставляется с держателем батареи для подключения батарейки, обеспечивающей точное хронометрирование. В случае отключения питания он по-прежнему может точно отслеживать время и сохранять все будильники.
Вы должны использовать аккумулятор LIR2032, который является перезаряжаемым. Не используйте CR2032 (неперезаряжаемый).
Если вы хотите использовать батарейку CR2032, которая является неперезаряжаемой, вам необходимо отключить цепь зарядки батареи, отпаяв и удалив резистор (обозначенный R4 на моём модуле) рядом с диодом.
Будильники 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
Вот список деталей, необходимых для этого руководства:
Плата 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 в ваших проектах всегда требует двух важных шагов.
Установка текущего времени: вы можете сделать это вручную, вставив текущее время (или другое желаемое время) в код; использовать локальное время системы; или получить время с NTP-сервера.
Сохранение времени: чтобы убедиться, что RTC сохраняет правильное время, даже при потере питания, к нему необходимо подключить батарейку. Модули RTC поставляются с держателем батареи, обычно для батарейки-таблетки.
Модуль DS3231 RTC для MicroPython
Существуют различные библиотеки с различными функциями, которые упрощают взаимодействие с модулем DS3231 RTC. Мы будем использовать слегка модифицированную версию модуля urtc.py, разработанного Adafruit.
Следуйте следующим шагам для установки необходимого модуля.
Загрузка и установка urtc.py
Скопируйте код в файл в Thonny IDE;
Перейдите в File > Save as… и выберите Raspberry Pi Pico;
Сохраните файл с именем 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: установка и получение времени
Следующий код синхронизирует время 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 с локальным временем и получить текущее время и температуру.
С этого момента, если к RTC подключена батарейка, он будет сохранять время, синхронизированное с вашим локальным временем. Поэтому вам больше не нужно синхронизировать его, и вы можете просто вызвать rtc.datetime(), чтобы получить текущее время.
DS3231 с Raspberry Pi Pico — настройка будильников
Модуль DS3231 RTC позволяет настроить до двух будильников: будильник 1 и будильник 2. Когда время достигает времени будильника, модуль изменяет состояние вывода SQW с HIGH на LOW (активный низкий уровень). Мы можем проверить изменение на этом выводе и выполнить определённую задачу при срабатывании будильника.
Важные замечания о будильниках:
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.
Тестирование будильников
Установите будильники на ближайшее время, чтобы увидеть их в действии. Затем запустите код.
Текущее время будет выводиться в консоль каждые пять секунд. Когда будильник сработает, вы получите сообщение в консоли и светодиод переключится.
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
Тестирование периодических будильников
Установите будильники на ближайшее время, чтобы увидеть их в действии. Затем запустите код.
Первый будильник сработает через одну минуту. Следующие будильники будут срабатывать каждые 10 минут. При срабатывании будильника мы также переключаем состояние светодиода.
Заключение
В этом руководстве вы узнали, как использовать модуль часов реального времени DS3231 с Raspberry Pi Pico, программируемым на MicroPython, для получения текущей даты и времени и настройки будильников. Этот модуль может быть очень полезен в проектах логирования данных, автоматизации, периодических задачах и многих других приложениях.
Надеемся, что это краткое руководство оказалось полезным. У нас есть руководства для других датчиков и модулей, которые могут быть вам полезны: