Шина I2C: адреса, подтяжки и сканер, который находит всё

Два провода на десяток устройств — как они договариваются, почему нужны подтягивающие резисторы и что делать, когда дисплей молчит

Дисплей, часы и датчик сидят на двух проводах и не мешают друг другу, пока у каждого свой адрес. Разбираем, как устроена шина I2C, как посчитать подтяжки и почему сканер шины — первое, что стоит запустить.

Дисплей подключён, библиотека установлена, пример из неё собран без ошибок — а экран пуст. Код не виноват, провода вроде бы на месте. В девяти случаях из десяти причина в одном из трёх: не тот адрес, не те выводы, нет подтяжки. Все три становятся понятны, если разобраться, как устроена шина I2C. А проверить их помогает программа из двадцати строк — сканер шины.

Два провода и открытый сток

I2C использует две линии: SDA для данных и SCL для тактов. Ведущий, обычно микроконтроллер, выдаёт такты, а ведомые — датчики, дисплеи, часы — отвечают, когда к ним обратились. На одной паре проводов может висеть десяток устройств.

Чтобы несколько устройств могли управлять одной линией и не сжечь друг друга, выходы у всех сделаны с открытым стоком. Такой выход умеет только прижать линию к земле или отпустить её. Высокий уровень никто не выдаёт активно: его создаёт подтягивающий резистор от линии к питанию. Если два устройства одновременно захотят выдать разные уровни, ноль победит, и короткого замыкания не будет.

Отсюда первое следствие: без подтягивающих резисторов шина не работает вообще. Линии либо висят в воздухе, либо прижаты к земле, и ни один бит не проходит.

Обмен начинается с условия START: SDA падает, пока SCL высокий. Затем ведущий передаёт байт: семь бит адреса и бит направления, 0 для записи, 1 для чтения. Устройство, узнавшее свой адрес, в девятом такте прижимает SDA к земле. Это подтверждение, ACK. Если никто не ответил, линия остаётся высокой, и ведущий получает NACK.

Семибитный адрес и путаница с восьмибитным

Семи битов хватает на 128 адресов. Часть из них зарезервирована, и для устройств используются адреса от 0x08 до 0x77. Адрес зашит в микросхему, а младшие один-два бита обычно выведены на ножку или перемычку, чтобы на одну шину можно было поставить два одинаковых датчика.

УстройствоАдресОт чего зависит
LCD 16x2 с платой PCF85740x27PCF8574A на плате — 0x3F
OLED SSD13060x3Cперемычка на плате — 0x3D
BME2800x76вывод SDO на питании — 0x77
DS3231, часы0x68не меняется
MPU6050, акселерометр0x68вывод AD0 на питании — 0x69

Частая ловушка — документация, где адрес указан вместе с битом направления. Для PCF8574 пишут «адрес записи 0x4E», а это 0x27, сдвинутый на разряд влево: 0x27 × 2 = 0x4E. Библиотека Wire ждёт семибитный адрес, и 0x4E ей передавать бесполезно — такого устройства на шине нет.

Вторая ловушка видна в таблице: часы DS3231 и акселерометр MPU6050 по умолчанию занимают один адрес 0x68. На одной шине они будут отвечать одновременно, и данные испортятся. Выход — перевести MPU6050 на 0x69 выводом AD0.

Сканер шины: перебор всех адресов

Сканер делает то, с чего начинается любой обмен: посылает START и адрес, а потом смотрит, пришло ли подтверждение. Данных он не передаёт.

Wire.begin(21, 22);   // SDA, SCL

for (byte addr = 1; addr < 127; addr++) {
  Wire.beginTransmission(addr);
  if (Wire.endTransmission() == 0) {
    Serial.printf("  найдено устройство: 0x%02X\n", addr);
    found++;
  }
}

endTransmission() возвращает код результата. В библиотеке Wire для AVR это 0 — устройство подтвердило адрес, 1 — данные не поместились в буфер, 2 — адрес не подтвердил никто, 3 — не подтверждены данные, 4 — прочая ошибка шины, в новых версиях ещё 5 — таймаут. На ESP32 набор ненулевых кодов немного другой, но ноль везде означает успех, и сканеру важен только он.

Сканер отвечает сразу на три вопроса. Нашлось ноль устройств — проблема в проводах, питании или подтяжке, код тут ни при чём. Нашлось устройство, но по другому адресу — надо поменять адрес в скетче. Нашлось два устройства там, где ожидалось одно, или наоборот — конфликт адресов или несовместимые модули.

В симуляторе PinPort пример сканер шины I2C запускается на голой плате и честно печатает «всего: 0». Добавьте на схему дисплей или датчик — и строка с его адресом появится в мониторе порта. Адрес у деталей меняется в свойствах, как перемычка на настоящем модуле: у LCD 0x27 или 0x3F, у OLED 0x3C или 0x3D, у BME280 0x76 или 0x77.

Настольные часы на OLED

Часы DS3231 (0x68) и экран SSD1306 (0x3C) сидят на одних и тех же D21 и D22. Замените код на сканер шины из примера выше и убедитесь, что он находит оба адреса. Затем поменяйте адрес экрана в свойствах на 0x3D: сканер покажет новый адрес, а скетч часов перестанет рисовать, пока в display.begin не будет исправлен 0x3C.

Два устройства на шине

Подтягивающие резисторы: как посчитать

Подтяжка определяет, как быстро линия поднимается из нуля в единицу. Линия вместе с проводами и входами всех устройств — это ёмкость, и резистор заряжает её с постоянной времени R × C. Спецификация I2C ограничивает время нарастания от 30 до 70 % уровня: 1000 нс для режима 100 кГц и 300 нс для 400 кГц. Для RC-цепи это время равно 0,847 × R × C.

Верхняя граница резистора при ёмкости шины 200 пФ:

100 кГц: R_max = 1000 нс / (0,847 × 200 пФ) ≈ 5,9 кОм
400 кГц: R_max =  300 нс / (0,847 × 200 пФ) ≈ 1,8 кОм

Нижняя граница — ток, который выход может потянуть к земле. По стандарту это 3 мА при напряжении низкого уровня не выше 0,4 В:

3,3 В: R_min = (3,3 − 0,4) / 0,003 ≈ 970 Ом
5 В:   R_min = (5,0 − 0,4) / 0,003 ≈ 1,5 кОм

Отсюда популярные 4,7 кОм для 100 кГц и 2,2 кОм для 400 кГц на коротких шинах. У готовых модулей подтяжки обычно уже стоят на плате, и тут кроется ловушка: несколько модулей на одной шине включают свои резисторы параллельно. Три модуля по 4,7 кОм дают 4,7 / 3 ≈ 1,6 кОм — для 5 В это почти предел.

Вторая ловушка — уровни. Модуль, рассчитанный на 5 В, подтягивает линии к 5 В. ESP32 работает от 3,3 В, и его входы 5 В не рассчитаны. Если дисплей питается от 5 В, подтяжки на нём надо убрать или поставить преобразователь уровней. В примерах для ESP32 устройства питаются от 3V3, а в примерах для Uno и Mega — от 5V.

Выводы шины на разных платах

У ESP32 контроллер I2C подключается к выводам через матрицу, поэтому Wire.begin(sda, scl) принимает почти любую пару. По умолчанию это 21 и 22, и все примеры в симуляторе используют их. У плат Arduino выводы шины разведены внутри контроллера и не меняются:

ПлатаSDASCL
ESP3221 (любой)22 (любой)
Arduino UnoA4A5
Arduino Mega2021

Отсюда классическая ошибка при переносе схемы. На Uno дисплей подключён к A4 и A5, схему переносят на Mega как есть — и экран молчит. Код верный, библиотека верная, адрес верный. У Mega A4 и A5 — обычные аналоговые входы.

Arduino Mega: шина I2C переехала на 20 и 21

Перенесите провода SDA и SCL дисплея с 20 и 21 на A4 и A5, как было бы на Uno, и запустите: экран останется пустым, а разбор схемы объяснит почему. Верните провода на 20 и 21.

Открыть пример Mega

Тот же урок в обратную сторону показывает LCD на Uno: в скетче не названо ни одного вывода шины, потому что менять их на Uno нельзя, а схема с ESP32 на 21 и 22 сюда не переносится.

Когда устройство не отвечает: порядок проверки

  1. Запустить сканер. Ноль устройств — искать в железе, не в коде.
  2. Проверить питание и землю модуля. Общая земля с контроллером обязательна.
  3. Проверить выводы шины для своей платы: 21/22, A4/A5 или 20/21. SDA и SCL часто путают местами.
  4. Сверить адрес из сканера с адресом в скетче. Для LCD это LiquidCrystal_I2C lcd(0x27, 16, 2), для OLED — display.begin(SSD1306_SWITCHCAPVCC, 0x3C), для BME280 — bme.begin(0x76).
  5. Проверить подтяжки и уровни, если сканер находит устройство через раз или шина длинная.
  6. Проверить результат инициализации в коде. bme.begin() и display.begin() возвращают false, если устройство не ответило. Пример BME280 печатает в этом случае «BME280 не отвечает: проверьте адрес и провода» и останавливается, а не выдаёт мусорные показания.

Отдельная история — память. Экран SSD1306 128 × 64 не умеет отдавать своё содержимое обратно, поэтому библиотека держит копию кадра у себя: 128 × 64 / 8 = 1024 байта. На ESP32 с его сотнями килобайт это незаметно, а на Uno это половина всей оперативной памяти, занятая до первой переменной.

Итог

I2C — это две линии с открытым стоком, подтянутые резисторами к питанию, и адрес из семи бит у каждого устройства. Шина молчит без подтяжки, путается при совпадении адресов и не работает на чужих выводах. Сканер шины за секунду показывает, какие адреса отвечают, и отделяет проблемы железа от проблем кода. В симуляторе PinPort это проверяется без паяльника: сменить адрес в свойствах детали, перенести провод и посмотреть, что напишет монитор порта.

Попробуйте в симуляторе

Сканер шины I2C

На плате нет ни одного устройства, и сканер честно пишет «всего: 0». Добавьте на схему дисплей LCD 16x2, подключите SDA к D21, SCL к D22 и питание — в мониторе порта появится 0x27. Смените адрес дисплея в свойствах на 0x3F и запустите снова.

Запустить сканер

LCD 16x2: первый текст

Поменяйте в свойствах дисплея адрес на 0x3F, не трогая скетч: экран останется пустым. Затем исправьте адрес в конструкторе LiquidCrystal_I2C и убедитесь, что текст вернулся.

Первый текст на LCD

BME280: три величины сразу

Переставьте адрес датчика в свойствах на 0x77: в порт уйдёт «BME280 не отвечает». Исправьте bme.begin(0x76) на 0x77 и проверьте, что показания температуры, влажности и давления вернулись.

Датчик BME280

Arduino Uno: экран OLED и его цена в памяти

Скопируйте функцию freeRam() из примера про оперативную память Uno и напечатайте её до и после display.begin(): свободной памяти станет меньше примерно на 1024 байта. Поменяйте высоту в конструкторе на 32 и проверьте, что разница уменьшилась вдвое.

OLED на Uno

OLED SSD1306: первый вывод

Закомментируйте display.display() и запустите: скетч отработает, а экран останется пустым, потому что кадр так и не ушёл из буфера по шине.

OLED на ESP32

← Все статьи