передача данных по i2c arduino

i2c передача данных между двумя arduino. Передаём числа float и int

от автора

в

Часто по разным причинам не хватает одного контроллера в проекте, особенно в процессе прототипирования. Допустим, подключили вы на одну плату дисплей и тачпад, и он занял почти все выводы, аппаратную шину 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 на одной стороне, а затем поделить на другой, ну и подобрать тип переменной под ваше значение, чтобы не нагружать лишними вычисления обе платы.


Комментарии

2 комментария на ««i2c передача данных между двумя arduino. Передаём числа float и int»»

  1. Аватар пользователя Виктор
    Виктор

    Тут все хорошо, но как применить тоже код, но чтобы мастер принимал данные, а не отсылал… А ведомый отсылал данные..?

  2. Аватар пользователя Игорь
    Игорь

    Спасибо за статью. Объясните пожалуйста почему двухбайтную переменную int Вы разбиваете на 4 байта. И в сериал выводятся 4 байта, которые после сборки вручную дают огромное число. Например, 3484 разбито на 156, 13, 5 и 82. Если сложить первое число со вторым, умноженный на 256, получается наши исходные 3484. Откуда ещё два более старших байта и что они обозначают?
    Спасибо.

Добавить комментарий