Многопоточный робот

Я работаю над небольшим проектом робота, код, который я для него создал, имеет последовательное выполнение в одном потоке. Требуется много функций Delay(). Из-за чего точность крайне плохая. я хочу реализовать движения в одном потоке, а обнаружение датчиков в другом потоке.

#define M1 3
#define M2 5
#define M3 6
#define M4 9
#define L_trig 2
#define L_echo 4
#define R_trig 7
#define R_echo 8
#define servo 10
#define solenoid_pin 11
#define LDR_pin1 12
#define LDR_pin2 13
void setup() 
{
// Инициализация двигателей
pinMode(M1,OUTPUT);
pinMode(M2,OUTPUT);
pinMode(M3,OUTPUT);
pinMode(M4,OUTPUT);
// Ультразвуковая инициализация
pinMode(L_trig, OUTPUT);
pinMode(L_echo, INPUT);
pinMode(R_trig, OUTPUT);
pinMode(R_echo, INPUT);
S.attach(servo);
}

void loop() 
{

 Runmotor(M1);
 Runmotor(M2);
 Runmotor(M3);
 Runmotor(M4);
 servo_rotation();
 Ultrasonic_value(L_trig,L_echo);
 Ultrasonic_value(R_trig,R_echo);

 if(Ultrasonic_value(L_trig,L_echo)<5 || Ultrasonic_value(R_trig,R_echo)<5)

   {
     turnLeft();
     //Serial.println("Работает");
   }

}

// Запуск двигателя
void Runmotor(int which)
{
  for(int i=0;i<1023;i++)
  {
 analogWrite(which, i);  
  }
}
// Остановка двигателя
void stopmotor(int which)
{
  digitalWrite(which,LOW);
}
// Чтобы повернуть робота на левую сторону
void turnLeft()
{
  stopmotor(M2);
  stopmotor(M3);
  delay(5000);
  Runmotor(M2);
  Runmotor(M3); 
}
// Чтобы повернуть робота вправо
void turnRight()
{
  stopmotor(M1);
  stopmotor(M4);
  delay(5000);
  Runmotor(M1);
  Runmotor(M4); 
}
// Вращение сервопривода
void servo_rotation()
{
 for(angle=0;angle<=180;angle++)
{ 
 S.write(angle); 
 if(angle==90)
 {
   delay(2000);
 }
}
delay(2000);
for(angle=180;angle>=0;angle--)
{
  S.write(angle);
  Serial.println(angle);
  if(angle==90)
 {
   delay(2000);
 }
}
 delay(2000); 
}

// Расчет расстояния
long Ultrasonic_value(int which_trig,int which_echo)
{
  long duration, distance;
  digitalWrite(which_trig, LOW);
  delayMicroseconds(2); 
  digitalWrite(which_trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(which_trig, LOW);
  duration = pulseIn(which_echo, HIGH);
  // расстояние = длительность*343*100/2;
  distance = (duration/2) / 29.1;
  delay(100);
  return distance;
}

void Solenoid_Fire()
{
  digitalWrite(solenoid_pin,LOW);
}

void Solenoid_Reload()
{
  digitalWrite(solenoid_pin,HIGH);
}
/*
bool LDR_reading(int LDR_pin1 , int LDR_pin2)
{


}
*/

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

, 👍1

Обсуждение

Я не думаю, что темы помогут. Это похоже на вождение автомобиля, когда один человек смотрит в окно, а другой управляет рулем, акселератором и тормозами. Вы должны согласовать свои входные данные с результатами. Распределение двух вещей (наблюдение и действие) в разные потоки просто создаст новые проблемы., @Nick Gammon

Как и многие вещи в жизни, это также вопрос личных предпочтений. Однако потоки имеют то преимущество, что четко показывают, что будет вытеснено. И они избегают циклов занятости. Конечно, за это приходится платить, а именно снижение простоты отладки и риск тупиковых ситуаций. Любой выбор хорош, если он осознан., @Igor Stoppa

Создайте функции startTurning и stopTurning вместо того, чтобы блокировать полное выполнение с задержкой. Вы можете использовать аппаратный таймер (или программный таймер) для вызова этих функций, чтобы тем временем вы могли выполнять другие вычисления/логику/код/взаимодействие., @Paul


5 ответов


0

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

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

,

1

Я думаю, что это требует серьезной доработки. Сначала здесь:

  for(int i=0;i<1023;i++)
  {
  analogWrite(which, i);  
  }

Максимальное число аналоговой записи — 255, поэтому измените 1023 на 255.

Далее избавьтесь от вызовов метода задержки().


Давайте посмотрим на turnLeft :

// Чтобы повернуть робота на левую сторону
void turnLeft()
{
  stopmotor(M2);
  stopmotor(M3);
  delay(5000);
  Runmotor(M2);
  Runmotor(M3); 
}

Зачем останавливать двигатель, а затем запускать его снова? Вы имеете в виду:

// Чтобы повернуть робота на левую сторону
void turnLeft()
{
  stopmotor(M1);
  stopmotor(M4);
  delay(5000);
  Runmotor(M2);
  Runmotor(M3); 
}

Я все равно не понимаю, к чему приводит задержка. Избавьтесь от этого:

// Чтобы повернуть робота на левую сторону
void turnLeft()
{
  stopmotor(M1);
  stopmotor(M4);
  Runmotor(M2);
  Runmotor(M3); 
}

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


См. мой пост Как делать несколько дел одновременно... например, готовить бекон и яйца, чтобы получить советы о том, как делать несколько дел одновременно. вещи без использования задержки().


Также рассмотрите возможность использования массивов. Вместо M1, M2, M3, M4 создайте массив:

const int NUMBER_OF_MOTORS = 4;
const int motorPins [NUMBER_OF_MOTORS] = { 3, 4, 6, 9 };

Теперь проще настроить их все для вывода:

// Инициализация двигателей

for (int i = 0; i < NUMBER_OF_MOTORS; i++)
  pinMode (motorPins [i], OUTPUT);

Также их проще запустить или остановить:

for (int i = 0; i < NUMBER_OF_MOTORS; i++)
  stopmotor (i);

Где сейчас находится стоп-мотор:

// Остановить двигатель
void stopmotor(int which)
{
  digitalWrite(motorPins [which],LOW);
}

(Отредактировано для добавления)

Я тоже не понимаю, чего это дает:

void Runmotor(int which)
{
  for(int i=0;i<1023;i++)
  {
 analogWrite(which, i);  
  }
}

Помимо того факта, что вы не сможете подняться до 1023, если вы остановитесь на 255, время, необходимое для выполнения всех этих аналоговых операций записи, составит чуть более 2 мс. Зачем беспокоиться? Почему бы просто не сделать это:

void Runmotor(int which)
{
  digitalWrite (which, HIGH);
}
,

M1 M2 M3 M4 Когда я хочу повернуть налево, мне следует остановить двигатели на 2-й диагонали, для поворота направо - наоборот., @Habibullah

Какой мотор какой? M1 и M2 слева, а M3 и M4 справа? Лучшие имена сделали бы это очевидным, например, frontLeftMotor, frontRightMotor и т. д., @Nick Gammon


1

Возможно, вам захочется взглянуть на LMX Дэвида Андерсона Легкий многозадачный руководитель.

У него есть очень хорошая подборка видеороликов о коде, который он сделал для Dallas Personal Robotics Group (DPRG).

Специальные видеоролики для LMX:

  • Дэвид Андерсон о программном обеспечении для робототехники – часть 1
  • Дэвид Андерсон о программном обеспечении для робототехники – часть 2
,

1

Вы можете использовать планировщик и просто запускать задачи с помощью:

#include <Scheduler.h>
...
void setup()
{
  ...
  Scheduler.start(servoSetup, servoLoop);
  Scheduler.start(sensorSetup, sensorLoop);
  ...
}

void servoSetup()
{
  S.attach(servo);
}

void servoLoop()
{
  int angle;
  for(angle = 0;angle <= 180; angle++) { 
    S.write(angle); 
    if (angle == 90) {
      delay(2000);
    }
  }
  delay(2000);
  ...
}
...

Задачи должны вызывать yield() или delay() для переключения контекста. Функция taskSetup вызывается задачей первым и один раз. Функция taskLoop неоднократно вызывается задачей. Это можно рассматривать как выполнение дополнительного скетча.

Подробнее см. в примерах скетчей.

,

2

Вы также можете попробовать мою библиотеку ThreadHandler

https://bitbu cket.org/adamb3_14/threadhandler/src/master/

Он использует планировщик прерываний, чтобы обеспечить переключение контекста без пересылки на метод yield() или задержки().

Я создал библиотеку, потому что мне нужно было три потока, и мне нужно, чтобы два из них выполнялись в определенное время, независимо от того, что делали остальные. Первый поток обрабатывал последовательную связь. Второй запускал фильтр Калмана с использованием умножения матрицы с плавающей запятой с библиотекой Eigen. И третьим был поток контура управления быстрым током, который должен был иметь возможность прерывать матричные вычисления.

Как это работает

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

Правила планирования

Схема планирования библиотеки ThreadHandler следующая:

  1. Сначала высший приоритет.
  2. Если приоритет одинаковый, то поток с самым ранним сроком выполнения выполняется первым.
  3. Если у двух потоков одинаковый крайний срок, первым будет выполнен первый созданный поток.
  4. Поток может быть прерван только потоками с более высоким приоритетом.
  5. После выполнения потока он блокирует выполнение всех потоков с более низким приоритетом до тех пор, пока функция запуска не завершит работу.
  6. Функция цикла имеет приоритет -128 по сравнению с потоками ThreadHandler.

Как использовать

Потоки можно создавать с помощью наследования C++

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

Или через createThread и лямбда-функцию

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //код для запуска
    });

Объекты потоков автоматически подключаются к ThreadHandler при их создании.

Чтобы начать выполнение созданных объектов потока, вызовите:

ThreadHandler::getInstance()->enableThreadExecution();
,