Skip to content

Использование I2C

Мы переходим к тематике передачи данных между NanoPi и Arduino. Одним из наиболее популярных интерфейсов для «общения» двух устройств является интерфейс I2C, разработанный инженерами компании Philips более 30 лет назад. При использовании I2C данные передаются по двум линиям –-- SDA (линия данных) и SCL (линия тактов). Всего на одной двупроводной шине может быть до 127 устройств. При этом один из них всегда является ведущим (master), а остальные ведомыми (slave).

Подключение с использованием I2C

Рисунок 1 – Подключение с использованием I2C

При этом обе линии всегда подтягиваются к напряжению питания (номинальному напряжению ведущего устройства (мастера) с помощью подтягивающих резисторов (они должны ставиться всегда, однако если используются МК с функцией подтяжки пинов к высокому уровню, можно соединять МК напрямую). Все устройства должны быть объединены общей землей. По умолчанию на обеих линиях всегда сохраняется логическая 1, а передача сигналов осуществляется подтягиванием линии к логическому 0. Вся передача данных состоит из «стартовой посылки», «битов» и «стоповой посылки». Порядок изменения уровня на шинах задает тип посылки. Особенности работы интерфейса таковы, что все процессы передачи сигналов от ведущего к ведомым и наоборот —всегда определяются ведущим устройством:

Мастер передает байт

Рисунок 2 – Мастер передает байт

Начало передачи регламентируется стартовым битом. Причем стартовый бит «активируется», когда SDA переключается в 0 при высоком SCL (в 1). И, наоборот, признаком стопового бита является случай, когда SDA переключается в «1», при единичном SCL.

Стартовый и стоповый биты

Рисунок 3 – Стартовый и стоповый биты

После старта передача одного бита данных идет по тактовому импульсу. То есть, когда линия SCL в нуле, master или slave выставляют бит на SDA (подтягивают — если «0» или не подтягивают — если «1» линию SDA), после чего SCL отпускается и master/slave считывают бит. Таким образом, протокол совершенно не зависит от временных интервалов, только от тактовых битов. То есть изменение на шине данных SDA в момент приема данных может быть только при низком уровне на SCL, когда SCL вверху — то идет чтение. Если же SDA меняется при высоком SCL, то это служебные команды START или STOP, как и говорилось раньше. После передачи каждого байта должен передаваться сигнал подтверждения — он позволяет:

  1. В случае передачи байта от master к slave, slave подтягивает линию SDA в 0, благодаря чему master понимает, что slave успешно получил и записал байт (иначе у master будет информация о том, что передачу байта нужно повторить):

Мастер принимает байт

Рисунок 4 – Мастер принимает байт

  1. В случае передачи байта от slave к master, master подтягивает линию SDA в 0, благодаря чему slave понимает, что master успешно получил и записал байт и что slave может продолжать передачу. Если же master не подтянет SDA в 0 (Неуспех), то это значит, что master не принял сообщение.

Бит подтверждения при передаче от master к slave

Рисунок 5 – Бит подтверждения при передаче от master к slave

За один раз может передаваться более 1 байта (то есть после передачи каждого байта должен следовать сигнал подтверждения). Однако в сообщении от START до STOP битов может быть несколько байт:

Бит подтверждения при передаче от slave к master

Рисунок 6 – Бит подтверждения при передаче от slave к master

Теперь разберемся с логическим уровнем. Как передаются отдельные биты понятно, а далее обсудим, что эти биты значат. Данные шлются пакетами, каждый пакет состоит из девяти бит. 8 бит данных и 1 бит подтверждения/не подтверждения приема. Первый пакет шлется от ведущего к ведомому (а на самом деле ко всем ведомым) –-- это физический адрес устройства и бит направления:

Передача двух байт от master к slave

Рисунок 7 – Передача двух байт от master к slave

Сам адрес состоит из семи бит (именно поэтому при I2C возможно размещение до 127 устройств на шине), а восьмой бит означает, что будет делать Slave на следующем байте — принимать R или передавать W данные. Девятым битом идет бит подтверждения ACK. Если Slave услышал свой адрес и считал полностью, то на девятом такте он придавит (в 0) линию SDA, сгенерировав ACK («Понял!»). Мастер, заметя это, «понимает», что все идет по плану и можно продолжать. Если Slave не обнаружился, пропустил адрес, неправильно принял байт, сгорел или еще что с ним случилось, то, соответственно, SDA на девятом такте будет прижать некому и ACK не получится (будет так называемый NACK).

Самый первый байт – это запрос от master к slave

Рисунок 8 – Самый первый байт –-- это запрос от master к slave

После адресного пакета идут пакеты с данными в ту или другую сторону, в зависимости от бита RW в заголовочном пакете. Вот, например, запись, то есть передача сообщения от master к slave. В квадратиках идут номера битов. W=0.

Передача сообщения от master к конкретному slave.

Рисунок 9 – Передача сообщения от master к конкретному slave.

А вот, например, чтение, то есть передача сообщения от slave к master. В квадратиках идут номера битов. R=1. Что в этом случае важно: при приеме последнего байта надо дать ведомому устройству slave понять, что его услуги больше не нужны, и отослать NACK на последнем байте.

Передача сообщения от конкретного slave к master.

Рисунок 10 – Передача сообщения от конкретного slave к master.

Так как при передаче сигналов идет подтяжка линий к 0 (и возвращение их в 1, когда ни одно устройство не подтягивает линию к 0), напрашивается вывод о скоростных ограничениях передачи информации по такому интерфейсу. Из-за довольно длинных переходных процессов (когда 1 переходит в 0 и наоборот), обусловленных паразитными емкостями, максимальная скорость передачи данных такого интерфейса составляет 100Кб/с. Также стоит отметить, что случай, когда номинальное напряжение питания ведущего устройства меньше, чем номинальные напряжения ведомых, — допустим. Так как напряжение логической единицы в этом случае задается именно ведущим устройством. Однако обратное неверно: если ведущим устройством сделать устройство, пины которого (и, в частности, те пины, которые подключаются к линиям SDA и SCL) выдают, например, 5В, а среди его ведомых будет устройство, рассчитанное максимально на 3.3В, то напряжение 5В может просто сжечь пины ведомого устройства и привести его в негодность.

Теперь рассмотрим конкретные примеры взаимодействия и «общения» по интерфейсу I2C двух устройств --- микрокомпьютера NanoPi и ArduinoUNO.

Прежде всего при использовании NanoPi необходимо «активировать» интерфейс I2C, то есть сделать GPIO, которые отвечают за него, доступными. Для этого необходимо в терминале прописать команду «sudo npi-config»:

Подключение с использованием I2C

Далее выбрать соответствующий пункт:

Подключение с использованием I2C

Далее активировать интерфейс I2C:

Подключение с использованием I2C

Выбрать необходимый i2c интерфейс:

Подключение с использованием I2C

Выбрать кнопку Enable:

Подключение с использованием I2C

Выйти из меню конфигураций. Проверить успешность активации интерфейса можно, введя ls /dev/*i2c*:

Подключение с использованием I2C

Распиновка NanoPi выглядит следующим образом:

NanoPi pinout

Распиновка ArduinoUNO выглядит следующим образом:

Arduino UNO pinout

Опасность: Подключение осуществляется только при выключенных платах, иначе есть возможность повредить их.

Рассмотрим простейший пример: соединим NanoPi и ArduinoUNO как на рисунке ниже:

Arduino UNO pinout

Напишем программу, благодаря которым диод (подключенный к 13 пину) на плате ArduinoUNO будет мигать. Для того чтобы использовать библиеотеку smbus ее необходимо сначала установить. Для этого пишем в терминал:

sudo apt install pip
pip install smbus

Программа на NanoPi будет выполняться в терминале, и выглядит она следующим образом:

from smbus import SMBus #импорт необходимой для работы с I2C библиотеки, обычно уже является частью ядра Raspbian

addr = 0x8 # адрес ведомого устройства
bus = SMBus(0) # адрес самого I2C: /dev/ic2-0

numb = 1   #переменная флаг, зануляется при вводе любых символов, кроме 0 и 1

print ("Enter 1 for ON or 0 for OFF")  #вывод для пользователя
while numb == 1:   #цикл до тех пор, пока вводятся 0 и 1

       ledstate = input(">>>>   ")    #чтение данных с терминала, в котором запущена программа
       if ledstate == "1":    #если введена единица
             bus.write_byte(addr, 0x1) #включение диода
       elif ledstate == "0":  #если введен ноль
            bus.write_byte(addr, 0x0) #выключение диода
       else:  #исли не введена 1 или 0
         numb = 0   #зануление флага, закрытие программы, которая была запушена в терминале

Программа на ArduinoUNO выглядит следующим образом:

// Подключение библиотеки Wire для I2C
#include <Wire.h>

// диод на 13 пине
const int ledPin = 13;

void setup() {
  // Активация Ардуино как ведомого устройства с адресом 8
  Wire.begin(0x8);

  // Вызов функции receiveEvent при приеме данных от мастера
  Wire.onReceive(receiveEvent);

  // Выставление 13 пина как выхода и гашение диода
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
}

// Функция, которая исполняется каждый раз, когда мастер передает
// ведомому устройству запрос на прием ведомым устройством сообщения
void receiveEvent(int howMany) {
  while (Wire.available()) { // пока ввсе сообщение не считано побайтно
    char c = Wire.read(); // принимаем байт как символ
    digitalWrite(ledPin, c); // передаем этот байт на пин диода, то есть зажигаем диод
  }
}
void loop() {
  delay(100); //в loop ничего не исполняется, а диод мигает "по прерыванию" от I2C
}

Подсказка: Если у вас выпадает ошибка при отправке команды в командной консоли, то можете воспользоваться командой i2cdetect -y 0. Благодаря ей вы просканируете все доступные адреса.

i2c scaner