Мне нужна помощь с моим проектом

Я создаю проект с использованием управления Bluetooth, а также ИК-датчика. Логика моей программы заключается в том, что когда я нажимаю клавишу, я хочу, чтобы мой робот выполнил серию программ, и пока программа выполняется, если обнаружено препятствие, мне нужно, чтобы двигатели остановились, а затем продолжили работу с той же точки после удаления препятствия. Я загружаю свой код для ref.

#include <Servo.h>
#include <AFMotor.h>
#include <Servo.h>
int obstaclePin = 12;
int obstacle = LOW;
AF_DCMotor motor1(1);
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
Servo myservo1;
Servo myservo2; 
int pos = 0;

char bt='S';
void setup()
{
  Serial.begin(9600);
  pinMode(obstaclePin,INPUT);
  myservo1.attach(10);
  myservo2.attach(9);
  motor1.setSpeed(100);
  motor2.setSpeed(100);
  motor3.setSpeed(100);
  motor4.setSpeed(100);
  Stop();
}


void loop() {
 bt=Serial.read();
  if(bt=='F')
{
  forward();
  delay(2000);
  backward();
  delay(2000);
  left();
  delay(2000);
  right();
  delay(2000);
  Stop();
}

if(bt=='B')
{
forward();
 delay(8000);
 left();
 delay(1500);
 ServoControl();
 delay(500);
 backward();
 delay(1000);
 right();
 delay(3000);
 forward();
 delay(8000);
 Stop();
}

if(bt=='L')
{
 forward();
 delay(10000);
 left();
 delay(1500);
 ServoControl();
 delay(500);
 backward();
 delay(1000);
 right();
 delay(3000);
 forward();
 delay(10000);
 Stop();
}

if(bt=='R')
{
 forward();
 delay(12000);
 left();
 delay(1500);
 ServoControl();
 delay(500);
 backward();
 delay(1000);
 right();
 delay(3000);
 forward();
 delay(12000);
 Stop();
}

if(bt=='S')
{
 Stop(); 
}
if(bt=='O')
{
  servoOpen();
}
if(bt=='C')
{
  servoCLose();

}

}
void forward()
{
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}

void backward()
{
  motor1.run(BACKWARD);
  motor2.run(BACKWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);
}
void left()
{
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(RELEASE);
  motor4.run(RELEASE);
}
void right()
{
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}
void Stop()
{
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  motor3.run(RELEASE);
  motor4.run(RELEASE);
}
void servoOpen()
{
  for (pos = 0; pos <= 100; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo1.write(pos);
    myservo2.write(pos);             
  }
}


void servoCLose()
{
  
  for (pos = 100; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo1.write(pos);
    myservo2.write(pos);
  }
}
void ServoControl()
{
  for (pos = 0; pos <= 100; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo1.write(pos);
    myservo2.write(pos);
    delay(40);                       // waits 15ms for the servo to reach the position
  }
  for (pos = 100; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo1.write(pos);
    myservo2.write(pos);            
    delay(40);                       // waits 15ms for the servo to reach the position
  }
}
void obstacleD()
{
  obstacle = digitalRead(obstaclePin);
   if(obstacle == HIGH)
  {
    Stop();
  }
}

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

, 👍1

Обсуждение

Вам нужно узнать о конечных автоматах. `delay () ' - ваш враг., @Majenko

"Работа как прерывание" может означать две разные вещи: во-первых, обнаружение препятствий (OD) может быть фактическим прерыванием, а во-вторых, вместо блокировки с помощью "задержки" мы можем использовать другие механизмы синхронизации. Проблема с задержкой заключается в том, что если OD является фактическим прерыванием после запуска ISR, вы вернетесь обратно в "задержку", которая, когда вы натыкаетесь на стену, не очень хороша. Вот почему комментарий относительно FSMS и поиск других способов "сделать что-то на некоторое время" без использования " задержки`., @Dave Newton


1 ответ


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

1

Ваша главная проблема заключается в том, что вы используете много блокирующих вызовов, особенно delay(). Во время этого Arduino не может делать ничего другого. Вам нужно использовать неблокирующий стиль кодирования, делая несколько вещей одна за другой в быстрой последовательности. Здесь очень полезен стиль из примера BlinkWithoutDelay с использованием millis (). Об этом есть много учебников в Интернете (на этом сайте или на многих других), поэтому я не буду вдаваться в подробности, как это работает.


Быстрое исправление: Если вам просто нужно быстрое исправление и структура кода вашего проекта не имеет отношения к вашей оценке, вы можете сделать следующее:

Замените функцию obstacleD() функцией, которая также будет ждать определенного времени и в течение этого времени проверять ИК-датчик. Что-то вроде этого:

void obstacleD(unsigned long wait, void (*function)(void)){
    unsigned long start_time = millis();
    unsigned long already_waited = 0;
    while(millis()-start_time < wait-already_waited){
        if(digitalRead(obstaclePin)){
            already_waited += millis()-start_time;
            Stop();
            while(digitalRead(obstaclePin));
            start_time = millis();
            function();
        }
    }
}

Затем в остальной части вашего кода вместо delay(1000) (или аналогичного) вы вызываете

obstacleD(1000, forward);

Первый параметр-это время ожидания, второй-функция, которая была вызвана в последний раз (так как нам нужно вспомнить ее, если препятствие найдено и затем удалено). Мы отмечаем время начала функции в start_time. Первый цикл while будет повторяться до тех пор, пока мы не подождем столько, сколько предусмотрено (разница между текущим временем и временем начала будет больше, чем время ожидания минус время, которое мы уже ждали). В этом цикле мы проверяем вывод ИК - датчика. Если он читает ВЫСОКО, мы добавляем прошедшее время в нашу переменную already_waited, останавливаемся и затем зацикливаемся, пока вывод ИК-датчика остается высоким. Когда штифт снова опускается, мы покидаем этот внутренний цикл while, обновляемся до нашего нового start_time и снова выполняем функцию для повторного запуска двигателей. Код должен продолжать путь, как без препятствия, даже если несколько раз препятствие помещается в течение одного элемента пути.

Обратите внимание, что приведенный выше код никак не тестируется. Возможно, вам придется внести незначительные изменения.


Быстрое исправление прерывания: Вы можете реализовать остановку на препятствии с фактическим прерыванием. Что-то вроде этого:

void obstacle_detected(){
    while(digitalRead(obstaclePin));
}

void setup(){
    ...
    attachInterrupt(obstaclePin, obstacle_detected, RISING);
}

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


Способ хорошего структурированного кода: чтобы получить желаемое поведение с помощью хорошего структурированного кода, вам необходимо полностью реструктурировать/переписать текущий код. Как уже упоминалось выше, вам нужно создать код, который не блокируется в течение длительного времени, а делает только то, что нужно сделать в это время, и в противном случае выполняет другой код. Вам нужно научиться использовать millis().

Кроме того, ваш код получил бы большую пользу от конечного автомата (FSM), как уже писал Майенко в комментарии. Здесь вы сначала рисуете диаграмму с вашей полной структурой поведения, разделенной на состояния, которые делают ровно одну вещь. Состояния соединяются переходами состояний (стрелками из одного состояния в другое) (и мы даже можем повторить состояние, проведя стрелку из состояния обратно в себя). Затем вы можете преобразовать это в структуру кода, используя переменную состояния (integer или enum) и оператор switch, где каждый случай имеет код для одного состояния. Вы можете посмотреть мой ответ на этот вопрос для получения дополнительной информации. Или вы можете найти термин "конечный автомат" или "FSM" на этом сайте или в Google (для Google вам, возможно, следует добавить "arduino" в поисковый запрос, чтобы получить релевантные результаты).

В общем, вы создадите код, который обрабатывает желаемое поведение без каких-либо аппаратных прерываний (на самом деле здесь это не нужно) и который легко расширяется, не вызывая проблем с потоком программы.


Примечание:

Цикл for в servoCLose() совершенно не нужен. У вас нет никакой задержки, чтобы замедлить его, поэтому он перейдет в конечное положение за гораздо меньшее время, чем нужно двигателю, чтобы даже начать движение. Просто используйте Servo.write() один раз с нужным углом. Этого достаточно, если только вы не хотите, чтобы он двигался медленно. Тогда вам понадобится способ задержки между записями (либо delay (), либо millis (), в зависимости от вашего тогдашнего общего подхода).

,

(Я бы педантично указал, что "задержка" не отключает прерывания, проблема в том, что как только ISR будет выполнен, он просто вернется к задержке :), @Dave Newton

@DaveNewton Хотя предлагаемое решение прерывания предполагает пребывание в прерывании до тех пор, пока препятствие не будет устранено. Это может быть очень долго, а значит, не очень хорошо. Я упомянул о том, что, возможно, не градуированная структура кода., @chrisl