Почему требуется задержка (1) перед Wire.requestFrom?

i2c

У меня возникла проблема при обмене данными между nodeMCU и Arduino Pro Mini с помощью I2C.

Чтобы найти виновника, я, наконец, написал Echo Server на Arduino: он получает байт и отвечает тем же байтом. В nodeMCU я написал тестовую программу: она отправляет байты на сервер Echo и проверяет ответ, в дополнение к любым условиям ошибки в методах Wire.

Оба процессора соединены GND-GND, VCC-VCC, SDA-SDA и SCL-SCL. SDA и SCL имеют подтягивающие резисторы 10K. Оба подключены через USB к моему ПК, и у меня есть два последовательных монитора для отслеживания выполнения.

В строке 38 программы тестера есть delay(1). Если вы закомментируете его, какой-то requestFrom() случайным образом завершится ошибкой с возвращаемым значением != 1, но Echo Server все равно получит байт.

Поскольку ATmega328 включила TWI в микросхему, я не могу понять, что здесь не так и почему delay(1), кажется, решает проблему. Что мне здесь не хватает?

Программа тестировщика

/*
* Тест эхо-сервера
* Отправьте один байт на сервер Echo и прочитайте его.
* Сделайте это для байтов от 0x01 до 0xDD
*/
#include <Wire.h>

#define SLAVE_ADDRESS  42
byte car = 1; // автомобиль для отправки на сервер Echo.

void setup() {
    Serial.begin(9600);
  // У нас есть nodeMCU с I2C на контактах D5 и D6.
    Wire.begin(D5, D6);
    delay(1000);
  Serial.println("");
    Serial.println(" Echo Server Tester");
    Serial.println("---------------------");
    Serial.println("###  Sent   Received");
    delay(1000);    
}

/*
 * Send one byte in each loop().
 */
void loop() {
    byte status;

    if(car > 0) {
        do {
            Wire.beginTransmission(SLAVE_ADDRESS);
            int written = Wire.write(car);
            status = Wire.endTransmission();

            switch(status) {
            case 0:
        delay(1); // Закомментируйте это и посмотрите, как requestFrom() не работает.
                if(Wire.requestFrom(SLAVE_ADDRESS, 1) == 1) {
                    byte r = Wire.read();
                    printChar(car, r);
                    car++;
                } else {
                    Serial.println("error in return");
                    status = 4;
                }

                break;

            case 1:
                Serial.println("Data too long");
                break;

            case 2:
                Serial.println("NACK on address");
                break;

            case 3:
                Serial.println("NACK on data");
                break;

            case 4:
                Serial.println("Some error");
                break;
            }
        }

        while(status != 0);
    }
}

/*
 * Utility: print in bytes sent and received 
 */
void printChar(byte s, byte r) {
    Serial.print(s);
    Serial.print(" ");
    printBits(s);
    Serial.print(' ');
    printBits(r);

    if(r != s) {
    // Отметить недопустимые ответы.
        Serial.print(" *");
    }

    Serial.println();
}

/*
 * Print a simple byte in binary
 */
void printBits(int car) {
    byte bit = 0x80;

    for(int i = 0; i < 8; i++) {
        int digit = car & bit;
        Serial.print(digit ? '1' : '0');
        bit >>= 1;
    }
}

Программа Echo Server

/*
* Эхо-сервер I2C
* Получите один байт от Мастера, повторите тот же байт Мастеру.
*/
#define MY_ADDRESS  42

#include <Wire.h>

volatile byte received;
volatile int count = 0;

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

  Wire.begin(MY_ADDRESS);
  Serial.println("--- Echo Server ---");

  Wire.onReceive(receiveCommand);
  Wire.onRequest(sendAnswer);
}

void loop() {
  static int lastCount = 0;

  if (count != lastCount) {
    Serial.print(lastCount + 1); Serial.print(" ");
    printChar(received);
    lastCount++;
  }
}

/*  
 * Receive a byte from Master  
 */
void receiveCommand(int howMany) {
  received = Wire.read();
  count++;
}

/* 
 * Send a byte to Master 
 */
void sendAnswer() {
  Wire.write(received);
}

/*
 * Utility print byte received/sent
 */
void printChar(byte car) {
  Serial.print("Char ");
  byte bit = 0x80;
  for (int i = 0; i < 8; i++) {
    byte digit = car & bit;
    Serial.print(digit ? '1' : '0');
    bit >>= 1;
  }
  Serial.println();
}

, 👍2

Обсуждение

интерфейс TWI должен ждать целый цикл перед повторным запросом, иначе часы и буфер могут быть перепутаны., @tuskiomi

ЭТО исправило мои проблемы с I2C, задержку (1) Я не видел этого нигде ранее, поэтому спасибо за вопрос и ответы, поскольку я ходил по кругу, почему мои первые 2 чтения работали, но не мое второе чтение 2, хотя комментируя первые 2 читаются, тогда пусть вторые 2 ведут себя правильно. Теперь я узнаю тактовый цикл и изменю задержку (1) на задержку в микросекундах., @jak Kennedy


1 ответ


Лучший ответ:

2

1 миллисекунда не нужна, попробуйте delayMicroseconds с 50 или, может быть, 20 мкс.

Ардуино как ведомое устройство полностью отличается от, например, датчика. Arduino в качестве ведомого использует комбинацию аппаратного и программного обеспечения. Функции onRequest и onReceive выполняются, пока Arduino поддерживает линию SCL на низком уровне. Это называется растяжением тактового импульса.

С Arduino Uno (микроконтроллер ATmega328P), подключенным к Arduino Uno, эта проблема задержки между записью и чтением на шине I2C существовала долгое время. Несколько лет назад она была решена. Это была ошибка, настоящая ошибка. В этой задержке больше нет необходимости.
Есть еще способы сделать его ненадежным, например, слишком долго отключая прерывания в подчиненном устройстве.

Я читал, что Arduino до сих пор существует Due. При использовании Due в качестве ведущего и, например, ATmega328P (на 3,3 В) в качестве ведомого, для Due по-прежнему требуется задержка.

У nodeMCU и Raspberry Pi могут возникать проблемы при использовании Arduino в качестве ведомого устройства. Arduino в качестве ведомого устройства должен работать как минимум при напряжении 3,3 В или следует использовать переключатель уровня I2C.
Я не знаю, поддерживает ли библиотека nodeMCU Wire растяжение тактовых импульсов.
Эта задержка все еще может понадобиться. Даже тогда я не знаю, насколько надежной будет связь.

Wire.endTransmission — это функция, которая передает данные (а не Wire.write). Если за этим слишком рано следует Wire.requestFrom и в библиотеке Wire есть ошибка, это может вызвать проблемы.

,