Шина I2C: адреса, подтяжки и сканер, который находит всё
Два провода на десяток устройств — как они договариваются, почему нужны подтягивающие резисторы и что делать, когда дисплей молчит
Дисплей, часы и датчик сидят на двух проводах и не мешают друг другу, пока у каждого свой адрес. Разбираем, как устроена шина I2C, как посчитать подтяжки и почему сканер шины — первое, что стоит запустить.
Дисплей подключён, библиотека установлена, пример из неё собран без ошибок — а экран пуст. Код не виноват, провода вроде бы на месте. В девяти случаях из десяти причина в одном из трёх: не тот адрес, не те выводы, нет подтяжки. Все три становятся понятны, если разобраться, как устроена шина I2C. А проверить их помогает программа из двадцати строк — сканер шины.
Два провода и открытый сток
I2C использует две линии: SDA для данных и SCL для тактов. Ведущий, обычно микроконтроллер, выдаёт такты, а ведомые — датчики, дисплеи, часы — отвечают, когда к ним обратились. На одной паре проводов может висеть десяток устройств.
Чтобы несколько устройств могли управлять одной линией и не сжечь друг друга, выходы у всех сделаны с открытым стоком. Такой выход умеет только прижать линию к земле или отпустить её. Высокий уровень никто не выдаёт активно: его создаёт подтягивающий резистор от линии к питанию. Если два устройства одновременно захотят выдать разные уровни, ноль победит, и короткого замыкания не будет.
Отсюда первое следствие: без подтягивающих резисторов шина не работает вообще. Линии либо висят в воздухе, либо прижаты к земле, и ни один бит не проходит.
Обмен начинается с условия START: SDA падает, пока SCL высокий. Затем ведущий передаёт байт: семь бит адреса и бит направления, 0 для записи, 1 для чтения. Устройство, узнавшее свой адрес, в девятом такте прижимает SDA к земле. Это подтверждение, ACK. Если никто не ответил, линия остаётся высокой, и ведущий получает NACK.
Семибитный адрес и путаница с восьмибитным
Семи битов хватает на 128 адресов. Часть из них зарезервирована, и для устройств используются адреса от 0x08 до 0x77. Адрес зашит в микросхему, а младшие один-два бита обычно выведены на ножку или перемычку, чтобы на одну шину можно было поставить два одинаковых датчика.
| Устройство | Адрес | От чего зависит |
|---|---|---|
| LCD 16x2 с платой PCF8574 | 0x27 | PCF8574A на плате — 0x3F |
| OLED SSD1306 | 0x3C | перемычка на плате — 0x3D |
| BME280 | 0x76 | вывод 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 выводы шины разведены внутри контроллера и не меняются:
| Плата | SDA | SCL |
|---|---|---|
| ESP32 | 21 (любой) | 22 (любой) |
| Arduino Uno | A4 | A5 |
| Arduino Mega | 20 | 21 |
Отсюда классическая ошибка при переносе схемы. На 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 сюда не переносится.
Когда устройство не отвечает: порядок проверки
- Запустить сканер. Ноль устройств — искать в железе, не в коде.
- Проверить питание и землю модуля. Общая земля с контроллером обязательна.
- Проверить выводы шины для своей платы: 21/22, A4/A5 или 20/21. SDA и SCL часто путают местами.
- Сверить адрес из сканера с адресом в скетче. Для LCD это
LiquidCrystal_I2C lcd(0x27, 16, 2), для OLED —display.begin(SSD1306_SWITCHCAPVCC, 0x3C), для BME280 —bme.begin(0x76). - Проверить подтяжки и уровни, если сканер находит устройство через раз или шина длинная.
- Проверить результат инициализации в коде.
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 и убедитесь, что текст вернулся.
Первый текст на LCDBME280: три величины сразу
Переставьте адрес датчика в свойствах на 0x77: в порт уйдёт «BME280 не отвечает». Исправьте bme.begin(0x76) на 0x77 и проверьте, что показания температуры, влажности и давления вернулись.
Датчик BME280Arduino Uno: экран OLED и его цена в памяти
Скопируйте функцию freeRam() из примера про оперативную память Uno и напечатайте её до и после display.begin(): свободной памяти станет меньше примерно на 1024 байта. Поменяйте высоту в конструкторе на 32 и проверьте, что разница уменьшилась вдвое.
OLED на UnoOLED SSD1306: первый вывод
Закомментируйте display.display() и запустите: скетч отработает, а экран останется пустым, потому что кадр так и не ушёл из буфера по шине.
OLED на ESP32