КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Урок 26.3 Соединяем две arduino по шине I2C

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

Общие сведения:

В этом уроке мы научимся соединять две arduino по аппаратной шине I2C.

Каждому ведомому устройству назначается уникальный адрес на шине I2C, по этому адресу мастер сможет выбирать требуемое ведомое устройство для общения по шине I2C. На ведомых устройствах созданных при помощи arduino создаются массивы типа byte до 255 элементов, значения которых с помощью библиотеки iarduino_I2C_connect.h станут доступны для чтения/записи мастеру на шине I2C.

Преимущества:

  • Реализуется возможность подключения более 100 устройств.
    (не используйте адреса 0-7 и 127)
  • Не требуются дополнительные модули.
  • Все устройства одинаково подключаются к шине.
  • Каждое ведомое устройство должно иметь уникальный адрес на шине.

Недостатки:

  • Код программы немного сложнее чем для шины UART.

Нам понадобится:

Для реализации проекта нам необходимо установить библиотеки:

  • Библиотека LiquidCrystal_I2C_V112 (для подключения дисплеев LCD1602 по шине I2C).
  • Библиотека iarduino_I2C_connect (для удобства соединения нескольких arduino по шине I2C).

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE, а о том, как работать с LCD дисплеями, на странице Wiki - Работа с символьными ЖК дисплеями.

Видео:

Схема подключения:

На шине i2С находятся 4 устройства: 3 arduino и 1 LCD дисплей. Все устройства шины I2C соединены через Trema I2C Hub. Для подключения Arduino используются аппаратные выводы шины I2C. Но можно использовать и программную реализацию шины I2C. О том как выбрать тип шины I2C рассказано на странице Wiki - расширенные возможности библиотек iarduino для шины I2C.

Схема соединения нескольких Arduino по шине I2C

На шине I2C не используются дополнительные подтягивающие резисторы (для линий SDA и SCL), так как они интегрированы в LDC I2C дисплее.

Алгоритм работы:

Ведомый 0x01 (у которого подключена только одна кнопка) должен дать возможность мастеру читать состояние этой кнопки.

Ведомый 0x02 (у которого подключён светодиод и потенциометр) должен дать возможность мастеру читать значение потенциометра и по команде от мастера включать светодиод.

Мастер должен прочитать и вывести на дисплей, состояние кнопки ведомого 0x01, и значение потенциометра ведомого 0x02. А так же отправить команду ведомому 0x02 включить светодиод если нажата кнопка мастера.

Код программы:

Arduino slave 0x01:

Данное устройство должно стать ведомым на шине I2C с адресом 0x01 и предоставить мастеру возможность читать состояние своей кнопки.

const byte PIN_Button = 2;                          // Вывод кнопки.
                                                    //
#include <Wire.h>                                   // Подключаем библиотеку для работы с шиной I2C.
#include <iarduino_I2C_connect.h>                   // Подключаем библиотеку для соединения arduino по шине I2C.
                                                    //
iarduino_I2C_connect bus;                           // Создаём объект класса iarduino_I2C_connect.
byte REG_Array[1];                                  // Объявляем массив, который станет доступен мастеру.
                                                    //
void setup(){                                       //
     Wire.begin(0x01);                              // Инициируем подключение к шине I2C в качестве ведомого (slave) устройства, с указанием своего адреса на шине.
     bus.begin(&Wire, REG_Array);                   // Инициируем возможность чтения/записи данных по шине I2C указав: ссылку на объект шины I2C, массив данных REG_Array.
//   Теперь массив REG_Array стал доступен мастеру. //
     pinMode(PIN_Button, INPUT);                    // Конфигурируем вывод для работы с кнопкой.
}                                                   //
                                                    //
void loop(){                                        //
//   Сохраняем состояние кнопки в массив:           //
     REG_Array[0] = digitalRead(PIN_Button);        // Сохраняем состояние кнопки в 0 ячейку массива REG_Array.
}                                                   //

Теперь мастер может читать регистр №0 у ведомого с адресом 0x01, значение которого будет совпадать со значением нулевого элемента массива REG_Array. Сколько элементов будет иметь массив REG_Array, столько регистров будет доступно мастеру для чтения/записи.

скачать старый код.

Arduino slave 0x02:

Данное устройство должно стать ведомым на шине I2C с адресом 0x02 и предоставить мастеру возможность читать значение потенциометра, а так же по команде мастера включать свой светодиод.

const byte PIN_Potentiometer = A0;                      // Вывод потенциометра.
const byte PIN_LED           = 13;                      // Вывод светодиода.
                                                        //
#include <Wire.h>                                       // Подключаем библиотеку для работы с шиной I2C.
#include <iarduino_I2C_connect.h>                       // Подключаем библиотеку для соединения arduino по шине I2C.
                                                        //
iarduino_I2C_connect bus;                               // Создаём объект класса iarduino_I2C_connect.
int  VAR_Potentiometer;                                 // Объявляем переменную для чтения значения с потенциометра.
byte REG_Array[3];                                      // Объявляем массив, который станет доступен мастеру.
                                                        //
void setup(){                                           //
     Wire.begin(0x02);                                  // Инициируем подключение к шине I2C в качестве ведомого (slave) устройства, с указанием своего адреса на шине.
     bus.begin(&Wire, REG_Array);                       // Инициируем возможность чтения/записи данных по шине I2C указав: ссылку на объект шины I2C, массив данных REG_Array.
//   Теперь массив REG_Array стал доступен мастеру.     //
     pinMode(PIN_LED, OUTPUT);                          // Конфигурируем вывод для работы со светодиодом.
}                                                       //
                                                        //
void loop(){                                            //
//   Управляем светодиодом по значению от мастера:      //
     digitalWrite(PIN_LED, REG_Array[0]);               // Вкл/выкл светодиод в соответствии со значением 0 элемента массива REG_Array.
//   Сохраняем значение потенциометра в массив:         //
     VAR_Potentiometer = analogRead(PIN_Potentiometer); // Считываем значение потенциометра.
     REG_Array[1] = VAR_Potentiometer>>8;               // Сохраняем старший байт значения потенциометра в  1 ячейку массива REG_Array.
     REG_Array[2] = VAR_Potentiometer;                  // Сохраняем младший байт значения потенциометра во 2 ячейку массива REG_Array.
}                                                       //

Теперь мастер может записывать ведомому с адресом 0x02 в регистр 0, команду включения/отключения светодиода, а так же читать регистры 1 и 2 в которых хранится текущее значение потенциометра. Номер элемента массива REG_Array совпадает с номером регистра ведомого.

скачать старый код.

Arduino master:

Данное устройство должно стать мастером на шине I2C. Мастер должен прочитать и вывести на дисплей, состояние кнопки ведомого 0x01, и значение потенциометра ведомого 0x02. А так же отправить команду ведомому 0x02 включить светодиод если нажата кнопка мастера.

const byte PIN_Button_master = 2;                  // Вывод кнопки.
                                                   //
#include <Wire.h>                                  // Подключаем библиотеку для работы с шиной I2C.
#include <LiquidCrystal_I2C.h>                     // Подключаем библиотеку для работы с LCD дисплеем.
#include <iarduino_I2C_connect.h>                  // Подключаем библиотеку для соединения arduino по шине I2C.
                                                   //
LiquidCrystal_I2C    lcd(0x27,16,2);               // Создаём объект класса LiquidCrystal_I2C, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2).
iarduino_I2C_connect bus;                          // Создаём объект класса iarduino_I2C_connect.
                                                   //
bool VAR_Button_master = 0;                        // Определяем переменную для чтения состояний собственной кнопки.
bool VAR_Button_slave  = 0;                        // Определяем переменную для чтения состояний кнопки ведомого 0x01.
int  VAR_Potentiometer = 0;                        // Определяем переменную для чтения значения с потенциометра ведомого 0x02.
                                                   //
void setup(){                                      //
     Wire.begin();                                 // Инициируем подключение к шине I2C в качестве ведущего (master) устройства.
     bus.begin(&Wire);                             // Указываем мастеру объект для работы с шиной I2C.
//   Конфигурируем вывод для работы с кнопкой:     //
     pinMode(PIN_Button_master, INPUT);            // Устанавливаем режим работы вывода собственной кнопки, как вход.
//   Выводим данные на LCD дисплей:                //
     lcd.init();                                   // инициируем LCD дисплей.
     lcd.backlight();                              // включаем подсветку LCD дисплея.
     lcd.setCursor(0,0); lcd.print("iArduino.ru"); // выводим текст "iArduino.ru".
     delay(1000);                                  // ждём 1 секунду.
     lcd.setCursor(0,0); lcd.print("button   = "); // выводим текст "button   = ".
     lcd.setCursor(0,1); lcd.print("resistor = "); // выводим текст "resistor = ".
}                                                  //
                                                   //
void loop(){                                             //
//   Считываем данные:                                   //
     VAR_Button_slave   = bus.readByte(0x01,0);          // Считываем состояние кнопки ведомого (адрес ведомого 0x01, номер регистра 0).
     VAR_Potentiometer  = bus.readByte(0x02,1)<<8;       // Считываем старший байт значения потенциометра ведомого (адрес ведомого 0x02, номер регистра 1), сдвигаем полученный байт на 8 бит влево, т.к. он старший.
     VAR_Potentiometer += bus.readByte(0x02,2);          // Считываем младший байт значения потенциометра ведомого (адрес ведомого 0x02, номер регистра 2), добавляя его значение к ранее полученному старшему байту.
//   Отправляем данные:                                  //
     VAR_Button_master  = digitalRead(PIN_Button_master);// Считываем состояние собственной кнопки.
     bus.writeByte(0x02, 0, VAR_Button_master);          // Отправляем состояние собственной кнопки ведомому (адрес ведомого 0x02, номер регистра 0, состояние кнопки VAR_Button_master).
//   Выводим данные на LCD дисплей:                      //
     lcd.setCursor(11, 0); lcd.print("    ");            // стираем предыдущее состояние кнопки ведомого 0x01
     lcd.setCursor(11, 0); lcd.print(VAR_Button_slave);  // выводим состояние кнопки ведомого 0x01
     lcd.setCursor(11, 1); lcd.print("    ");            // стираем предыдущее значение потенциометра ведомого 0x02
     lcd.setCursor(11, 1); lcd.print(VAR_Potentiometer); // выводим значение потенциометра ведомого 0x02
     delay(50);                                          // ждём 0.05 секунд, иначе данные на LCD дисплее будут заметно мерцать
}

Мастер читает у ведомого 0x01 регистр 0 bus.readByte(0x01,0); это состояние кнопки ведомого, затем читает у ведомого 0x02 регистры 1 и 2 bus.readByte(0x02,n); это значение потенциометра ведомого, после чего записывает ведомому 0x02 в регистр 0 состояние своей кнопки bus.writeByte(0x02, 0, VAR_Button_master); по данному значению ведомый управляет светодиодом. Далее мастер отправляет на дисплей прочитанные значения о состоянии кнопки ведомого 0x01 и потенциометра ведомого 0x02 lcd.print();.

скачать старый код.

Настройка параметров шины I2C:

Максимальная, аппаратно реализуемая частота передачи данных, может достигать 1/16 от тактовой частоты.

Библиотека Wire.h позволяет устанавливать скорость передачи данных через функцию setClock(), которую требуется вызвать до функции begin().

Библиотека Wire.h позволяет аппаратно подключить Arduino к шине I2C с указанием роли Arduino на шине: ведущий или ведомый.

  • Wire.setClock(400000); // скорость передачи данных 400 кБит/с.
  • Wire.begin(); // подключение к шине I2C в роли ведущего.
  • Wire.begin(адрес); // подключение к шине I2C в роли ведомого, с указанием адреса.

Библиотека iarduino_I2C_Software.h позволяет создать программную шину I2C на любых выводах Arduino. Синтаксис библиотеки совместим с Wire.h, для работы с шиной требуется создать объект как экземпляр класса SoftTwoWire, подробнее о работе библиотеки можно узнать на странице Wiki - Работа с программной шиной I2C.

Функции библиотеки iarduino_I2C_connect:

В библиотеке iarduino_I2C_connect реализована одна функция инициализации и 4 функции чтения записи данных мастером.

На ведомом устройстве достаточно вызвать функцию инициализации begin() с указанием  шины I2C и массива, данные которого требуется сделать доступными для мастера. Далее можно работать с этим массивом, «забыв» про шину I2C. Мастер обращаясь к ведомому сможет получать и изменять данные элементов указанного массива (обращаясь к номеру элемента массива как к номеру регистра).

Если требуется запретить мастеру менять значения некоторых ячеек массива (сделать их доступными только для чтения), то при инициализации в качестве третьего параметра функции begin() нужно указать маскировочный массив, каждый элемент которого является флагом, разрешающим запись в соответствующий элемент массива доступного по шине I2C.

Для мастера доступны функции readByte(), readBytes(), writeByte() и writeBytes(), для чтения и записи данных соответственно.

Библиотека iarduino_I2C_connect может использовать как аппаратную, так и программную реализацию шины I2C. О том как выбрать тип шины I2C рассказано ниже в разделе «Подключение библиотеки», а так же на странице Wiki - расширенные возможности библиотек iarduino для шины I2C.

Подключение библиотеки:

  • Если используется аппаратная шина I2C для создания мастера:
#include <Wire.h>                  // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотеки iarduino_I2C_4LED.h
#include <iarduino_I2C_connect.h>  // Подключаем библиотеку для соединения arduino по шине I2C.
iarduino_I2C_connect bus;          // Создаём объект класса iarduino_I2C_connect.
                                   //
void setup(){                      //
     ...                           //
     Wire.begin();                 // Инициируем подключение к шине I2C в качестве мастера.
     bus.begin(&Wire);             // Инициируем доступ к данным ведомого, указав ссылку на объект для работы с шиной I2C (по умолчанию &Wire).
     ...                           // Доступны объекты: &Wire, &Wire1, &Wire2...
}                                  //
  • Если используется аппаратная шина I2C для создания ведомого:
#include <Wire.h>                  // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотеки iarduino_I2C_4LED.h
#include <iarduino_I2C_connect.h>  // Подключаем библиотеку для соединения arduino по шине I2C.
iarduino_I2C_connect bus;          // Создаём объект класса iarduino_I2C_connect.
                                   //
byte arr[255];                     // Создаём массив который разрешим читать/менять мастеру.
                                   // Размер массива от 1 до 255 элементов.
void setup(){                      //
     ...                           //
     Wire.begin(0x09);             // Инициируем подключение к шине I2C в качестве ведомого с адресом 0x09. Можно указывать адрес до 0x7E.
     bus.begin(&Wire, arr);        // Инициируем доступ мастеру к данным в массиве arr, указав ссылку на объект для работы с шиной I2C (по умолчанию &Wire).
     ...                           //
}                                  //
  • Если используется программная шина I2C для создания мастера:
#include <iarduino_I2C_Software.h> // Подключаем библиотеку для работы с программной шиной I2C, до подключения библиотеки iarduino_I2C_4LED.h
SoftTwoWire sWire(3,4);            // Создаём объект программной шины I2C указав выводы которым будет назначена роль линий: SDA, SCL.
                                   //
#include <iarduino_I2C_connect.h>  // Подключаем библиотеку для соединения arduino по шине I2C.
iarduino_I2C_connect bus;          // Создаём объект класса iarduino_I2C_connect.
                                   //
void setup(){                      //
     ...                           //
     Wire.begin();                 // Инициируем подключение к шине I2C в качестве мастера.
     bus.begin(&Wire);             // Инициируем доступ к данным ведомого, указав ссылку на объект для работы с шиной I2C (по умолчанию &Wire).
     ...                           //
}                                  //
  • Во всех примерах сначала подключается библиотека для работы с шиной I2C. Для аппаратной шины библиотека Wire.h (предустановлена в Arduino IDE), а для программной шины библиотека iarduino_I2C_Software.h. с созданием объекта которому указываются выбранные вами выводы шины, в примере выводы (3-SDA, 4-SCL).
  • Далее подключается библиотека и создаётся объект для соединения arduino по I2C.
  • В коде Setup(), вызываются две функции begin(), одна для объекта работы с шиной I2C, а вторая для объекта предоставления доступа к массиву данных.

Функция begin();

  • Назначение: Инициализация доступа к данным ведомого.
  • Синтаксис для мастера: begin( [ &ШИНА ] );
  • Синтаксис для ведомого: begin( [ &ШИНА ] , ДАННЫЕ [, МАСКА ] );
  • Параметры:
    • &ШИНА - Ссылка на объект для работы с выбранной шиной I2C.
      • Для аппаратной шины: &Wire, &Wire1, &Wire2..., если подключена библиотека Wire.h
      • Для программной шины: ссылка на объект библиотеки iarduino_I2C_Software.h.
      • Параметр является не обязательным, по умолчанию используется ссылка &Wire.
    • ДАННЫЕ - массив типа byte к которому требуется дать доступ.
    • МАСКА - необязательный массив типа bool разрешающий запись данных мастером.
  • Возвращаемое значение: Нет.
  • Примечание:
    • Функцию необходимо вызвать до обращения к остальным функциям библиотеки.
    • Все элементы массива ДАННЫЕ будут доступны для чтения мастером по шине I2C, вне зависимости от наличия массива МАСКА.
    • Если массив МАСКА не указан, то все элементы массива ДАННЫЕ будут доступны мастеру как для чтения, так и для записи.
    • Если массив МАСКА указан, то эго элементы являются флагами разрешения записи для мастера. Например, маскировочный массив bool mask[4] = {0,0,1,1}; запрещает запись в элементы 0 и 1 массива ДАННЫЕ и разрешает запись в элементы 2 и 3 массива ДАННЫЕ.
    • Если в маскировочном массиве меньше элементов чем в массиве данных, то те элементы массива данных для которых нет элементов в маскировочном массиве будут доступны для записи.
  • Пример:
byte data[100];                // Создаём массив данных для ведомого.
bool mask[100];                // Создаём маскировочный массив для ведомого.
...                            //
bus.begin();                   // Инициируем работу в качестве мастера на шине I2C по умолчанию (&Wire).
bus.begin(&Wire);              // Инициируем работу в качестве мастера на основной шине I2C.
bus.begin(&Wire1);             // Инициируем работу в качестве мастера на первой шине I2C.
bus.begin(&Wire2, data);       // Инициируем работу в качестве ведомого на второй шине I2C, предоставив мастеру доступ к массиву data.
bus.begin(&Wire3, data, mask); // Инициируем работу в качестве ведомого на третей шине I2C, предоставив мастеру доступ к массиву data, ограничив запись массивом mask.

Функция writeMask();

  • Назначение: Устаревшая функция указания маскировочного массива.
  • Синтаксис: writeMask( МАСКА );
  • Параметры: МАСКА - массив типа bool разрешающий запись данных мастером.
  • Возвращаемое значение: Нет.
  • Примечание:
    • Функция устарела, но осталась для совместимости.
    • Функция предназначена для устройства инициированного как ведомое.
  • Пример:
bool mask[100];      // Создаём маскировочный массив для ведомого.
bus.writeMask(mask); // Указываем маскировочный массив если он не был указан в функции begin().

Функция readByte();

  • Назначение: Чтение мастером одного байта по шине I2C.
  • Синтаксис: readByte( АДРЕС, РЕГИСТР );
  • Параметры:
    • АДРЕС - значение типа byte, адрес ведомого на шине I2C (от 0 до 127).
    • РЕГИСТР - значение типа byte, адрес регистра (от 0 до 255).
  • Возвращаемое значение: byte прочитанное значение регистра ведомого.
  • Примечание:
    • Функция предназначена для устройства инициированного как мастер.
    • Позволяет мастеру прочитать регистр не только того ведомого которое создано библиотекой iarduino_I2C_connect, но и обычного модуля, датчика, чипа и т.д.
  • Пример:
byte i = bus.readByte(0x09,5); // Прочитать 5 регистр ведомого устройства с адресом 0x09.

Функция readBytes();

  • Назначение: Чтение мастером нескольких байт по шине I2C.
  • Синтаксис: readBytes( АДРЕС , РЕГИСТР , МАССИВ , РАЗМЕР );
  • Параметры:
    • АДРЕС - значение типа byte, адрес ведомого на шине I2C (от 0 до 127).
    • РЕГИСТР - значение типа byte, адрес первого регистра (от 0 до 255).
    • МАССИВ - имя массива типа byte, для получения прочитанных данных.
    • РАЗМЕР - значение типа byte, количество читаемых байт (от 1 до 255).
  • Возвращаемое значение: bool флаг успешного чтения данных.
  • Примечание:
    • Функция предназначена для устройства инициированного как мастер.
    • Позволяет мастеру читать регистры не только того ведомого которое создано библиотекой iarduino_I2C_connect, но и обычного модуля, датчика, чипа и т.д.
  • Пример:
byte i[20];
if( bus.readBytes(0x09,5,i,20) ){ Serial.print("Успех"); } // Прочитать 20 регистров, в массив i, начиная с 5 регистра, ведомого устройства с адресом 0x09.
else                            { Serial.print("Ошибка"); } // Чтение не выполнено.

Функция writeByte();

  • Назначение: Запись мастером одного байта по шине I2C.
  • Синтаксис: writeByte( АДРЕС , РЕГИСТР , БАЙТ );
  • Параметры:
    • АДРЕС - значение типа byte, адрес ведомого на шине I2C (от 0 до 127).
    • РЕГИСТР - значение типа byte, адрес регистра (от 0 до 255).
    • БАЙТ - значение типа byte для записи в регистр (от 0 до 255).
  • Возвращаемое значение: bool флаг успешной записи байта.
  • Примечание:
    • Функция предназначена для устройства инициированного как мастер.
    • Позволяет мастеру записать байт в регистр не только того ведомого которое создано библиотекой iarduino_I2C_connect, но и обычного модуля, датчика, чипа и т.д.
  • Пример:
if( bus.writeByte(0x09,5,100) ){ Serial.print("Успех"); }  // Записать значение 100 в 5 регистр ведомого устройства с адресом 0x09.
else                           { Serial.print("Ошибка"); } // Запись не выполнена.

Функция writeBytes();

  • Назначение: Запись мастером нескольких байт по шине I2C.
  • Синтаксис: writeBytes( АДРЕС , РЕГИСТР , МАССИВ , РАЗМЕР );
  • Параметры:
    • АДРЕС - значение типа byte, адрес ведомого на шине I2C (от 0 до 127).
    • РЕГИСТР - значение типа byte, адрес первого регистра (от 0 до 255).
    • МАССИВ - имя массива типа byte, содержащего данные для записи.
    • РАЗМЕР - значение типа byte, количество записываемых байт (от 1 до 255).
  • Возвращаемое значение: bool флаг успешной записи данных.
  • Примечание:
    • Функция предназначена для устройства инициированного как мастер.
    • Позволяет мастеру записывать данные в регистры не только того ведомого которое создано библиотекой iarduino_I2C_connect, но и обычного модуля, датчика, чипа и т.д.
  • Пример:
byte i[3] = {5,6,7};
if( bus.writeBytes(0x09,5,i,3) ){ Serial.print("Успех"); } // Записать 3 байта, из массива i, начиная с 5 регистра, ведомого устройства с адресом 0x09.
else                            { Serial.print("Ошибка"); } // Запись не выполнена.

Ссылки:




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями