Arduino I2C Wire.onReceive зависает после нескольких циклов

У меня есть Arduino Leonardo (мастер) и Polulu A-star 328 PB (ведомый). Я намерен использовать ведомого для приведения в действие некоторых сервоприводов. Я изменил код с https://www.arduino.cc/en/Tutorial/MasterWriter, чтобы также мигает встроенный светодиод ведомого устройства, и оно зависает после нескольких циклов. Без мигания все работает нормально. При мигании он зависает после нескольких циклов.

Мастер:

#include <Wire.h>

void setup() {
  Wire.begin(); // подключение к шине i2c (адрес необязателен для мастера)
}

byte x = 0;

void loop() {
  Wire.beginTransmission(8); // передаем на устройство №8
  Wire.write("x is ");        // отправляет пять байт
  Wire.write(x);              // отправляет один байт
  Wire.endTransmission();    // прекращаем передачу
  x++;
  delay(2000);
}

Подчиненный:

#include <Wire.h>

void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Wire.begin(8);                // присоединяемся к шине i2c с адресом #8
Wire.onReceive(receiveEvent); // регистрируем событие
Serial.begin(9600);           // запускаем сериал для вывода
}

void loop() {
delay(100);
}

void receiveEvent(int howMany) {
while (1 < Wire.available()) {
  char c = Wire.read(); 
  Serial.print(c);
}
int x = Wire.read();
Serial.println(x);
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}

Последовательный монитор:

x is 16
x is 17
x is 18
x 

Есть идеи, что вызывает это и что я могу сделать, чтобы отладить или исправить это? Спасибо!

, 👍1

Обсуждение

onRecieve - это процедура прерывания. Вы не можете использовать такие вещи, как delay(), millis() или Serial.print внутри процедур прерывания., @Majenko


2 ответа


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

2

Обратный вызов onReceive вызывается из ISR (процедура обслуживания прерывания). Во время выполнения этой ISR MCU не может реагировать на другие прерывания, поскольку прерывания автоматически отключаются при запуске ISR.

Некоторые функции, такие как delay() и millis(), для правильной работы полагаются на прерывания, поскольку хронометраж в среде Arduino осуществляется с помощью прерывания таймера. Это означает, что вы не можете использовать delay() внутри ISR. Он заблокируется навсегда, ожидая приращения внутреннего времени от указателя, который никогда не наступит.

У вас также могут возникнуть проблемы с большими файлами Serial.print(). Эти функции Serial заполняют внутренний буфер библиотеки, и библиотека будет использовать прерывания для фактической передачи данных. Если прерывания отключены (как в ISR), буфер заполняется до тех пор, пока он не будет заполнен. Если вы затем снова вызовете Serial.print() или один из его братьев и сестер, библиотека заблокирует выполнение до тех пор, пока в буфере не будет достаточно места, чего никогда не произойдет. (Здесь у вас нет этой проблемы, так как вы печатаете всего несколько символов, но вы должны это знать)

Как правило, ISR должен быть как можно короче, чтобы предотвратить пропуск прерывания Arduino (что может означать потерю данных). Вместо того, чтобы выполнять мерцание в ISR, вы должны просто установить переменную флага, которую вы проверяете в своей функции loop() и выполняете мерцание соответственно. Это выглядит примерно так:

volatile byte flag=0;

void loop(){
    if(flag){
        // Выполнить мигание здесь
    }
}

void receiveEvent(int howMany) {
    // Получаем здесь
    flag=1; //Установите для флага ненулевое значение, чтобы активировать соответствующий код в цикле()
}
,

4

Немного повозившись, я заметил, что удаление вызова delay() в ведомом устройстве помогло, и зависаний больше не было. Кроме того, при использовании библиотеки Servo для перемещения сервопривода вместо мигания светодиода Servo.write() зависает, а Servo.writeMicroseconds() — нет. Я бы отметил ответ, в котором есть объяснение этого странного поведения.

,

проголосуйте за продолжение вашего расследования ... одна вещь, которую вы могли бы попытаться выяснить, это «будет ли код работать с вызовом минимальной задержки (1)?», @jsotola