Мы любим придумывать и решать всякие задачки. Ниже вы найдете наши задачки по робототехнике и решения к некотором из них. Если у вас есть свое решение для одной из наших задач или своя оригинальная задачка - пишите, мы с радостью разместим их.
Казалось бы - что тут сложного? Дели расстояние на время и получай скорость. Если Вы попробуете подобрать мощность на моторах таким образом, чтобы робот проезжал заданное расстояние за определенное время то обнаружите, что и робот движется не с постоянной скоростью (ему нужно время на разгон) и поверхность пола/стола не идеальна и от заряда батареек зависит мощность, причем нелинейно.
Мы предлагаем два решения задачи. Первое решение - для робототехников, еще не знакомых с регуляторами. Алгоритм в общем виде, может быть описан на любом языке программирования.
// пусть роботу нужно проехать 2 м за 35 секунд
R = 2000
T = 35
pi = 3.14
// Размер колеса робота, мм
W = 56
// Рассчитываем, сколько градусов всего нужно проехать, с учетом размера колес
S = R * 360 / (W * pi)
// Рассчитываем среднюю скорость в град/сек
С = S / T
// Мощность на моторах, с которой робот проезжает заданное расстояние за +/- приблизительное заданное время
P = 50
// начинаем движение
Моторы(P)
// Коэф-т ускорения
K = 2
ЦИКЛ
{
E1 = считываем показание энкодера моторов
Ждать (1 сек)
E2 = считываем показание энкодера моторов
// смотрим сколько проехал робот за секунду
E = E2 - E1
// корректируем мощность на моторах
ЕСЛИ (E > C) ТОГДА P = P - K
ЕСЛИ (E < C) ТОГДА P = P + K
Моторы(P)
// корректируем среднюю скорость с учетом оставшегося расстояния, осталось проехать:
S = R * 360 / (W * pi) - E2
// обновление средней скорости
C = S / (T - таймер)
}
Второй вариант решения, с использованием ПИД-регулятора:
// пусть роботу нужно проехать 2 м за 35 секунд
R = 2000
T = 35
pi = 3.14
// Размер колеса робота, мм
W = 56
// Рассчитываем, сколько мм он должен проезжать за 0.1 сек
S = R / (T * 10)
// Теперь в градусах оборотов мотора c учетом размера колес
S = ((R / (T * 10)) * 360 / (W * pi)
// Мощность на моторах, с которой робот проезжает заданное расстояние за +/- приблизительное заданное время
P = 50
// Коэфт-ты ПИД регулятора
Kp = 0.1
Ki = 0.0001
Kd = 0.2
// начальное значение ошибки регулятора
E = 0
ЦИКЛ
{
// предыдущее значение ошибки
Eold = E
E1 = считываем показание энкодера моторов
Ждать (0.1 сек)
E2 = считываем показание энкодера моторов
?/ вычисляем ошибку - на сколько градусов
// быстрее или медленнее у робота получилось
E = (E2-E1) - S
// сумма всех ошибок - интеграл
I = I + E
// ПИД-регулятор
V = (Kp * E) + (Ki * I) + (Kd * (E - Eold))
// подаем мощность M на моторы
M = P - V
Моторы(М)
}
' Устанавливаем режим работы датчика касания
Sensor.SetMode(1,0)
' Переменные K и D - количество длинных и коротких нажатий
K = 0
D = 0
' Далее начинаем бесконечный цикл
While "True"
' Ждем пока датчик не будет нажат
While Sensor.ReadPercent(1) = 0
EndWhile
' Запоминаем в переменной А время когда нажали кнопку
A = EV3.Time
'Ждем пока датчик не отпустят
While Sensor.ReadPercent(1) = 100
EndWhile
' Запоминаем в переменной В время отпускания кнопки
B = EV3.Time
' Вычисляем время удерживания кнопки нажатой
X = B - A
' Если удерживаали долее 500мс - нажатие длинное
If X > 500 Then
D = D + 1
Else
K = K + 1
EndIf
' Выводим данные на экран
LCD.Clear()
LCD.Write(0, 0, "short:")
LCD.Write(0, 32, K)
LCD.Write(0, 64, "long:")
LCD.Write(0, 96, D)
EndWhile
Решение задачи на языке NXC:
float x,d,speed,es,D1,D2,e,u,PD,Pk;
task main()
{
// инициализация датчиков
SetSensorLight(IN_2);
SetSensorLight(IN_3);
SetSensorLowspeed(IN_4);
// размер колеи
x = 180;
// диаметр колес
d = 43.2;
// средняя скорость
speed = 30;
// "старая" ошибка Д-регулятора
es = 0;
// пропорциональный коэф-т
Pk = 2;
// дифференциальный коэф-т
PD = 4;
// сбрасываем энкодеры
ResetRotationCount(OUT_BС);
while(true)
{
// движемся по линии пока не обнаружим препятствие
while(SensorUS(IN_4) > 28)
{
// показания датчиков линии
D1 = Sensor(IN_2);
D2 = Sensor(IN_3);
// ошибка регулятора
e = D2-D1;
// управляющее воздействие
u = e*Pk+PD*(e-es);
// подаем его на моторы
OnFwd(OUT_B,u+speed);
OnFwd(OUT_C,-u+speed);
// сохраняем ошибка для Д-регулятора
es = e;
}
// моторы - стоп
Off(OUT_BC);
// поворот в сторону внутреннего изгиба трассы
if (MotorRotationCount(OUT_B)>MotorRotationCount(OUT_C))
{
RotateMotorEx(OUT_BC, speed, x/d*52, -100, true, true);
}
else
{
RotateMotorEx(OUT_BC, speed, x/d*52, 100, true, true);
}
Wait(500);
// съезжаем с линии
RotateMotorEx(OUT_BC, speed, 1000, 0, true, true);
// поворот в сторону линии
if (MotorRotationCount(OUT_B)<MotorRotationCount(OUT_C))
{
RotateMotorEx(OUT_BC, speed, x/d*52, -100, true, true);
OnRev(OUT_B,-18);
OnRev(OUT_C,-15);
}
else
{
RotateMotorEx(OUT_BC, speed, x/d*52, 100, true, true);
OnRev(OUT_B,-15);
OnRev(OUT_C,-18);
}
Wait(500);
// едем до линии
while(Sensor(IN_3) >= 46 && Sensor(IN_2) >= 44)
{
if (Sensor(IN_3)<=46)
{
Off(OUT_C);
}
if(Sensor(IN_2)<=44)
{
Off(OUT_B);
}
}
Off(OUT_BC);
Wait(1000);
// пересекаем линию
RotateMotorEx(OUT_BC, 25, 200, 0, true, true);
Wait(1000);
// разворот для продолжения движения по линии
if (MotorRotationCount(OUT_B)>MotorRotationCount(OUT_C))
{
RotateMotorEx(OUT_BC, 25, x/d*90, -100, true, true);
}
else
{
RotateMotorEx(OUT_BC, 25, x/d*30, 100, true, true);
}
}
}
float speed,es,D1,D2,e,u,PD,Pk,speednew1,speednew2;
long time1,time2,time3;
float es1,PD1,PD2,D,es2,e2,e1,u1,u2,Pk1,Pk2;x,d;y;
bool t,r;
int robot;
task main()
{
// инициализация дальномера
SetSensorLowspeed(IN_4);
// направление синхронного разворота
r = true;
// номер робота 1 или 2
robot = 1;
// у нас в роботах разные датчики, поэтому разная инициализация
if(robot == 2)
{
SetSensorLight(IN_2);
SetSensorLight(IN_3);
// один робот будет убегать
t = true;
}
else
{
SetSensorColorRed(IN_2);
SetSensorColorRed(IN_3);
// другой робот будет догонять
t = false;
}
while(true)
{
// размер колеи
x = 180;
// диаметр колес, мм
d = 43.2;
if (t == true)
{
// таймер для синхронного разворота
time2 = CurrentTick();
// таймер для периодической смены скорости
time1 = CurrentTick();
// первые 2 секунды робот равняется, вперед не едет
speed = 0;
// "старая" ошибка Д-регулятора
es = 0;
// пропорциональный коэф-т
Pk = 2;
// дифференциальный коэф-т
PD = 4;
// таймер для 2-секундного выравнивания перед началом движения
time3 = CurrentTick();
// запоминаем, было ли уже выравнивание
y = 0;
// фактическая средняя скорость
speednew1 = 0;
while(true)
{
// через две секунды начинаем движение после выравнивания
if(CurrentTick() > time3+2000 && y == 0)
{
speednew1 = speed + Random(80);
y = 1;
}
// смена ролей между роботами каждые 29 секунд
if(CurrentTick() > time2+29000)
{
break;
}
// каждые 5 секунд смена скорости
if(CurrentTick() > time1+5000)
{
speednew1 = speed + Random(80);
time1 = CurrentTick();
}
// показания датчиков
D1 = Sensor(IN_3);
D2 = Sensor(IN_2);
// ошибка регулятора
e = D2-D1;
// управляющее воздействие ПД-регулятора
u = e*Pk+PD*(e-es);
// подаем его на моторы
OnFwd(OUT_B,u+speednew1);
OnFwd(OUT_C,-u+speednew1);
// запоминаем ошибку для Д-регулятора
es = e;
}
// моторы - стоп
Off(OUT_BC);
Wait(1000);
// смена ролей - разворот
if (r == true) RotateMotorEx(OUT_BC, 30, x/d*150, 100, true, true);
else RotateMotorEx(OUT_BC, 30, x/d*150, -100, true, true);
Wait(1000);
t = !t;
r = !r;
}
else
{
time2 = CurrentTick();
time3 = CurrentTick();
speed = 0;
es1 = 0;
PD1 = 0;
Pk1 = 2.5;
es2 = 0;
Pk2 = 2;
PD2 = 6;
y = 0;
while(true)
{
if(CurrentTick()> time3+2000 && y == 0)
{
speed = 50;
y = 1;
}
if(CurrentTick() > time2+29000)
{
break;
}
D = SensorUS(IN_4);
D2 = Sensor(IN_2);
D1 = Sensor(IN_3);
// ограничиваем зону видимости робота - 60 см
if (D > 60)
{
D = 60;
}
// здесь у нас целых два регулятора
// один ведет по линии
// второй - подстраивает скорость движения
e2 = D2 - D1;
e1 = D - 40;
u2 = e2*Pk2+PD2*(e2-es2);
u1 = e1*Pk1+PD1*(e1-es1);
OnFwd(OUT_B,speed+u1+u2);
OnFwd(OUT_C,speed+u1-u2);
es1 = e1;
es2 = e2;
}
Off(OUT_BC);
Wait(1000);
// смена ролей - разворот
if (r == true) RotateMotorEx(OUT_BC, 30, x/d*150, 100, true, true);
else RotateMotorEx(OUT_BC, 30, x/d*150, -100, true, true);
Wait(1000);
t = !t;
r = !r;
}
}
}
На видео можно посмотреть как роботы решают задачи 5 и 6:
Детская заводная игрушка РОБОТ, выпуска 70-80гг. решала нашу восьмую задачу без всякого сложного программирования и даже не обладая никакой электроникой.
Заводить робота можно в несколько подходов, перехватывая руку между ними, при этом робот не должен начинать движение.
Робот поедет вперед после последнего завода воображаемой пружины, без нажатия каких-либо кнопок. Чем сильнее мы "завели" робота, тем дальше он проедет.
Алгоритм решения данной задачи приведен ниже.
// Робот начнет движение, если его "завести"
// и не трогать 1 секунду
// Переменная С равна 1 пока мы "заводим" робота
С = 1
ЦИКЛ_ПОКА (C равно 1)
{
// В переменные E1 и E2 записываем показания
// с датчика оборотов с разницей в 1 секунду
E1 = ДАТЧИК_ОБОРОТОВ(В)
ЖДАТЬ (1 сек)
E2 = ДАТЧИК_ОБОРОТОВ(В)
ЕСЛИ (E2 равно E1)
{
// Робота перестали "заводить", выходии из цикла
С = 0
}
}
// запускаем моторы B и C на E2 градусов вперед
МОТОРы (ВС, E2)
Давайте попробуем решить эту задачу. Как обычно мы ищем, например, банки в круге для простейшего кегельринга? Робот разворачивается на месте и как только заметил датчиком расстояния объект, ближе чем указанная граница - останавливается. Причем робот видит край объекта, а инерция доворачивает его до центра банки. Подбирая скорсоть разворота мы отлаживаем направление на центр объекта.
В случае, еесли объект более широкий, чем банка (например ящик), такой способ будет работать криво. Что же делать? Нужно вычислить угловые размеры объекта, ориентируясь на момент, когда робот нашел объект и момент, когда объект потерян.
Предположим поиск идет по часовой стрелке. В процессе разворота отмечаем доступным способом (с помощью энкодера мотора или хотя бы таймером) метку левого края объекта, затем правого. Находим между ними середину и возвращаемся на центр объекта движением против часовой стрелки на половину угла между метками.
Пусть моторы подключены к портам В и С. На псевдокоде программа c использованием энкодера будет выглядеть следующим образом:
МОТОРЫ (50,-50)
ЖДИ (РАССТОЯНИЕ < 50)
А = ЭНКОДЕР (В)
ЖДИ (РАССТОЯНИЕ > 50)
B = ЭНКОДЕР (В)
МОТОРЫ (СТОП)С = (B -A) / 2
МОТОРЫ_НА)ГРАДУСЫ(-50,50,С)
Если у робота нет энкодеров в моторах, можно попробовать обойтись таймером. Здесь вернуться назад "на половину времени" не удастся, т.к. в момент поворота робота перед объектом он уже набрал скорость. Поэтому первый поворот мы делаем для поиска и замера ширины объекта, затем возвращаемся в исходную позицию и разворачиваемся на время, необходимое для поворота на левую границу объекта плюс на половину времени, в течении которого робот видел объект.
A = ТАЙМЕР (1)
МОТОРЫ (50,-50)
ЖДИ (РАССТОЯНИЕ < 50)
B = ТАЙМЕР (1)
ЖДИ (РАССТОЯНИЕ > 50)
C = ТАЙМЕР (1)
МОТОРЫ (СТОП)МОТОРЫ(50,-50)
ЖДИ (С-A)
МОТОРЫ (СТОП)
D = (B - A) + (C -B) / 2
МОТОРЫ (50,-50)
ЖДИ (D)
МОТОРЫ (СТОП)
МОТОРЫ (СТОП)
На псевдокоде решение задачи выглядит так:
// L - Освещенность (яркость внешнего освещения)
L = ДАТЧИК(ОСВЕЩЕННОСТЬ)
// T - смена направления поиска
T = ИСТИНА
ЦИКЛ (ВСЕГДА)
ЕСЛИ (T = ИСТИНА)
// ВЫБИРАЕМ НАПРАВЛЕНИЕ ПОВОРОТА
N = СЛУЧАЙНОЕ_ЧИСЛО (2)
ЕСЛИ (N = 1) МОТОРЫ(ПОВОРОТ_ВЛЕВО)
ЕСЛИ (N = 2) МОТОРЫ(ПОВОРОТ_ВПРАВО)
// ИЗМЕРЯЕМ ОСВЕЩЕННОСТЬ В НОВОЙ ОБЛАСТИ
МОТОРЫ (ВПЕРЕД_НА_ДЛИНУ_РОБОТА)
МОТОРЫ (СТОП)
ЕСЛИ (ДАТЧИК(ОСВЕЩЕННОСТЬ) > L)
L = ДАТЧИК(ОСВЕЩЕННОСТЬ)
T = ЛОЖЬ
ИНАЧЕ
МОТОРЫ (НАЗАД_НА_ДЛИНУ_РОБОТА)
МОТОРЫ (СТОП)
Т = ИСТИНА
КОНЕЦ_ЦИКЛА
Усложненный кегельринг. Робот должен выбить все кегли за пределы круга, при этом подвижный флажок, установленный на роботе должен всегда смотреть в направлении старта робота.
Код программы на EV3 Basic:
Sensor.SetMode(2,0)
Sensor.SetMode(2,1)
Sensor.SetMode(2,0)
Sensor.SetMode(1,0)
Sensor.SetMode(3,0)
Sensor.SetMode(4,0)
Thread.Run = FLAG
S = 15
Sub FLAG
Motor.ResetCount("A")
Pk = 0.15
Kk = 0.0001
While "True"
Er = Sensor.ReadRawValue(2,0) + Motor.GetCount("A")
V = Er*Pk + Er*Er*Er*Kk
Motor.Start("A",-V)
EndWhile
EndSub
While Sensor.ReadPercent(4) < 50
Program.Delay(1)
EndWhile
While "True"
Motor.StartSync("BC", S, -S)
If Sensor.ReadPercent(1) < 63 Then
Motor.Stop("BC","True")
Motor.ResetCount("BC")
Motor.StartSync("BC", 50, 50)
While Sensor.ReadPercent(3) > 25
Program.Delay(1)
EndWhile
Motor.Stop("BC","True")
Motor.MoveSync("BC", -50, -50, Motor.GetCount("C"), "True")
Motor.Stop("BC","True")
EndIf
EndWhile
Движение по "восьмерке", используя только датчик-гироскоп.
Код программы на EV3 Basic:
Sensor.SetMode(2,0)
Sensor.SetMode(2,1)
Sensor.SetMode(2,0)
Sensor.SetMode(2,1)
Sensor.SetMode(2,0)
Napr = 0
Er = 0
Pk = 1.8
Kk = 0.003
Sp = 20
Thread.Run = NAPRAVLENIE
Sub NAPRAVLENIE
While "True"
For Napr = 0 To 360
Program.Delay(30)
EndFor
For Napr = 360 To 0 Step -1
Program.Delay(30)
EndFor
EndWhile
EndSub
While "True"
Er = Napr - Sensor.ReadRawValue(2,0)
V = Er * Pk + Er * Er * Er * Kk
Motor.StartSync("BC", Sp-V, Sp+V)
EndWhile
// Создаем переменные
float x,d;
int speed,es,D1,D2,e,u,PD,Pk;
task main()
{
// Инициализируем датчики
SetSensorLight(IN_2); // Датчик освещенности слева 2й порт
SetSensorLight(IN_3); // Датчик освещенности справа 3й порт
SetSensorLowspeed(IN_4); // Ультразвуковой датчик - 4й порт
x = 180; // Ширина колеи
d = 43.2; // Диаметр колес робота
speed = 70; // Средняя скорость
es = 0; // "старая" ошибка ПД - регулятора
Pk = 2; // Пропорциональный коэф-т ПД-регулятора
PD = 4; // Дифференциальный коэф-т ПД-регулятора
OnFwdSync(OUT_BC, 20, -100); // разворачиваемся в поисках банки
while(SensorUS(IN_4) >= 50); // ожидаем банку
while (SensorUS(IN_4) <= 50); // ожидаем потерю банки
Off(OUT_BC); // останавливаем моторы
// разворачиваемся для выезда рядом с банкой
RotateMotorEx(OUT_BC, speed, x/d*85, - 100, true, true);
Wait(50);
OnFwdSyncEx(OUT_BC, 50, 0,RESET_NONE);
// едем вперед пока не обнаружена линия
while(Sensor(IN_3) >= 44 && Sensor(IN_2) >= 40.5);
Off(OUT_BC); // останавливаемся
Wait(50);
// немного вперед, чтобы развернуться над линией
RotateMotorEx( OUT_BC , speed , 200 , 0 , true , true );
// поворачиваем вправо, УЗ-датчик смотрит в круг
RotateMotorEx(OUT_BC , speed , x / d * 75, -100, true , true );
// цикл для 4 банок
for(int i=0; i<4; i++)
{
// пока нет банки - едем по линии на ПД-регуляторе
while(SensorUS(IN_4) >=10)
{
// показания с датчиков
D1 = Sensor(IN_3);
D2 = Sensor(IN_2);
// ошибка регулятора - разница показаний
e = D2-D1;
// управляющее воздействие по формуле ПД-регулятора
u = e*Pk+PD*(e-es);
// рассчитанную мощность подаем на моторы
OnFwd(OUT_B,u+speed);
OnFwd(OUT_C,-u+speed);
// сохраняем ошибку для следующей итерации цикла
es = e;
}
// останавливаемся при обнаружении банки
Off(OUT_BC);
// поворачиваемся к банке
RotateMotorEx(OUT_BC , speed , x / d * 95, -100, true , true );
Wait(50);
// заталкиваем банку в центр круга
RotateMotorEx( OUT_BC , speed , 1100 , 0 , true , true );
Wait(50);
// возвращаемся на линию
RotateMotorEx( OUT_BC , -1*speed , 1100 , 0 , true , true );
// разворачиваемся влево, чтобы продолжить движение
RotateMotorEx(OUT_BC , speed , x / d * 87, 100, true , true );
}
}
int B_enkoder,C_enkoder,D1,D2,trigger;
bool out;
int e,u,Pk,PD,speed,es;
float d,x;
task main()
{
// инициализируем датчики освещенности
SetSensorLight(IN_2);
SetSensorLight(IN_3);
x = 180; // ширина колеи
d = 43.2; // Диаметр колес робота
Pk = 2; // Пропорциональный коэф-т ПД-регулятора
PD = 6; // Дифференциальный коэф-т ПД-регулятора
speed = 75; // Средняя скорость
while(true)
{
// сбрасываем показания энкодеров
ResetRotationCount(OUT_B);
ResetRotationCount(OUT_C);
// сохраняем показания энкодеров
B_enkoder = 0;
C_enkoder = 0;
es = 0; // "старая" ошибка ПД - регулятора
out = true; // по изменению этой переменной будем вылетать из цикла
Wait(10);
while(out)
{
// показания с датчиков
D1 = Sensor(IN_3);
D2 = Sensor(IN_2);
// ошибка регулятора - разница показаний
e = D2-D1;
// управляющее воздействие по формуле ПД-регулятора
u = e*Pk+PD*(e-es);
// рассчитанную мощность подаем на моторы
OnFwd(OUT_B,u+speed);
OnFwd(OUT_C,-u+speed);
// сохраняем ошибку для следующей итерации
es = e;
// если проехали половину круга - вылетаем из цикла
if ((MotorRotationCount(OUT_B)+MotorRotationCount(OUT_C))/2*(d*PI/360) >= (1070*PI)/2)
{
out = false;
}
}
// стоп моторы
Off(OUT_BC);
// это пауза 50 мс
Wait(50);
// смотрим на каком энкодере набралось больше показаний
// в зависимости от этого разворачиваемся к соответствующую
// сторону и сохраняем в trigger
if (MotorRotationCount(OUT_B)-B_enkoder>MotorRotationCount(OUT_C)-C_enkoder)
{
RotateMotorEx(OUT_BC, speed, x/d*90, -100, true, true);
trigger = 1;
}
else
{
RotateMotorEx(OUT_BC, speed, x/d*90, 100, true, true);
trigger = 2;
}
// выравниваемся о линию OnRev(OUT_B,15);
OnRev(OUT_C,15);
while(Sensor(IN_3) >= 46 && Sensor(IN_2) >= 44)
{
if (Sensor(IN_3)<=44)
{
Off(OUT_C);
}
if(Sensor(IN_2)<=40.5)
{
Off(OUT_B);
}
}
Off(OUT_BC);
Wait(50);
// едем через круг
OnFwdSyncEx(OUT_BC, speed, 0,RESET_NONE);
while(Sensor(IN_3) >= 44 && Sensor(IN_2) >= 40.5)
{
}
// при обнаружении линии - останавливаемся
Off(OUT_BC);
Wait(50);
// немного вперед, чтобы развернуться над линией
RotateMotorEx(OUT_BC, speed, 200, 0, true, true);
// в trigger - направление разворота
if (trigger == 1)
{
RotateMotorEx(OUT_BC, speed, x/d*90, 100, true, true);
}
else
{
RotateMotorEx(OUT_BC, speed, x/d*90, -100, true, true);
}
// сохраняем показания энкодеров
B_enkoder = MotorRotationCount(OUT_B);
C_enkoder = MotorRotationCount(OUT_C);
}
}
Это великолепно. Мой сын занимается робототехникой и эти задания мне очень пригодятся. Спасибо за то, что делитесь опытом.
ОтветитьУдалитьКстати, как вы установили ev3dev на NXT? В нем же нет SD-карты?
На NXT ev3dev мы не устанавливали, это действительно невозможно. Сейчас мы используем NXC - это Си для NXT. В планах поставить на него leJOS, когда доберемся до изучения Java.
Удалить