Всем привет! В результате перехода на удаленную работу у меня появилось больше свободного времени на разработку гексапода (+2 часа в день за счет экономии на дороге). Я наконец-то смог сделать универсальный алгоритм для построения траектории движения в реальном времени. Новая математика позволила реализовать базовые движения путем изменения всего двух параметров. Это очередной шаг к внедрению «автопилота». В этой статье я постараюсь подробно рассказать о новой математике и как это вообще работает. Будет много картинок и gif.
Этапы разработки:
Часть 1 — проектирование
Часть 2 — сборка
Часть 3 — кинематика
Часть 4 — математика траекторий и последовательности
Часть 5 — электроника
Часть 6 — переход на 3D печать
Часть 7 — новый корпус, прикладное ПО и протоколы общения
Часть 8 — улучшенная математика передвижения
Назад во времени
До этого момента все базовые движения (вперед, назад, поворот) задавались в виде «перемести ногу из текущей позиции в точку (X, Y, Z), используя линейное/синусоидальное/какое-нибудь перемещение». Это работает достаточно хорошо и надежно, но сильно ограничивает в движении. Например, чтобы реализовать движение по дуге с радиусом R1 нужно заранее рассчитать координаты начала и окончания движения для каждой конечности, добавить дополнительную кнопку в приложение, чтобы можно было выбрать это движение. Соответственно для добавления движения по дуге с другим радиусом R2, нужно опять рассчитать координаты и добавить еще одну кнопку. Крайне неудобно.
Сейчас для реализации базовых движений используется один математический блок, который можно адаптировать под различные ситуации двумя параметрами. Главным бонусом новой математики стала поддержка движения по дуге с изменением её радиуса прямо во время движения!
Идея алгоритма
Для начала стоит объяснить на пальцах как это работает. Что будет, если выбрать на окружности окно фиксированного размера и начать увеличивать её радиус? Вот что:
Всё просто правда? Изменяя радиус окружности мы можем получить различные траектории движения и с некоторой точность прямую линию. На этом можно было и остановиться, но не все так радужно. Нельзя просто взять и реализовать это на основе одного только радиуса — есть нюансы.
Начальные положения конечностей гексапода могут находиться на разных окружностях, соответственно параметры уравнения уже будут другими. В моем случае конечности располагаются следующим образом (примерно):
Значение расстояния, которое должна пройти конечность зависит от радиуса окружности, на которой она находится. Это заставляет переходить к индивидуальному расчету траектории для каждой конечности. Для решения этой проблемы необходимо найти окружность с максимальным радиусом, рассчитать начальный и конечный углы дуги. Дальше относительно полученной дуги находятся дуги для других конечностей. Я сделал анимацию работы данного куска алгоритма:
Есть анимация для наглядности работы всей этой магии (как же я рад что в наше время есть Excel). В начале изменяется значение расстояния, которое должен пройти гексапод за цикл (аналогично скорости), затем изменяется значение кривизны траектории (аналогично повороту руля).
В целом в этом и заключается идея новой математики, надеюсь получилось доступно объяснить. Теперь можно разобрать алгоритм подробнее и попробовать посчитать все руками на практике.
Математика
Входные параметры
Переменными входными параметрами являются расстояние (distance) и кривизна траектории движения (curvature). Значение кривизны траектории должно находится в диапазонах [-1.999; -0.001] и [0.001; 1.999], в то время как максимальное значение расстояния зависит от физических характеристик гексапода. В моем случае максимальное расстояние за цикл равно 110мм, при больших значениях конечности начинают упираться в друг друга. Для примера расчета возьмем значения curvature = 1.5 и distance = 20.
Так же для работы алгоритма необходимо знать начальные положения конечностей. Это точки, в которых находятся конечности, когда гексапод встал на ноги. Для примера будем использовать следующие точки (начало координат каждой конечности находится в месте крепления COXA):
Note: Я работаю в плоскости XZ и на координату Y можно не обращать внимания. Ознакомиться с системой координат гексапода можно в третьей части цикла
В итоге мы имеем следующее:
Формулы и расчеты
Начнем с расчета координат центра движения гексапода в зависимости от значения кривизны и расстояния:
В итоге мы получили точку [R; 0] и траекторию движения тела гексапода. Относительно нее будут рассчитываться траектории для каждой конечности.
Далее необходимо вычислить радиусы траекторий для каждой конечности относительно центра движения (точки [R; 0]) c учетом начального положения конечности [x0; z0]. Более понятным языком — найти длину вектора, проведенного из точки [R; 0] в точку [x0; z0]:
Картинка для наглядности. Синим показаны нужные вектора.
Находим из полученных значений максимальное:
Дальше нужно найти угол для каждого вектора, которые мы пытали в предыдущем шаге.
Теперь находим угол дуги на самой большой окружности радиуса R_maх (самой удаленной от центра движения траектории), длина которой должна быть равна значению distance. Этот угол определяет начальные и конечные углы других дуг, по которым будут двигаться конечности. Думаю картинка ниже поможет понять это.
Угол вычисляется следующим образом:
Дальше используя этот угол мы можем вычислить начальные и конечные углы дуг для других конечностей. Как то так это должно получиться:
Небольшое отступление. Для реализации этого алгоритма необходимо ввести понятие времени, значение которого лежит в диапазоне [0; 1]. Так же необходимо, чтобы каждая конечность при значении времени 0.5 возвращалась в свою начальную точку. Данное правило является проверкой корректности расчетов — все окружности должны проходить через начальные точки каждой конечности.
Дальше начинается вычисление точек по полученным параметрам дуг, используя следующие формулы:
Исходный код функции
static bool process_advanced_trajectory(float motion_time) {
// Check curvature value
float curvature = (float)g_current_trajectory_config.curvature / 1000.0f;
if (g_current_trajectory_config.curvature == 0) curvature = +0.001f;
if (g_current_trajectory_config.curvature > 1999) curvature = +1.999f;
if (g_current_trajectory_config.curvature < -1999) curvature = -1.999f;
//
// Calculate XZ
//
float distance = (float)g_current_trajectory_config.distance;
// Calculation radius of curvature
float curvature_radius = tanf((2.0f - curvature) * M_PI / 4.0f) * distance;
// Common calculations
float trajectory_radius[SUPPORT_LIMBS_COUNT] = {0};
float start_angle_rad[SUPPORT_LIMBS_COUNT] = {0};
float max_trajectory_radius = 0;
for (uint32_t i = 0; i < SUPPORT_LIMBS_COUNT; ++i) {
float x0 = g_motion_config.start_positions[i].x;
float z0 = g_motion_config.start_positions[i].z;
// Calculation trajectory radius
trajectory_radius[i] = sqrtf((curvature_radius - x0) * (curvature_radius - x0) + z0 * z0);
// Search max trajectory radius
if (trajectory_radius[i] > max_trajectory_radius) {
max_trajectory_radius = trajectory_radius[i];
}
// Calculation limb start angle
start_angle_rad[i] = atan2f(z0, -(curvature_radius - x0));
}
if (max_trajectory_radius == 0) {
return false; // Avoid division by zero
}
// Calculation max angle of arc
int32_t curvature_radius_sign = (curvature_radius >= 0) ? 1 : -1;
float max_arc_angle = curvature_radius_sign * distance / max_trajectory_radius;
// Calculation points by time
for (uint32_t i = 0; i < SUPPORT_LIMBS_COUNT; ++i) {
// Inversion motion time if need
float relative_motion_time = motion_time;
if (g_motion_config.time_directions[i] == TIME_DIR_REVERSE) {
relative_motion_time = 1.0f - relative_motion_time;
}
// Calculation arc angle for current time
float arc_angle_rad = (relative_motion_time - 0.5f) * max_arc_angle + start_angle_rad[i];
// Calculation XZ points by time
g_limbs_list[i].position.x = curvature_radius + trajectory_radius[i] * cosf(arc_angle_rad);
g_limbs_list[i].position.z = trajectory_radius[i] * sinf(arc_angle_rad);
// Calculation Y points by time
if (g_motion_config.trajectories[i] == TRAJECTORY_XZ_ADV_Y_CONST) {
g_limbs_list[i].position.y = g_motion_config.start_positions[i].y;
}
else if (g_motion_config.trajectories[i] == TRAJECTORY_XZ_ADV_Y_SINUS) {
g_limbs_list[i].position.y = g_motion_config.start_positions[i].y;
g_limbs_list[i].position.y += LIMB_STEP_HEIGHT * sinf(relative_motion_time * M_PI);
}
}
return true;
}
Тут я решил показать еще и расчет Y координаты (Calculation Y points by time). Расчет зависит от выбранной траектории, которая задана в коде жестко и необходима для реализации движения конечности по земле и по воздуху.
Так же имеется кусок для реализации обратного направления движения (Inversion motion time if need). Он нужен, чтобы во время движения трех конечностей по земле, другие три конечности двигались в обратном направлении по воздуху.
netricks
А почему вы перемещаете конечности по дугам, а не по прямым линиям?
Neoprog Автор
Если перемещать конечности по прямым линиям, то получится походка без поворота тела (не помню как называется). Я хотел, чтобы нос гексапода всегда был направлен в сторону движения.
Я правильно понял Вашу мысль?
netricks
Но дугами тоже ведь не все движения корпуса можно нарисовать…
Neoprog Автор
Верно. Данная математика используется только для базовых движений: вперед, назад, поворот, движение по дуге. В основном она была введена для реализации движения по дуге — очень уж хотелось этого.
Для подъема гексапода и различных танцулек используется старый вариант: «передвинь ногу из текущей позиции в XYZ по линейной траектории».
netricks
Так получилось, что я сейчас разрабатываю смежную тематику
Возможно вас заинтересует управление на тензорных регуляторах.
Neoprog Автор
Это очень круто! Я потихоньку к этому хочу придти, но пока есть ряд других проблем и хотелок. Спасибо большое за информацию, обязательно ознакомлюсь.
semennikov
Только имейте в виду, что сервомоторы должны управляться именно заданием скорости а следование по траектории должно поддерживаться регулятором верхнего уровня. Это та проблема на которую я напоролся при установке серводвигателей на свои станки. Я сначала пытался использовать контур позиционирования в самом двигателе, но постоянно возникало рассогласование координат, потом оставил в двигателе только контур скорости а контур позиционирования перевел выше в контроллер положения
netricks
А с манипуляторами по другому и не получается. Потому что если вы делаете уставку по положению, вам приходится решать обратную задачу кинематики. А обратная задача кинематики — это то, что нужно гнать тряпкой из подобных систем, потому как она нелинейна, а обратная скоростная задача линейна.
Конкретно в этом видео создаётся точка устойчивости по позиции ноги за счет введения небольшого сигнала обратной связи по положению, но он исключительно вспомогательный (совсем без него нельзя, система потеряет устойчивость).
semennikov
Если быть совсем точным, то нам нужна какая то точность перемещения по траектории, т.е всегда есть допустимое отклонение, а с учетом этого любую траекторию можно апроксимировать даже прямыми отрезками. Если есть еще и дуги то очень сильно упрощается анализ и на порядки уменьшается объем примитивов движения
anprs
Кстати о походках, где-то с 1:02
yarkov
У меня так 3 кошки топчут пол. Ещё железяки для полной клиники не хватает ))
Kriminalist
Может я неправ, но подумалось что при движении прямо вперед имеет смысл ходить не тройками ног, а парами, тогда расчет математики можно делать для одной стороны, а вторую просто зеркалировать. А при повороте, если точки крепления ног осесимметричны, можно вообще считать для одной ноги, и на две по 120 градусов просто поворачивать.
Neoprog Автор
Да, можно. Такая походка будет медленнее, чем тройками (tripod gait). Приводы я использую довольно мощные, соответственно скорость поворота у них не высокая. В этом случае проект можно переименовать с hexapod в turtle :)
Да и ресурсы МК позволяют выбрать более сложный и универсальный путь с большим запасом.
Kriminalist
Вообще, самый интересный вопрос — ходьба по пересеченной местности, когда высота пола разная для разных ног.
Neoprog Автор
Это будет чуть позже. Для реализации этого на концах лап находятся концевики, но пока они не задействованы. Не думаю, что будет сложно. Все сводится к манипуляции с Y координатой.
semennikov
Я делаю станки с ЧПУ, и рассматриваю такой аппарат для для резки больших листов с точным позиционированием в каждый момент, меня привлекает абсолютная маневренность
Neoprog Автор
Вы наверное путаете с hexapod платформой. В данном случае hexapod это шестиногий робот.
semennikov
Нет, не путаю. Есть большие листы металла скажем 3х6 метра(есть и больше), если делать станок для раскроя такого листа, то он будет очень большой и неперемещаемый, а вот если установить на подвижный робот плазмотрон и систему позиционирования достаточной точности, то это будет и дешево и удобно
netricks
А есть идеи о том, как такая система позиционирования будет выглядеть?
semennikov
Предполагается что с помощью СВЧ видимо 8 или 3 мм с частотной модуляцией. 4 синхронизированных излучателя размещенных по границам поля обработки и анализ на гексаподе с точностью по фазе не хуже 10 градусов. Можно добиться ошибки 0,1 мм
Zenitchik
Чтобы обеспечить постоянную погонную энергию — нужно добиться равномерности движения.
netricks
Равномерности движения в такой системе добиться можно, но ошибки всё равно будут накапливаться. Понадобится внешняя коррекция, а вот как её делать с достаточной точностью не очень понятно.
drWhy
Существует подход к крупногабаритной печати, когда печатающая головка (аэрозольный баллончик с электрическим клапаном) подвешивается на стене многоэтажного здания, где множество факторов мешает равномерному перемещению головки, но так как её фактическое положение отслеживается с высокой точностью, команда на открытие клапана всегда соответствует реальному положению.
Что если попробовать такой подход? Положение робота можно измерять точно при помощи стационарных лазерных дальномеров. Также робота можно оснастить дополнительным актуатором, осуществляющим прецизионное позиционирование плазменной головки (как в приводе CD/DVD, лазер в магнитной подвеске нивелирует неидеальность позиционирования головки), чтобы не пришлось прерывать линию реза.
semennikov
Можно и лазер, но с ним побольше сложностей, он конечно на порядки точнее и приемник имеет меньше габариты но с СВЧ проще обеспечить сферические волны, он не чувствителен к пыли и дыму, при работе плазменного аппарата еще и яркое излучение и т.д. Я все таки больше склоняюсь к СВЧ, хотя у него тоже большие сложности в основном с отражением от поверхности металла.
MShekunov
semennikov
Да, да! Примерно так и должно это выглядеть! Достаточно большое расстояние до брюшка чтобы плазма его не обжигала!
Neoprog Автор
Скоро скоро, совсем немного осталось :)
netricks
Вот, кстати, математика управления гибкими конечностями весьма интересна. Для такой системы, можно считать перемещение каждого сочленения, но это врядли разумно. Нужна какая-то упрощённая модель.
Neoprog Автор
Я думаю проще сделать не получится. Если условиться, что гибкая конечность состоит из сегментов (как на картинке), то все равно нужно вычислять поворот для каждого сегмента. В данном случае получается манипулятор с N степенями свободы, где N явно больше 20 и отсюда выливается лютый матан. Я сложно представляю систему уравнений для такого манипулятора :)
netricks
Думаю, можно расчленить конечность на отдельные сегменты по нескольку звеньев. Каждый сегмент будем решать независимо, а поверх управления отдельными сегментами добавим систему управления конечностью в целом. Это позволит решать не все 30 уравнений разом, а иерархически, по 5 штук в 6 группах и одну в 6 уравнений для 6 сочленений.
Кстати, это идея. Я внесу это мысль в свою статью по теме управления манипуляторами :).