Мультимастер I2C с тем же адресом
Я использую библиотеку Wire и мне нужно настроить несколько мастеров. Я вижу примеры с несколькими мастерами, где мастера имеют свои собственные идентификаторы, и это имеет смысл.
Теперь я только что подключил к шине три устройства с одинаковым идентификатором (например, 0x05
), и они могут общаться! Каждый узел может прослушивать весь трафик, что встречается нечасто, но это именно то, что мне нужно.
Вопрос: возникнут ли у меня проблемы в будущем? Является ли моя настройка чем-то вроде «мульти-ведомого»?
@Andy, 👍0
Обсуждение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
- Отправка и получение различных типов данных через I2C в Arduino
- Как работают функции вне цикла void?
- Как отображать переменные на 0,96-дюймовом OLED-дисплее с библиотекой u8glib?
- Как отправить строку на мастер с помощью i2c
- Как выбрать альтернативные контакты I2C на ESP32?
- Что означает в I2C «NACK получен»?
- NodeMCU с RFID RC522 и LCD-модулем интерфейса I2C вместе
- Несколько датчиков I2C с одинаковым адресом
Если все, что делают рабы, — это слушают, это не должно быть проблемой. Если вы когда-нибудь захотите, чтобы подчиненные переговорили с мастером, вы мгновенно получите коллизии, и все развалится., @Majenko
на самом деле эта проблема решается с помощью строкового протокола, и любые узлы отправляют сообщения только в том случае, если они запрошены. Но не проверял, что будет, если попытаются отправить одновременно. Я использую Wire.write и получаюEvent., @Andy
Энди, это проблема. @Маженко, возможно, библиотека Wire в данный момент принимает неправильный подтверждение/отказ, но это может измениться в будущем. А как насчет растяжения тактового импульса для разных подчиненных устройств? Я вижу слишком много проблем. При **отправке** данных нескольким подчиненным устройствам вы можете использовать широковещательную рассылку. Он не включен в библиотеке Wire, но Ник Гаммон показывает, как включить его на ведомых устройствах: http://www.gammon.com.au/i2c Запрос данных должен выполняться для каждого ведомого устройства отдельно. Вам придется придерживаться стандартных правил. Если вы сможете заставить его работать, он может потерпеть неудачу в будущем, если библиотеки Wire будут улучшены., @Jot
Про трансляцию не знал, спасибо. Ваш источник кажется очень подробным, @Andy