Общение двух и более ардуин по шине I2C в формате master-master с одновременным использованием радиоканала.

Paul_B
Offline
Зарегистрирован: 05.12.2016

Проблема родилась отсюда: http://arduino.ru/forum/obshchii/problema-virtualware-i-serialsoftwareserial

Нужно чтобы две (а может и более ардуин) общались между собой в независимом формате (т.е. master-master), т.к. важные события могут наступать у любой ардуине и необходимо оповещать о них останьные ардуины, а также передавать данные по радиоканалу.

Решение было найдено с использованием шины I2C. Ниже приведен код для обоих ардуин, отличия в котором только в адресе ардуины, ну и в главном цикле разный формат передаваемых данных. Для усложнения задачи ардуины общаются между собой как по шине I2C, так и по радиоканалу, т.к. к обоим приделаны радипередатчик и приемник на частоту 433 МГц.

Привожу коды, т.к. в такой реализации нигде не нашел, пишлось быстро сваять самому. Написаны простейшие функции, позволяющие передавать строковые данные. При частой передаче радиоданные могут теряться, поэтому для боле "взрослых" функций необходимо реализовывать подтверждения приема, что тоже сделано, но из-за громоздкости не привожу.

Вот код для первой ардуины (адрес 1)

#include <Wire.h>
#include <VirtualWire.h>

#define R_TX 5              // пин для передатчика D5
#define R_RX 2              // пин для приемника D2, чтобы повесить функцию приема на прерывание
int All=0;

void setup() {
  Serial.begin(9600);

  Wire.begin(1);               // устанавливаем адрес ардуины для шины I2C
  Wire.onReceive(receive_I2C); // если приходят данные, то вызывается эта команда
  
  pinMode (R_TX, OUTPUT);
  pinMode (R_RX, INPUT);
  vw_set_ptt_inverted(true); // для передатчика
  vw_set_tx_pin(R_TX);       // устанавливаем пин передатчика
  vw_set_rx_pin(R_RX);       // устанавливаем пин приемника
  vw_setup(3000);            // скорость передачи данных в Kbps
  vw_rx_start();             // Start the receiver PLL running
  attachInterrupt(0, receive_Radio, CHANGE); // вешаем функцию приема на прерывание 

}

void loop() {
  // put your main code here, to run repeatedly:
  All++;
  send_I2C(2,"I2C   1->2 ("+String(All,DEC)+")");
  send_Radio("Radio 1->2 ("+String(All,DEC)+")");
  delay(1000);
}


uint8_t send_I2C(uint8_t adr, String S) // передача по шине I2C 
{
  uint8_t c;
  char msg[32];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты 
  S.toCharArray(msg, S.length()+1);  
  
// печатаем для наглядности передаваемую команду
  Serial.print("Send I2C <");        
  for (int i = 0; i < strlen(msg); i++) Serial.print(msg[i]);
  Serial.println(">");   

// передаем последовательность байт по шине I2C на устройство с адресом adr  
  Wire.beginTransmission(adr); // transmit to device #8
  Wire.write((byte *)msg, strlen(msg));       // sends five bytes
  c=Wire.endTransmission();    // stop transmitting
  if(c!=0) Serial.println("Error Send="+String(c,DEC));
  return(c);
}


void receive_I2C(int howMany) // прием по шине I2C
{ 
  // прием последовательность байтов и печать для наглядности
  Serial.print("Read I2C <");
  while (Wire.available()) 
    { // loop through all but the last
     char c = Wire.read(); // receive byte as a character
     Serial.print(c);         // print the character
    }
  Serial.println(">");         // print the integer
}


void send_Radio(String S) // передача по радиоканалу 
{
    char msg[27];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты     
    S.toCharArray(msg, S.length()+1);
    vw_send((byte *)msg, strlen(msg));
    vw_wait_tx();                     // Wait until the whole message is gone

// для наглядности печатаем отправляемую команду       
    Serial.println("Send Radio <"+S+">");
  
}


void receive_Radio()    // прием по радиоканалу
{
 uint8_t i; 
 char buf[VW_MAX_MESSAGE_LEN];
 uint8_t buflen = VW_MAX_MESSAGE_LEN;

 if (vw_get_message(buf, &buflen))
    {
     Serial.print("Read Radio <");   
     for (i = 0; i < buflen; i++)
         Serial.print(buf[i]);
     Serial.println(">");
    }
}

 

Paul_B
Offline
Зарегистрирован: 05.12.2016

Вот код для второй ардуины (адрес 2). Функцию радиоприема повесил на прерывания, если главный цикл не длинный, то функцию приема можно снять с прерывания и вставить в тело цикла. К сожалению, функции передачи не работают из  MSTimer2, т.е. сам MSTimer2 работает, но передачу нельзя вставить в функцию, вызываемую по таймеру 2.

#include <Wire.h>
#include <VirtualWire.h>

#define R_TX 5              // пин для передатчика D5
#define R_RX 2              // пин для приемника D2, чтобы повесить функцию приема на прерывание
int All=0;

void setup() {
  Serial.begin(9600);

  Wire.begin(2);               // устанавливаем адрес ардуины для шины I2C
  Wire.onReceive(receive_I2C); // если приходят данные, то вызывается эта команда
  
  pinMode (R_TX, OUTPUT);
  pinMode (R_RX, INPUT);
  vw_set_ptt_inverted(true); // для передатчика
  vw_set_tx_pin(R_TX);       // устанавливаем пин передатчика
  vw_set_rx_pin(R_RX);       // устанавливаем пин приемника
  vw_setup(3000);            // скорость передачи данных в Kbps
  vw_rx_start();             // Start the receiver PLL running
  attachInterrupt(0, receive_Radio, CHANGE); // вешаем функцию приема на прерывание 

}


void loop() {
  // put your main code here, to run repeatedly:
  All++;
  send_I2C(1,"*** I2C 2->1 ("+String(All,DEC)+")");
  delay(200);
}



uint8_t send_I2C(uint8_t adr, String S) // передача по шине I2C 
{
  uint8_t c;
  char msg[32];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты 
  S.toCharArray(msg, S.length()+1);  
  
// печатаем для наглядности передаваемую команду
  Serial.print("Send I2C <");        
  for (int i = 0; i < strlen(msg); i++) Serial.print(msg[i]);
  Serial.println(">");   

// передаем последовательность байт по шине I2C на устройство с адресом adr  
  Wire.beginTransmission(adr); // transmit to device #8
  Wire.write((byte *)msg, strlen(msg));       // sends five bytes
  c=Wire.endTransmission();    // stop transmitting
  if(c!=0) Serial.println("Error Send="+String(c,DEC));
  return(c);
}


void receive_I2C(int howMany) // прием по шине I2C
{ 
  // прием последовательность байтов и печать для наглядности
  Serial.print("Read I2C <");
  while (Wire.available()) 
    { // loop through all but the last
     char c = Wire.read(); // receive byte as a character
     Serial.print(c);         // print the character
    }
  Serial.println(">");         // print the integer
}


void send_Radio(String S) // передача по радиоканалу 
{
    char msg[27];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты     
    S.toCharArray(msg, S.length()+1);
    vw_send((byte *)msg, strlen(msg));
    vw_wait_tx();                     // Wait until the whole message is gone

// для наглядности печатаем отправляемую команду       
    Serial.println("Send Radio <"+S+">");
  
}


void receive_Radio()    // прием по радиоканалу
{
 uint8_t i; 
 char buf[VW_MAX_MESSAGE_LEN];
 uint8_t buflen = VW_MAX_MESSAGE_LEN;

 if (vw_get_message(buf, &buflen))
    {
     Serial.print("Read Radio <");   
     for (i = 0; i < buflen; i++)
         Serial.print(buf[i]);
     Serial.println(">");
    }
}

 

Paul_B
Offline
Зарегистрирован: 05.12.2016

Получилось реализовать общение между 3-мя ардуинами, залив этот код в 3-ю ардуину, изменив ее адрес на 3.

Вот примеры распечаток с экрана ардуины 1.

Read I2C <### I2C 3->1 (26)>
Read I2C <*** I2C 2->1 (599)>
Read I2C <### I2C 3->1 (27)>
Read I2C <*** I2C 2->1 (600)>
Send I2C <I2C   1->2 (16)>
Send I2C <I2C   1->3 (16)>
Read I2C <### I2C 3->1 (28)>
Send Radio <Radio 1->2 (16)>
Read I2C <*** I2C 2->1 (601)>
Read I2C <### I2C 3->1 (29)>
Read I2C <*** I2C 2->1 (602)>

Вот с ардуины 2

Read I2C <### I2C 3->2 (271)>
Send I2C <*** I2C 2->1 (3)>
Send I2C <** I2C 2->3 (3)>
Read I2C <### I2C 3->2 (272)>
Read I2C <I2C   1->2 (87)>
Read I2C <### I2C 3->2 (273)>
Read Radio <Radio 1->2 (87)>
Read I2C <### I2C 3->2 (274)>
Send I2C <*** I2C 2->1 (4)>
Send I2C <** I2C 2->3 (4)>

Вот с адруины 3

Send I2C <### I2C 3->2 (15)>
Read I2C <** I2C 2->3 (16)>
Send I2C <### I2C 3->1 (16)>
Send I2C <### I2C 3->2 (16)>
Read I2C <I2C   1->3 (205)>
Send I2C <### I2C 3->1 (17)>
Send I2C <### I2C 3->2 (17)>
Send I2C <### I2C 3->1 (18)>

Причем, если к одной из ардуин подключен LCD по шине I2C, то на него выводить информацию можно сразу с всех трех ардуин. Вот код, который опять же един для всех трех ардуин, отличия в адресе и куда курсоры выставлять для LCD-дисплея.

#include <Wire.h>
#include <VirtualWire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2); 

#define R_TX 5              // пин для передатчика D5
#define R_RX 2              // пин для приемника D2, чтобы повесить функцию приема на прерывание
int All=0;

void setup() {
  Serial.begin(9600);

  Wire.begin(3);               // устанавливаем адрес ардуины для шины I2C
  Wire.onReceive(receive_I2C); // если приходят данные, то вызывается эта команда
  
  pinMode (R_TX, OUTPUT);
  pinMode (R_RX, INPUT);
  vw_set_ptt_inverted(true); // для передатчика
  vw_set_tx_pin(R_TX);       // устанавливаем пин передатчика
  vw_set_rx_pin(R_RX);       // устанавливаем пин приемника
  vw_setup(3000);            // скорость передачи данных в Kbps
  vw_rx_start();             // Start the receiver PLL running
  attachInterrupt(0, receive_Radio, CHANGE); // вешаем функцию приема на прерывание 

  lcd.init();                      // инициализируем дисплей
  lcd.backlight();                 // Включаем подсветку дисплея

}


void loop() {
  // put your main code here, to run repeatedly:
  All++;
  send_I2C(1,"### I2C 3->1 ("+String(All,DEC)+")");
  send_I2C(2,"### I2C 3->2 ("+String(All,DEC)+")");
  lcd.setCursor(0, 1);             // (X,Y)
  lcd.print("I-3/"+String(All,DEC));
  delay(500);
}



uint8_t send_I2C(uint8_t adr, String S) // передача по шине I2C 
{
  uint8_t c;
  char msg[32];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты 
  S.toCharArray(msg, S.length()+1);  
  
// печатаем для наглядности передаваемую команду
  Serial.print("Send I2C <");        
  for (int i = 0; i < strlen(msg); i++) Serial.print(msg[i]);
  Serial.println(">");   

// передаем последовательность байт по шине I2C на устройство с адресом adr  
  Wire.beginTransmission(adr); // transmit to device #8
  Wire.write((byte *)msg, strlen(msg));       // sends five bytes
  c=Wire.endTransmission();    // stop transmitting
  if(c!=0) Serial.println("Error Send="+String(c,DEC));
  return(c);
}


void receive_I2C(int howMany) // прием по шине I2C
{ 
  // прием последовательность байтов и печать для наглядности
  Serial.print("Read I2C <");
  while (Wire.available()) 
    { // loop through all but the last
     char c = Wire.read(); // receive byte as a character
     Serial.print(c);         // print the character
    }
  Serial.println(">");         // print the integer
}


void send_Radio(String S) // передача по радиоканалу 
{
    char msg[27];

// преобразуем строковую переменную в последовательность байт т.к. Wire отправляет байты     
    S.toCharArray(msg, S.length()+1);
    vw_send((byte *)msg, strlen(msg));
    vw_wait_tx();                     // Wait until the whole message is gone

// для наглядности печатаем отправляемую команду       
    Serial.println("Send Radio <"+S+">");
  
}


void receive_Radio()    // прием по радиоканалу
{
 uint8_t i; 
 char buf[VW_MAX_MESSAGE_LEN];
 uint8_t buflen = VW_MAX_MESSAGE_LEN;

 if (vw_get_message(buf, &buflen))
    {
     Serial.print("Read Radio <");   
     for (i = 0; i < buflen; i++)
         Serial.print(buf[i]);
     Serial.println(">");
    }
}

 

Olger
Offline
Зарегистрирован: 28.02.2015

А если еще одно чисто ведомое устройство на шину подключить (у меня это INA219), то не работает. Обмена данными нет.

 

Morroc
Offline
Зарегистрирован: 24.10.2016

А если одну ардуину оставить ? Лучше всего лог. анализатором глянуть, обычно этого достаточно бывает.

Olger
Offline
Зарегистрирован: 28.02.2015

Одна ведомая ардуина с INA прекрасно работает, но если в режиме мастер-мастер + INA, то никак.