При создании некоторых проектов, требуется разделить выполняемые задачи между несколькими arduino.
В этом уроке мы научимся соединять две arduino по аппаратной шине I2C.
Преимущества:
- Реализуется возможность подключения до 126 устройств.
(не рекомендуется присваивать устройствам адреса 0x00 и 0x7F) - Не требуются дополнительные модули.
- Все устройства одинаково подключаются к шине.
- Каждое ведомое устройство имеет свой уникальный адрес на шине.
Недостатки:
- Код программы немного сложнее чем для шины UART.
Нам понадобится:
- Arduino х 3шт.
- LCD дисплей LCD1602 IIC/I2C(синий) или LCD1602 IIC/I2C(зелёный) х 1шт.
- Trema Shield х 3шт.
- Trema кнопка x 2шт.
- Trema светодиод x 1шт.
- Trema потенциометр x 1шт.
- Trema I2C Hub x 1шт.
- Шлейф «мама-мама» (4 провода) для шины I2С х 4шт.
Для реализации проекта нам необходимо установить библиотеки:
- Библиотека 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.
- Arduino master - к цифровому выводу D2 подключена trema кнопка.
- Arduino slave 0x01 - к цифровому выводу D2 подключена trema кнопка.
- Arduino slave 0x02 - к цифровому выводу D13 подключён trema светодиод, к аналоговому выводу A0 подключён Trema потенциометр.
- LCD I2C дисплей - является устройством slave 0x27.
На шине I2C не используются дополнительные подтягивающие резисторы (для линий SDA и SCL), так как они интегрированы в LDC I2C дисплее.
Код программы:
Arduino master:
// Подключаем библиотеки: #include <Wire.h> // подключаем библиотеку для работы с шиной I2C #include <LiquidCrystal_I2C.h> // подключаем библиотеку для работы с LCD дисплеем #include <iarduino_I2C_connect.h> // подключаем библиотеку для соединения arduino по шине I2C // Объявляем переменные и константы: iarduino_I2C_connect I2C2; // объявляем переменную для работы c библиотекой iarduino_I2C_connect LiquidCrystal_I2C lcd(0x27,16,2); // объявляем переменную для работы с LCD дисплеем, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2) const byte PIN_Button_master = 2; // объявляем константу с указанием номера цифрового вывода, к которому подключена кнопка bool VAR_Button_master = 0; // объявляем переменную для чтения состояний собственной кнопки bool VAR_Button_slave = 0; // объявляем переменную для чтения состояний кнопки ведомого 0x77 int VAR_Potentiometer = 0; // объявляем переменную для чтения значения с потенциометра ведомого 0x77 void setup(){ //Wire.setClock(400000); // устанавливаем скорость передачи данных по шине I2C = 400кБит/с Wire.begin(); // инициируем подключение к шине I2C в качестве ведущего (master) устройства 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_master = digitalRead(PIN_Button_master);// Считываем состояние собственной кнопки VAR_Button_slave = I2C2.readByte(0x01,0); // Считываем состояние кнопки ведомого (адрес ведомого 0x01, номер регистра 0) VAR_Potentiometer = I2C2.readByte(0x02,1)<<8; // Считываем старший байт значения потенциометра ведомого (адрес ведомого 0x02, номер регистра 1), сдвигаем полученный байт на 8 бит влево, т.к. он старший VAR_Potentiometer += I2C2.readByte(0x02,2); // Считываем младший байт значения потенциометра ведомого (адрес ведомого 0x02, номер регистра 2), добавляя его значение к ранее полученному старшему байту //Отправляем данные: I2C2.writeByte(0x02,0,VAR_Button_master); // Отправляем состояние собственной кнопки ведомому (адрес ведомого 0x02, номер регистра 0, состояние кнопки) //Выводим данные на 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 дисплее будут заметно мерцать }
Arduino slave 0x01:
// Подключаем библиотеки: #include <Wire.h> // подключаем библиотеку для работы с шиной I2C #include <iarduino_I2C_connect.h> // подключаем библиотеку для соединения arduino по шине I2C // Объявляем переменные и константы: iarduino_I2C_connect I2C2; // объявляем переменную для работы c библиотекой iarduino_I2C_connect const byte PIN_Button = 2; // объявляем константу с указанием номера цифрового вывода, к которому подключена кнопка byte REG_Array[1]; // объявляем массив, данные которого будут доступны для чтения/записи по шине I2C void setup(){ //Wire.setClock(400000); // устанавливаем скорость передачи данных по шине I2C = 400кБит/с Wire.begin(0x01); // инициируем подключение к шине I2C в качестве ведомого (slave) устройства, с указанием своего адреса на шине. I2C2.begin(REG_Array); // инициируем возможность чтения/записи данных по шине I2C, из/в указываемый массив pinMode(PIN_Button, INPUT); // Устанавливаем режим работы вывода кнопки, как вход } void loop(){ REG_Array[0] = digitalRead(PIN_Button); // Сохраняем состояние кнопки в 0 ячейку массива REG_Massive }
Arduino slave 0x02:
// Подключаем библиотеки: #include <Wire.h> // подключаем библиотеку для работы с шиной I2C #include <iarduino_I2C_connect.h> // подключаем библиотеку для соединения arduino по шине I2C // Объявляем переменные и константы: iarduino_I2C_connect I2C2; // объявляем переменную для работы c библиотекой iarduino_I2C_connect const byte PIN_Potentiometer = 0; // объявляем константу с указанием номера аналогового вывода, к которому подключён потенциометр const byte PIN_LED = 13; // объявляем константу с указанием номера цифрового вывода, к которому подключен светодиод int VAR_Potentiometer = 0; // объявляем переменную для чтения значения с потенциометра byte REG_Massive[3]; // объявляем массив, данные которого будут доступны для чтения/записи по шине I2C void setup(){ //Wire.setClock(400000); // устанавливаем скорость передачи данных по шине I2C = 400кБит/с Wire.begin(0x02); // инициируем подключение к шине I2C в качестве ведомого (slave) устройства, с указанием своего адреса на шине. I2C2.begin(REG_Massive); // инициируем возможность чтения/записи данных по шине I2C, из/в указываемый массив pinMode(PIN_LED, OUTPUT); // Устанавливаем режим работы вывода светодиода, как выход } void loop(){ VAR_Potentiometer = analogRead(PIN_Potentiometer); // Считываем значение потенциометра REG_Massive[1] = VAR_Potentiometer>>8; // Сохраняем старший байт значения потенциометра в 1 ячейку массива REG_Massive REG_Massive[2] = VAR_Potentiometer; // Сохраняем младший байт значения потенциометра во 2 ячейку массива REG_Massive digitalWrite(PIN_LED, REG_Massive[0]); // вкл/выкл светодиод в соответствии со значением 0 элемента массива REG_Massive }
Алгоритм работы:
- Arduino master проверяет состояние собственной кнопки.
- Arduino master опрашивает две Arduino Slave.
- Arduino Slave 0x01 возвращает состояние кнопки.
- Arduino Slave 0x02 возвращает значение падения напряжения плеча потенциометра.
- Arduino master мастер отправляет состояние своей кнопки в Arduino Slave 0x02.
- Arduino Slave 0x02 включает или выключает светодиод, в соответствии с полученными данными.
- Arduino master отправляет данные о состояниях кнопки Arduino Slave 0x01 и потенциометра Arduino Slave 0x02 на LCD I2C дисплей.
Настройка параметров шины I2C:
Максимальная, аппаратно реализуемая частота передачи данных, может достигать 1/16 от тактовой частоты.
Библиотека Wire позволяет устанавливать скорость передачи данных через функцию setClock(), которую требуется вызвать до функции begin().
Библиотека Wire позволяет аппаратно подключить Arduino к шине I2C с указанием роли Arduino на шине: ведущий или ведомый.
- Wire.setClock(400000); // скорость передачи данных 400 кБит/с.
- Wire.begin(); // подключение к шине I2C в роли ведущего.
- Wire.begin(адрес); // подключение к шине I2C в роли ведомого, с указанием адреса.
Функции библиотеки iarduino_I2C_connect:
В библиотеке iarduino_I2C_connect реализованы 4 функции: 2 для ведущего и 2 для ведомого.
На ведомом устройстве достаточно вызвать функцию begin() с указанием массива, данные которого требуется сделать доступными для мастера. Далее можно работать с этим массивом, «забыв» про шину I2C. Мастер обращаясь к ведомому сможет получать и изменять данные элементов указанного массива (обращаясь к номеру элемента массива как к номеру регистра).
Если требуется запретить мастеру менять значения некоторых ячеек массива, то достаточно вызвать «необязательную» функцию writeMask() с указанием маскировочного массива, каждый элемент которого является флагом, разрешающим запись в соответствующий элемент массива доступного по шине I2C.
На ведущем устройстве доступны функции readByte() и writeByte(). Указывая в качестве параметров функций, адрес ведомого устройства и номер регистра, можно побайтно читать и записывать данные.
Функции для ведомого:
begin():
- Назначение: указание массива, элементы которого будут доступны для чтения/записи по шине I2C.
- Синтаксис: begin(массив); // тип данных массива - byte.
- Возвращаемые значения: Нет.
- Примечание: Вызывается 1 раз в коде функции Setup().
writeMask():
- Назначение: указание маскировочного массива, каждый элемент которого является флагом разрешения записи по шине I2C.
- Синтаксис: writeMask(маскировочный_массив); // тип данных массива - bool.
- Возвращаемые значения: Нет.
- Примечание: Вызывается 1 раз в коде функции Setup().
- Пример:
#include <Wire.h>
#include <iarduino_I2C_connect>
iarduino_I2C_connect ccc;
byte aaa[5]; // объявляем 5 элементов массива, к которому разрешим доступ.
bool bbb[5] = {0,1,1,1,0}; // объявляем 5 элементов маскировочного массива.
Wire.begin(0x33); // подключаемся к шине I2C в роли ведомого с адресом 0x33.
ccc.begin(aaa); // разрешаем доступ к массиву aaa по шине I2C.
ccc.wtiteMask(bbb); // маскируем разрешение на запись в массив aaa.
- В соответствии со значениями массива bbb, мастер сможет записывать данные в ячейки 1,2,3 массива aaa, а попытка записи в 0 и 4 элементы будет проигнорирована.
- Если массив aaa имеет больше элементов чем массив bbb, то попытка мастера записать данные в «лишние» элементы будет проигнорирована.
- Если не объявлять маскировочный массив (не вызывать функцию writeMask() вообще), то все элементы массива aaa будут доступны для записи мастером.
Функции для ведущего:
readByte():
- Назначение: Чтение одного байта данных из устройства на шине I2C.
- Синтаксис: readByte(адрес_устройства, адрес_регистра);
- Возвращаемые значения: uint8_t байт - данные считанные из указанного адреса, указанного устройства.
- Примечание: Если производится чтение по шине I2C из массива объявленного функцией begin(), то адрес регистра соответствует номеру элемента массива.
writeByte():
- Назначение: Запись одного байта данных в устройство на шине I2C.
- Синтаксис: writeByte(адрес_устройства, адрес_регистра, байт_данных);
- Возвращаемые значения: uint8_t байт - результат операции записи, соответствует значению возвращаемому функцией Wire.endTransmission().
- 0 - успех.
- 1 - переполнение буфера передачи данных.
- 2 - получен NACK на переданный адрес устройства.
- 3 - получен NACK на переданный адрес регистра.
- 4 - неизвестная ошибка.
- Примечание: Если производится запись по шине I2C в массив объявленный функцией begin(), то адрес регистра соответствует номеру элемента массива.
Обсуждение