Теплица на утеплённой лоджии, сервер под столом, между ними — четыре метра стены. Первую версию я связал UART'ом: пины 0 и 1, крест-накрест, TX к RX. Работало, но каждое обновление прошивки превращалось в операцию: отключи провода, потому что пины 0 и 1 делят обвязку USB-порта (про эту пару я писал в самом первом уроке про Serial), прошьй, подключи обратно. Третий раз я забыл отключить — и полчаса вылавливал «случайные» символы в мониторе, грея подозрениями кабель, который был ни при чём. Потом я посчитал: показания нужны раз в секунду, гнаться за скоростью незачем, а вот пины занимать — жалко. Так я и пришёл к шине на A4 и A5.
I2C решает обе беды сразу: шина живёт на A4 и A5, которые к USB не имеют никакого отношения, и прошивать можно, не трогая проводов. В этом уроке соберём классическую пару из официальных примеров — master reader и slave sender: мастер опрашивает слейв раз в секунду, слейв отвечает двумя байтами показаний, а мастер ещё и передаёт слейву команду. Пакет из двух байтов, общая земля и никаких модулей-посредников — на этой связке у меня держалась вся теплица, включая автополив, о котором я писал в разборе Millis-планировщика. За два сезона я ни разу не трогал провода связи — менялись только прошивки.
Три провода и никакого креста
Соединение из трёх проводов: SDA первой платы — к SDA второй (пин A4), SCL — к SCL (пин A5), земля — к земле. Не крест-накрест! По привычке от UART руки тянутся поменять местами — здесь крестить нельзя в принципе: SDA и SCL — общие линии, по которым ходят и вопросы, и ответы; у них нет «передатчика» и «приёмника», оба конца одинаковые. Я один раз перекрестил по памяти от старого скетча — и час искал, почему «шина сломалась», хотя сломал её руками за десять секунд до этого. Мнемоника, которой я пользуюсь теперь: у шины нет отправителя, перекрещивать нечего — одинаковый провод к одинаковому пину.
Подтяжки: при Wire.begin() Uno включает внутренние подтяжки порядка 30–50 кОм, и на коротких проводах до 20–30 см их хватает. Дальше — внешние 4,7 кОм, про механику которых я подробно писал в первом уроке раздела. Питание: каждая плата может сидеть на своём USB — уровни у обеих одинаковые, 5 В, и плюсы объединять не нужно. А вот общую землю никто не отменял: без провода GND между платами шина не работает в принципе — уровни сигналов сравниваются относительно общего минуса, о чём я уже обжигался в уроке про общий провод. На паре Uno внутренние подтяжки обеих плат работают параллельно — суммарно 15–25 кОм, и для двух плат на одном столе этого достаточно; внешние резисторы оставьте для длинных линий и шин, где устройств три и больше.
И заранее договоримся о ролях, чтобы не путаться: плата с датчиками — слейв с адресом 0x08, плата с экраном — мастер. Мастер командует темпом: сам решает, когда спросить и когда сказать. Слейв не просит внимания — он всегда готов, в этом и прелесть роли ведомого; как устроен его скетч изнутри, мы разбирали в прошлом уроке про слейва, здесь возьмём его целиком. Если ваш слейв уже прошит и проверен сканером — считайте, треть работы сделана.
| Критерий | UART (Serial) | I2C (Wire) |
|---|---|---|
| Соединение | TX→RX и RX→TX, крест-накрест | SDA→SDA и SCL→SCL, напрямую |
| Пины на Uno | 0 и 1 — делят обвязку USB-порта | A4 и A5 — прошивке не мешают |
| Кто задаёт темп обмена | оба конца равноправны | только мастер |
| Третья плата в сети | отдельная пара проводов на каждую | тот же кабель, новый адрес |
| Сигнальных проводов | два | два, плюс общий GND |
#include <Wire.h> volatile int level = 0; // свежие показания датчикаvolatile int ustavka = 512; // уставка, присланная мастеромvolatile byte command = 0; // что мастер просит сейчас void sendAnswer() { // мастер просит два байта int value = (command == 2) ? ustavka : level; Wire.write(value >> 8); // старший байт числа Wire.write(value & 0xFF); // младший байт} void gotCommand(int howMany) { // мастер назвал команду while (Wire.available() > 0) { command = Wire.read(); // 1 = показания, 2 = уставка }} void setup() { Wire.begin(0x08); // слейв, адрес 0x08 Wire.onRequest(sendAnswer); Wire.onReceive(gotCommand); Serial.begin(9600); Serial.println("Слейв 0x08 готов");} void loop() { level = analogRead(A0); // «датчик» теплицы на A0 delay(200); // обновление раз в 200 мс}#include <Wire.h> unsigned long lastPoll = 0; // расписание опроса void setup() { Wire.begin(); // мастер Serial.begin(9600);} void loop() { if (millis() - lastPoll < 1000) return; // опрос раз в секунду lastPoll = millis(); // 1. Команда: 1 — отдай показания Wire.beginTransmission(0x08); Wire.write(1); Wire.endTransmission(); // STOP delay(20); // слейву пауза между операциями // 2. Просим два байта и собираем число int n = Wire.requestFrom(0x08, 2); if (n == 2) { int hi = Wire.read(); // старший байт int lo = Wire.read(); // младший байт Serial.print("pokazanie="); Serial.println(hi * 256 + lo); } else { Serial.println("Слейв молчит"); }}Арифметика пакета: 1023 = 0x03 и 0xFF
Почему два байта: analogRead() отдаёт 0–1023, а один байт вмещает максимум 255. Пакет из старшего и младшего байтов покрывает диапазон целиком: 1023 — это 0x03 и 0xFF, потому что 3 × 256 + 255 = 1023. Проверка на середине: показание 700 — это 2 × 256 + 188, то есть пакет 0x02 и 0xBC. Мастер собирает число обратно одним выражением: hi * 256 + lo. Дробные типы тут не нужны: для процентов влажности точности 1/1024 хватает с запасом, а float ехал бы четырьмя байтами и требовал одинакового представления на обеих платах — лишний повод для расхождений. Собирать число лучше не в голове, а в калькуляторе рядом с макеткой: старший байт умножили на 256, прибавили младший, сверили с ожидаемым — и идём дальше.
Порядок байтов — старший первым — чистая договорённость, без которой пакет не работает. Так принято по умолчанию, но встречается и обратный порядок; важно одно — обе стороны должны договориться об одном и том же. Я три вечера получал «дышащие» значения после правки, потому что складывал байты в разном порядке на двух платах: 3 × 256 + 255 и 255 × 256 + 3 — разные числа из одинаковых деталей. Если шестнадцатеричная запись пока путается, я собрал её разбор в отдельном уроке про BIN, HEX и DEC. А саму договорённость о порядке байтов я записываю в комментарий к обоим скетчам — с тех пор эти проблемы не возвращались.
Панель I2C показывает две операции на каждый цикл: «START, адрес 0x8» с байтом команды и стопом, затем «Запрос 0x8: 2 байт». Между ними — пауза 20 мс, которую я вставил специально: слейв успевает выйти из обработчика и подготовить ответ. В Serial каждые 1000 мс падает строка с двумя байтами и собранным числом; в симуляторе это 42, 42 и 10794 — подпись заглушки, а на живой паре байты будут прыгать за ручкой потенциометра на A0 слейва, и собранное число ляжет в диапазон 0–1023. Первое, что я делаю на живой паре: кручу ручку из конца в конец и слежу, чтобы собранное число прошло от 0 до 1023 без скачков; отрицательные или прыгающие через сотню значения означают перепутанный порядок байтов.
Про расписание. Опрос завязан на millis(), а не на delay(), и период — 1000 мс — заведомо длиннее цикла слейва: тот обновляет показания каждые 200 мс. Правило то же, что в уроке про планировщик: мастер никогда не блокируется, поэтому между опросами он успевает перерисовать экран и опросить кнопки. Сожмёте период до 100 мс — начнёте читать одни и те же значения дважды: слейв просто не успеет обновить уровень. Это не сбой связи, а арифметика: 200 мс цикла против 100 мс опроса — старое значение приходится на каждый второй запрос. В симуляторе этой стороны не видно — слейва-то нет, — но привычка выбирать период закладывается именно здесь.
Эксперименты для закрепления
- Соберите число в обратном порядке: lo * 256 + hi. Сравните с правильным — так выглядит перепутанный порядок байтов, моя самая дорогая договорённость из трёх вечеров.
- Попросите один байт: Wire.requestFrom(0x08, 1). Без младшего байта собранное значение будет прыгать шагами по 256 — наглядно, зачем нужен второй байт.
- Поставьте период опроса 300 мс вместо 1000 и посчитайте в Serial, как вырастет частота строк; на живой паре это быстро упрётся в 200-миллисекундный цикл слейва.
- Связь двух Uno: SDA→SDA (A4), SCL→SCL (A5) напрямую; крест-накрест, как в UART, — нельзя
- GND между платами обязателен и соединяется первым; питание у каждой может быть своё
- Показания 0–1023 едут пакетом из двух байтов: старший, младший, сборка hi * 256 + lo
- Слейв: Wire.begin(0x08) + onRequest/onReceive; мастер: beginTransmission → requestFrom
- Опрос на millis() с периодом заведомо длиннее цикла слейва — иначе мастер читает прошлое
- 1. Как соединить две Uno по I2C?
- 2. Число 1023 в пакете из двух байтов — это:
- 3. Чем пины A4/A5 удобнее UART-пинов 0 и 1 для межплатной связи?
- 4. Слейв обновляет показания каждые 200 мс. Каким делать период опроса мастера?
- 5. В симуляторе мастер собрал из заглушки число 10794. Откуда оно взялось?
Две платы на одной шине — это уже маленькая сеть: мастер собирает данные и раздаёт команды, слейв работает по месту, а вся сеть занимает два пина мастера. Дальше — последний рубеж раздела: заглянем внутрь готовых датчиков. Экраны, часы и акселерометры, которые вы подключаете одной библиотекой, — тоже слейвы, и у каждого внутри карта регистров. Разобравшись с ней в следующем уроке, вы перестанете зависеть от чужих примеров: datasheet начнёт читаться за двадцать минут, а библиотека — в случае чего — за десять. А пока два упражнения на сон грядущий: выключите питание и перекрестите провода, предскажите, что покажет сканер при включении, — это быстрее, чем читать теорию про «открытый сток».
Частые вопросы по теме урока
- Как соединить две Uno по I2C?
- Линии I2C общие: у них нет передатчика и приёмника, поэтому крестить не нужно. Земля между платами обязательна — уровни не с чем сравнивать относительно общего минуса. SDA→SDA, SCL→SCL, плюс общий GND
- Число 1023 в пакете из двух байтов — это:
- 3 × 256 + 255 = 1023. Старший байт считает по 256, младший — остаток. Сборка обратно на мастере: hi * 256 + lo. 0x03 и 0xFF
- Чем пины A4/A5 удобнее UART-пинов 0 и 1 для межплатной связи?
- Пины 0 и 1 делят обвязку USB-порта: каждая прошивка через USB дёргает их линии. A4/A5 с USB не связаны вообще. Они не связаны с USB-обвязкой: прошивку заливаешь, не трогая провода
- Слейв обновляет показания каждые 200 мс. Каким делать период опроса мастера?
- Мастер, опрашивающий чаще цикла слейва, читает одни и те же значения: данные не успевают обновиться между запросами. Это не сбой, а арифметика двух периодов. Заведомо длиннее цикла слейва — 500–1000 мс
- В симуляторе мастер собрал из заглушки число 10794. Откуда оно взялось?
- Движок наполняет буфер чтения константой 42, но арифметика сборки настоящая: 42 × 256 + 42 = 10794. На живой паре будет 0–1023. Это случайное число генератора