Картинка 3dtoday.ru

Некоторое время назад, автору этой статьи пришла в голову своеобразная мысль: а что если сделать удалённо-управляемый манипулятор, который может перемещаться по XY и совершать некую полезную работу?

Сказано — сделано, и работа закипела… В качестве основы для подобного манипулятора было решено взять широко известный принцип кинематики H-bot.

Сразу следует оговориться, что сам выбор подобной тематики для проработки, был осуществлён автором не случайно, так как уже упоминалось в предыдущих статьях, автор имеет ярко выраженную склонность к «выведению виртуала — в реал» и исключительно виртуальные вещи, вращающиеся где-то там далеко за экраном, не так интересны для автора.

В качестве ещё одного допущения, как следует относиться ко всему последующему тексту, следует отметить, что его нужно воспринимать больше как принцип (по управлению H-bot манипулятором), который можно реализовать на базе любой платформы, любого языка или любым иным способом, а не только описанным здесь. Тем не менее в самом конце статьи автор приложил свою реализацию концепции подобного управления, которую вы можете взять и использовать.

Использование подобной кинематики для управления человеком автором ещё нигде не наблюдалось (что, тем не менее, не исключает её наличия где-либо :-)) )

В каком статусе находится подобная разработка в данный момент: практически реализована, однако, до момента окончательного запуска её, не будем о ней более подробно распространяться. Для чего она будет использована: для игры в настольный хоккей или футбол, в одной из школ. Для системы практически полностью напечатаны компоненты с использованием 3D принтера, а также она практически собрана (после ряда предварительных тестов).

Для чего ещё может быть применена подобная система: например, на той же стройке, для перемещения грузов в пределах строительной площадки и т.д.
Однако перейдём ближе к делу…

Существует целый ряд возможных кинематических решений, для перемещения целевой каретки в пределах координатной системы XY. Тем не менее автору приглянулась решение, носящее название H-bot:

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

А теперь попробуем прикинуть, каким образом можно было бы управлять подобным робототехническим приводом? Так как автор периодически возвращается в мыслях к идее стартапа, дающего возможность удалённого управления большими промышленными механизмами (например, экскаваторами, грейдерами и т.д. и т. п.), к примеру, чтобы сотрудники из одного региона, могли удалённо управлять устройствами, устроившись виртуально на работу в другом регионе («Скайнет» всё ближе, хе-хе), то, все технологии, которые могут облегчить реализации подобной затеи, вызывают повышенный интерес.

Здесь, конечно, возможно великое множество различных способов удалённого контроля, однако одним из наиболее простых и доступных (несмотря на то, что многие автора уверяли в том, что будет очень большой временной лаг и в реальном времени это будет невозможно, — эксперименты показали, что это не так) — является управление с использованием протокола MQTT.

Судя по информации, которую предоставляет один из известных MQTT-брокеров, среди его клиентов имеются известные компании, бренды которых у всех на слуху, не гнушающиеся использованием подобного протокола и этого брокера, для управления промышленными роботами в цеху. Соответственно, сделаем вывод, что и для нас это не грех.

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

Долгий опыт работы со стиками, подобными показанному ниже, убедили автора в том, что чудес не бывает и любая механика рано или поздно изнашивается и начинает ощутимо «шуметь»:



Поэтому был выбран условно более надёжный способ, который заключается в использовании виртуального джойстика. Поверхностный анализ существующих решений выдал интересный вариант, который можно скачать с Google Play:



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

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

Например, на принтскрине ниже показано, как меняются цифры, для левого джойстика, если нажать на его центральную зону и перетащить этот шарик куда-либо. Первая цифра показывает, соответственно, угол наклона стика, а вторая цифра показывает степень дальности шарика от центра стика. Другими словами, первая цифра показывает, «в каком направлении мы едем», а вторая цифра показывает «с какой скоростью мы едем»:



Если мы ещё раз обратимся к логике кинематики H-bot, то мы увидим, что в составе подобной схемы, из механики используются два двигателя. Так как человечество ещё ничего принципиально более нового и совершенного не изобрело для управления двигателями, кроме ШИМ-контроля (хотя, учитывая технический прогресс, автор уже ни в чём не уверен :-)) ), то и мы будем использовать этот же подход.

Разрешение ШИМ-сигнала будем использовать восьмибитное, то есть, другими словами, дающее 256 градаций значения скорости. Теоретически можно было бы использовать и большее разрешение, но в дальнейшем могут возникнуть проблемы с его реализацией (с аппаратной поддержкой), да и к тому же — подобное разрешение видится вполне достаточным (спорно, согласен).

Теперь, с учётом всего вышеизложенного, а также после того как мы ознакомились с логикой H-bot, попробуем нанести на круговую схему, — как направления вращения двигателей, так и требуемые для этого углы.



На картинке двумя разными цветами показаны два двигателя и направление их вращения для реализации упомянутой выше кинематики. Символ в виде горизонтальной линии показывает, что двигатель в этот момент не работает. Также можно заметить, что отмечены только восемь углов. Однако, это совершенно не значит, что двигатели имеют скачкообразные изменения скорости только при этих значениях. Скорость двигателя может плавно расти или увеличиваться в определённом интервале.

Например, если мы обратимся к интервалу 0-45° (в финальной программе интервал 0-44° и т.д., то есть, на одно значение меньше в каждом интервале), то можно увидеть, например, что в этом интервале скорость правого двигателя плавно убавляется и, с 45° и вплоть до 89°- скорость правого двигателя плавно нарастает, инвертированная по направлению в другую сторону.

Цифры внутри круга показывают, какое цифровое значение ШИМ-управления вращения двигателя является максимальным, а какое минимальным. Не забываем, что при инвертировании направления вращения меняется также и диапазон чисел. Например, если мы обратимся к интервалу от 0 до 45° (на картинке выше), то там можно увидеть, что для правого двигателя минимальным значением является 0, а максимальным 255.

Однако, если мы обратимся опять же к правому двигателю, в интервале от 45 до 90°, мы заметим, что для него диапазон значений ШИМ инвертировался также в противоположную сторону, и для него теперь в этом положении максимальным значением становится 0, а минимальным 255 и т.д.

Теперь, когда у нас есть понимание работы всей системы в целом, мы можем реализовать изложенные идеи в программном коде. Одним из наиболее простых способов видится использование C++ и её функции map, которая позволяет пропорционально перенести значение из одного диапазона в другой, так как нам требуется перевести угол наклона и силу «газа» в конкретные значения ШИМ целевого двигателя.

Синтаксис функции выглядит следующим образом:

map(value, fromLow, fromHigh, toLow, toHigh)
value: значение для переноса
fromLow: нижняя граница текущего диапазона
fromHigh: верхняя граница текущего диапазона
toLow: нижняя граница нового диапазона, в который переносится значение
toHigh: верхняя граница нового диапазона

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

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

Код управления двигателями

void Motors_Control (int ugol,int PWM)
{

  if (PWM != 0)
  {
    if (ugol != ugol_current)
    {
    //записали текущее значение угла
    //для чего это надо: будем теперь менять скорость,
    //только если пришла новая цифра значения угла - которая отличается
    //от текущего(т.к. с брокера приходит непрерывная череда цифр,
    //которые могут быть одинаковыми, если вы удерживаете джойстик в одном
    //положении, нам надо реагировать только на отличающиеся):
    ugol_current = ugol;

      //
      if ( (ugol>=0)&&(ugol<=44) )
      {
        //вычисляем скорость левого двигателя
        int left_motor_PWM = map(PWM, 0, 100, PWM_min, PWM_max);
        
        //вычисляем скорость правого двигателя
        //тут 0-мин.скорость, 255- макс.скорость
        int right_motor_PWM = map(ugol, 0, 44, PWM_max, PWM_min) ;
        
        Left_Motor (false, left_motor_PWM);
        Right_Motor (false, right_motor_PWM);
      }
      else if ( (ugol>=45)&&(ugol<=89) )
      {
        //вычисляем скорость левого двигателя
        int left_motor_PWM = map(PWM, 0, 100, PWM_min, PWM_max);
    
        //вычисляем скорость правого двигателя
        //тут 255-мин.скорость, 0- макс.скорость
        int right_motor_PWM = map(ugol, 45, 89, PWM_max, PWM_min);

        Left_Motor (false, left_motor_PWM);
        Right_Motor (true, right_motor_PWM);      
      }
      else if ( (ugol>=90)&&(ugol<=134) )
      {
        //вычисляем скорость правого двигателя
        int right_motor_PWM = map(PWM, 0, 100, PWM_max, PWM_min);
        
        //вычисляем скорость левого двигателя
        //тут 0-мин.скорость, 255- макс.скорость  
        int left_motor_PWM = map(ugol, 90, 134, PWM_min, PWM_max);  

        Left_Motor (false, left_motor_PWM);
        Right_Motor (true, right_motor_PWM);      
      }
      else if ( (ugol>=135)&&(ugol<=179) )
      {
        //вычисляем скорость правого двигателя
        int right_motor_PWM = map(PWM, 0, 100, PWM_max, PWM_min);
    
        //вычисляем скорость левого двигателя
        //тут 255-мин.скорость, 0- макс.скорость
        int left_motor_PWM = map(ugol, 135, 179, PWM_max, PWM_min);   

        Left_Motor (true, left_motor_PWM);
        Right_Motor (true, right_motor_PWM);        
      }
      else if ( (ugol>=180)&&(ugol<=224) )
      {
        //вычисляем скорость левого двигателя
        int left_motor_PWM = map(PWM, 0, 100, PWM_max, PWM_min);
        
        //вычисляем скорость правого двигателя
        //тут 255-мин.скорость, 0- макс.скорость
        int right_motor_PWM = map(ugol, 180, 224, PWM_max, PWM_min);       

        Left_Motor (true, left_motor_PWM);
        Right_Motor (true, right_motor_PWM);      
      }
      else if ( (ugol>=225)&&(ugol<=269) )
      {
        //вычисляем скорость левого двигателя
        int left_motor_PWM = map(PWM, 0, 100, PWM_max, PWM_min);
    
        //вычисляем скорость правого двигателя
        //тут 0-мин.скорость, 255- макс.скорость
        int right_motor_PWM = map(ugol, 225, 269, PWM_min, PWM_max);           

        Left_Motor (true, left_motor_PWM);
        Right_Motor (false, right_motor_PWM);       
      }
      else if ( (ugol>=270)&&(ugol<=314) )
      {
        //вычисляем скорость правого двигателя
        int right_motor_PWM = map(PWM, 0, 100, PWM_min, PWM_max);
    
        //вычисляем скорость левого двигателя
        //тут 255-мин.скорость, 0- макс.скорость
        int left_motor_PWM = map(ugol, 270, 314, PWM_max, PWM_min);           

        Left_Motor (true, left_motor_PWM);
        Right_Motor (false, right_motor_PWM);      
      }
      else if ( (ugol>=315)&&(ugol<=359) )
      {
        //вычисляем скорость правого двигателя
        int right_motor_PWM = map(PWM, 0, 100, PWM_min, PWM_max);
    
        //вычисляем скорость левого двигателя
        //тут 0-мин.скорость, 255- макс.скорость
        int left_motor_PWM = map(ugol, 315, 359, PWM_min, PWM_max);
             
        Left_Motor (false, left_motor_PWM);
        Right_Motor (false, right_motor_PWM);        
      }    
    }
  }
  else
  {
    Stop ();
  }
 
}


В коде выше: PWM_min = 0; PWM_max = 255; true или false — передающиеся в подфункции, непосредственно связанные с двигателями — управляют прямым или обратным направлением вращения.

В свою очередь, эта функция будет вызываться внутри другой, которая будет осуществлять первичный парсинг сообщения, полученного в виде строки с брокера, и выдёргивать из неё две требующиеся цифры:

Парсер сообщения с брокера
  void Motors (char* topic, char ch [])
    {
              //строка, куда мы записали название топика,
              //которое ниже будем анализировать
              String s = topic;

              //преобразовали массив символов - в строку
              String s2 = String(ch);
//            Serial.println (s2);              

              //Выдернули из строки только её кусок - с первой цифрой,
              //для этого - нашли, на какой позиции (т.е. номер) стоит пробел в этой строке
              String s3 = s2.substring (0, s2.indexOf(" "));
 
              //Преобразовали выдернутый кусок строки - в число
              //(т.к. компьютер понимает пока этот кусок как строку,
              //он не знает что это число. Ему надо явно "пальцем показать")
              int ugol = s3.toInt();
//              Serial.println (motor_direction);  
              //Выдернули из строки только её кусок - со второй цифрой,
              //для этого - нашли, на какой позиции (т.е. номер) стоит пробел в этой строке
              String s4 = s2.substring (s2.indexOf(" "), s2.length());

              //Преобразовали выдернутый кусок строки - в число
              //(т.к. компьютер понимает пока этот кусок как строку,
              //он не знает что это число. Ему надо явно "пальцем показать")
              int motor_PWM = s4.toInt();
//              Serial.println (motor_PWM);
//              Serial.println ( motor_direction );


             
              //теперь у нас есть 2 выдернутые цифры
              //и теперь мы вызываем функцию, которая запустит двигатели нужным образом
              //(и в эту функцию мы передаём полученные 2 цифры):
              Motors_Control (ugol, motor_PWM);

} 


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

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

Если хорошо подумать, для подобного манипулятора может быть найдено множество полезных применений:

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

По крайней мере, использование такого манипулятора подобным неожиданным образом, а не только в 3D-принтерах/ЧПУ-станках видится достаточно любопытным…

В версии автора статьи к каретке будет ещё прикреплён сервопривод, на конце которого будет находиться двигатель с крыльчаткой, наносящей удар по мячу:



Как альтернатива этому – установить бьющий соленоид, и сделать из всей этой истории – бильярд :-).

Соответственно, код будет тоже доработан под эти элементы в будущем.

Играй в нашу новую игру прямо в Telegram!

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


  1. sim31r
    01.02.2023 10:53

    Так как человечество ещё ничего принципиально более нового и совершенного не изобрело для управления двигателями, кроме ШИМ-контроля (хотя, учитывая технический прогресс, автор уже ни в чём не уверен :-)) ), то и мы будем использовать этот же подход.

    На картинках и в видео показаны шаговые двигатели, там не ШИМ сигнал в управлении, а скорость определяется частотой тактовых импульсов, один импульс один шаг двигателя, для NEMA 17 один оборот это 200 шагов, или через драйвер микрошаги можно использовать 1/16 или 1/256 (у драйверов A4988, DRV8825 и TMC2208), подробней тут можно посмотреть
    https://alexgyver.ru/gyverstepper/?ysclid=ldlddpa7yd830943549


    1. DAN_SEA Автор
      01.02.2023 11:03

      Совершенно верно, забыл дописать: для ускорения работы я решил использовать не шаговые двигатели, а обычные мотор-редукторы постоянного тока. Почему: быстрая работа (если сравнивать с шаговыми) + минимум шума. Сверхточность для настольной игры не требуется - это не 3D печать :-). Поэтому....

      P.S. кстати, именно поэтому там в статье упомянута возможность увеличения разрешения ШИМ сигнала (взять не 8-ми битный, а, например, 16- ти). Чтобы можно было более точно контролировать скорость двигателей, и , соответственно, точнее наводиться. Такая своеобразная альтернатива точности шаговых двигателей.


      1. sim31r
        01.02.2023 12:03

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


        1. DAN_SEA Автор
          01.02.2023 12:06

          Просто у меня в наличии были мотор-редукторы. А шаговых не было (нужной мощности) ;-). Насчёт обратной связи - знаю. Решил игнорировать это и кинуть на человека (будет подруливать). Так даже интереснее.


          1. sim31r
            01.02.2023 12:08

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


            1. DAN_SEA Автор
              01.02.2023 12:11

              Ну да...я про это и говорю выше...То есть в данный момент: система предназначена для игры и поэтому точность наведения "исключительно на глазок" + подруливать надо. Т.к. скорости двигателей программно никак не выравниваются. Как я и говорил, думал над этим всем. Но решил - что так интереснее. Эффект неожиданности появляется. Кто заинтересуется -вполне могут допилить этот момент: по энкодеру на каждый двигатель + алгоритм ПИД-регулятора.


    1. maeris
      01.02.2023 17:13

      А как микростеппинг в этих драйверах сделан?


      1. sim31r
        01.02.2023 17:48

        Это интересная тема, тут с картинками сигнал и статистика по микрошагам
        https://electroprivod.ru/microstepping_mode.htm?ysclid=ldls7dlmcn729635052

        Тут размышления о минимально возможном шаге
        https://3dtoday.ru/blogs/dagov/what-they-sing-motors?ysclid=ldls7bubv327884521


        1. maeris
          01.02.2023 20:31

          К сожалению, обе ссылки так себе, и не описывают, как сделаны сами драйверы. На самом деле либо у драйвера на одну обмотку по одной ноге и ШИМ для микростеппинга (потому что как иначе MOSFETами усиливать полушаги? они ж сгорят без насыщения), либо там несколько ножек и каскад из MOSFETов, подтягивающих обмотку на разные уровни.


          1. sim31r
            02.02.2023 01:17

            По аналогии с частотными инверторами конечно, через ШИМ всё. Там не нужны разные уровни, все равно обмотка как дроссель усреднит ток, который точно контролируется через шунт для обратной связи.


  1. Opaspap
    02.02.2023 03:51

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