Wire.requestFrom() не является блокирующей функцией, хотя она должна быть(?)

i2c

Я работаю над созданием простого рукопожатия ACK/NACK между двумя Arduino, которое по сути будет выглядеть следующим образом:

  1. Arduino 1 сигнализирует Arduino 0 о доступности
  2. Arduino 0 отправляет байт подключения на Arduino 1 (скажем, «c»)
  3. Arduino 1 отправляет байт подтверждения на Arduino 0 (скажем, «s»)

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

// подчиненный
#include <Wire.h>

void setup() {
  Serial.begin(9600);
  Wire.begin(1);
  Wire.requestFrom(0, 1, false);
  Serial.println("finished");
}
// мастер
#include <Wire.h>

bool last = false;

void setup() {
  Serial.begin(9600);
  Wire.begin(0);
  pinMode(2, INPUT);
}

void loop() {
  if (digitalRead(2) && !last) {
    Wire.beginTransmission(1);
    Wire.write('c');
    Wire.endTransmission(1);
  } else if (!digitalRead(2)) {
    last = false;
  }
  delay(1000);
}

Здесь мастер-код ожидает сигнала (вывод 2) (который выдается Arduino 1) о том, что он доступен, а затем при подключении отправляет один байт подключения на Arduino 1. В Arduino 1 (подчиненный) s, я поместил вызов requestFrom, который должен блокироваться до тех пор, пока Arduino 0 не отправит этот байт соединения.

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

, 👍2


2 ответа


1

Главные устройства запрашивают данные у подчиненных, а не наоборот.

См. http://gammon.com.au/i2c

Подчиненные устройства настраивают "получение" обработчику сообщать, что есть входящие данные.

Wire.onReceive (receiveEvent);

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

Да, но у вас нет этого запроса на ведущем, он есть на ведомом. Вот ваша проблема.

,

3

Вы совершенно неправильно понимаете, как работает связь I2C (это то, что делает библиотека Wire). Я не буду объяснять здесь полный интерфейс I2C, так как это слишком много для этого ответа.

Wire.requestFrom() не блокируется, пока не получит данные. Он настроит интерфейс I2C как ведущий (если ранее он находился в подчиненном режиме, это будет временно). Затем он выполняет условие запуска на шине, отправляет адрес ведомого устройства, а затем отправляет тактовый импульс для бита подтверждения. Во время этого тактового импульса ведомое устройство может перевести линию SDA в низкий уровень и сигнализировать таким образом, что оно доступно. После этого мастер продолжает генерировать часы для всех необходимых ему байтов, ожидая, что ведомый будет управлять линией SDA. Если ведомое устройство этого не делает (например, потому что вы не подключили обратный вызов onRequest()), то полученные данные будут просто 1 с (то есть каждый байт 0xFF).

Поэтому Wire.requestFrom() блокируется только на время, необходимое для передачи. Если ведомое устройство не подтверждает свой адрес, после этого функция завершает работу. В любом случае транзакция займет всего несколько миллисекунд.

избегайте любых видов многопоточности или функций обратного вызова

На Arduino на основе AVR (таких как Uno или Nano) многопоточность на самом деле не имеет значения (кроме использования библиотеки разделения времени, но все же только на одном ядре), хотя обратные вызовы от прерываний есть. И пока вы используете библиотеку Wire (а вместе с ней и аппаратный интерфейс I2C микроконтроллера), вы должны использовать прерывания и, следовательно, функции обратного вызова. И, честно говоря, обычно нет причин не делать этого.


Еще одна проблема с тем, что вы делаете в настоящее время, заключается в том, что оба Arduino действуют как мастера на шине. Никакие два мастера не могут посылать по шине одновременно. Хотя в протоколе I2C есть способ определить, какой мастер может отправить первым, это часто реализуется с ошибками. Я не знаю, насколько хороша в настоящее время библиотека Wire, но по возможности следует избегать установки с несколькими мастерами.


Небольшая проблема: в адресном пространстве I2C есть несколько зарезервированных разделов для специального использования. Вы можете начать подчиненные адреса с 8 (см. спецификацию I2C в Интернете). Адрес 0 предназначен как общий адрес вызова, то есть для транзакций мастер-запись, которые нацелены на каждое подчиненное устройство на шине. У вас не должно быть подчиненного устройства с таким адресом.


Что теперь делать? Просто реализуйте код с помощью библиотеки Wire, как это и предполагалось, включая функцию обратного вызова onRequest(). См. пример основного чтения библиотеки Wire. Установите второй Arduino в качестве ведущего, а первый — в качестве ведомого. На втором Arduino вы можете вызвать Wire.requestFrom() с адресом подчиненного устройства. Это вызовет функцию обратного вызова onReqest() на первом Arduino, где вы можете использовать Wire.write(), чтобы позволить библиотеке отправить нужные данные обратно мастеру. На мастере (Arduino 2) вы затем читаете эти данные через Wire.read(). И все.

,