Мультимастер I2C с тем же адресом

i2c

Я использую библиотеку Wire и мне нужно настроить несколько мастеров. Я вижу примеры с несколькими мастерами, где мастера имеют свои собственные идентификаторы, и это имеет смысл.

Теперь я только что подключил к шине три устройства с одинаковым идентификатором (например, 0x05), и они могут общаться! Каждый узел может прослушивать весь трафик, что встречается нечасто, но это именно то, что мне нужно.

Вопрос: возникнут ли у меня проблемы в будущем? Является ли моя настройка чем-то вроде «мульти-ведомого»?

, 👍0

Обсуждение

Если все, что делают рабы, — это слушают, это не должно быть проблемой. Если вы когда-нибудь захотите, чтобы подчиненные переговорили с мастером, вы мгновенно получите коллизии, и все развалится., @Majenko

на самом деле эта проблема решается с помощью строкового протокола, и любые узлы отправляют сообщения только в том случае, если они запрошены. Но не проверял, что будет, если попытаются отправить одновременно. Я использую Wire.write и получаюEvent., @Andy

Энди, это проблема. @Маженко, возможно, библиотека Wire в данный момент принимает неправильный подтверждение/отказ, но это может измениться в будущем. А как насчет растяжения тактового импульса для разных подчиненных устройств? Я вижу слишком много проблем. При **отправке** данных нескольким подчиненным устройствам вы можете использовать широковещательную рассылку. Он не включен в библиотеке Wire, но Ник Гаммон показывает, как включить его на ведомых устройствах: http://www.gammon.com.au/i2c Запрос данных должен выполняться для каждого ведомого устройства отдельно. Вам придется придерживаться стандартных правил. Если вы сможете заставить его работать, он может потерпеть неудачу в будущем, если библиотеки Wire будут улучшены., @Jot

Про трансляцию не знал, спасибо. Ваш источник кажется очень подробным, @Andy


1 ответ


1
const byte MY_ADDRESS = x;  // где x — любой уникальный адрес, который вам нравится

byte RxByte[32]  //32-байтовый буфер

void setup() {  
  //вставить уникальный адрес
  Wire.begin(MY_ADDRESS);  

// эта строка позволяет arduino прослушивать широковещательную передачу i2c по адресу 0

TWAR = (MY_ADDRESS << 1) | 1;  
//настраиваем обработчик приема
 Wire.onReceive(receive_data);

then call 
Wire.beginTransmission (0)  //передача широковещательной передачи, чтобы все устройства получили

Wire.write( address of destination) // делаем первый байт назначением

Wire.endTransmission ();

then you need a receive handler 

void receive_data() { 
//используем буфер для временного хранения данных

for ( int d = 0; Wire.available() > 0 ;d++){ 

RxByte[d] = Wire.read();} // ЧИТАТЬ БУФЕР WIRE ДО 32 БАЙТОВ В МАССИВ
datareceived = 1;

//затем проверить, было ли это для этого устройства
if (RxByte[0] == MYADDRESS){ do somthing with RxBytes[1-31];}
else {}

Я широко использую это, это очень удобно и хорошо работает. У меня есть сложная таблица передачи, которая имеет заголовок из 4 байтов, а остальные байты можно использовать для отправки данных.

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


Дополнение к оригинальному ответу. Ниже представлен полный рабочий пример.

//итак, основа таблицы передачи — это первые 4 байта (т.е. байты Rx или Tx 0-3 в зависимости от того, говорим ли мы об отправителе или получателе) //используются как заголовок 0= получатель (0 для широковещательной рассылки или адреса) 1= адрес отправителя 2= это команда (т.е. сообщает получателю, что делать) //и 4= количество байтов данных, которые следуют в пакете //не все из них нужно указывать в любое время, но полезно иметь структуру, которой нужно следовать на каждом конце, которая никогда не меняется //скопируйте и вставьте эту загрузку в 2 Arduino, подключенные по I2C, измените 2 адреса, и это должно заработать. это пример того, как данные могут перемещаться в ситуации с несколькими главными устройствами Надеюсь, это даст вам некоторые идеи. Это выглядит сложным, но половина этого - просто генерация некоторых примеров данных и метода запуска, а часть передачи повторяется 3 раза для отправки разных данных. Вы должны быть в состоянии создать более универсальный блок передачи для своего приложения

Чтобы отправить данные, просто подключите кнопку или провод к контакту 2 непосредственно к земле

посмотрите 2 серийных окна

#include <Wire.h>
//адрес устройства СДЕЛАЙТЕ РАЗНЫМ ДЛЯ КАЖДОГО

const byte MY_ADDRESS = 20;
const byte PARTNER_DEVICE = 25;

// таблица передач
byte RxByte[32]; //настраиваем приемный буфер для хранения данных
byte TxByte[32]; //настраиваем буфер передачи для хранения данных
byte datareceived;

// что-то для генерации данных
const int Sensor1 = A0;
byte SensorVal1 = 0;

//вещи для устранения дребезга контакта 2
const byte digitalpin = 2;
byte pinstate = 0;
byte lastpinstate = 0;
unsigned long checktime = 0;
const byte debouncetime = 50;

// таймер
unsigned long timing_loop = 5000;

void setup() {
  Wire.begin (MY_ADDRESS);
  TWAR = (MY_ADDRESS << 1) | 1;                                                      // РАЗРЕШИТЬ ПРИЕМ ТРАНСЛЯЦИЙ
  Wire.onReceive(ReceiveData);     // ПРЕРЫВАНИЕ ПРИ ПРИЕМЕ ДАННЫХ I2C
  Serial.begin(9600);
  pinMode (  digitalpin  , INPUT_PULLUP );// устанавливаем вывод в режим подтяжки входа, чтобы мы могли запустить код, подключившись напрямую к земле
  Serial.println( "READY" );
}

void loop() {
  // сгенерировать некоторые случайные данные

  GenRandData();
  //Serial.print(pinstate);Serial.print( " " );Serial.println(lastpinstate );
  // устраняет дребезг контакта 2, чтобы мы могли запустить передачу данных
  pinstate = digitalRead (digitalpin);
  if ( pinstate != lastpinstate && millis() >= checktime) {
    if ( pinstate == 0 && lastpinstate == 1 ) {
      checktime = millis() + debouncetime;
      Serial.println( " button working" );
      REQUEST();
    }
    lastpinstate = pinstate ;
  }


  // это запускает метод обработки данных после получения данных
  if (datareceived == 1  ) {
    datareceived = 0 ;
    ProcessData();
  }

  // 10-секундный таймер запускает передачу некоторых данных
  if ( millis() >= timing_loop ) {
    BROADCAST();
  }
}
void GenRandData() {
  // это просто считывает analog0 и сопоставляет его с байтом, а затем последовательно сдвигает буфер передачи
  SensorVal1 = random(255);
  TxByte[7] = TxByte[6];
  TxByte[6] = TxByte[5];
  TxByte[5] = TxByte[4];
  TxByte[4] = SensorVal1;
}

void ReceiveData() {
  // ЧИТАТЬ ДОСТУПНЫЕ БАЙТЫ В 32-БАЙТНЫЙ БУФЕР (пока данные доступны)
  for ( byte d = 0; Wire.available() > 0 ; d++) {
    RxByte[d] = Wire.read();
  }
  //установить событие-триггер
  datareceived = 1;
  Serial.println( "receive" );
}

void ProcessData() {
  Serial.println( "process" );
  // проверить, что это за данные и для кого они предназначены, а затем что-то с ними сделать (ПОЛУЧИТЬ ТАБЛИЦУ)
  if (RxByte[0] == MY_ADDRESS) { // для этого устройства
    switch (RxByte[2]) {
      case 1:       Serial.print("RECEIVED DATA FOR THIS DEVICE, = "); Serial.println( RxByte[4] ); break;
      case 255:       Serial.println("RECEIVED A REQUEST FOR DATA, SENDING READ FROM  > Sensor1"  ); ActAsMaster();              break;
      default:      Serial.print("D-Type byte = error"); Serial.println(RxByte[2]);                   break;
    }
  }

  else if (RxByte[0] == 0) {       //обработка широковещательной передачи
    Serial.print( "RECEIVED BROADCAST FROM DEVICE = " ); Serial.println( RxByte[1] );
    Serial.print( "DATASIZE = " ); Serial.print( RxByte[3] ); Serial.println( " Bytes" );
    Serial.print( "DATABYTE 1 = " ); Serial.println( RxByte[4] );
    Serial.print( "DATABYTE 2 = " ); Serial.println( RxByte[5] );
    Serial.print( "DATABYTE 3 = " ); Serial.println( RxByte[6] );
    Serial.print( "DATABYTE 4 = " ); Serial.println( RxByte[7] );
    Serial.println( "DONE" );
  }
  Serial.println(  );
}

void BROADCAST() {
  // это таблица передачи данных для отправки всем
  Serial.println("Sending broadcast");
  byte count = 0;
  byte errorcode;
Tx:
  Wire.beginTransmission (0);                       // трансляция
  Wire.write(0);                                    // АДРЕС =0 ТРАНСЛЯЦИЯ
  Wire.write(MY_ADDRESS);                           // ОТПРАВИТЬ мой адрес
  Wire.write(0);                                    // КОД ТИПА ДАННЫХ
  Wire.write(4);                                    //количество отправленных байтов
  Wire.write(TxByte[4]);                            //данные
  Wire.write(TxByte[5]);                            //данные
  Wire.write(TxByte[6]);                            //данные
  Wire.write(TxByte[7]);                            //данные
  errorcode = Wire.endTransmission ();
  if (errorcode != 0 && count <= 2) {
    count++;
    Serial.println( "fail" );
    goto Tx;
  }
  //сбросить таймер, чтобы запустить это снова
  timing_loop = millis() + 10000 ;
  Serial.println(  );
}

void ActAsMaster() {
  //это таблица передачи для отправки данных на определенное устройство ИЛИ возврата результата задачи
  Serial.println("Sending data");
  byte count = 0;
  byte errorcode;
Tx:
  Wire.beginTransmission (RxByte[1]);               //адрес возврата
  Wire.write(RxByte[1]);                            //адрес получателя (возврат)
  Wire.write(MY_ADDRESS);                           // ОТПРАВИТЬ ГЛАВНЫЙ АДРЕС
  Wire.write(1);                                  // КОД ТИПА ДАННЫХ
  Wire.write(1);
  Wire.write(SensorVal1);
  errorcode = Wire.endTransmission ();
  if (errorcode != 0 && count <= 2) {
    count++;  // если не удалось и не превышено количество, попробуйте снова
    Serial.println( "fail" );
    goto Tx;
  }
  Serial.println(  );

}
void REQUEST() {
  //это таблица передачи для отправки запроса данных партнерского устройства
  Serial.println("Sending request");
  byte count = 0;
  byte errorcode;
Tx:
  Wire.beginTransmission (PARTNER_DEVICE);               //адрес возврата
  Wire.write(PARTNER_DEVICE);                            //адрес получателя (возврат)
  Wire.write(MY_ADDRESS);                           // ОТПРАВИТЬ ГЛАВНЫЙ АДРЕС
  Wire.write(255);                                  // КОД ТИПА ДАННЫХ (МАСТЕР-АДРЕС)
  Wire.write(0);
  errorcode = Wire.endTransmission ();
  if (errorcode != 0 && count <= 2) {
    count++;  // если не удалось и не превышено количество, попробуйте снова
    Serial.println( "fail" );
    goto Tx;
  }
  Serial.println(  );
}
,

Я старался использовать его по минимуму, но при этом продемонстрировать полную производительность. Думаю, я не очень хорош в минимализме., @R Lloyd

//итак, основы таблицы передачи - вы пишете код или отвечаете на вопрос?, @Nick Gammon

просто пытаюсь ясно объяснить, что происходит.. этот код — просто сильно отредактированная часть крупного проекта, поэтому было значительно проще быстро собрать его, чем пытаться объяснить ответ.. после предыдущего комментария >>Пожалуйста, опубликуйте минимальный, но полный набросок, который нормально компилируется. Ваш код сбивает с толку новичков, потому что он смешивает свободный текст с кодом и имеет код, не находящийся внутри какой-либо функции (например, цикла). – Посмотрите вчерашний Alterno<<, @R Lloyd

спасибо за подробный пример. Мой код такой же, как и в первом примере, и работает. Мне нужно протестировать второй пример. Но я не вижу никакого программного предотвращения столкновений. Я бы предпочел игнорировать столкновенные и поврежденные кадры просто с помощью "программного мастера", также будет сменный мастер, который проверит связь и, если есть столкновение, попытается его исправить. На самом деле, для меня любое программное решение лучше, чем третья линия., @Andy

Я ничего не делал с обнаружением и исправлением столкновений программно, так как мое аппаратное решение предотвращает это, но эта часть >>errorcode = Wire.endTransmission (); если (код ошибки != 0 && количество <= 2) { count++; // если не удалось и не превышено количество, попробуйте снова Serial.println( "неудача" ); перейти к Tx; } следует по крайней мере попытаться убедиться, что пакет доставлен, поскольку маловероятно, что столкновение произойдет трижды., @R Lloyd

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