Метео-лоток в огороде я собирал с прицелом на годы: корпус, солнечная панель, логгер на карту, строка замера раз в десять минут. Часы в нём вёл DS3231, и однажды мне в голову пришла взрослая мысль — подстраховаться. Лоток стоит без присмотра по два месяца: батарейка CR2032 подсядет, контакт отсыреет, чип однажды просто умрёт — и вся колонка замеров останется без времени, превратив журнал в набор красивых цифр без календаря. В ящике как раз лежал лишний DS1307: соберу-ка резерв, думал я, воткнул его в ту же шину параллельно — и посадил оба модуля. Сканер увидел один-единственный адрес 0x68, а вместо времени из резервного чипа читался мусор.
Полчаса я подозревал брак и перепаивал провода, пока не открыл два даташита рядом. Адрес на шине I2C у DS1307 — 0x68. У DS3231 — тоже 0x68. Жёстко зашит, без ножек выбора и без регистров настройки. Два RTC на одной шине по-простому не живут: они отвечают одновременно и глушат друг друга. Урок о том, как их всё-таки подружить — и стоит ли вообще это делать.
Сначала договоримся о смысле. Резервные часы нужны проектам, где потеря времени дороже лишних деталей: инкубатор, теплица за городом, счётчик наработки. Схема такая: главный чип держит время, резервный хранит второе мнение, скетч периодически их сверяет и решает, кому верить. Дальше — три обхода конфликта адресов, политика «кто главный» и скетч сверки с подгонкой, который прогоняет себя сам.
Один адрес на двоих
На шине I2C каждое устройство обязано иметь уникальный адрес — на этом держится вся адресация, о ней подробно говорилось в уроке про два провода I2C. Большинство периферии даёт выбирать: у EEPROM-микросхем серии 24Cxx есть ножки A0–A2, у некоторых датчиков — паяная перемычка. У DS1307 и DS3231 такой свободы нет: оба отвечают на 1101000b, то есть 0x68, и сделать с этим ничего нельзя — заядлые форумчане на arduino.ru подтверждают это с 2016 года дословно: «у DS3231 нет возможности изменить свой адрес на шине I2C».
Что происходит при двух одинаковых адресах на одной шине? Мастер шлёт запрос 0x68 — и отвечают оба чипа сразу. На линии данных возникает коллизия: два передатчика тянут провод в разные стороны, арбитраж шины ломается, в буфер прилетает каша или подтверждение не приходит вовсе. Хуже всего промежуточный вариант: иногда байты читаются «почти нормально», и кажется, что схема жива. На подоконнике такая система врёт непредсказуемо — классический класс багов, которые ловятся неделями.
Попутный вывод на будущее: 0x68 — вообще популярный адрес. Гироскопы MPU6050 и L3G4200D сидят на нём же — на форуме Амперки целая ветка про дружбу таких гироскопов с DS1307. Прежде чем дополнять плату новым модулем, пробегите шину сканером адресов и сверьтесь с даташитом кандидата.
| Способ | Что понадобится | Грабли |
|---|---|---|
| Мультиплексор TCA9548A | модуль за копейки: адрес 0x70, 8 каналов, переключение одним байтом | сам мультиплексор — новое звено в цепочке отказа |
| SoftwareWire: вторая шина | два свободных пина и библиотека SoftwareWire | скорость и тайминги ниже аппаратных, у ARM-плат бывают нюансы |
| Вторая аппаратная шина | плата с Wire1: ESP32, Arduino Mega | на классической Uno свободной шины нет |
Кто главный и как сверять
Два часа на плате — это не демократия, а иерархия. Главным я ставлю DS3231: его термокомпенсированный кварц держит ±2 ppm, около пяти секунд в месяц — вся механика разобрана в уроке про точность и компенсацию. Резервом идёт DS1307 с его уходом в минуты за месяц — полное сопоставление двух чипов лежит в сравнении DS1307 и DS3231. Правило простое: главный и резерв — не равноправные напарники, а эталон и страховой экземпляр.
Сверка выглядит так: раз в час скетч читает unixtime обоих чипов и считает разницу. unixtime — просто целое число секунд с 1970 года; вычитание двух целых надёжнее сравнения полей «час-минута-секунда», где легко напороться на переход через полночь: в 00:00:05 «резерв больше главного» превращается в ложную тревогу на целые сутки. Порог подгонки я держу на 5 секундах: если резерв уполз дальше — ему командой adjust записывают показания главного. Честное замечание: пара DS3231 плюс DS1307 уползает на те самые 5 секунд за считаные дни, поэтому подгонка здесь случается почти ежедневно — и это не признак неисправности, а плановая процедура: сама операция копеечна, один байт маски и пакет записи. Если хочется год между подгонками — ставьте резервом второй DS3231: два одинаковых чипа расходятся на секунды за месяцы, но тогда их всё равно придётся разводить по каналам мультиплексора.
Отдельный сценарий — смерть главного: master.begin() вернул отказ или чтение не отвечает. Тогда скетч поднимает флаг аварии, сигнализирует в журнал или зуммером и живёт от резерва. Но давайте не приукрашивать: DS1307 уплывает сам, и резерв спасает от ситуации «чип умер», а не от «время разъехалось». Настоящее второе мнение для критичной теплицы — синхронизация по сети, о которой шла речь в уроке про первый Wi-Fi на ESP32: NTP и RTC работают парой, как уже говорилось в уроке про millis против часов.
#include <Wire.h>#include <RTClib.h> #define MUX 0x70 // TCA9548A: адрес самого мультиплексора RTC_DS3231 master; // DS3231 на канале 0 — главныйRTC_DS1307 backup; // DS1307 на канале 1 — резерв void muxChannel(uint8_t ch) { Wire.beginTransmission(MUX); Wire.write(1 << ch); // открыть один канал, остальные закрыть Wire.endTransmission(); delay(2); // дать шине устаканиться после переключения} void setup() { Serial.begin(9600); Wire.begin(); muxChannel(0); if (master.begin() == false) Serial.println("DS3231 ne otvechaet"); muxChannel(1); if (backup.begin() == false) Serial.println("DS1307 ne otvechaet");} void loop() { muxChannel(0); DateTime m = master.now(); long uMaster = m.unixtime(); muxChannel(1); DateTime b = backup.now(); long uBackup = b.unixtime(); long diff = uMaster - uBackup; if (diff < 0) diff = -diff; Serial.print("diff="); Serial.println(diff); if (diff > 5) { // порог подгонки, секунд muxChannel(1); backup.adjust(m); // резерву — показания главного Serial.println("Rezerv podgnan k glavnomu"); } delay(3600000UL); // сверка раз в час}Как читать скетч. Функция muxChannel отправляет мультиплексору байт-маску: единица сдвигается на номер канала, остальные семь каналов закрываются. Всё общение с конкретным модулем идёт через его класс — RTC_DS3231 или RTC_DS1307, — а библиотека RTClib одна на оба чипа, классы отличаются только набором бонусов вроде getTemperature(). Между обращениями к разным модулям канал переключается, и в этом весь трюк: шина как будто одна, а «слышат» мастера по очереди.
Переключение стоит байт на шине — на стандартных 100 кГц это около 90 микросекунд на передачу, плюс пара миллисекунд паузы на всякий случай. Раз в час такая копейка неинтересна даже для батареи. А вот подгонку я сознательно делаю показаниями объекта: backup.adjust(m) пишет резерву весь снимок главного целиком — дата, время, секунды — вместо ручной сборки из шести полей, где на каждом шаге можно ошибиться в порядке аргументов.
Про аварии — последняя деталь каркаса. Отказ главного ловится на двух уровнях: begin() вернул false при старте или чтение now() отдало ерунду — в обоих случаях скетч поднимает флаг и переходит на резерв, а решение «работать дальше или замигать зуммером» уже вопрос политики проекта. У меня в лотке флаг прилетает строкой в журнал: по ней после двух месяцев я вижу, что главный чип нездоров, и меняю его по плану, а не по факту гибели прибора.
Что смотреть в прогоне
В симуляторе ниже та же логика сверки, но с честной оговоркой: второй RTC-объект движок не эмулирует — оба объекта делят одну внутреннюю ячейку. Поэтому резерв изображает константа-дельта, которая методично дрейфует; заодно учтите, что rtc.now() в движке возвращает застывший снимок на момент запуска. На настоящей плате обе шкалы идут сами, полный код — в блоке выше.
Смотрите на вывод как на три акта. Сначала diff = 12 — резерв накопил расхождение за время вашей спячки, и порог тут же отправляет его на подгонку. Затем дрейф: каждые три секунды демо-дельта подрастает на секунду — в реальности DS1307 набирает эту же секунду за дни. И снова подгонка — цикл замыкается: на плате весь этот виток растянут в тысячи раз, но ритм сверки и подгонки вы теперь видели вживую.
- У DS1307 и DS3231 один жёсткий адрес 0x68: без ухищрений их нельзя вешать на одну шину — отвечая одновременно, они глушат друг друга
- Три обхода: мультиплексор TCA9548A (0x70, 8 каналов, переключение одним байтом), SoftwareWire на двух пинах или вторая аппаратная шина Wire1 на ESP32/Mega
- Иерархия: главный — точный DS3231, резерв — DS1307; сверка unixtime раз в час, порог подгонки 5 секунд
- Подгонка — backup.adjust(master.now()); для пары 3231+1307 она плановая, раз в несколько дней
- Резерв спасает от смерти чипа, но не от расхождения хода: настоящее второе мнение — NTP-синхронизация по сети
- 1. Какой I2C-адрес у DS1307 и у DS3231?
- 2. Что происходит при запросе к двум устройствам с адресом 0x68 на одной шине?
- 3. Сколько каналов у мультиплексора TCA9548A и по какому адресу он сам живёт?
- 4. Кого назначить главным в паре DS3231 + DS1307?
- 5. Как выполняется подгонка резервных часов?
Резервные часы — вещь для проектов с ценой провала, и теперь у вас есть рабочий каркас: мультиплексор, иерархия, сверка с порогом. А что делать, когда модуля нет вовсе и ждать его с маркетплейса две недели? Можно собрать приличные программные часы прямо на millis() — с заводом из монитора порта, переходом через полночь и счётчиком суток. Их механика и честные пределы — в уроке про часы без модуля.
Частые вопросы по теме урока
- Какой I2C-адрес у DS1307 и у DS3231?
- Оба чипа жёстко зашиты на 0x68: ни ножек выбора, ни регистра настройки. Именно поэтому на одной шине они конфликтуют. У обоих 0x68, изменить нельзя
- Что происходит при запросе к двум устройствам с адресом 0x68 на одной шине?
- Два передатчика тянут линию одновременно, коллизия калечит данные. Иногда байты «почти читаются» — это худший вариант, врать будет непредсказуемо. Отвечают оба: арбитраж ломается, данные портятся
- Сколько каналов у мультиплексора TCA9548A и по какому адресу он сам живёт?
- TCA9548A — восемь каналов по адресу 0x70. Канал выбирается одним байтом-маской: 1 << номер канала. 8 каналов, адрес 0x70
- Кого назначить главным в паре DS3231 + DS1307?
- Главным ставят точный чип: DS3231 уходит на секунды за месяц, DS1307 — на минуты. Резерв — страховой экземпляр, а не равноправный напарник. DS3231: ±2 ppm против минут в месяц у DS1307
- Как выполняется подгонка резервных часов?
- Резерву записывают полный снимок главного командой adjust, когда разница unixtime перевалила за порог — в скетче урока это 5 секунд. backup.adjust(master.now()) при превышении порога расхождения