На кухонной полке у меня стоял термометр-табло: время сверху, температура снизу. Часы DS3231 уже жили на шине с прошлого проекта, и экран хотелось добавить тем же способом — теми же двумя проводами. Но первый скетч я писал с лёгкой тревогой новичка: LiquidCrystal_I2C для экрана, RTClib для часов — не задушат ли две библиотеки друг друга за одну пару линий A4/A5? Экран, между прочим, молчал в тот вечер не из-за библиотек — из-за адреса 0x3F вместо 0x27, и это отдельная история, которую предупреждает сканер I2C из своего урока.
Спойлер: библиотеки не подерутся никогда, если у устройств разные адреса. В этом уроке соберём полноценную пару — LCD1602 на 0x27 и часы DS3231 на 0x68 — в одном скетче: время тикает на экране, обе библиотеки ходят через один и тот же Wire. Два модуля займут ровно два адреса из ста семнадцати — простор на шине ещё есть. Разберём, почему шина не путается, сколько трафика ест обновление экрана и какой порядок подключения второго модуля экономит вечера.
Почему LCD и RTC не подерутся
Вся магия — в адресации. Мастер называет адрес в начале каждой отдельной, замкнутой передачи: старт-условие, адрес 0x27, байты, стоп-условие. Следующая передача — снова старт, уже 0x68, свои байты, свой стоп. Оба устройства слушают обе линии постоянно, но откликается только тот, чьё имя прозвучало. LCD на 0x27 (это 39 в десятичной) и часы на 0x68 (104) в передачах не встречаются — как соседи по подъезду, которые отзываются только на свою фамилию. Отсюда и первое правило отладки: если на шине ожил только один из двух модулей — начните со второго адреса, а не со скетча.
Библиотеки при этом ничего не знают друг о друге. LiquidCrystal_I2C и RTClib обе ходят через один объект Wire — beginTransmission, write, endTransmission — и не лезут в чужие передачи. Второй Wire.begin() в скетче не нужен: шина одна на всех, инициализируется один раз. Я однажды по невнимательности оставил второй begin() в loop — шина перезапускалась каждый оборот, и экран помигивал. Именно поэтому «несколько устройств на I2C» — не продвинутая магия, а нормальный режим работы шины: диапазон 0x03-0x77 позволяет расселить десяток соседей, и весь механизм мы разбирали в первом уроке раздела.
Посмотреть на разделение труда можно прямо в панели I2C симулятора, а на живой плате — в логическом анализаторе за пару долларов. Кадр записи на экран выглядит так: старт, байт адреса 0x27 с битом записи, байты данных, стоп. Кадр чтения времени — старт, 0x68 с битом чтения, семь байтов от часов, стоп. Между кадрами шина свободна: очередь не образуется, пока общий трафик на порядок ниже пропускной способности шины.
| Устройство | Адрес | Кто ходит по шине |
|---|---|---|
| LCD1602 с PCF8574 | 0x27 | только запись: символы и команды экрана |
| Часы DS3231 | 0x68 | запись и чтение: время и температура чипа |
| Память AT24C32 на плате часов | 0x50 | запись и чтение: 4 КБ энергонезависимой памяти |
| OLED SSD1306 | 0x3C | только запись: кадры экрана |
Отдельная ловушка для внимательных: типовой модуль часов ZS-042 с фиолетовой платой несёт два чипа. Сами часы DS3231 живут на 0x68, а рядом припаяна микросхема памяти AT24C32 — её адрес 0x50. Первый сканер на таком модуле пугает: на плате «два устройства», хотя вы подключали одно. Это не конфликт, это норма: запишите оба адреса, память потом пригодится под журнал показаний — с ней проект переживает отключение питания без потерь, а пока она просто не мешает.
Перед сборкой любого многосборного скетча мой порядок железный: сначала сканер, потом скетч. Тридцать секунд показывают, кто на какой двери живёт и не столкнулись ли новые соседи. Пин SQW на модуле — программируемый меандр, для базового скетча он не нужен и остаётся свободным. А если выбор самих часов ещё впереди — разница между DS1307 и DS3231 по точности, цене и батарейке разобрана в сравнительном уроке.
Один скетч, две библиотеки, одна шина
#include <Wire.h>#include <LiquidCrystal_I2C.h>#include <RTClib.h> LiquidCrystal_I2C lcd(0x27, 16, 2); // экран: адрес 0x27RTC_DS3231 rtc; // часы: адрес 0x68 void p2(int v) { // две цифры с ведущим нулём if (v < 10) lcd.print("0"); lcd.print(v);} void setup() { Serial.begin(9600); Wire.begin(); // шина одна на всех lcd.init(); // экран: init -> backlight -> печать lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Vremya:"); rtc.begin(); // часы на той же шине if (rtc.lostPower()) { // питание пропадало? rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); Serial.println("lost=1, vremya zavedeno"); }} void loop() { DateTime now = rtc.now(); // чтение с адреса 0x68 lcd.setCursor(0, 1); // вторая строка экрана p2(now.hour()); lcd.print(":"); p2(now.minute()); lcd.print(":"); p2(now.second()); Serial.print("h="); Serial.print(now.hour()); Serial.print(" m="); Serial.print(now.minute()); Serial.print(" s="); Serial.println(now.second()); delay(1000);}Разбор — по церемониям, потому что тут их три вида вперемешку. Инициализация экрана строго в порядке init(), backlight(), потом печать; нарушение порядка я обжигался ещё в уроке про первый текст на LCD: подсветка до init выглядит как мёртвый экран, хотя шина исправна. Часы инициализируются rtc.begin() и проверкой lostPower(): если питание пропадало, время заводится строкой компиляции — вся механика adjust и батарейки разобрана в уроке про DS3231.
Функция p2() печатает число с ведущим нулём, чтобы 9:05 не выглядело как 9:5. Заметьте, обе библиотеки живут в одном скетче мирно и не требуют ни второго Wire.begin(), ни разделения по времени: каждая передача открывается своим адресом, и шина сама разносит трафик по нужным дверям.
Одна деталь под капотом: DS3231 хранит время в двоично-десятичном формате — каждая цифра в своей тетраде битов, 23 минуты лежат как 0x23. RTClib декодирует BCD за вас, поэтому now.minute() возвращает честное число. Если когда-нибудь будете читать регистры часов напрямую через Wire, про декодирование придётся вспомнить самому — а пока библиотека прячет эту кухню за готовыми методами.
Сколько трафика это ест? Строка «HH:MM:SS» — восемь байтов, и каждый байт на 100 кГц занимает около 90 мкс, значит перерисовка строки — меньше миллисекунды. Чтение времени — семь байтов от 0x68 — ещё примерно 0,6 мс. За секунду шина занята около полутора миллисекунд из тысячи: полторы сотых процента. Чтение температуры из чипа через getTemperature() добавит два байта на кадр — тоже ни о чём; никакой тесноты даже десяток модулей не создал бы.
Проблемы начинаются не с трафика, а с дисциплины цикла. delay(1000) в примере — учебное упрощение: в реальном проекте, где в loop живут кнопки и датчики, экран обновляют по millis-таймеру, как в уроке про millis вместо delay — там скелет неблокирующего таймера разобран по косточкам. Разгон шины до 400 кГц тут не нужен: часы и экран живут на 100 кГц из коробки, а выигрыш в микросекундах никто не увидит. Библиотекам всё равно: rtc.now() можно дёргать хоть раз в секунду, хоть раз в минуту, экран — по своему расписанию.
Пара «экран плюс часы» хороша ещё и разной скоростью жизни. Экрану всё равно, как часто его перерисовывать, а часы тикают сами, без мастера: выключите плату — DS3231 продолжит счёт от батарейки, и включится всё обратно уже с правильным временем. На проектах-логгерах мне хватает этого с запасом: плата просыпается раз в час, читает время, пишет показания и засыпает — шина занята считанные миллисекунды, а счёт времени не прерывается ни на секунду.
Порядок подключения второго модуля
Мой порядок, отработанный на четырёх проектах с парой «экран плюс часы». Шаг первый: подключить модуль и прогнать сканер — записать адреса. Шаг второй: вписать адреса в скетч, залить, проверить lostPower и ход времени. Шаг третий: вычистить отладку и оставить рабочий цикл. Тот же порядок распространяется на третий и четвёртый модуль: датчик BME280 встанет на 0x76, OLED — на 0x3C, и никто ни с кем не столкнётся. Если сканер не увидел новый адрес — не вписывайте его в скетч вслепую: сначала питание и земля нового модуля, потом повторный скан.
Две границы здравого смысла. Первая — электрическая: больше пяти-шести модулей на макетке я не вешаю, бортовые подтяжки складываются параллельно и просаживают шину; вся арифметика — в уроке про подтяжку и номиналы. Вторая — адресная: правило на память — два одинаковых модуля на одну шину не ставят; сначала смотрят на джамперы, а если их нет — на мультиплексор, ему посвящён следующий урок.
- Мастер называет адрес в начале каждой передачи, поэтому LCD 0x27 и RTC 0x68 спокойно живут на одной шине
- Обе библиотеки ходят через один Wire: второй begin() не нужен, шину инициализируют один раз
- На модуле DS3231 (ZS-042) два устройства: часы 0x68 и память AT24C32 на 0x50 — это не конфликт
- Экран: init() → backlight() → печать; часы: begin() + lostPower() и adjust один раз в setup
- Восемь байтов строки занимают на 100 кГц меньше миллисекунды; обновляйте экран по millis-таймеру
- 1. Почему LCD на 0x27 и RTC на 0x68 не мешают друг другу?
- 2. Сколько раз в скетче с двумя I2C-библиотеками вызывают Wire.begin()?
- 3. Сканер нашёл на модуле часов два устройства: 0x68 и 0x50. Что это значит?
- 4. Что случится, если rtc.adjust(DateTime(F(__DATE__), F(__TIME__))) оставить в loop()?
- 5. Строка из восьми символов уезжает на LCD по шине 100 кГц. Сколько это займёт?
Частые вопросы по теме урока
- Почему LCD на 0x27 и RTC на 0x68 не мешают друг другу?
- Каждая передача открывается стартом и адресом: откликается только устройство с этим адресом, остальные молчат и не слушают данные. Мастер адресует каждую передачу отдельно, откликается только названное устройство
- Сколько раз в скетче с двумя I2C-библиотеками вызывают Wire.begin()?
- Шина одна на всех, инициализируется единожды в setup(). Повторный begin() не сломает шину, но и не нужен. Один раз — шина общая
- Сканер нашёл на модуле часов два устройства: 0x68 и 0x50. Что это значит?
- Типовой модуль ZS-042 несёт два чипа: сами часы на 0x68 и энергонезависимую память AT24C32 на 0x50. Конфликта тут нет. На плате часы DS3231 и память AT24C32 — так и задумано
- Что случится, если rtc.adjust(DateTime(F(__DATE__), F(__TIME__))) оставить в loop()?
- adjust зашивает время компиляции скетча; в loop() он отматывает часы при каждом проходе — время замирает или прыгает в прошлое. Время будет отматываться к моменту компиляции при каждом проходе
- Строка из восьми символов уезжает на LCD по шине 100 кГц. Сколько это займёт?
- Байт на 100 кГц занимает около 90 мкс, восемь байтов — примерно 0,7 мс. Трафика даже десяток модулей не создаст тесноты. Около 0,7 мс — копейки