Глава 4. Манипуляторы DIY
Рассмотренные в предыдущей главе модели деталей манипулятора являются составными частями собираемого манипулятора с угловой кинематикой. Таким образом, используя 3D принтер, можно выполнить печать всех необходимых конструктивных элементов углового манипулятора DIY, а затем собрать его. DIY — аббревиатура английского Do It Yourself, то есть — сделай сам.
Угловой манипулятор DIY


**Рис. 4.1. **Внешний вид манипулятора DIY с угловой кинематикой
Расчеты
Максимальная масса груза
Проведем оценку грузоподъемности робота. В рассматриваемой модели манипулятора применяются сервоприводы DYNAMIXEL XL431- T250-T и сервоприводы AR-S430-01, производимые OOO “Прикладная робототехника”. Их технические характеристики известны. Зная максимальный момент сервопривода, а также длины элементов конструкции модели, можно сделать расчет максимальной массы нагрузки, которую сможет поднять манипулятор.
В документации на сервоприводы можно найти, что максимальный момент, при котором сервоприводы способны к плавному и точному перемещению нагрузки – 0,53 Н∙м, а максимальный момент, который могут удерживать сервоприводы – 1,4 Н∙м.
Используя эти данные, рассчитаем массу полезной нагрузки для 2-го сервопривода в статическом положении, поскольку он является самым нагруженным из всех сервоприводов манипулятора. Первая степень в расчетах не учитывается, так как ось вращения вертикальна. Второму сервоприводу, помимо массы груза, также требуется перемещать массу всей конструкции манипулятора, которая расположена после него – то есть, 5 звеньев и 4 привода. Расчет будем проводить для самой нагруженной конфигурации манипулятора для второго сервопривода – горизонтальном положение, параллельно поверхности (рис. 4.2).


**Рис. 4.2. **Максимально нагруженное положение манипулятора
Формула для вычисления момента нагрузки на втрое звено имеет вид:

$K_{дин}$ - коэффициент динамики. Расчет производится для статического положения манипулятора, однако требуется учитывать динамические нагрузки. Стандартное значение $K_{дин}$ = 1,4.
$g$ - ускорение свободного падения.
$m_г$ - масса груза.
$l_м$ - расстояние до центра масс зацепа груза.
$m_{пр_i}$ - масса i-го сервопривода.
$l_{пр_i}$ - расстояние до центра масс i-го сервопривода.
$m_{зв_i}$ - масса i-го звена манипулятора.
$l_{зв_i}$ - расстояние до центра масс i-го звена манипулятора.
Примем расстояние до захватываемого груза 328,8 мм – длина от оси вращения второго сочленения до окончания схвата манипулятора.
$\displaystyle\sum_{i=3}^{6} m_{пр_i}$= 228 грамм - суммарная масса сервоприводов.
Для упрощения решения задачи будем считать, что центр масс сервопривода располагается в центре вала вращения. Таким образом, найдем плечо до каждого сервопривода от оси вращения второго сочленения (рис. 4.2).
Необходимую массу звеньев и центра масс узнаем с помощью модели в САП.
Масса звеньев робота зависит от формы, используемых материалов и технологии изготовлении. В случае использования стандартной модели звеньев манипулятора суммарная масса:
$\displaystyle\sum_{i=3}^{6} m_{зв_i}$=116грамма
Центр масс, учитывающий звенья третьей, четвертой, пятой и шестой степеней свободы, располагается на расстоянии 220 мм от оси вращения второго сервопривода.
Для нахождения массы груза с которым манипулятор имеет возможность взаимодействовать составим уравнение:
$$ M_{нагрузки}=M_{серв} $$
Таким образом момент, $M{нагрузки}$ создаваемый нагрузкой на второе сочленение, равняется моменту сервопривода $M{серв}$
$$M_{серв}=1,4Нм$$
Из электромеханической характеристики сервопривода найдем крутящий момент:
Приведем известные значения к системе СИ и подставим в формулу:

Преобразуем значения:
Выразим и найдем массу груза $m_г$:
Таким образом, максимальная масса груза для обеспечения нормальной работы манипулятора составляет 88 грамм. Однако, это приблизительный расчет, так как исследовалось статическое и наиболее нагруженное положение манипулятора.
Обратная задачи кинематики
Рассмотрим решение обратной задачи кинематики (ОЗК) для манипулятора с угловой кинематикой. Для исключения избыточных случаев можно принять положение выходного звена манипулятора вертикальным, при этом, захват будет все время находиться в вертикальном положении. В этом случае, ОЗК будет решаться однозначно.
Манипулятор можно условно разделить на 4 линейных сегмента с
соответствующими длинами l1,l2 ,l3 ,l4 . Причем, на первом и четвертом
звеньях находятся сервоприводы, дающие вращательную степень свободы вдоль продольной оси звена (Рис. 4.3).
Зеленым цветом показаны звенья манипулятора. Крестиками отмечены продольные степени вращения.
При данных условиях (звено 1 имеет только продольную степень вращения, расположено вертикально, звено 4 расположено вертикально, а 5-ый сервопривод также дает вертикальную степень вращения) можно абстрагироваться от всех звеньев манипулятора, и рассматривать только 2 и 3 звенья, перенеся ось координат в точку стыковки 1 и 2 звеньев, получив в результате ранее рассмотренный случай задачи кинематики (Рис.4.5).

**Рис. 4.3. **Схема для решения обратной задачи кинематики

l1 = 102.50
l2 = 105
l3 = 80
l4 = 171.80
**Рис. 4.4. **Схема манипулятора
Обратная задача кинематики в данном случае будет решаться для точки А и относительно нововведенного начала координат, поэтому необходимо вычислить положение точки А относительно этого нового начала координат.
$z_{реш}=z+l_4-l_1$

**Рис. 4.5. **Приведение кинематической схемы к известной
$a_1=arctg(\frac{y}{x})$
$a_5=a_1$
$k=\sqrt {x^2+y^2}$
$d=\sqrt{k^2+z^2_p}$
$a_2=\frac {\pi}{2}-(arctg(\frac{z_p}{K})+arccos(\frac{d^2+l^2_2-l^2_3}{2dl_2}))$
$a_3=\pi-arccos(\frac{-d^2+l^2_2-l^2_3}{2l_2l_3})$
$a_4=\pi-(a_2+a_3)$
Разработка управляющей программы
Для программирования манипулятора с угловой кинематикой используется то же самое программное обеспечение и такой же набор аппаратных средств, которые использовались для программирования манипулятора с плоско-параллельной кинематикой, рассматриваемого в учебном пособии «СТЕМ Мастерская. Часть 1»
Чтение позиций сервоприводов
Для чтения позиций всех сервоприводов манипулятора с угловой кинематикой можно использовать следующий пример:
cpp
#include <Dynamixel2Arduino.h>
#define DXL_SERIAL Serial3 //Последовательный порт DXL платы OpenCM9.04 EXP. (Чтобы использовать порт DXL на плате OpenCM 9.04, используйте Serial1 для Serial. И из-за кода драйвера OpenCM 9.04 используйте Serial1.setDxlMode(true); перед dxl.begin();.)
#define DEBUG_SERIAL Serial // Установка константы, отвечающей за последовательный порт, подключаемый к компьютеру
const uint8_t DXL_DIR_PIN = 22; // ПИН-код DIR платы STEM Board (Чтобы использовать порт DXL на плате OpenCM 9.04, используйте 28 для PIN-кода DIR.)
const float DXL_PROTOCOL_VERSION = 2.0; // инициализация переменной, отвечающей за протокол передачи данных от OpenCM9.04 к сервоприводам
Dynamixel2Arduino dxl(DXL_SERIAL, DXL_DIR_PIN); // инициализация указателя на команды из библиотеки Dynamixel
#define jointN 6 // задание количества ненулевых столбцов массива #define pages 3 // задание количества ненулевых строк массива
int i=0; // задание переменной-счетчика i
int j=1; // задание переменной-счетчика j
int buf[pages+1][jointN+1]={ // инициализация двумерного массива, его размер задается: 3+1=4 на 6+1=7, 4 на 7, так как в программировании нумерация
{0,0, 0, 0, 0, 0, 0}, // элементов начинается с 0, но нулевой элемент мы использовать не будем, в связи с чем начнем с первого
{0, 507, 492, 531, 498, 521, 626, },
{0, 506, 443, 403, 385, 607, 626, },
{0, 508, 532, 627, 613, 412, 626, },
};
void setup() {
// поместите код установки здесь, чтобы запустить:
DEBUG_SERIAL.begin(57600); // установка скорости обмена данными по последовательному порту компьютера
dxl.begin(1000000); // установка скорости обмена данными по последовательному порту манипулятора
dxl.setPortProtocolVersion(DXL_PROTOCOL_VERSION); // выбор протокола обмена данными
for (i=1; i<jointN+1; i++) // цикл с изменением i от 1 до 6 с шагом 1
{
dxl.setOperatingMode(i, OP_POSITION); // установка режима работы привода с номером i в качестве шарнира
}
}
void loop() {
// поместите свой основной код, чтобы запускать повторно:
for (j=1;j<=pages;j++) // цикл с изменением j от 1 до 6 с шагом 1
{
for(i=1;i<=jointN;i++) // цикл с изменением i от 1 до 6 с шагом 1
{
dxl.torqueOn(i); //подаем питание на i серву
dxl.setGoalVelocity(i, 50); // задание целевой скорости 50 сервоприводу с номером i
dxl.setGoalPosition(i, buf[j][i]); // задание целевого положения, получаемого из элемента ([j],[i]) сервоприводу с номером i
}
delay(5000);
}
}Данные после считывания будут выводиться на экран (в монитор порта) в заданном формате.
Воспроизведение записанных позиций
Далее рассматривается программа, позволяющая воспроизводить позиции, которые ранее были считаны предыдущей программой. С помощью этой программы можно достичь цели – переноса небольшого предмета из одной точки стола в другую.
cpp
#include <Dynamixel2Arduino.h> // подключение библиотеки Dynamixel
#define DXL_SERIAL Serial3 // Последовательный порт DXL платы OpenCM9.04 EXP. (Чтобы использовать порт DXL на плате OpenCM 9.04, используйте Serial1 для Serial. И из-за кода драйвера OpenCM 9.04 используйте Serial1.setDxlMode(true); перед dxl.begin();.)
#define DEBUG_SERIAL Serial // Установка константы, отвечающей за последовательный порт, подключаемый к компьютеру
const uint8_t DXL_DIR_PIN = 22; // инициализация переменной, отвечающей за номер пина, подключенного к информационному пину сервоприводов манипулятора
const float DXL_PROTOCOL_VERSION = 2.0; // инициализация переменной, отвечающей за протокол передачи данных от OpenCM9.04 к сервоприводам
Dynamixel2Arduino dxl(DXL_SERIAL, DXL_DIR_PIN); // инициализация указателя на команды из библиотеки Dynamixel
#define jointN 6 // инициализация константы, обозначающей количество сервоприводов (и, соответственно, элементов массива)
int pos=0; // инициализация переменной
pos int i=0; // инициализация переменной i
int buf[jointN+1]; // инициализация одномерного массива, его размер задается 6+1=7, так как в программировании нумерация
// // элементов начинается с 0, но нулевой элемент мы использовать не будем, в связи с чем начнем с первого
//
void setup() {
//
/DEBUG_SERIAL.begin(57600); // установка скорости обмена данными по последовательному порту компьютера
dxl.begin(1000000); // установка скорости обмена данными по последовательному порту манипулятора
dxl.setPortProtocolVersion(DXL_PROTOCOL_VERSION); // выбор протокола обмена данными
for (i=1; i<jointN+1; i++) // цикл с изменением i от 1 до 6 с шагом 1
// {
dxl.setOperatingMode(i, OP_POSITION); // установка режима работы сервопривода с номером i в качестве шарнира
// }
//}
//
void loop() {
//
for (i=1; i<=jointN; i++) // цикл с изменением i от 1 до 6 с шагом 1
{
pos = dxl.getPresentPosition(i); // получение текущей позиции привода с номером i и запись в переменную pos
buf[i]=pos; // запись переменной значения pos в i-тый элемент массива buf
Serial.println(buf[i]);
}
Serial.print(«{0, «); //вывод в монитор порта текста «{0, «
for (i=1; i<=jointN; i++) // цикл с изменением i от 1 до 6 с шагом 1
{
Serial.print(buf[i]); // вывод в монитор порта значения ячейки массива с номером i
Serial.print(«, «); // вывод в монитор порта текста «, «
}
Serial.print(«}, «); // вывод в монитор порта текста «}, «
Serial.println(‘ ‘); // вывод в монитор порта текста « « и перенос курсора на следующую строку
//
delay(1000); // задержка, пауза в 1 секунду
}Обратите внимание, в этой программе задается двумерный массив, в котором записаны положения сервоприводов. Построчно записаны различные положения манипулятора, а по столбцам – отдельные положения каждого из сервоприводов. Поэтому массив имеет 6 столбцов и переменное количество строк, которое зависит от количества воспроизводимых положений (на самом деле, число строк и столбцов на 1 больше, так как есть неиспользуемые нулевые строка и столбец; они задаются нулевыми).
В основной части программы при помощи двух циклов перебираются элементы массива. Соответствующие значения отправляются на сервоприводы для воспроизведения, в результате чего манипулятор будет отрабатывать положения, заданные в массиве. Положения в массив будут предварительно записаны самим пользователем с использованием предыдущей программы.
Таким образом, 2 программы, работающие в связке, позволят организовать копирующий режим работы манипулятора. Данный режим в рассматриваемом примере работает следующим образом:
Считывание и вывод на экран позиций (используется программа для чтения позиций).
Затем человек вручную задает некоторую позицию манипулятора (поворачивая манипулятор рукой и придерживая его при этом). В монитор порта через каждую секунду выводится текущая конфигурация манипулятора. Когда значения построчно начинают повторяться, то есть манипулятор вручную установлен в целевую позицию, в мониторе порта необходимо выключить автопрокрутку, а затем, выделив курсором одну из нужных строчек (обозначающих целевую позицию), скопировать эту строку (используя связку горячих клавиш Ctrl+C). Далее эту строку необходимо будет вставить в программу для воспроизведения позиций.
Эти действия нужно повторять до тех пор, пока все позиции для воспроизведения не будут записаны.
Воспроизведение позиций.
Когда все позиции считаны и записаны в массив, необходимо убедиться, что программа работает корректно. Это можно сделать, проведя компиляцию. Также необходимо следить за тем, чтобы переменная, отвечающая за количество строк в массиве, была задана верно – под именно текущее количество строк (а оно зависит от пользователя).
Таким образом, предварительно «научив» манипулятор переносить какой-либо предмет (последовательно задавая все положения манипулятора, необходимые для переноса предмета: начальное, наведение на предмет, подход к предмету, схват, перенос, опускание предмета, отпускание предмета), можно добиться того, что манипулятор будет воспроизводить позиции, которым он ранее научился.
SCARA-манипулятор DIY
SCARA-манипуляторы представляют собой механизмы с рычажной кинематикой, рабочий орган которых перемещается в одной плоскости за счет вращательного движения рычагов, на конце которых он расположен. Как правило, такие манипуляторы состоят из двух рычагов, соединенных в одной точке и двух независимых приводов, один из которых находится в точке соединения рычагов и вращает их друг относительно друга. Второй же привод находится в точке крепления рычага к основанию и вращает его относительно рабочей плоскости. Внешний вид такого манипулятора представлен на рисунке 4.6:

**Рис. 4.6. **Типичный промышленный SCARA-манипулятор
Обратная задача кинематики SCARA-манипулятора
Для запуска манипулятора необходимо научиться решать обратную (инверсную) кинематическую задачу. Исходя из известной позиции, в которую требуется переместить манипулятор робота (например, схватить объект, который находится на конвейере в точке с координатами x, y) требуется определить величины углов, на которые необходимо повернуть двигатели, связанные с рычагами робота, чтобы установить его в правильное положение для захвата. Примеры положений звеньев манипулятора при достижении рабочим органом требуемого положения представлены на рисунке 4.7

**Рис. 4.7. **Варианты положений рабочего органа манипулятора
Формализуем задачу: неподвижное основание и его движущиеся рычаги можно представить в виде отрезков, соединенных между собой шарнирами. Углы поворота рычагов робота обозначим как $0_1$, $0_2$,$0_3$ а координаты точек конца каждого рычага $Х_0,Х_1,Х_2 и Y_0, Y_1, Y_2,$ где в реальности будут закреплены сервопривода. Конечные координаты $Х_3$ и $Y_3$ нам известны. Это точка, в которую манипулятор должен прийти. Первый угол поворота рассматриваем от положительного направления оси Х до рычага. Последующие углы рассматриваются от предыдущего звена до рассматриваемого. Положительный угол поворота направлен против часовой стрелки.
Манипулятор SCARA имеет некоторую сложность с решением задачи. Это связано с тем, что для достижения конечной точки С могут способствовать различные положения манипулятора на плоскости.
**Рис. 4.8. **Возможность положения звеньев манипулятора при достижения конечной точки
В данном случае, механизм обладает избыточными степенями подвижности – движение звеньев может осуществляться при неподвижном схвате, что позволяет преодолевать внешние препятствия или проводить работы в замкнутых объемах. Поэтому для неподвижности конечного звена со схватом необходимо ввести еще одну координату γ – это угол, под которым конечное звено должно подойти к предмету относительно положительного направления оси Х. Таким образом, неопределенность будет возникать только между первым и вторым звеном манипулятора. Эта проблема решается программным способом.

**Рис. 4.9. **Варианты положений рабочего органа манипулятора
Для решения задачи введем несколько ключевых параметров, которые определяются геометрическими размерами робота: длины звеньев $l_1,l_2,l_3;$угол подхода схвата к объекту относительно оси Х, γ. Решение задачи осуществляется в программе MathCad. Зададим раз-
меры звеньев:
$l_1$ := 92
$l_2$ := 66
$l_3$ := 106
Укажем координаты, в которые должен прийти манипулятор, и угол, под которым он будет располагаться:
$x_3$ := 264
$y_3$ := 0
$\varphi$ := 0 deg
После того, как необходимые данные введены, можем начать решение.
- Определим координаты конца второго звена:
$x_2:=x_3-l_3*cos(\varphi)$
$y_2:=y_3-l_3*sin(\varphi)$
- Найдем координаты конца первого звена. Для этого составим систему уравнений двух окружностей. Центр координат первой окружности находится в начале системы координат (0,0), а её радиус равен длине первого звена l**1. Центр второй окружности находится в координатах конца второго звена (Х**2, Y2), а её радиус равен l**2. Найдем координаты пересечения этих двух окружностей. В результате получим квадратное уравнение и два корня. Приведем решение в программе MathCad:
$a:=x^2_2+l^2_1+y^2_2-l^2_2$
$D:=(4x_2a)^2-4*(-4y^2_2-4x^2_2)(-a^2+4l^2_1*y^2_2)=0$
$x_{11}:=\frac{(-4x_2a-\sqrt D)}{[2*(-4y^2_2-4x^2_2)]}=92$
$x_{12}:=\frac{(-4x_2a-\sqrt D)}{[2*(-4y^2_2-4x^2_2)]}=92$
В данном случае решение одинаковое, так как _D** _= 0, а пересечение окружностей осуществляется лишь в одной точке.
- Теперь можно найти угол поворота первого звена для двух вариантов:
$fi_{11}:=a cos(\frac{x_11}{l_1}=0*deg)$
$fi_{12}:=a cos(\frac{x_12}{l_1}=0*deg)$
- Найдем координаты $Y$ конца первого звена двух вариантов:
$y_{11}:=\sqrt {l^2_1-x^2_{11}}=0$
$y_{12}:=\sqrt {l^2_1-x^2_{12}}=0$
- Далее рассчитаем длину проекции второго звена на оси:
$x_{21}:=x_2-x_{11}=66$ $x_{22}:=x_2-x_{12}=66$ $y_{21}:=y_2-y_{11}=66$ $y_{22}:=y_2-y_{12}=66$
- Следующим шагом вычислим углы наклона проекции второго звена к оси Х:
$t_{21}:=a tan2(x_{21},y_{21})=0*deg$
$t_{22}:=a tan2(x_{22},y_{22})=0*deg$
Напомним, что данная функция определяется следующим образом:
$$ a tan2(a,b)= \begin{cases} arctg\frac{a}{b},b>0, \ arctg\frac{a}{b}+\pi,b>0,a>0,\ arctg\frac{a}{b}-\pi,b<0,a<0; \end{cases} $$
$$-\pi\leq a tan2(a,b)\leq\pi$$ Т.е. она учитывает принадлежность аргументов к одному из четырех квадрантов.
- Определив разницу между углом наклона первого звена и второго, можно вычислить угол поворота второго звена относительно первого:
$fi_{21}:=t_{21}-fi_{11}=0deg$ $fi_{22}:=t_{22}-fi_{12}=0deg$ 8. Произведем рассчет для нахождения третьего угла поворота:
$fi_{31}:=\varphi-fi_{11}-fi_{21}$ $fi_{32}:=\varphi-fi_{12}-fi_{22}$
- Необходимо сделать преобразование координат, поскольку наша декартовая система отличается от системы координат робота. Это связано с тем, что 0° для сервоприводов находится в другом положении от нашего представления. Координатная плоскость робота представлена на рис. 4.10.
Таким образом, обратная задача кинематики была решена. Теперь необходимо разработать систему управления манипулятором с использованием управляющего микроконтроллера.
**Рис. 4.10. **Координатная плоскость робота
Устройство SCARA-манипулятора
Инструкция по сборке робота, а также исходные файлы конструктивных элементов под лазерную резку находятся в свободном доступе и доступны для скачивания с официального сайта appliedrobotics.ru из раздела «Учебные материалы». Между каждым звеном установим три сервопривода. В качестве управляющего контроллера используется Arduino-подобная аппаратная платформа OpenCM, установленная на периферийной плате расширения STEM Board. Питание обеспечивается с помощью либо стандартного LiPo аккумулятора, выдающего номинальное напряжение в 11,1 В, либо через сетевой адаптер питания. Соединение сервоприводов рекомендуется выполнить последовательно и подключить получившуюся цепь к плате расширения, как на рисунке 4.11.
Также работ имеет подвижное конечно звено, которое перемещается в плоскости, перпендикулярной к основным звеньям. Оно будет управляться отдельно.
Перед тем, как приступить к разработке управляющей программы, необходимо уточнить ID сервоприводов, установленных на манипуляторе. ID сервоприводов указаны на рисунке 4.11.

**Рис. 4.11. **Схема подключения сервоприводов и их ID
Для уточнения и установки ID сервоприводов можно использовать стандартные примеры из Arduino IDE с установленной поддержкой платы OpenCM9.04. Для этого необходимо открыть два примера под названием: “o_Find_Dynamixel” и “c_ID_Change”. (“Файл“ => “Примеры“ => OpenCM9.04 => DynamixelWorkbench).
С помощью примера “o_Find_Dynamixel” можно узнать текущее ID сервопривода следующим образом: сервопривод подключается к плате, подключается питание и загружается пример на плату. После чего в Мониторе порта можно увидеть информацию о текущем значении ID, которое необходимо запомнить.
После этого нужно открыть второй пример - “c_ID_Change”. В оглавлении, при необходимости, нужно изменить эти строки:
#define DXL_ID 1 // 1 – это текущее значение ID сервопривода
#define NEW_DXL_ID 2 // 2 – это значение, на которое требуется поменять IDПомимо этого, в обоих случаях необходимо поменять скорость “общения” платы со входами Dynamixel
#define BAUDRATE 57600 // изменяем значение на 1 000 000
Таким образом, устанавливаются необходимые ID для сервоприводов.
Для обеспечения безопасной работы рекомендуется перед запуском определить, в каком положении находится вал сервопривода при 0°.

**Рис. 4.12. **SCARA-манипулятор
Разработка управляющей программы
В модели данного робота в качестве управляющего контроллера используется Arduino-подобный контроллер OpenCM. Для работы с ним используется среда разработки Arduino IDE.
Для работы с Dynamixel подключаем библиотеку и инициализируем переменные:
#include <DynamixelWorkbench.h>
Для обмена данных по протоколу Dynamixel в коде программы необходимо указать номер аппаратной шины – другими словами порты, на плате, к которым будет выполнено подключение сервоприводов.
#if defined( OPENCM904 )
#define DEVICE_NAME «3» //Dynamixel on Serial3(USART3) <-OpenCM 485EXP
#elif defined( OPENCR )
#define DEVICE_NAME «»
#endifВ рассматриваемом примере требуется выбрать 3 вариант Указываем скорость обмена данными через порт подключения
#define BAUDRATE 1000000 // Скорость передачи данных
Далее необходимо указать ID сервоприводов, которыми необходимо управлять. Все это делается в коде программы до функции setup() при помощи следующих команд:
#define DXL_ID1 1 // адрес сервопривода DYNAMIXEL
#define DXL_ID2 2
#define DXL_ID3 3
#define DXL_ID4 4Инициализируем геометрические данные робота в качестве глобальных переменных. Они нам необходимы для расчетов. В рассматриваемой модели робота нужно проинициализировать следующие переменные:
DynamixelWorkbench dxl_wb;
float theta1, theta2, theta3;
float m = 0;
float n = 0;
float k= 0;
float t = 0;
float l1 = 92;
float l2 = 66;
float l3 = 106;
float x11;
bool flag = false;Функция setup()
Зададим переменные model_number и присвоим им ID сервоприводов. После этого производим подключение всех сервоприводов манипулятора.
void setup() {
const char *log ;
Serial.begin(57600);
uint16_t model_number1 = 1;
uint16_t model_number2 = 2;
uint16_t model_number3 = 3;
uint16_t model_number4 = 4;
dxl_wb.init(DEVICE_NAME, BAUDRATE, &log);
dxl_wb.ping(DXL_ID1, &model_number1, &log);
dxl_wb.ping(DXL_ID2, &model_number2, &log);
dxl_wb.ping(DXL_ID3, &model_number3, &log);
dxl_wb.ping(DXL_ID4, &model_number4, &log);
dxl_wb.jointMode(DXL_ID1, 1, 1, &log);
dxl_wb.jointMode(DXL_ID2, 1, 1, &log);
dxl_wb.jointMode(DXL_ID3, 1, 1, &log);
dxl_wb.jointMode(DXL_ID4, 1, 1, &log);
}Функция find_angle(float x, float y, float a)
В данной функции происходит решение обратной задачи. Входные данные:
x – координата схвата по оси Х y - координата схвата по оси Y
a – угол относительно оси Х, под которым необходимо, чтобы схват подошел к объекту
void find_angle(float x, float y, float a)
{
float x2 = x - l3*cos(a); // Нахождение координат конца второго звена
float y2 = y - l3*sin(a);
float d = sqrt(x2*x2 + y2*y2); // Введем параметр
if (d > (l1+l2)) // Проверка дискриминанта
{
Serial.println(«Out of zone»); // Если значение дискриминанта меньше нуля, манипулятор занимает базовое положение
theta1 = 0;
theta2 = 0;
theta3 = 0;
}
else
{
if (y < 0 && a <= 0 )
{
y = -y;
a = -a;
flag = true;
}
float A = ((l1*l1)-(l2*l2)+(d*d))/(d*2);
float alfa = acos(x2/d);
float delta = acos(A/l1);
float q1 = alfa - delta;
float x1 = l1*cos(q1);
float y1 = l1*sin(q1);
float q2 = acos((x2-x1)/l2);
theta1 = q1;
theta2 = q2-q1;
theta3 = a-q1-theta2;
if(flag)
{
Serial.println(«FLAG»);
theta1 = -theta1;
theta2 = -theta2;
theta3 = -theta3;
flag = false;
}
}
}Функция set_pos()
В данной функции осуществляется задание угла поворота вала двигателя на манипуляторе
void set_pos( float p1 , float p2, float p3, float p4, int del)
// Функция для задания угла поворота вала двигателя на манипулятора в радианах
{
dxl_wb.goalPosition(DXL_ID1,p1);
dxl_wb.goalPosition(DXL_ID2,p2);
dxl_wb.goalPosition(DXL_ID3,p3);
dxl_wb.goalPosition(DXL_ID4,p4);
delay(del);
}Функция loop()
Рассмотрим функцию loop(), которая представляет собой бесконечный цикл.
void loop() {
if (Serial.available() > 0 ) {
m = Serial.parseInt();
n = Serial.parseInt();
k = Serial.parseInt();
find_angle(m, n, k );
Serial.print(«Angle #1: « );
Serial.println(theta1*180/3.14);
Serial.print(«Angle #2: « );
Serial.println(theta2*180/3.14);
Serial.print(«Angle #3: « );
Serial.println(theta3*180/3.14);
}
set_pos(theta1, theta2, theta3, 0, 700); // Поворот валов приводов на вычисленный угол – достижение заданных координат
}Также можно корректировать положения робота в реальном времени. С помощью консоли пользователь вручную может вводить координаты, которые будет занимать робот.
Координаты вводятся в миллиметрах. Значения можно вводить через любые знаки препинания, главное вводить их в правильном порядке и разделять пробелом.
В результате, код управляющей программы будет выглядеть следующим образом:
#include <DynamixelWorkbench.h>
#if defined( OPENCM904 )
#define DEVICE_NAME «3» //Dynamixel on Serial3(USART3) <-STEM Board
#elif defined( OPENCR )
#define DEVICE_NAME «»
#endif
#define BAUDRATE 1000000 // Скорость передачи данных
#define DXL_ID1 1 // адрес сервопривода DYNAMIXEL
#define DXL_ID2 2
#define DXL_ID3 3
#define DXL_ID4 4
DynamixelWorkbench dxl_wb;
float theta1, theta2, theta3;
float m = 0;
float n = 0;
float k= 0;
float t = 0;
float l1 = 92;
float l2 = 66;
float l3 = 106;
float x11;
bool flag = false;
void set_pos( float p1 , float p2, float p3, float p4, int del)
// Функция для задания угла поворота вала двигателя на манипулятора в радианах
{
dxl_wb.goalPosition(DXL_ID1,p1);
dxl_wb.goalPosition(DXL_ID2,p2);
dxl_wb.goalPosition(DXL_ID3,p3);
dxl_wb.goalPosition(DXL_ID4,p4);
delay(del);
}
void find_angle(float x, float y, float a)
{
float x2 = x - l3*cos(a);
float y2 = y - l3*sin(a);
float d = sqrt(x2*x2 + y2*y2);
if (d > (l1+l2))
{
Serial.println(«Out of zone»);
theta1 = 0;
theta2 = 0;
theta3 = 0;
}
else
{
if (y < 0 && a <= 0 )
{
y = -y;
a = -a;
flag = true;
}
float A = ((l1*l1)-(l2*l2)+(d*d))/(d*2);
float alfa = acos(x2/d);
float delta = acos(A/l1);
float q1 = alfa - delta;
float x1 = l1*cos(q1);
float y1 = l1*sin(q1);
float q2 = acos((x2-x1)/l2);
theta1 = q1;
theta2 = q2-q1;
theta3 = a-q1-theta2;
if(flag)
{
Serial.println(«FLAG»);
theta1 = -theta1;
theta2 = -theta2;
theta3 = -theta3;
flag = false;
}
}
}
void setup() {
const char *log ;
Serial.begin(57600);
uint16_t model_number1 = 1;
uint16_t model_number2 = 2;
uint16_t model_number3 = 3;
uint16_t model_number4 = 4;
dxl_wb.init(DEVICE_NAME, BAUDRATE, &log);
dxl_wb.ping(DXL_ID1, &model_number1, &log);
dxl_wb.ping(DXL_ID2, &model_number2, &log);
dxl_wb.ping(DXL_ID3, &model_number3, &log);
dxl_wb.ping(DXL_ID4, &model_number4, &log);
dxl_wb.jointMode(DXL_ID1, 1, 1, &log);
dxl_wb.jointMode(DXL_ID2, 1, 1, &log);
dxl_wb.jointMode(DXL_ID3, 1, 1, &log);
dxl_wb.jointMode(DXL_ID4, 1, 1, &log);
}
void loop() {
if (Serial.available() > 0 ) {
m = Serial.parseInt();
n = Serial.parseInt();
k = Serial.parseInt();
k = (k*3.14)/180;
find_angle(m, n, k );
Serial.print(«Angle #1: « );
Serial.println(theta1*180/3.14);
Serial.print(«Angle #2: « );
Serial.println(theta2*180/3.14);
Serial.print(«Angle #3: « );
Serial.println(theta3*180/3.14);
}
set_pos(theta1, theta2, theta3, 0, 700);
}Таким образом, была решена обратная задача для плоского трехзвенного манипулятора, а также написана управляющая программа для реализации задачи на физической модели с микроконтроллером OpenCM.
STEWART-платформа DIY
STEWART - платформа (платформа Стюарта) представляет собой один из видов параллельного манипулятора, имеющего 6 степеней свободы. Отличительной особенностью данного вида манипуляторов является компоновка его сервоприводов – все сервопривода располагаются в одной плоскости тремя парами, что приводит к созданию шести тяг на шарнирных соединителях (Рис. 4.13).
Рабочий орган такого манипулятора чаще всего представляется в виде горизонтальной платформы, на котором размещается объект. За счет большого количества тяг становится возможно точное и быстрое изменение положения находящегося на платформе объекта в пространстве. Вследствие чего, платформы Стюарта используются в задачах, где необходимо точное позиционирование объекта в пространстве.

**Рис. 4.13. **Типовая модель платформы Стюарта
Обратная задача кинематики
Для запуска платформы необходимо научиться решать обратную (инверсную) кинематическую задачу. Исходя из требуемой позиции, в которую необходимо переместить платформу (например, для удержания в равновесии объекта, находящегося на платформе) требуется определить величины углов, на которые необходимо повернуть фланцы сервоприводов, связанные с тягами манипулятора, чтобы установить его платформу в правильное положение. Платформа является одним из видов параллельного манипулятора, имеющим шесть степеней свободы и координаты, по которым она может передвигаться линейное перемещение по осям $X,Y,Z,$ а также углы поворота: крен (Θ), тангаж (φ) и рысканье (ψ).
Манипулятор состоит из двух основных частей: неподвижной плоскости – базы, и подвижной плоскости – платформы. На базе находятся шесть сервоприводов, к фланцам которых прикреплены рычаги. В свою очередь, усилие от рычагов передается на тяги через шарнирные соединители, которые так же имеют шарнирное соединение с подвижной частью манипулятора.
Решение обратной задачи необходимо начать с размещения в центре основания точки отсчета системы координат с осями $х, у, z$. В таком случае, платформа будет иметь свою подвижную систему координат $х’,у’,z’$. Платформа определяется с помощью трех поступательных перемещений вдоль осей $х, у, z$ относительно основания. Три угла поворота вокруг осей определяют ориентацию платформы по отношению к основанию: поворот на угол $ψ$ вокруг оси $z$, поворот на угол $Θ$ вокруг оси $у$, поворот на угол $φ$ вокруг оси $х$.
Рассмотрим поворот относительно оси $z$ на угол $ψ$. Тогда матрица вращения $Rz(y)$ имеет вид:
$$ \begin{bmatrix} x \ y \ z \end{bmatrix} = R_Z(ψ) \begin{bmatrix} x' \ y' \ z' \end{bmatrix} $$
$$ R_z(ψ)=\begin{bmatrix} cosψ & -sinψ & 0\ sinψ & cosψ & 0\ 0 & 0 & 1 \end{bmatrix} $$
Аналогично, имеем матрицу вращения для поворота вокруг оси у на угол $Θ$:
$$ R_y(0)=\begin{bmatrix} cos0 & 0 & sin0\ 0 & 1 & 0\ -sin0 & 0 & cos0 \end{bmatrix} $$
Для третьего поворота на угол $φ$ вокруг оси $х$ имеем:
$$ R_z(ψ)=\begin{bmatrix} 1 & 0 & 0 \ 0 & cosφ & -sinφ\ 0 & sinφ & cosφ \end{bmatrix} $$
Полная матрица вращения платформы по отношению к основанию определяется по следующей формуле:
$$ {}^P R_B = R_z(\psi) \cdot R_y(\theta) \cdot R_x(\varphi) = $$
$$ \begin{pmatrix} \cos\psi & -\sin\psi & 0 \ \sin\psi & \cos\psi & 0 \ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} \cos\theta & 0 & \sin\theta \ 0 & 1 & 0 \ -\sin\theta & 0 & \cos\theta \end{pmatrix} \begin{pmatrix} 1 & 0 & 0 \ 0 & \cos\varphi & -\sin\varphi \ 0 & \sin\varphi & \cos\varphi \end{pmatrix} $$
$$
\begin{pmatrix} \cos\psi \cos\theta & -\sin\psi & \cos\psi \sin\theta \ \sin\psi \cos\theta & \cos\psi & \sin\psi \sin\theta \ -\sin\theta & 0 & \cos\theta \end{pmatrix} \begin{pmatrix} 1 & 0 & 0 \ 0 & \cos\varphi & -\sin\varphi \ 0 & \sin\varphi & \cos\varphi \end{pmatrix} $$
$$
\begin{pmatrix} \cos\psi \cos\theta & -\sin\psi \cos\varphi + \cos\psi \sin\theta \sin\varphi & \sin\psi \sin\varphi + \cos\psi \sin\theta \cos\varphi \ \sin\psi \cos\theta & \cos\psi \cos\varphi + \sin\psi \sin\theta \sin\varphi & -\cos\psi \sin\varphi + \sin\psi \sin\theta \cos\varphi \ -\sin\theta & \cos\theta \sin\varphi & \cos\theta \cos\varphi \end{pmatrix} $$
Теперь рассмотрим i-тую тягу платформы Стюарта (Рис.4.14).

**Рис. 4.14. **Рассмотрение i-той тяги платоформы Стюарта
Координаты qi точки соединения верхней точки Pi относительно системы координат основания задаются уравнением:
$q_1=T+{}^pR_b*p_i$
Здесь вектор T описывает линейное перемещение начала координат платформы по отношению к системе координат основания, pi – является вектором, определяющим координаты точки соединения Pi относительно системы координат платформы.
Аналогичным образом задается длина i-ой тяги в следующем виде:
$l_i=T+{}^pR_b*p_i-b_i$
где bi – вектор, определяющий координаты нижней точки Bi соединения тяги. С помощью 6 уравнений определяют длины 6 тяг, таким образом, определяется положение и ориентация платформы.
Далее необходимо определить угол поворота вала сервопривода. При нахождении формулы использовались следующие обозначения (Рис. 4.15):
а – длина рычага сервопривода,
Аi – точки соединения рычага с нижней точкой стержня i-го сервопривода с координатами а = [xa, ya, za] в системе координат основания,
Вi – точки вращения центра рычага сервопривода с координатами b = [xb, yb, zb] в системе основания,
Pi – точки соединения верхнего шарнира стержня с платформой, с координатами р = [xр, yр, zр] в системе координат платформы,
S – длина стержня,
li = длина i-ого стержня,
α – угол между рычагом сервопривода и горизонталью,
β – угол между рычагом сервопривода и осью х После некоторых преобразований имеем:
$a=sin^-1\frac{L}{\sqrt M^2+N^2}-tan^-1\frac{N}{M}$
где
$$ L=l^2-(s^2-a^2) $$
$$ M=2a(z_p-z_b) $$
$$ N=2a[cos\beta(x_p-x_b)+sin\beta(y_p-_b)] $$
При расчете объявим все константы и приведем платформу в базовое положение, при котором все рычаги будут стоять параллельно базе.
$s := 211$ $\psi := 0 deg$ $x := 0$ $p := 110$ $е := 0 deg$ $y := 0$ $a := 35$ $\varphi := 0 deg$ $z := 200$ $b := 100$
**Рис. 4.15. **Схема расчета угла поворота вала сервопривода
Найдем проекции точек b и p на оси X и Y:
$b_{1x} := bcos(0.61) = 81.965$ $p_{1x} := pcos(0.13) = 109.072$ $b_{1y} := bsin(0.61) = 57.287$ $p_{1y} := psin(0.13) = 14.26$ $b_{2 x} := bcos(1.48) = 9.067$ $p_{2 x} := pcos(1.95) = -40.72$ $b_{2 y} := bsin(1.48) = 99.588$ $p_{2 y} := psin(1.95) = 102.186$ $b_{3x} := bcos(2.7) = -90.407$ $p_{3x} := pcos(2.22) = -66.501$ $b_{3 y} := bsin(2.7) = 42.738$ $p_{3 y} := psin(2.22) = 87.622$ $b_{4 x} := bcos(3.58) = -90.543$ $p_{4 x} := pcos(4.04) = -68.514$ $b_{4 y} := bsin(3.58) = -42.45$ $p_{4 y} := psin(4.04) = -86.057$ $b_{5x} := bcos(4.8) = 8.75$ $p_{5x} := pcos(4.31) = -43.078$ $b_{5 y} := bsin(4.8) = -99.616$ $p_{5 y} := psin(4.31) = -101.214$ $b_{6 x} := bcos(5.6) = 77.557$ $p_{6 x} := pcos(6.13) = 108.712$ $b_{6 y} := bcos(5.6) = -63.127$ $p_{6 y} := pcos(6.13) = -16.785$
Найдем матрицу поворота:
$$ R_i = \begin{pmatrix} \cos\psi \cos\theta & -\sin\psi \cos\varphi + \cos\psi \sin\theta \sin\varphi & \sin\psi \sin\varphi + \cos\psi \sin\theta \cos\varphi \ \sin\psi \cos\theta & \cos\psi \cos\varphi + \sin\psi \sin\theta \sin\varphi & -\cos\psi \sin\varphi + \sin\psi \sin\theta \cos\varphi \ -\sin\theta & \cos\theta \sin\varphi & \cos\theta \cos\varphi \end{pmatrix} $$
$$
\begin{pmatrix} 1 & 0 & 0 \ 0 & 1 & 0 \ 0 & 0 & 1 \end{pmatrix} $$
Найдем вектор l**1:
$$ \begin{pmatrix} x \ y \ z \end
R_i \begin{pmatrix} p_{lx} \ p_{ly}\ 0 \end
\begin{pmatrix} b_{lx} \ b_{ly} \ 0 \end
\begin{pmatrix} 27.107 \ -43.027 \ 200 \end{pmatrix} $$
Найдем длину вектора l**1:
$$ l = \sqrt{(27.1)^2 + (43)^2 + (200)^2} = 206.357 $$
Рассчитаем оставшиеся компоненты для конечной формулы:
$$ L := l^2 - (s^2 - a^2) = -712.59 $$
$$ M := 2a \cdot 1 = 1.445 \times 10^4 $$
$$ N := a * 2 \left[ \cos(0.61),(p_{lx} - b_{lx}) + \sin(0.61)*(p_{ly} - b_{ly}) \right] = -140.139 $$
$$ \alpha_i := a,sin!\left(\frac{L}{\sqrt{M^2 + N^2}}\right) - a,tan!\left(\frac{N}{M}\right) = -2.153deg $$
Небольшая ошибка присутствует из-за округлений и неточности измерения базовой высоты в 200 мм. Из-за конструкции в дальнейшем перед нечетными углами поворота необходимо ставить знак “ - ” для вращения в противоположную сторону.
Расчет для оставшихся пяти сервоприводов аналогичен.
Устройство платформы STEWART
Инструкция по сборке робота, а также исходные файлы конструктивных элементов под лазерную резку находятся в свободном доступе и доступны для скачивания с официального сайта appliedrobotics.ru из раздела «Учебные материалы». Сборка платформы Стюарта представляет собой размещение 6 сервоприводов на базе, установке на их фланцы рычагов и соединение подвижной платформы с рычагами через тяги с использованием шарнирного соединения. Перед установкой рычага необходимо перевести сервопривода в нулевое положение и присоединить рычаг параллельно базе. Также необходимо обратить внимание, что соседние рычаги располагаются навстречу друг другу в базовом положении. Тяги должны располагаться, как показано на рисунке 4.15.
В качестве управляющего контроллера используется Arduino-подобная аппаратная платформа OpenCM, установленная совместно со встраиваемой платой NanoPi-AR на плате расширения STEM Board. Питание обеспечивается либо при помощи стандартного LiPo аккумулятора, выдающего номинальное напряжение в 11,1 В, либо через сетевой адаптер питания. Соединение сервоприводов рекомендуется выполнить последовательно и подключить получившуюся цепь к плате расширения STEM Board как показано на Рис. 4.16.
Перед тем как приступить к разработке управляющей программы, необходимо уточнить ID сервоприводов, установленных на манипуляторе. ID сервоприводов указаны на следующей схеме, приведенной на рисунке 4.16.
Для уточнения и установки ID сервоприводов можно использовать стандартные примеры из Arduino IDE с установленной поддержкой платы OpenCM9.04. Для этого необходимо открыть два примера под названием: “oFind_Dynamixel” и “c_ID_Change”. (“Файл“ => “Примеры“ => OpenCM9.04 => DynamixelWorkbench). С помощью примера “o_Find Dynamixel” можно узнать текущее ID сервопривода следующим образом: сервопривод подключается к плате, к нему подключается питание, после чего на контроллер загружается пример. После чего в Мониторе порта сможем увидеть информацию о текущем значении ID, которое необходимо запомнить.
Теперь необходимо открыть второй пример “c_ID_Change”. В оглавлении, при необходимости, нужно изменить эти строки:

**Рис. 4.16. **Схема подключения сервоприводов и их ID
#define DXL_ID 1 // 1 – это текущее значение ID сервопривода #define NEW_DXL_ID 2 // 2 – это значение, на которое требуется поменять IDПомимо этого, в обоих случаях необходимо поменять скорость “общения” платы со входами DYNAMIXEL.
#define BAUDRATE 57600 // изменяем значение на 1 000 000 Таким образом, устанавливаются необходимые ID для сервоприводов.Для обеспечения безопасной работы, рекомендуется перед запуском определить, в каком положении находится вал сервопривода в положении 0°.
Разработка управляющей программы
В модели данного робота в качестве управляющего контроллера используется контроллер OpenCM. Для работы с ним используется среда разработки Arduino IDE.
Для работы с Dynamixel подключаем библиотеку и инициализируем переменные:
#include <DynamixelWorkbench.h>
После чего подключаем библиотеку для работы с математическими выражениями
#include <math.h>
Для обмена данных по протоколу Dynamixel в коде программы необходимо указать аппаратную шину. Другими словами, порты на плате, к которым будет выполнено подключение сервоприводов.
#if defined( OPENCM904 )
#define DEVICE_NAME «3» //Dynamixel on Serial3(USART3) <-OpenCM 485EXP
#elif defined( OPENCR )
#define DEVICE_NAME «»
#endifУказываем скорость обмена данными через порт подключения:
#define BAUDRATE 1000000
Далее необходимо указать ID сервоприводов, которыми необходимо управлять. Все это делается в коде программы до функции setup() при помощи следующих команд:
#define DXL_ID1 1
#define DXL_ID2 2
#define DXL_ID3 3
#define DXL_ID4 4
#define DXL_ID5 5
#define DXL_ID6 6
DynamixelWorkbench dxl_wb;Инициализируем геометрические данные робота в качестве глобальных переменных. Они нам необходимы для расчетов. В рассматриваемой модели робота инициализируем следующие переменные:
float s = 211;
float a = 35;
float b = 100;
float p = 110;
float fi_b = 0.61;
float fi_p = 0.13;
float bx, by, px, py;
float R[3][3];
float alfa1 = 0;
float beta1 = 0;
float gama1 = 0;
float x1;
float y_1;
float z1;
float angle0, angle1, angle2, angle3,angle4, angle5, angle6;Функция setup()
Создаем переменные model_number и присваиваем им номер сервопривода. После производим подключение всех сервоприводов манипулятора.
void setup()
{
const char *log ;
Serial.begin(1000000);
uint16_t model_number1 = 1;
uint16_t model_number2 = 2;
uint16_t model_number3 = 3;
uint16_t model_number4 = 4;
uint16_t model_number5 = 5;
uint16_t model_number6 = 6;
dxl_wb.init(DEVICE_NAME, BAUDRATE, &log); dxl_wb.ping(DXL_ID1, &model_number1, &log); dxl_wb.ping(DXL_ID2, &model_number2, &log); dxl_wb.ping(DXL_ID3, &model_number3, &log); dxl_wb.ping(DXL_ID4, &model_number4, &log); dxl_wb.ping(DXL_ID5, &model_number5, &log); dxl_wb.ping(DXL_ID6, &model_number6, &log);
dxl_wb.jointMode(DXL_ID1, 20, 20, &log);
dxl_wb.jointMode(DXL_ID2, 20, 20, &log);
dxl_wb.jointMode(DXL_ID3, 20, 20, &log);
dxl_wb.jointMode(DXL_ID4, 20, 20, &log);
dxl_wb.jointMode(DXL_ID5, 20, 20, &log);
dxl_wb.jointMode(DXL_ID6, 20, 20, &log);
}Теперь рассмотрим действие каждой функции отдельно.
Функция matrix(float alfa, float beta, float gama)
В данной функции находится матрица поворота, которая соответствует уравнению:
void matrix(float alfa, float beta, float gama)
{
R[0][0] = cos(alfa)*cos(beta);
R[1][0] = -sin(alfa)*cos(gama) + cos(alfa)*sin(beta)*sin(gama); R[2][0] = sin(alfa)*sin(gama) + cos(alfa)*sin(beta)*cos(gama); R[0][1] = sin(alfa)*cos(beta);
R[1][1] = cos(alfa)*cos(gama) + sin(alfa)*sin(beta)*sin(gama); R[2][1] = -cos(alfa)*sin(gama) + sin(alfa)*sin(beta)*cos(gama); R[0][2] = -sin(beta);$$ R_i = \begin{pmatrix} \cos\psi \cos\theta & -\sin\psi \cos\varphi + \cos\psi \sin\theta \sin\varphi & \sin\psi \sin\varphi + \cos\psi \sin\theta \cos\varphi \ \sin\psi \cos\theta & \cos\psi \cos\varphi + \sin\psi \sin\theta \sin\varphi & -\cos\psi \sin\varphi + \sin\psi \sin\theta \cos\varphi \ -\sin\theta & \cos\theta \sin\varphi & \cos\theta \cos\varphi \end
\begin{pmatrix} 1 & 0 & 0 \ 0 & 1 & 0 \ 0 & 0 & 1 \end{pmatrix} $$
R[1][2] = cos(beta)*sin(gama);
R[2][2] = cos(beta)*cos(gama);}
Функция angle(float fi_b, float fi_p, float x, float y, float z)
При помощи данной функции можно определить угол поворота для i-го рычага
void angle(float fi_b, float fi_p, float x, float y, float z)
{
bx = b*cos(fi_b);
by = b*sin(fi_b);
px = p*cos(fi_p);
py = p*sin(fi_p);
float A = R[0][0]*px + R[1][0]*py;
float B = R[0][1]*px + R[1][1]*py;
float C = R[0][2]*px + R[1][2]*py ;
A = A - bx + x;
B = B - by + y;
C = C + z;
float l = sqrt(A*A + B*B + C*C);
float L = l*l - s*s + a*a;
float M = 2*a*l;
float N = 2*a*(cos(fi_b)*(px - bx) + sin(fi_b)*(py - by));
angle0 = -(asin(L/(sqrt(M*M + N*N))) - atan(N/M));
}Функция set_pos(float p1 , float p2, float p3, float p4, float p5, float p6,int del)
В данной функции осуществляется задание угла поворота вала двигателя на манипуляторе
void set_pos( float p1 , float p2, float p3, float p4, float p5, float p6, int del)
{
dxl_wb.goalPosition(DXL_ID1,p1); dxl_wb.goalPosition(DXL_ID2,p2); dxl_wb.goalPosition(DXL_ID3,p3); dxl_wb.goalPosition(DXL_ID4,p4); dxl_wb.goalPosition(DXL_ID5,p5); dxl_wb.goalPosition(DXL_ID6,p6);
delay(del);
}Функция loop()
Теперь рассмотрим функцию loop(), которая представляет собой бесконечный цикл. Через нее можно корректировать положения робота в реальном времени. С помощью консоли пользователь может вручную вводить координаты, которые будет занимать робот.
void loop()
{
if (Serial.available() > 0 ) {
x1 = Serial.parseInt();
y_1 = Serial.parseInt();
z1 = Serial.parseInt();
alfa1 = Serial.parseInt();
beta1 = Serial.parseInt();
gama1 = Serial.parseInt();
Serial.print(«X = « );
Serial.println(x1);
Serial.print(«Y = « );
Serial.println(y_1);
Serial.print(«Z = « );
Serial.println(z1);
Serial.print(«Alfa = « ); Serial.println(alfa1);
Serial.print(«Beta = « ); Serial.println(beta1);
Serial.print(«Gamma = « ); Serial.println(gama1);
alfa1 = alfa1*3.14/180;
beta1 = beta1*3.14/180;
gama1 = gama1*3.14/180;
matrix(alfa1, beta1, gama1);
angle(0.61, 0.13, x1, y_1, z1);
angle1 = angle0;
angle(1.48, 1.95, x1, y_1, z1);
angle2 = -angle0;
angle(2.7, 2.22, x1, y_1, z1);
angle3 = angle0;
angle(3.58, 4.04, x1, y_1, z1);
angle4 = -angle0;
angle(4.8, 4.31, x1, y_1, z1);
angle5 = angle0;
angle(5.6, 6.13, x1, y_1, z1);
angle6 = -angle0;
Serial.print(«Angle #1: « ); Serial.println(angle1);
Serial.print(«Angle #2: « ); Serial.println(angle2);
Serial.print(«Angle #3: « ); Serial.println(angle3);
Serial.print(«Angle #4: « ); Serial.println(angle4);
Serial.print(«Angle #5: « ); Serial.println(angle5);
Serial.print(«Angle #6: « ); Serial.println(angle6);
set_pos(angle1, angle2, angle3, angle4, angle5, angle6, 50);
}
}Координаты вводятся в миллиметрах. Значения можно вводить через любые знаки препинания, но в правильном порядке, и разделяя их пробелом.
В результате, код управляющей программы будет выглядеть следующим образом:
cpp
#include <DynamixelWorkbench.h>
#include <math.h>
#if defined( OPENCM904 )
#define DEVICE_NAME «3» //Dynamixel on Serial3(USART3) <-OpenCM 485EXP
#elif defined( OPENCR )
#define DEVICE_NAME «» #endif
#define BAUDRATE 1000000
#define DXL_ID1 1
#define DXL_ID2 2
#define DXL_ID3 3
#define DXL_ID4 4
#define DXL_ID5 5
#define DXL_ID6 6
DynamixelWorkbench dxl_wb;
float s = 211;
float a = 35;
float b = 100;
float p = 110;
float fi_b = 0.61;
float fi_p = 0.13;
float bx, by, px, py;
float R[3][3];
float alfa1 = 0;
float beta1 = 0;
float gama1 = 0;
float x1;
float y_1;
float z1;
float angle0, angle1, angle2, angle3, angle4, angle5, angle6;
void set_pos( float p1 , float p2, float p3, float p4, float p5, float p6, int del)
{
dxl_wb.goalPosition(DXL_ID1,p1);
dxl_wb.goalPosition(DXL_ID2,p2);
dxl_wb.goalPosition(DXL_ID3,p3);
dxl_wb.goalPosition(DXL_ID4,p4);
dxl_wb.goalPosition(DXL_ID5,p5);
dxl_wb.goalPosition(DXL_ID6,p6);
delay(del);
}
void matrix(float alfa, float beta, float gama)
{
R[0][0] = cos(alfa)*cos(beta);
R[1][0] = -sin(alfa)*cos(gama) + cos(alfa)*sin(beta)*sin(gama);
R[2][0] = sin(alfa)*sin(gama) + cos(alfa)*sin(beta)*cos(gama);
R[0][1] = sin(alfa)*cos(beta);
R[1][1] = cos(alfa)*cos(gama) + sin(alfa)*sin(beta)*sin(gama);
R[2][1] = -cos(alfa)*sin(gama) + sin(alfa)*sin(beta)*cos(gama);
R[0][2] = -sin(beta);
R[1][2] = cos(beta)*sin(gama);
R[2][2] = cos(beta)*cos(gama);
}
void angle(float fi_b, float fi_p, float x, float y, float z)
{
bx = b*cos(fi_b);
by = b*sin(fi_b);
px = p*cos(fi_p);
py = p*sin(fi_p);
float A = R[0][0]*px + R[1][0]*py;
float B = R[0][1]*px + R[1][1]*py;
float C = R[0][2]*px + R[1][2]*py ;
A = A - bx + x;
B = B - by + y;
C = C + z;
float l = sqrt(A*A + B*B + C*C);
float L = l*l - s*s + a*a;
float M = 2*a*l;
float N = 2*a*(cos(fi_b)*(px - bx) + sin(fi_b)*(py - by));
angle0 = -(asin(L/(sqrt(M*M + N*N))) - atan(N/M));
}
void setup()
{
const char *log ;
Serial.begin(1000000);
uint16_t model_number1 = 1;
uint16_t model_number2 = 2;
uint16_t model_number3 = 3;
uint16_t model_number4 = 4;
uint16_t model_number5 = 5;
uint16_t model_number6 = 6;
dxl_wb.init(DEVICE_NAME, BAUDRATE, &log);
dxl_wb.ping(DXL_ID1, &model_number1, &log);
dxl_wb.ping(DXL_ID2, &model_number2, &log);
dxl_wb.ping(DXL_ID3, &model_number3, &log);
dxl_wb.ping(DXL_ID4, &model_number4, &log);
dxl_wb.ping(DXL_ID5, &model_number5, &log);
dxl_wb.ping(DXL_ID6, &model_number6, &log);
dxl_wb.jointMode(DXL_ID1, 20, 20, &log);
dxl_wb.jointMode(DXL_ID2, 20, 20, &log);
dxl_wb.jointMode(DXL_ID3, 20, 20, &log);
dxl_wb.jointMode(DXL_ID4, 20, 20, &log);
dxl_wb.jointMode(DXL_ID5, 20, 20, &log);
dxl_wb.jointMode(DXL_ID6, 20, 20, &log);
}
void loop()
{
if (Serial.available() > 0 ) {
x1 = Serial.parseInt();
y_1 = Serial.parseInt();
z1 = Serial.parseInt();
alfa1 = Serial.parseInt();
beta1 = Serial.parseInt();
gama1 = Serial.parseInt();
Serial.print(«X = « );
Serial.println(x1);
Serial.print(«Y = « );
Serial.println(y_1);
Serial.print(«Z = « );
Serial.println(z1);
Serial.print(«Alfa = « );
Serial.println(alfa1);
Serial.print(«Beta = « );
Serial.println(beta1);
Serial.print(«Gamma = « );
Serial.println(gama1);
alfa1 = alfa1*3.14/180;
beta1 = beta1*3.14/180;
gama1 = gama1*3.14/180;
matrix(alfa1, beta1, gama1);
angle(0.61, 0.13, x1, y_1, z1);
angle1 = angle0;
angle(1.48, 1.95, x1, y_1, z1);
angle2 = -angle0;
angle(2.7, 2.22, x1, y_1, z1);
angle3 = angle0;
angle(3.58, 4.04, x1, y_1, z1);
angle4 = -angle0;
angle(4.8, 4.31, x1, y_1, z1);
angle5 = angle0;
angle(5.6, 6.13, x1, y_1, z1);
angle6 = -angle0;
Serial.print(«Angle #1: « );
Serial.println(angle1);
Serial.print(«Angle #2: « );
Serial.println(angle2);
Serial.print(«Angle #3: « );
Serial.println(angle3);
Serial.print(«Angle #4: « );
Serial.println(angle4);
Serial.print(«Angle #5: « );
Serial.println(angle5);
Serial.print(«Angle #6: « );
Serial.println(angle6);
set_pos(angle1, angle2, angle3, angle4, angle5, angle6, 50);
}
}Таким образом, была решена обратная задача кинематики для платформы Стюарта, а также написана управляющая программа для реализации задачи на физической модели с микроконтроллером OpenCM.