Теплица с микрозеленью требовала два расписания сразу: свет включать каждый день в 6:00, а форточку проветривания открывать каждый час ровно на :30. Я развёл их по двум будильникам чипа — и первый запуск похоронил классически: утром Alarm 1 сработал один раз и навсегда. Флаг A1F в регистре статуса я не сбросил, пин остался прижат к земле, в полдень казалось, что часы сломались. Чинится записью нуля в один бит — но со стороны эта строка выглядит необязательной, поэтому её пропускают все, кто читает даташит по диагонали. Я тоже пропустил.
В этом уроке раскладываем оба будильника по полкам: регистры, режимы срабатывания от секунды до дня недели, общий пин прерывания и флаги, которые переживают даже перезагрузку платы. Соберём скетч с setAlarm1 и setAlarm2 для настоящей платы, а в симуляторе воспроизведём ту же логику сравнениями и флагами: регистры DS3231 движок не эмулирует, и я честно покажу, где проходит эта граница. Софтверную версию будильника на флаге в loop мы уже собирали в уроке про часы с будильником — здесь то же расписание уходит внутрь чипа. Зачем переносить: аппаратный будильник сверяет время сам, даже когда скетч занят или спит, а различение двух сработок берёт на себя чип, а не ваша логика флагов.
Два будильника внутри чипа
Alarm 1 сидит в регистрах 0x07–0x0A: секунды, минуты, часы и день/дата, у каждого поля свой бит-маска A1M — она решает, сверять ли поле вообще. Alarm 2 занимает регистры 0x0B–0x0D, и поля секунд в нём нет: точность до минуты. Отсюда главное различие пары: Alarm 1 умеет звонить каждую секунду и в заданную секунду, Alarm 2 — каждую минуту и в заданную минуту. Сверка идёт на каждом обновлении секунд: раз в секунду чип перечитывает время и сравнивает его с обоими наборами регистров, независимо от того, чем занят ваш скетч.
Под капотом setAlarm1 пишет в регистры байты в BCD-упаковке — двоично-десятичном формате, где один байт несёт две цифры: 0x30 — это тридцать секунд, а не сорок восемь. Библиотека конвертирует сама, но если однажды загляните в дамп регистров через сканер шины, не пугайтесь «нечитаемых» значений. При режиме DS3231_A1_Hour имеют смысл только час, минута и секунда — дату чип проигнорирует по маске, и один и тот же DateTime годится для всех режимов подряд.
В RTClib вся эта кухня спрятана за двумя вызовами: rtc.setAlarm1(DateTime, режим) и rtc.setAlarm2(DateTime, режим) — в DateTime значимы только поля, выбранные режимом. Ряд режимов Alarm 1: DS3231_A1_PerSecond — каждую секунду; DS3231_A1_Second — раз в минуту на заданной секунде; DS3231_A1_Minute — каждый час на заданной минуте; DS3231_A1_Hour — каждый день в заданный час; DS3231_A1_Date — в конкретное число месяца; DS3231_A1_Day — каждую неделю в заданный день недели. У Alarm 2 тот же ряд без секундных вариантов, плюс DS3231_A2_PerMinute — каждую минуту. Проверка и сброс: rtc.alarmFired(1) читает флаг, rtc.clearAlarm(1) гасит его. Кстати, DS1307 будильников не имеет вовсе — ещё один аргумент из сравнения DS1307 и DS3231.
Как я выбираю режим под задачу. Вопрос «во сколько каждый день» — это A1_Hour: свет в аквариуме в 7:00:00. «По вторникам» — A1_Day с днём недели в DateTime. Полив первого числа — A1_Date: дата в регистре, месяц ни при чём, чип сработает и в январе, и в июле. Для теста скетча, не дожидаясь утра, ставлю A1_PerSecond — звонит каждую секунду, идеально для отладки ветки сработки. Второй будильник обычно служит метрономом: A2_PerMinute шевелит лог раз в минуту, A2_Hour — почасовой пинг в журнал. Два независимых таймера закрывают оба слоя расписания — событие и периодику — без единого if в loop.
#include <Wire.h>#include <RTClib.h> RTC_DS3231 rtc; void setup() { Serial.begin(9600); if (rtc.begin() == false) { while (1) { } } // A1: каждый день в 06:00:00 — значимы только час, минута, секунда rtc.setAlarm1(DateTime(2025, 1, 1, 6, 0, 0), DS3231_A1_Hour); // A2: каждый час ровно на :30 — значима только минута rtc.setAlarm2(DateTime(2025, 1, 1, 0, 30, 0), DS3231_A2_Minute);} void loop() { if (rtc.alarmFired(1)) { Serial.println("A1: svet v 06:00"); rtc.clearAlarm(1); // без этого пин останется низким } if (rtc.alarmFired(2)) { Serial.println("A2: provetrivanie :30"); rtc.clearAlarm(2); } delay(200);}Пин, флаги и цена забытого сброса
Флаги A1F и A2F живут в регистре статуса 0x0F. Чип ставит их аппаратно в момент совпадения, а сбрасывает только мастер — записью нуля в бит; ровно это делает rtc.clearAlarm(). Пока флаг поднят, пин INT/SQW держится низким: выход — открытый сток, поэтому внешняя подтяжка резистором 10 кОм к питанию обязательна, а «сработало» выглядит как падение уровня на пине. Оба будильника делят этот пин на двоих, и различить, кто звенел, можно только по флагам — потому в loop я читаю оба alarmFired() подряд, а не выхожу после первого. Включённая пара не мешает друг другу: два независимых сравнителя в одном чипе, каждый со своим флагом и своим битом разрешения.
Роль пина переключается битом INTCN в регистре управления 0x0E: единица даёт сигнал прерывания от будильников, ноль — прямоугольную волну SQW с частотой из битов RS: 1 Гц, 1.024 кГц, 4.096 кГц или 8.192 кГц на выбор. RTClib выставляет нужные биты сама при setAlarm, но помнить про INTCN стоит: если будильник молчит, а скетч правильный, первым делом смотрите, не остался ли пин в режиме меандра. За температурным соседом этих регистров — датчиком кристалла с шагом 0,25 °C — ходят в уроке про точность и компенсацию.
Теперь ловушка, с которой начался урок. Регистры DS3231 питаются от CR2032, поэтому флаг A1F переживает не только будильную минуту, но и перезагрузку самой платы. Мой тепличный сценарий: свет отключили на день, плата перезапустилась, а флаг от утреннего срабатывания всё ещё поднят — скетч при старте честно решил, что будильник звенел только что, и включил свет в полночь. Лечение: сразу после rtc.begin() в setup() спрашиваем оба alarmFired() и сбрасываем старые сработки с записью в журнал — вместе с проверкой lostPower() из урока про установку RTClib это две обязательные строки шапки. Время из rtc.now() рядом с флагом сохраняйте в лог — момент пропущенной сработки стоит восстанавливать хотя бы с точностью до минуты.
| Alarm 1 | Alarm 2 | |
|---|---|---|
| Регистры | 0x07–0x0A + маски A1M | 0x0B–0x0D + маски A2M |
| Точность | до секунды | до минуты — поля секунд нет |
| Особых режимов | каждую секунду и раз в минуту на заданной секунде | каждую минуту |
| Типовое применение | будильник, точный импульс по времени | почасовой и поминутный пинг, лог |
| Максимальный ритм | раз в секунду (A1_PerSecond) | раз в минуту (A2_PerMinute) |
- Секундная точность. Отправьте a1 30 и засеките: сработка пройдёт через тридцать секунд секунда в секунду — так удобно проверять скетч, не дожидаясь шести утра.
- Граница минуты. Отправьте a2 2 и следите за stat: флаг A2F поднимется на :00 нужной минуты — раньше ждать бессмысленно, у Alarm 2 поля секунд нет.
- Перезарядка. После сработки отправьте ту же команду снова: в симуляторе это аналог нового setAlarm, а на плате после clearAlarm() будильник заряжается сам до следующего совпадения.
- Звук на плате. В ветку alarmFired(1) добавьте tone(8, 1000, 500) — и настольные часы из первого урока превратятся в будильник с аппаратной точностью.
- Alarm 1 — регистры 0x07–0x0A с точностью до секунды; Alarm 2 — 0x0B–0x0D, только минуты и грубее
- Флаги A1F/A2F в регистре статуса 0x0F ставит чип, сбрасывает только мастер: rtc.clearAlarm(1) и rtc.clearAlarm(2)
- Пин INT/SQW общий для обоих будильников, открытый сток с подтяжкой: сработка — LOW, различение — по флагам
- Режимы от «каждую секунду» до «в день недели» задаются масками — в RTClib это константы DS3231_A1_* и DS3231_A2_*
- Регистры и флаги живут от CR2032: после перезагрузки платы читайте alarmFired() в setup() и сбрасывайте старые сработки
- 1. Чем Alarm 2 отличается от Alarm 1?
- 2. Где живут флаги A1F и A2F?
- 3. Что случится, если не вызвать clearAlarm() после сработки?
- 4. Почему сработавший будильник распознают по флагам, а не по уровню пина?
- 5. Флаг A1F поднят, и в этот момент плату перезагрузили. Что увидит скетч после старта?
Два аппаратных будильника закрывают расписания, которые на софтверных флагах превращаются в кашу из if-ов. Следующий шаг — пин: сработка может не опрашиваться в loop, а ловиться прерыванием — механика разобрана в уроке про attachInterrupt, останется соединить INT/SQW с пином 2 и настроить FALLING. Показания на экран выводить умеют оба циферблата этой тройки — клеточный LCD и пиксельный OLED со статусной строкой ALARM. А счёт срабатываний удобно вести в энергонезависимой памяти: приёмы из урока про счётчик на EEPROM ложатся сюда без переделок — записать момент сработки в память стоит пару строк, а журналу даёт дату и время.
Частые вопросы по теме урока
- Чем Alarm 2 отличается от Alarm 1?
- В регистрах Alarm 2 поля секунд нет — грубейшая граница одна минута. Зато есть режим «каждую минуту», на котором удобно держать метроном лога. Не умеет секунды: точность срабатывания — минута
- Где живут флаги A1F и A2F?
- Оба флага — биты регистра статуса 0x0F. Чип ставит их в момент совпадения, мастер сбрасывает записью нуля через clearAlarm(). В регистре статуса 0x0F
- Что случится, если не вызвать clearAlarm() после сработки?
- Флаг держит пин низким — так устроен открытый сток. Пока флаг поднят, вторая сработка не видна, а скетч, читающий уровень пина, уверен, что будильник звонит без паузы. Пин INT останется прижатым низким, пока флаг не сброшен
- Почему сработавший будильник распознают по флагам, а не по уровню пина?
- Оба будильника тянут общий INT/SQW к земле. Единственный способ различить источник — прочитать A1F и A2F в регистре статуса в одном проходе loop. Пин один на оба будильника — уровень не скажет, кто звенел
- Флаг A1F поднят, и в этот момент плату перезагрузили. Что увидит скетч после старта?
- Питание регистров держит CR2032, поэтому сработка переживает и перезагрузку, и полное отключение платы. Шапка скетча читает и сбрасывает флаги сразу после begin() — иначе первый же проход loop поверит в свежую сработку. Флаг всё ещё поднят: регистры живут от батарейки