Часто по разным причинам не хватает одного контроллера в проекте, особенно в процессе прототипирования. Допустим, подключили вы на одну плату дисплей и тачпад, и он занял почти все выводы, аппаратную шину SPI, а какой-нибудь датчик отнимает всё время для вычислений. Нужно временно расшириться, ставим рядом ещё одну плату, но возникает логический вопрос – как передавать данные между ними?
Есть несколько способов, самые распространённые – серийный порт и шина i2c. Мне больше нравится второй вариант, даже если вы используете ещё другие устройства i2c, шина может содержать в себе их до 127 единиц. Вкратце, интерфейс использует два провода для передачи информации – тактовый сигнал и сигнал данных. Соединить платы проще простого – нужно соединить контакты A4 и A5
В отличии от 1-wire, это упрощает “беседу” между двумя устройствами, можно легко брать паузу, в моменты получения/передачи данных. Также у нас есть Master устройство и Slave, отличие в том, что тактирует именно Master. Попробуем передать какое-нибудь число, первым делом для этого подключим библиотеку
#include <Wire.h> // библиотека I2C
Затем инициализируем её в setup()
Wire.begin();
Передать значение – проще простого, нужно начать передачу для определённого адреса slave, записать байт информации, ну и следовательно закончить её
Wire.beginTransmission(9); // Цифра 9 - адрес ведомой платы
Wire.write(x); // Передает значение х
Wire.endTransmission();
Соберём код для Master платы, где будем инкрементировать переменную x и отправлять её раз в секунду.
// Код для Основной платы
#include <Wire.h> // библиотека I2C
int x;
void setup()
{
Wire.begin();
Serial.begin(115200);
}
void loop()
{
x++;
Wire.beginTransmission(9); // Цифра 9 - адрес ведомой платы
Wire.write(x); // Передает значение х
Wire.endTransmission();
Serial.println(x);
delay(1000);
}
Теперь рассмотрим код для платы slave. Здесь есть парочка отличий. В момент инициализации мы задаём адрес slave устройства.
Wire.begin(9); // 9 здесь адрес Slave
Создаём событие, которое будет обрабатываться при поступлении данных
Wire.onReceive(receiveEvent);
Теперь мы можем задать функцию, в которой будем считывать данные, когда они собственно поступают по шине. Для удобства выведем их в терминал
void receiveEvent(int bytes) {
x = Wire.read(); // Получаем значения х от основной платы
Serial.println(x);
}
Теперь нам не составит труда собрать код для slave-устройства. Обратите внимание, функция receiveEvent срабатывает без вызова её в бесконечном цикле loop().
// Код для ведомой платы
#include <Wire.h>
int x;
void setup() {
Wire.begin(9); // 9 здесь адрес Slave (упоминается также в коде основной платы)
Wire.onReceive(receiveEvent);
Serial.begin(115200);
}
void receiveEvent(int bytes) {
x = Wire.read(); // Получаем значения х от основной платы
Serial.println(x);
}
void loop() {
}
Запустим терминал с обеих сторон, и посмотрим на данные. Всё передаётся!
Но после 255 радость не ощущается так сильно, ведь значение сбрасывается в ноль. Происходит так, потому-что Wire.write(x); и x = Wire.read(); работают с одним байтом информации, поэтому больше 255 не передать.
Как же нам передать float и int? Если помните, когда мы работали с EEPROM для arduino, разделяли с помощью указателей переменную на байты, так вот этот способ отлично подойдёт и здесь. Изменим тип переменной на int, и зададим её значение в setup()
int x;
void setup()
{
x = 3484;
Теперь после передачи данных нам нужно сделать цикл for, где мы с помощью указателей разобьём наше число на 4 байта. Не забываем объявить локальную переменную byte
Wire.beginTransmission(9); // Цифра 9 - адрес ведомой платы
byte raw[4];
(int&)raw = x;
Потом создаём цикл for на 4 итерации, где по байтам отправляем наши 4 байта, на которые мы разложили наше число int. Отправляем попутно каждый байт в серийный порт.
for (byte i = 0; i < 4; i++)
{
Wire.write(raw[i]);
Serial.println(raw[i]);
}
Завершаем передачу данных, и отправляем итоговое значение со строкой val=, выжидаем паузу 1 секунду
Wire.endTransmission();
Serial.print("Val =");
Serial.println(x);
delay(1000);
Теперь перейдём к нашему slave-устройству. Также локально объявляем переменную byte,
byte raw[4];
for (byte i = 0; i < 4; i++)
{
raw[i] = Wire.read();
Serial.println(raw[i]);
}
Теперь собираем наше число обратно в int из принятых байтов byte. И также выводим в строку значение.
int &x = (int&)raw;
Serial.print("Val =");
Serial.println(x);
}
Мы выводили в строку значение каждого байта и итоговое значение, посмотрим результат вывода в серийный порт обеих плат.
Как видите у нас получилось передать значение int, и сохранить его в переменную второй платы. Тоже самое можно сделать и с float, переписав int на float в коде. Проверяем
Также я нашёл удобную библиотеку для передачи структур таких данных, это будет стабильно работать в ваших проектах. Давайте посмотрим. Вам нужно создать файл I2C_Anything.h в директории вашего проекта
// Written by Nick Gammon
// May 2012
#include <Arduino.h>
#include <Wire.h>
template <typename T> int I2C_writeAnything (const T& value)
{
const byte * p = (const byte*) &value;
unsigned int i;
for (i = 0; i < sizeof value; i++)
Wire.write(*p++);
return i;
} // end of I2C_writeAnything
template <typename T> int I2C_readAnything(T& value)
{
byte * p = (byte*) &value;
unsigned int i;
for (i = 0; i < sizeof value; i++)
*p++ = Wire.read();
return i;
} // end of I2C_readAnything
Подключаем наш файл в проект, задаём переменную адреса устройства slave, и переменные, которые хотим передать. У меня это три параметра температуры и три параметра давления, в формате float.
#include "I2C_Anything.h"
const byte SLAVE_ADDRESS = 42;
float ftemp1 = 34.6;
float ftemp2 = 54.2;
float ftemp3 = 45.7;
float fpreasure1 = 0.15;
float fpreasure2 = 0.23;
float fpreasure3 = 0.11;
Теперь в проекте, вот таким простым кодом отправляем данные
Wire.beginTransmission (SLAVE_ADDRESS);
I2C_writeAnything (ftemp1);
I2C_writeAnything (ftemp2);
I2C_writeAnything (ftemp3);
I2C_writeAnything (fpreasure1);
I2C_writeAnything (fpreasure2);
I2C_writeAnything (fpreasure3);
Wire.endTransmission ();
На стороне slave устройства, тоже ничего сложного нет, идентично подключаем наш файл, и задаём адрес
#include "I2C_Anything.h"
const byte MY_ADDRESS = 42;
Инициализируем интерфейс i2c, и задаём переменные, но со значением volatile, это означает, что значение может в любой момент поменяться извне, это даёт понять компилятору, что её не нужно оптимизировать, что поможет избежать в дальнейшем проблем.
Wire.begin (MY_ADDRESS);
Serial.begin (9600);
Wire.onReceive (receiveEvent);
volatile boolean haveData = false;
volatile float ftemp1;
volatile float ftemp2;
volatile float ftemp3;
volatile float fpreasure1;
volatile float fpreasure2;
volatile float fpreasure3;
В обработчике данных recieveEvent, мы проверяем количество принятых переменных, где перечисляем их в условие howMany, и считываем эти переменные.
void receiveEvent (int howMany)
{
if (howMany >= (sizeof ftemp1) + (sizeof ftemp2) + (sizeof ftemp3) + (sizeof fpreasure1) + (sizeof fpreasure2) + (sizeof fpreasure3))
{
I2C_readAnything (ftemp1);
I2C_readAnything (ftemp2);
I2C_readAnything (ftemp3);
I2C_readAnything (fpreasure1);
I2C_readAnything (fpreasure2);
I2C_readAnything (fpreasure3);
haveData = true;
} // end if have enough data
} // end of receiveEvent
Теперь в бесконечном цикле loop, чтобы не выводить не принятые данные, делаем проверку переменной haveData (которую мы устанавливаем в значение = Истина в обработчике), и выводим их в серийный порт
if (haveData)
{
Serial.print ("Temp1 float = ");
Serial.println (ftemp1);
Serial.print ("Temp2 float = ");
Serial.println (ftemp2);
Serial.print ("Temp3 float = ");
Serial.println (ftemp3);
Serial.print ("Preasure1 float = ");
Serial.println (fpreasure1);
Serial.print ("Preasure2 float = ");
Serial.println (fpreasure2);
Serial.print ("Preasure3 float = ");
Serial.println (fpreasure3);
haveData = false;
}
Вот таким образом можно передавать несколько разных значений с одной платы на другую.
Используйте в своих проектах эти удобные и простые способы взаимодействия плат. Добавлю под конец ещё пару отступлений – я стараюсь не работать с float в среде Arduino, если передаёте температуру, будет лучше умножить её на 10 или 100 на одной стороне, а затем поделить на другой, ну и подобрать тип переменной под ваше значение, чтобы не нагружать лишними вычисления обе платы.
Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.