Хотел бы разобрать код открытого проекта робота-"паука".

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

Первое знакомство и основные характеристики

Распечатанная версия с Printables
Распечатанная версия с Printables

Есть несколько версий данного робота.

В частности я наткнулся на одну из модификаций, ища что бы такого напечатать на свежекупленном 3D-принтере.

Основной репозиторий исходного проекта располагается на GitHub автора, но там нет некоторых деталей. В частности там нет кода, отвечающего за выравнивание сервоприводов перед затяжкой винтов.

Инструкции по сборке и первоначальной настройке можно найти по ссылке: http://www.instructables.com/id/DIY-Spider-RobotQuad-robot-Quadruped/ (есть проблемы с отображением страницы - CDN попали под блокировки, копия здесь)

Характеристики робота:

  • 4 "ноги"

  • Каждая "нога" состоит из трех сервоприводов типа SG90/MG90

  • В качестве "мозга" - Arduino Nano(но можно использовать любую совместимую с Arduino плату)

В качестве дополнений существует версии кода с удаленным управлением по Bluetooth.

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

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

Скелеты и кости

Coxa - Femur - Tibia
Coxa - Femur - Tibia

Можно сказать что робот симметричен относительно своей оси.

Поэтому можно рассмотреть только одну "лапу":

  • coxa - "сустав", вращает ногу вокруг оси Z, тем самым задает плоскость, в которой может перемещаться остальная часть

  • femur - средняя "кость"

  • tibia - опорная кость, в некоторых роботах отсутствует

Начальное положение костей относительно нейтральных позиций сервоприводов:

Сервоприводы в нейтральном положении / Скетч legs_init
Сервоприводы в нейтральном положении / Скетч legs_init

Разгребая код

Структурно код выглядит стандартно для большинства "скетчей" - масса глобальных переменных, функции setup и loop, и т.д.

Из нестандартных библиотек используется FlexiTimer2 и Servo

Блок переменных и констант требует дополнительных комментариев:

//define servos' ports
/// 4 ноги по три сервы
/// Тут ВНИМАНИЕ: каждая нога апределена в поряде: {femur, tibia, coxa}
/// Что несколько противоречит ожидаемому {coxa, femur, tibia}
/// И это нужно учитывать при подключении серв к плате
const int servo_pin[4][3] = { {2, 3, 4}, {5, 6, 7}, {8, 9, 10}, {11, 12, 13} };

/* Size of the robot ---------------------------------------------------------*/
// Длина femur
const float length_a = 55;
// Длина tibia
const float length_b = 77.5;
// Длина coxa
const float length_c = 27.5;
// ???
const float length_side = 71;
// Смещение низа корпуса от оси соединения coxa-femur
const float z_absolute = -28;
/* Constants for movement ----------------------------------------------------*/
const float z_default = -50, z_up = -30, z_boot = z_absolute;
const float x_default = 62, x_offset = 0;
const float y_start = 0, y_step = 40;
const float y_default = x_default;

/* variables for movement ----------------------------------------------------*/
// Текущее РАССЧИТАННОЕ положение
volatile float site_now[4][3];    //real-time coordinates of the end of each leg
// Текущее заданное положение
volatile float site_expect[4][3]; //expected coordinates of the end of each leg
// дельты, на которые сервы смещаются каждый тик
float temp_speed[4][3];   //each axis' speed, needs to be recalculated before each movement

Основные функции, отвечающие за передвижение робота:

  • set_site - установка целевого положения опоры для конкретной "лапы", расчет скоростей

  • wait_all_reach - ожидание достижения установленных точек - в цикле сравнивает текущее и заданное положение

  • servo_service - наиболее интересная функция, отвечает за пересчет координат в углы поворота сервоприводов

В set_site есть баг: при дельте перемещения равной нулю в скорости получается Infinity. Но "стреляет он только на платформах с аппаратной float-математики. На ATMega этого нет и используется программная реализация и вероятно она чем то отличается от поведения аппаратной. Меня это затронуло, когда переносил код на SBC под управлением Linux.

Самое вкусное

Самое интересное находится в функции servo_service, вызываемой с частотой 50Hz

#define E_DELTA 0.01
void servo_service(void)
{
  sei();
  static float alpha, beta, gamma;

  // для каждой ноги
  for (int i = 0; i < 4; i++)
  {
    // для каждй сервы
    for (int j = 0; j < 3; j++)
    {
      // определить следующее положение
      if (abs(site_now[i][j] - site_expect[i][j]) < (abs(temp_speed[i][j])+E_DELTA))
        site_now[i][j] = site_expect[i][j];
      else
        site_now[i][j] += temp_speed[i][j];
    }

    // перевести декартовы координаты в углы поворота
    cartesian_to_polar(alpha, beta, gamma, site_now[i][0], site_now[i][1], site_now[i][2]);
    // установить углы серв, скорректировав их в зависимости от номера ноги
    polar_to_servo(i, alpha, beta, gamma);
  }

  rest_counter++;
}

А точнее в cartesian_to_polar:

void cartesian_to_polar(volatile float &alpha, volatile float &beta, volatile float &gamma, volatile float x, volatile float y, volatile float z)
{
  //calculate w-z degree
  float v, w;
  w = (x >= 0 ? 1 : -1) * (sqrt(pow(x, 2) + pow(y, 2)));
  v = w - length_c;
  alpha = atan2(z, v) + acos((pow(length_a, 2) - pow(length_b, 2) + pow(v, 2) + pow(z, 2)) / 2 / length_a / sqrt(pow(v, 2) + pow(z, 2)));
  beta = acos((pow(length_a, 2) + pow(length_b, 2) - pow(v, 2) - pow(z, 2)) / 2 / length_a / length_b);
  //calculate x-y-z degree
  gamma = (w >= 0) ? atan2(y, x) : atan2(-y, -x);
  //trans degree pi->180
  alpha = alpha / pi * 180;
  beta = beta / pi * 180;
  gamma = gamma / pi * 180;
}
Когда не понимаешь, что происходит...
Когда не понимаешь, что происходит...

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

А если перейти в другую плоскость?
А если перейти в другую плоскость?

По шагам:

  1. Рассчитывается длина вектора w (от центра координат до целевой точки в плоскости XY)

  2. "Виртуально" перемещаемся в плоскость, задаваемую вектором w и осью Z.

  3. На эту плоскость проецируется положение костей

  4. Смещаем центр координат в соединение coxa-femur

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

И становится понятно назначение переменных:

  • alpha - угол между coxa и femur

  • beta - угол между femur и tibia

  • gamma - угол поворота coxa

P.S.:

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

В разборе кода очень помогла статья "Инверсная кинематика в 2D"

Комментарии (6)


  1. Sencis
    10.08.2026 06:06

    Кажется, что квадроподам (паукам) не сильно подходят сервоприводы обычные, имхо что-бы они быстрее передвигались нужно исключить контроллер из каждой сервы оставив только механнику, энкодер и двигатель и подключить их через драйверы к МК на прямую, тогда МК будет знать положение каждой ноги в реальном времени и без ожиданий на поворот каждой сервы двигаться быстрее. temp_speed предпологает, что скорость у сервы какая-то константа однако в реальности движение состоит из ускорений и замедлений + различные внешние силы когда нога становится на землю и получает нагрузку, когда отталкивается.

      - set one of end points' expect site
      - this founction will set temp_speed[4][3] at same time
      - non - blocking function
       ---------------------------------------------------------------------------*/
    void set_site(int leg, float x, float y, float z)
    {
      float length_x = 0, length_y = 0, length_z = 0;
    
      if (x != KEEP)
        length_x = x - site_now[leg][0];
      if (y != KEEP)
        length_y = y - site_now[leg][1];
      if (z != KEEP)
        length_z = z - site_now[leg][2];
    
      float length = sqrt(pow(length_x, 2) + pow(length_y, 2) + pow(length_z, 2));
    
      temp_speed[leg][0] = length_x / length * move_speed * speed_multiple;
      temp_speed[leg][1] = length_y / length * move_speed * speed_multiple;
      temp_speed[leg][2] = length_z / length * move_speed * speed_multiple;
    
      if (x != KEEP)
        site_expect[leg][0] = x;
      if (y != KEEP)
        site_expect[leg][1] = y;
      if (z != KEEP)
        site_expect[leg][2] = z;
    }
    
    /*
      - wait one of end points move to expect site
      - blocking function
       ---------------------------------------------------------------------------*/
    void wait_reach(int leg)
    {
      while (1)
        if (site_now[leg][0] == site_expect[leg][0])
          if (site_now[leg][1] == site_expect[leg][1])
            if (site_now[leg][2] == site_expect[leg][2])
              break;
    }
    
    /*
      - wait all of end points move to expect site
      - blocking function
       ---------------------------------------------------------------------------*/
    void wait_all_reach(void)
    {
      for (int i = 0; i < 4; i++)
        wait_reach(i);
    }```


    1. max_dark Автор
      10.08.2026 06:06

      Вполне возможно. Но это “игрушечный” проект, построенный на доступных компонентах. Переделку серв я не рассматриваю. По крайней мере сейчас.


    1. max_dark Автор
      10.08.2026 06:06

      Если же рассматривать переделку сервоприводов, то потребуется 12 каналов аналогово-цифровых входов, что не у каждого MCU есть.


      1. Sencis
        10.08.2026 06:06

        Можно использовать CD74HC4067 это 16-канальный двунаправленный аналоговый и цифровой мультиплексор/демультиплексор (коммутатор). Он позволяет подключить до 16 датчиков или сигналов к одному пину.


        1. max_dark Автор
          10.08.2026 06:06

          Да, порой не хватает знания о реальном текущем положении сервопривода, но не думаю, что такое усложнение нужно в этой “игрушке”. И напомню, что код принадлежит не мне - я только попытался разобраться, как он работает.


        1. max_dark Автор
          10.08.2026 06:06

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