Ардуино зависает/не отвечает- не могу понять почему

Я строю лодку с дистанционным управлением. Мой удаленный проект зависает через произвольное время. Последовательный порт перестает выводить, OLED больше не обновляется и инструкции больше не отправляются/не принимаются.

На всю жизнь я не могу понять, почему мой проект зависает. Использование Arduino Nano.

Я пробовал следующее:

  • Держите память небольшой
  • Отключите Serial, чтобы избежать переполнения.
  • Не используйте рискованные переменные; как строка
  • Определить, вызывает ли зависание конкретное обстоятельство (количество X времени, определенные инструкции, потеря радиосвязи и т. д.)
  • Замените Arduino
  • Замените блок питания
  • Замените кабель питания.
  • Переведите радио в режим отладки, оно станет медленным и ничего интересного не будет показываться по последовательному каналу.

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

Arduino подключен к NRF24L01 с платой адаптера, OLED и двумя потенциометрами.

Это мой код:

#include <SPI.h>
#include "printf.h"
#include "RF24.h"
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#include <DifferentialSteering.h>

// Режим отладки:
int debugging_serial = 0;
int debugging_delay = 0;
int debugging_verbose = 0;

// Инициализация таймеров;
unsigned long start_timer = 0;
unsigned long end_timer = 0;

// Старое состояние для очистки
int oledstate = 0;

// Создайте трансивер nRF24L01, используя контакты 9 и 10.
RF24 radio(9, 10);

// OLED-инициализация
#define I2C_ADDRESS 0x3C // 0X3C+SA0 - 0x3C или 0x3D
#define RST_PIN -1 // При необходимости определить правильный RST_PIN.
SSD1306AsciiAvrI2c oled;

// Установка контактов потенциометра
int pot_one_pin = A0;
int pot_two_pin = A1;

// Установить управление разницей
DifferentialSteering DiffSteer;

// Зарегистрируйте два радиоадреса, чтобы использовать их для связи.
uint8_t address[][6] = { "1Node", "2Node" };


// Определяем отправку и получение структур полезной нагрузки. Примечание: они должны быть инвертированы для удаленного и лодочного кода.
// ОПРЕДЕЛЯЕМ, ЧТО МЫ БУДЕМ ОТПРАВЛЯТЬ
struct SendingPayloadObject 
{
  // Мы отправляем два числа
  uint8_t Motor1Speed; 
  uint8_t Motor2Speed;
};
// Также создайте объект полезной нагрузки для подготовки и загрузки со значениями по умолчанию в настройках
SendingPayloadObject payload;

// ОПРЕДЕЛЯЕМ, ЧТО МЫ ПОЛУЧИМ
struct ReceivingPayloadObject 
{
  // Мы отправляем сообщение и номер
  char message[7];  // использование только 6 символов для TX & Полезная нагрузка ACK
  uint8_t counter; // переменная-счетчик структуры в качестве примера
};

void setup() 
{
  if(debugging_serial) 
  {
    Serial.begin(115200);
    Serial.println("Remote Code Started!");
  }

  // Настройка Oled
  // Вызываем oled.setI2cClock(частота) для изменения частоты по умолчанию.
  oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
  //oled.setFont(System5x7);
  oled.clear();
  oled.println("Boat Remote Started!");
  oled.clear();
  
  // Проверяем, работает ли радио
  if (!radio.begin()) 
  {
    if(debugging_serial) 
    {
      Serial.println(F("Radio hardware is not responding!"));
    }
      
    while (1) {}  // держать
  }

  // Установите низкий уровень PA, чтобы попытаться предотвратить проблемы, связанные с питанием
  // потому что эти примеры, скорее всего, выполняются с узлами в непосредственной близости от
  // друг друга. (RF24_PA_MAX по умолчанию)
  radio.setPALevel(RF24_PA_LOW);

  // Чтобы использовать полезную нагрузку ACK, нам нужно включить динамическую длину полезной нагрузки (для всех узлов). Полезные нагрузки ACK имеют динамический размер
  radio.enableDynamicPayloads();

  // Пакеты подтверждения не имеют полезной нагрузки по умолчанию. Нам нужно включить эту функцию для всех узлов (TX и RX), чтобы использовать полезные данные ACK.
  radio.enableAckPayload();

  // Установите адрес TX узла RX в канал TX и наоборот в канал RX. Удаленный радиоадрес равен 1!
  radio.openWritingPipe(address[1]);
  radio.openReadingPipe(1, address[0]);

  // Код роли отправителя
  // Установить значения полезной нагрузки по умолчанию и перевести радио в режим отправки (TX)
  payload.Motor1Speed = 0;  // устанавливаем полезную нагрузку payloadMotor1Speed
  payload.Motor2Speed = 0;  // устанавливаем полезную нагрузку payloadMotor1Speed
  radio.stopListening();                 // перевести радио в режим TX
  
  if(debugging_verbose)
  {
    printf_begin();             // требуется только один раз для печати деталей
    radio.printDetails();       // (меньшая) функция, которая печатает необработанные значения регистров
    radio.printPrettyDetails(); // (более крупная) функция, которая печатает удобочитаемые данные
  }

  // Инициализация DiffSteer
  DiffSteer.begin(0);
}

void loop() 
{
  //// ДИСТАНЦИОННОЕ УПРАВЛЕНИЕ:

  // НОВАЯ ЛОГИКА ДЛЯ МАНЕВРИРОВАНИЯ
  int motorSpeed1 = 0;
  int motorSpeed2 = 0;

  // Управление с помощью потенциометров
  int steering_pot_value = map(analogRead(pot_two_pin), 0, 1023, -127, 127);
  int power_pot_value = map(analogRead(pot_one_pin), 0, 1023, 0, 127);

  // Идти прямо, когда рулишь почти прямо
  if(steering_pot_value <= 15 && steering_pot_value >= -15)
  {
    steering_pot_value = 0;
  }

  // Вычисление дифференциального рулевого управления
  DiffSteer.computeMotors(steering_pot_value, power_pot_value);
  
  // сопоставляем выходы мотора с нужным диапазоном
  motorSpeed1 = map(DiffSteer.computedLeftMotor(), 0, 127, 0, 255);
  motorSpeed2 = map(DiffSteer.computedRightMotor(), 0, 127, 0, 255);

  // Корректирующие меры для более плавного управления
  // Исправлено деление на 0, полная мощность, когда банк полностью выключен
  if(power_pot_value <= 1) { motorSpeed1 = 0; motorSpeed2 = 0; };

  // Код управления двигателем OLED (для тестирования)
  // Скорости напрямую от счетчиков
  // motorSpeed1 = map(analogRead(pot_one_pin), 0, 1023, 0, 255);
  // motorSpeed2 = map(analogRead(pot_two_pin), 0, 1023, 0, 255);
  //
  
  // Установить скорость двигателя в полезной нагрузке
  payload.Motor1Speed = motorSpeed1;
  payload.Motor2Speed = motorSpeed2;

  //// РАДИОМАТЕРИАЛЫ:
  
  // Рассчитываем время передачи для маагда/ботаника
  if(debugging_serial) 
  {
     start_timer = micros();                  // запускаем таймер
  }

  // Отправляем по радио TX;
  bool report = radio.write(&payload, sizeof(payload));  // передать & сохранить отчет

  if(debugging_serial) 
  {
    end_timer = micros();                    // конец таймера
  }

  if (report) 
  {
    if(debugging_serial) 
    {
      Serial.print(F("Time to transmit = "));
      Serial.print(end_timer - start_timer);  // распечатать результат таймера
      Serial.print(F(" us. Sent (Motor 1 Speed: "));
      Serial.print(payload.Motor1Speed);
      Serial.print(F(" , Motor 2 Speed: "));
      Serial.print(payload.Motor2Speed);
      Serial.print(F(")"));
    }

    // Определяем трубу
    uint8_t pipe;
    
    if (radio.available(&pipe)) 
    {
      if (oledstate != 1) { oled.clear(); }; oledstate = 1;
      oled.set1X();
      oled.setFont(System5x7);
      oled.setCursor(0 , 0);
      oled.print("Transmission OK \n");
      oled.setCursor(0 , 30);
      oled.print("Motors L:");
      // Добавляем ведущие нули, чтобы получить истинные значения (иначе останется старое число)
      if(payload.Motor1Speed < 100 && payload.Motor1Speed >= 10) { oled.print("0"); };
      if(payload.Motor1Speed < 10) { oled.print("00"); };
      oled.print(payload.Motor1Speed);
      oled.setCursor(80 , 30);
      oled.print("R:");
      if(payload.Motor2Speed < 100 && payload.Motor2Speed >= 10) { oled.print("0"); };
      if(payload.Motor2Speed < 10) { oled.print("00"); };
      oled.print(payload.Motor2Speed);
      
      // есть ли полезная нагрузка ACK? возьмите номер трубы, которая его получила
      ReceivingPayloadObject received;
      radio.read(&received, sizeof(received));  // получаем входящую полезную нагрузку ACK

      if(debugging_serial) 
      {
        Serial.print(F(" Recieved "));
        Serial.print(radio.getDynamicPayloadSize());  // вывод размера входящей полезной нагрузки
        Serial.print(F(" bytes on pipe "));
        Serial.print(pipe);  // вывести номер канала, который получил ACK
        Serial.print(F(": "));
        Serial.print(received.message);    // печатаем входящее сообщение
        Serial.println(received.counter);  // вывести входящий счетчик
      }
    } 
    else 
    {
      if (oledstate != 2) { oled.clear(); }; oledstate = 2;
      oled.set1X();
      oled.setCursor(0, 0);
      oled.setFont(System5x7);
      oled.print("Transmission: \n \n");
      oled.setCursor(0, 80);
      oled.set2X();
      oled.print("No Reply (empty ACK)");

      if(debugging_serial) 
      {
        Serial.println(F(" Recieved: an empty ACK packet"));
      }
    }
  } 
  else 
  {
    if (oledstate != 3) { oled.clear(); }; oledstate = 3;
    oled.set1X();
    oled.setCursor(0, 0);
    oled.setFont(System5x7);
    oled.print("Transmission: \n \n");
    oled.setCursor(0, 80);
    oled.set2X();
    oled.print("Failed");

    if(debugging_serial) 
    {    
      Serial.println(F("Transmission failed or timed out"));
    }
  }

  // Добавляем задержку при программировании для чтения
  if(debugging_verbose)
  {
    delay(10000); 
  }
  else
  {
    if(debugging_delay)
    {
      delay(1000); 
    }
  }
}

, 👍3

Обсуждение

Возможно, добавьте в свой пост статистику использования ОЗУ/хранилища, которую компилятор предоставляет в конце вывода. Также скажите, что это за плата., @6v6gt

где программа висит?, @jsotola

Подходя к проблеме с другой стороны, почему бы не использовать сторожевой таймер для перезагрузки Arduino, когда он зависает., @Bra1n

Спасибо за ваши ответы! У меня нет под рукой числа использования, но оно было очень низким. Может 30%. Использование Нано. Программа где-то висит если радио.доступно насколько я могу определить. Сторожевой таймер не лучшее решение, так как он выведет лодку из строя на несколько секунд. Жду еще мыслей и советов!, @user2863494

Похоже, вы использовали относительно сложную кодовую базу nrf24l01 для своего проекта, который изначально поддерживал переключение ролей между частями «передатчик» и «приемник». Если у вас есть один передатчик и один приемник, то более простой пример, также обрабатывающий пакеты подтверждения, можно найти здесь: https://forum.arduino.cc/t/simple-nrf24l01-2-4ghz-transceiver-demo/405123 /3 . Если вы не используете параметры отладки, передатчик, скорее всего, перегрузит приемник, потому что в противном случае в цикле() не будет паузы., @6v6gt

Спасибо за ваш ответ. Не могли бы вы немного рассказать о последней части? Зачем мне пауза, если я стремлюсь сохранять контроль мгновенным и быстрым? Как я могу подтвердить и предотвратить наводнение? Можно ли настроить его больше как UDP, чем как TCP, говоря с точки зрения сети? @6v6gt, @user2863494

Приемнику потребуется конечное время, чтобы ответить передатчику. Существуют также непредсказуемые эффекты, такие как повторная передача полезной нагрузки или подтверждения, влияющие на время приема-передачи. Вы можете взять это под некоторый контроль с помощью метода setRetries(). Я предполагаю, что по крайней мере пауза в 50 мс или около того в конце цикла была бы хорошей, но это также зависит от того, что делает получатель после отправки подтверждения. Протокол для обработки чистой сквозной передачи данных является собственностью "Shock Burst". Варианты здесь: https://nrf24.github.io/RF24/classRF24.html., @6v6gt

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


1 ответ


1

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

Я бы начал с простейшей настройки с одним приемопередатчиком на каждом конце. Никаких других периферийных устройств, никакого другого кода; что-то, что просто отправляет "привет" туда и обратно с большими интервалами — например, раз в секунду, чтобы убедиться, что радиочастотный канал не будет перегружен.

Как только вы дойдете до базового уровня, при котором ваша тестовая конфигурация будет работать бесконечно, начните добавлять код и/или периферийные устройства по одному, проводя длительные тесты на каждом этапе. В конце концов вы достигнете точки, в которой система станет прерывистой; когда это произойдет, выполните один шаг назад, чтобы подтвердить правильную работу, а затем повторите этот шаг вперед/назад, пока не убедитесь, что это шаг, который приводит к ошибочному поведению.

Затем разделите последний шаг (тот, который вызывает плохое поведение) на более мелкие шаги и выясните, какой из этих шагов вызывает плохое поведение, и в конечном итоге вы дойдете до одной строки кода или одного конкретного периферийного устройства. это вызывает проблему, и, по всей вероятности, это будет то, о чем вы скажете: «О! Я должен был подумать об этом давным-давно!».

Напоследок: вы упомянули функцию if radio.availible() в качестве возможного виновника. Вы можете начать процесс, например, изменив код библиотеки (вы можете сделать это — это просто еще один фрагмент кода Arduino), чтобы он просто возвращал «истину» (или ложь — не имеет значения) каждый раз, когда он вызывается. . Если ваша система нормально работает с этой настройкой, но не работает с исходным кодом библиотеки, то это вполне может быть источником проблемы.

Глядя на RF24.cpp, я вижу, что available() вызывает get_status(), так что, возможно, это get_status() зависает.

Некоторые мысли,

Фрэнк

,