Ардуино зависает/не отвечает- не могу понять почему
Я строю лодку с дистанционным управлением. Мой удаленный проект зависает через произвольное время. Последовательный порт перестает выводить, 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);
}
}
}
@user2863494, 👍3
Обсуждение1 ответ
Основное правило отладки сложных проблем состоит в том, чтобы упростить конфигурацию до тех пор, пока не будет достигнут рабочий базовый уровень, а затем восстанавливать его, по частям за раз.
Я бы начал с простейшей настройки с одним приемопередатчиком на каждом конце. Никаких других периферийных устройств, никакого другого кода; что-то, что просто отправляет "привет" туда и обратно с большими интервалами — например, раз в секунду, чтобы убедиться, что радиочастотный канал не будет перегружен.
Как только вы дойдете до базового уровня, при котором ваша тестовая конфигурация будет работать бесконечно, начните добавлять код и/или периферийные устройства по одному, проводя длительные тесты на каждом этапе. В конце концов вы достигнете точки, в которой система станет прерывистой; когда это произойдет, выполните один шаг назад, чтобы подтвердить правильную работу, а затем повторите этот шаг вперед/назад, пока не убедитесь, что это шаг, который приводит к ошибочному поведению.
Затем разделите последний шаг (тот, который вызывает плохое поведение) на более мелкие шаги и выясните, какой из этих шагов вызывает плохое поведение, и в конечном итоге вы дойдете до одной строки кода или одного конкретного периферийного устройства. это вызывает проблему, и, по всей вероятности, это будет то, о чем вы скажете: «О! Я должен был подумать об этом давным-давно!».
Напоследок: вы упомянули функцию if radio.availible() в качестве возможного виновника. Вы можете начать процесс, например, изменив код библиотеки (вы можете сделать это — это просто еще один фрагмент кода Arduino), чтобы он просто возвращал «истину» (или ложь — не имеет значения) каждый раз, когда он вызывается. . Если ваша система нормально работает с этой настройкой, но не работает с исходным кодом библиотеки, то это вполне может быть источником проблемы.
Глядя на RF24.cpp, я вижу, что available() вызывает get_status(), так что, возможно, это get_status() зависает.
Некоторые мысли,
Фрэнк
- Как автоматически сбросить модуль NRF24L01
- Загрузка Arduino Nano дает ошибку: avrdude: stk500_recv(): programmer is not responding
- как быстро loop() работает в Arduino
- Использовать Arduino Nano V3 для программирования другого Arduino (Pro Mini)?
- Как автоматически сбросить nrf24l01+ с кодом?
- Пустое значение не игнорируется, как должно быть.
- Как прочитать значение PIN PWM-выхода?
- Как мигать светодиодом и одновременно запускать другой код?
Возможно, добавьте в свой пост статистику использования ОЗУ/хранилища, которую компилятор предоставляет в конце вывода. Также скажите, что это за плата., @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