4.09. Интерфейсы связи - I2C.md


В предыдущем параграфе мы научились связывать Рудирон с другими устройствами с помощью UART. Это надёжный способ, но у него есть недостаток - для каждого нового устройства нужен отдельный аппаратный UART или его программная эмуляция, что занимает драгоценные пины и ресурсы микроконтроллера. А что, если нам нужно подключить к плате десяток разных датчиков: температуры, влажности, давления, гироскоп, магнитометр? Пинов не хватит!

Именно для таких ситуаций был придуман гениальный по своей простоте и эффективности интерфейс — I2C (произносится как «ай-ту-си»), который расшифровывается как Inter-Integrated Circuit.

Принцип работы I2C - общая шина

Представьте себе автобусный маршрут. Есть всего одна дорога (шина), по которой ездит автобус (данные), и множество остановок (устройств). У каждой остановки есть свой уникальный номер (адрес). Когда кому-то нужно передать посылку, он выходит на дорогу, громко называет номер остановки-получателя и передаёт посылку. Все слышат, но забирает посылку только тот, чей номер назвали.

I2C работает по очень похожему принципу. Это шинный интерфейс, где все устройства подключаются параллельно всего к двум проводам:

  • SDA (Serial Data) — линия данных. По этому «проводу-дороге» передаются сами данные.
  • SCL (Serial Clock) — линия тактирования. Это «регулировщик», который задаёт ритм передачи данных, гарантируя, что все устройства работают синхронно.

Ведущий и ведомый (Master и Slave)

В шине I2C всегда есть одно главное устройство — Ведущий (Master). Как правило, это наш микроконтроллер Рудирон. Он инициирует всё общение и генерирует тактовые сигналы на линии SCL. Все остальные устройства на шине — датчики, дисплеи, микросхемы памяти — являются Ведомыми (Slave). Они только слушают команды от ведущего и отвечают, когда к ним обращаются.

У каждого ведомого устройства на шине должен быть уникальный 7-битный адрес. Этот адрес обычно жёстко задан производителем микросхемы (иногда его можно частично изменить с помощью специальных перемычек на плате модуля). Именно по этому адресу ведущий обращается к нужному устройству.

I2C на плате Рудирон

На плате Рудирон для работы с I2C выделены специальные пины:

  • SCL: Пин 18.
  • SDA: Пин 19.

Для работы с этим интерфейсом в среде Arduino используется стандартная библиотека Wire.

Библиотека Wire

Библиотека Wire предоставляет простой и удобный набор функций для общения по шине I2C.

Инициализация — Wire.begin() Прежде чем начать работу, нужно инициализировать Рудирон в качестве ведущего на шине. Это делается один раз в setup().

#include <Wire.h> // Подключаем библиотеку

void setup() {
  Wire.begin(); // Инициализируем I2C как ведущее устройство
  Serial.begin(9600);
}

Поиск устройств на шине Как узнать адреса устройств, подключённых к шине? Можно посмотреть в документации на датчик, а можно попросить Рудирон просканировать шину и сообщить адреса всех, кто откликнется.

Пример (сканер I2C-устройств):

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("\nI2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) 
        Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
    else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000); // Ждём 5 секунд перед следующим сканированием
}

Передача и приём данных Обмен данными с ведомым устройством — это всегда сеанс связи, который инициирует ведущий: 1. Wire.beginTransmission(адрес). Начать сеанс связи с устройством по его адресу. 2. Wire.write(данные). Отправить один или несколько байт данных. Например, адрес регистра, из которого мы хотим прочитать, или значение, которое хотим записать. 3. Wire.endTransmission(). Завершить сеанс передачи. 4. Wire.requestFrom(адрес, количество_байт). Запросить у ведомого устройства определённое количество байт. 5. Wire.read(). Прочитать байт, полученный от ведомого устройства.

Работа с конкретным устройством всегда зависит от его внутреннего устройства и команд, которые оно понимает. Эту информацию нужно искать в документации (datasheet) на компонент.

Зачем это нужно?

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

  • Датчики: гироскопы и акселерометры (MPU6050), барометры (BMP280), датчики освещённости (BH1750).
  • Дисплеи: небольшие OLED-экраны.
  • Память: микросхемы EEPROM для хранения настроек.
  • Расширители портов: микросхемы, которые добавляют Рудирону больше GPIO пинов.

Теперь вы знаете, как организовать целую сеть устройств с помощью I2C. Если вы хотите немедленно применить эти знания на практике, переходите к лабораторным работам, где мы напишем полезную программу — сканер, который поможет находить адреса любых подключённых I2C-устройств. Если же вам нужна ещё большая скорость и надёжность для таких задач, как работа с SD-картами или быстрыми дисплеями, то отправляйтесь в следующий параграф, где мы изучим интерфейс SPI.