Многопоточный робот
Я работаю над небольшим проектом робота, код, который я для него создал, имеет последовательное выполнение в одном потоке. Требуется много функций 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)
{
}
*/
Пожалуйста, помогите мне. Я хочу реализовать этот код в многопоточном режиме. Заранее спасибо
@Habibullah, 👍1
Обсуждение5 ответов
Я смотрю на это, чтобы аналогичный проект. Это кажется легким и достаточно простым, чтобы вы могли быстро опробовать его. Преобразование этих функций в потоки не должно занять слишком много времени.
После того, как каждое действие реализовано в виде потока, вы можете использовать семафоры, чтобы заблокировать их, а затем создать конечный автомат, который будет управлять ими через семафоры.
Я думаю, что это требует серьезной доработки. Сначала здесь:
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
Возможно, вам захочется взглянуть на LMX Дэвида Андерсона Легкий многозадачный руководитель.
У него есть очень хорошая подборка видеороликов о коде, который он сделал для Dallas Personal Robotics Group (DPRG).
Специальные видеоролики для LMX:
- Дэвид Андерсон о программном обеспечении для робототехники – часть 1
- Дэвид Андерсон о программном обеспечении для робототехники – часть 2
Вы можете использовать планировщик и просто запускать задачи с помощью:
#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 неоднократно вызывается задачей. Это можно рассматривать как выполнение дополнительного скетча.
Подробнее см. в примерах скетчей.
Вы также можете попробовать мою библиотеку ThreadHandler
https://bitbu cket.org/adamb3_14/threadhandler/src/master/
Он использует планировщик прерываний, чтобы обеспечить переключение контекста без пересылки на метод yield() или задержки().
Я создал библиотеку, потому что мне нужно было три потока, и мне нужно, чтобы два из них выполнялись в определенное время, независимо от того, что делали остальные. Первый поток обрабатывал последовательную связь. Второй запускал фильтр Калмана с использованием умножения матрицы с плавающей запятой с библиотекой Eigen. И третьим был поток контура управления быстрым током, который должен был иметь возможность прерывать матричные вычисления.
Как это работает
Каждый циклический поток имеет приоритет и период. Если поток с более высоким приоритетом, чем текущий выполняющийся поток, достигает следующего времени выполнения, планировщик приостановит текущий поток и переключится на поток с более высоким приоритетом. Как только поток с высоким приоритетом завершает свое выполнение, планировщик переключается обратно на предыдущий поток.
Правила планирования
Схема планирования библиотеки ThreadHandler следующая:
- Сначала высший приоритет.
- Если приоритет одинаковый, то поток с самым ранним сроком выполнения выполняется первым.
- Если у двух потоков одинаковый крайний срок, первым будет выполнен первый созданный поток.
- Поток может быть прерван только потоками с более высоким приоритетом.
- После выполнения потока он блокирует выполнение всех потоков с более низким приоритетом до тех пор, пока функция запуска не завершит работу.
- Функция цикла имеет приоритет -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();
- Как создать несколько запущенных потоков?
- Могу ли я подключить несколько устройств к одному контакту 5V и одному GND Arduino Uno R3
- Питание нескольких сервоприводов от одной батареи. Чего не хватает в схеме?
- Как использовать как ультразвуковой, так и ИК-датчик и ультразвуковой датчик с Arduino
- Как правильно запустить 4 двигателя постоянного тока с помощью Arduino?
- Управление 2 двигателями постоянного тока с L293D и батарейным блоком 6V?
- Подключение датчика Winsen ZE11-C2H4 к Arduino
- Функция Pulsein() блокирует одновременное выполнение других задач
Я не думаю, что темы помогут. Это похоже на вождение автомобиля, когда один человек смотрит в окно, а другой управляет рулем, акселератором и тормозами. Вы должны согласовать свои входные данные с результатами. Распределение двух вещей (наблюдение и действие) в разные потоки просто создаст новые проблемы., @Nick Gammon
Как и многие вещи в жизни, это также вопрос личных предпочтений. Однако потоки имеют то преимущество, что четко показывают, что будет вытеснено. И они избегают циклов занятости. Конечно, за это приходится платить, а именно снижение простоты отладки и риск тупиковых ситуаций. Любой выбор хорош, если он осознан., @Igor Stoppa
Создайте функции startTurning и stopTurning вместо того, чтобы блокировать полное выполнение с задержкой. Вы можете использовать аппаратный таймер (или программный таймер) для вызова этих функций, чтобы тем временем вы могли выполнять другие вычисления/логику/код/взаимодействие., @Paul