Руководства · 16 мин чтения

Робот-манипулятор на Arduino: выбор сервоприводов, момент, PCA9685 и Bluetooth

Собираем робот-манипулятор на Arduino шаг за шагом: корпус из МДФ или 3D-печати, расчёт момента для MG996R, управление потенциометрами и джойстиком, запись и воспроизведение, код для PCA9685 и Bluetooth.

Робот-манипулятор на Arduino — это настольный робот, суставы которого приводятся в движение сервоприводами: он берёт предмет, поднимает его, поворачивается и кладёт в другое место. Основание, плечо, локоть, запястье и захват, и в одном проекте сразу есть механика, подбор моторов, управление питанием и программирование. А главное, такую руку можно собрать дома из Arduino Uno, нескольких сервоприводов и деталей из МДФ или 3D-печати и запустить у себя на столе маленькую копию заводского робота.

В этом руководстве разбираем роботизированные руки на 4 и 6 осей от начала до конца: выбор материала корпуса, расчёт момента для выбора между SG90, MG90S и MG996R, внешнее питание 5 V, управление потенциометрами и джойстиками, запись и воспроизведение движений, 16-канальный драйвер сервоприводов PCA9685, управление с телефона по Bluetooth и решения двух самых частых проблем: дрожания и перезагрузок. Если принцип работы сервопривода пока непонятен, начни со статьи Что такое сервопривод?.

SG90
SG90
MG90S
MG90S
MG996R
MG996R

Как работает робот-манипулятор и сколько осей нужно?

Представь робот-манипулятор как человеческую руку. В каждом суставе стоит сервопривод, и Arduino говорит каждому сервоприводу: «встань на этот угол». Благодаря потенциометру внутри сервопривод знает, где находится его вал, и удерживает угол даже под нагрузкой. Меняя углы суставов вместе, ты перемещаешь захват в нужную точку. Число независимых движений, которые может выполнять рука, называется степенями свободы (DOF), или просто осями.

СуставДвижениеНагрузкаРекомендуемый сервопривод
Основание (поворот)Поворачивает руку влево и вправоНичего не поднимает, но несёт всю рукуMG996R + подшипник
ПлечоПоднимает руку вперёд и назадСамый нагруженный суставMG996R или сервопривод класса 20 кг·см
ЛокотьСгибает предплечьеОт средней до высокойMG996R
Запястье (наклон)Задаёт угол захватаНизкаяMG90S
Поворот запястьяВращает захват вокруг своей осиОчень низкаяMG90S или SG90
ЗахватБерёт и отпускает предметСила сжатияSG90 или MG90S

У большинства наборов «6-осевая роботизированная рука» на рынке 6 сервоприводов; так как захват не считается осью, технически это рука с 5 степенями свободы плюс захват. Для первой руки лучше всего начать с 4-осевой конструкции (основание, плечо, локоть, захват): хватит 4 сервоприводов, 4 потенциометров и Arduino Uno, подключение остаётся простым, а проблемы с моментом редки. Суставы запястья нужны, когда захват надо держать горизонтально или брать предметы под разными углами; при переходе на 5–6 сервоприводов драйвер PCA9685 становится почти обязательным.

Список деталей

ДетальРука на 4 осиРука на 6 сервоприводовПримечание
КонтроллерArduino Uno или NanoArduino Uno, Nano или ESP32Дополнительные входы A6 и A7 у Nano позволяют подключить больше потенциометров
Сервоприводы4× SG90 или MG90S3× MG996R + 3× MG90SПроверь выбор расчётом момента ниже
Драйвер сервоприводовНе нужен16-канальный модуль PCA9685Всего 2 сигнальных провода по I2C
ПитаниеАдаптер 5 V 3 AАдаптер 5–6 V, не менее 5 AСервоприводы никогда не питают от Arduino
Конденсатор470–1000 µF, 16 V1000–2200 µF, 16 VНа шине питания сервоприводов, соблюдай полярность
Органы управления4× потенциометра 10 kΩ2× модуля джойстика или HC-05Можно использовать и то и другое
Кнопки2 (запись, воспроизведение)Хватит кнопок джойстиковС INPUT_PULLUP дополнительные резисторы не нужны
МеханикаМДФ 3 мм или детали из 3D-печатиМДФ 4 мм, 3D-печать или алюминиевые кронштейныПодшипник 608, болты M3 и самоконтрящиеся гайки

Механика: МДФ или 3D-печать?

МатериалПлюсыМинусыЛучше всего для
МДФ 3–4 мм (лазерная резка)Дёшево, легко, удобно крутить и клеитьБоится влаги, отверстия под винты со временем разбалтываютсяПервые руки в стиле MeArm, школьные проекты
Акрил 3 ммОтлично выглядит, не боится влагиХрупкий; может треснуть при затягивании винтовДемонстрационные и выставочные сборки
3D-печать (PLA / PETG)Сложные детали, гнёзда под сервоприводы точно по размеруДолгая печать; PLA ползёт от тепла и под постоянной нагрузкойСобственные конструкции; PETG для нагруженных деталей
Набор алюминиевых кронштейновОчень жёсткий, готовые детали под MG996RТяжёлый и дорогой, добавляет нагрузку на плечоРуки на 6 сервоприводов для подъёма более тяжёлых предметов
Картон, палочки для мороженогоПочти бесплатно, быстрое прототипированиеНепрочно, в суставах появляется люфтПроверка размеров, первые сборки с детьми
  • Делай руку короткой и лёгкой: Нагрузка на плечо растёт прямо пропорционально расстоянию. Если сделать руку длиной 24 см вместо 30 см, момент на плече уменьшится примерно на пятую часть.
  • Ставь тяжёлые сервоприводы ближе к основанию: Перенести сервопривод локтя вниз, к плечу, и двигать локоть через тягу (параллелограммный механизм) — хитрый приём, который используют руки вроде MeArm; плечу больше не нужно поднимать этот сервопривод.
  • Используй подшипник в основании: Не вешай вес всей руки на вал сервопривода основания. Подшипник 608 (внутренний диаметр 8 мм, наружный 22 мм, толщина 7 мм; классический подшипник от скейтборда) или поворотный подшипник для вращающейся подставки несёт нагрузку, а сервопривод только вращает руку.
  • Опирай суставы с двух сторон: Пока сервопривод держит сустав со стороны качалки, добавь ось с противоположной стороны из болта M3 и самоконтрящейся гайки. Так вал сервопривода не работает на изгиб, а шестерни служат гораздо дольше.
  • Используй металлические качалки: Надень на каждый MG996R круглую металлическую качалку на 25 зубьев (25T) и закрепи её на валу центральным винтом. Пластиковые качалки проскальзывают и срывают шлицы на сильно нагруженных суставах.
  • Добавь резину на пальцы захвата: Тонкая полоска резины на кончиках пальцев помогает захвату держать предметы без проскальзывания и с меньшей силой сжатия.

Выбор сервоприводов: SG90, MG90S или MG996R?

МодельМоментСкорость (на 60°)МассаШестерниТок при заторможенном валуРоль в роботе-манипуляторе
SG901,8 кг·см (4,8 V)0,10 с (4,8 V)9 гПластикОколо 0,6–0,75 AЗахват, лёгкое запястье, маленькая рука на 4 оси
MG90S1,8 кг·см (4,8 V), 2,2 кг·см (6 V)0,10 с (4,8 V)13,4 гМеталлОколо 0,7–1 AЗапястье, поворот запястья, захват
MG996R9,4 кг·см (4,8 V), 11 кг·см (6 V)0,17 с (4,8 V)55 гМеталл2,5 A (6 V)Основание, плечо, локоть
DS321819 кг·см (5 V), 21,5 кг·см (6,8 V)0,16 с (5 V)60 гМеталлОколо 2–3 AПлечо длинных или тяжёлых рук

Момент в таблице — это момент при заторможенном валу (stall torque): максимальное усилие, которое сервопривод выдаёт перед тем, как вал остановится, а не значение для постоянной работы. Когда сервопривод приближается к этому пределу, его ток растёт к току при заторможенном валу, он греется и начинает дрожать. Практическое правило: момент, нужный суставу, должен быть не больше половины момента сервопривода при заторможенном валу. Важно и напряжение: MG996R даёт 9,4 кг·см при 4,8 V и 11 кг·см при 6 V, то есть тот же сервопривод становится примерно на 17% сильнее только за счёт напряжения питания. Поэтому для рук на MG996R имеет смысл питание 6 V, но для SG90 и MG90S 6 V — верхний предел. Подключение и ограничения MG996R описаны в руководстве Сервопривод MG996R.

Сервопривод MG996R с металлическими шестернями
MG996R: металлические шестерни, момент 11 кг·см при 6 V и выходной шлиц на 25 зубьев; стандартный выбор для основания, плеча и локтя робота-манипулятора.

Расчёт момента: поднимет ли плечевой сервопривод эту руку?

Момент, который должен поднять сервопривод, — это сумма масс всех деталей, умноженных на их горизонтальное расстояние от оси сустава: момент (кг·см) = Σ масса (кг) × расстояние (см). Худший случай — рука, вытянутая горизонтально, потому что все расстояния максимальны; если рука смотрит прямо вверх, плечо рычага падает почти до нуля. Чтобы перевести в ньютон-метры, умножь на 0,098 (1 кг·см ≈ 0,098 Н·м). Для примера посчитаем нагрузку на плечо настольной руки: 12 см от плеча до локтя, 12 см от локтя до запястья, 6 см от запястья до кончика захвата, MG996R в локте, а захват держит предмет массой 50 г:

ДетальМассаРасстояние от оси плечаМомент
Детали верхнего звена (МДФ и болты)40 г6 см (центр масс)0,04 × 6 = 0,24 кг·см
Сервопривод локтя (MG996R)55 г12 см0,055 × 12 = 0,66 кг·см
Детали предплечья30 г18 см0,03 × 18 = 0,54 кг·см
Сервопривод запястья + захват (MG90S, SG90 и детали)50 г25 см0,05 × 25 = 1,25 кг·см
Удерживаемый предмет50 г30 см0,05 × 30 = 1,50 кг·см
Итого225 г—4,19 кг·см (≈ 0,41 Н·м)

В неподвижном положении этой руке нужно 4,19 кг·см на плече. Чтобы покрыть дополнительную нагрузку при разгоне и торможении и держать сервопривод подальше от остановки, умножь на 2: 8,4 кг·см. Значит, на плечо нужен как минимум MG996R (9,4 кг·см при 4,8 V, 11 кг·см при 6 V); 1,8–2,2 кг·см у SG90 или MG90S эту руку даже не сдвинут. Если посчитать так же для локтя, предплечье (0,03 × 6), запястье с захватом (0,05 × 13) и предмет (0,05 × 18) дают в сумме 1,73 кг·см, а с запасом 3,5 кг·см, так что в локте тоже нужен MG996R. На запястье остаются только захват и предмет, нагрузка около 0,4 кг·см, и MG90S хватает с запасом. Тот же расчёт показывает самый тяжёлый предмет, который рука может нести: увеличивай массу предмета, пересчитывай сумму и оставайся ниже половины момента сервопривода.

Есть три дешёвых способа уменьшить момент на плече: укоротить руку, перенести сервопривод локтя к плечу и двигать локоть тягой, а также добавить противовес за плечом или пружину, которая тянет руку вверх. Например, груз 400 г в 5 см позади оси плеча создаёт противомомент 0,4 × 5 = 2 кг·см и почти вдвое снижает нагрузку от руки; подшипник основания при этом несёт чуть больший вес.

Питание: почему сервоприводы не питают от Arduino?

Большинство проблем робота-манипулятора идут от питания. MG996R потребляет 0,5–0,9 A в движении и до 2,5 A при заторможенном вале; даже маленький SG90 при остановке может превысить 0,6 A. Когда Arduino Uno работает от USB, общий ток с пина 5V составляет около 500 mA, это предел USB, и часть его тратит сама плата. Когда три больших сервопривода начинают двигаться одновременно, напряжение проседает, микроконтроллер перезагружается, и рука прыгает в начальную позу. Решение — питать сервоприводы от отдельного источника 5–6 V и соединить все земли вместе; Arduino можно и дальше питать от USB или собственного адаптера. Варианты питания описаны в руководстве Питание Arduino, а оценить, сколько проработает рука от аккумулятора, поможет калькулятор времени работы от батареи.

Тип рукиОриентировочный токРекомендуемое питание
Маленькая рука на 4× SG90 или MG90S0,5–1 A в движении, 2–3 A в пикеАдаптер 5 V 3 A или модуль LM2596 (максимум 3 A, около 2 A без радиатора)
Рука на 6 сервоприводов: 3× MG996R + 3× MG90S2–3 A в движении, 8–10 A, если все заторможеныИмпульсный адаптер 5–6 V, не менее 5 A (10 A с комфортным запасом)
Переносная рукаЗависит от руки2S LiPo (7,4 V) + понижающий модуль (UBEC) на 5 A или больше, настроенный на 6 V
Чего избегать—Батарейка-«крона» (PP3) на 9 V (не выдаёт нужный ток), пин 5V у Arduino, 4 щелочные батарейки AA для руки на MG996R
Никогда не питай сервоприводы от пина 5V у Arduino и всегда соединяй минус внешнего источника с GND Arduino; без общей земли сервоприводы не могут прочитать сигнал и двигаются хаотично. Поставь электролитический конденсатор на шину питания сервоприводов прямо рядом с ними, из расчёта не менее 100 µF на сервопривод (1000–2200 µF, 16 V или больше для руки на 6 сервоприводов), длинной ножкой к плюсу; электролитический конденсатор, установленный наоборот, может вздуться и лопнуть. Шины питания макетной платы рассчитаны примерно на 1 A, поэтому на руке с MG996R веди провода питания прямо на винтовую клемму или вход V+ модуля PCA9685.

Подключение руки на 4 оси

В таблице ниже — подключение Arduino Uno и Raspberry Pi Pico для руки на 4 оси с управлением потенциометрами; код в этом руководстве использует эти пины. На Uno библиотека Servo может управлять сервоприводом с любого цифрового пина, но так как она использует Timer1, analogWrite на пинах 9 и 10 перестаёт работать, пока сервоприводы подключены (attach). Расположение пинов своей платы можно проверить в утилите распиновки.

ПодключениеArduino UnoRaspberry Pi Pico
Сигнал сервопривода основания (оранжевый провод)D3GP10
Сигнал сервопривода плечаD5GP11
Сигнал сервопривода локтяD6GP12
Сигнал сервопривода захватаD9GP13
Средний вывод потенциометров 1–3 (основание, плечо, локоть)A0, A1, A2GP26, GP27, GP28
Средний вывод потенциометра 4 (захват)A3Нет; захват управляется кнопкой на GP14
Крайние выводы потенциометров5V и GND3V3 и GND
Кнопки записи и воспроизведения (другой вывод на GND)D7 и D8—
Красный провод сервопривода (+)Плюс внешнего источника 5–6 VПлюс внешнего источника 5–6 V
Коричневый провод сервопривода (−)Минус внешнего источника + GND ArduinoМинус внешнего источника + GND Pico
Поворотный потенциометр 10 kΩ
Потенциометр 10 kΩ: крайние выводы идут на 5V и GND, средний (движок) — на аналоговый вход; при повороте вала сустав поворачивается в той же пропорции.

Калибровка до сборки: выставь сервоприводы в центр

  1. Пока качалки не надеты, загрузи код ниже: все четыре сервопривода по очереди встают на 90 градусов и ждут.
  2. Держи руку в нейтральной позе (например, верхнее звено вертикально, предплечье горизонтально, захват наполовину открыт), надень каждую качалку на ближайший к этой позе зуб шлица и закрепи центральным винтом.
  3. Установи в коде CENTER_ONLY в false, выбери сустав через TEST и медленно поворачивай потенциометр на A0.
  4. Следи за углом в мониторе порта и запиши наименьший и наибольший угол, которых сустав достигает, ни во что не упираясь и не натягивая кабели.
  5. Угол закрытия захвата выставь в точку, где пальцы только касаются друг друга; сервопривод, который продолжает сжимать сильнее, остаётся заторможенным и греется.
  6. Впиши найденные значения в массивы MIN_A и MAX_A следующих скетчей.
C++ (Arduino IDE)
1#include <Servo.h>
2
3const byte SERVO_PIN[4] = {3, 5, 6, 9};   // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
4const byte TEST = 1;                       // denenecek servo (0–3) / servo under test (0–3)
5const bool CENTER_ONLY = true;             // true: hepsi 90°'de bekler / true: all wait at 90°
6
7Servo servo[4];
8
9void setup() {
10  Serial.begin(9600);
11  for (byte i = 0; i < 4; i++) {
12    servo[i].write(90);                    // önce açıyı ver / set the angle first
13    servo[i].attach(SERVO_PIN[i]);         // sonra bağla: zıplamaz / then attach: no jump
14    delay(300);                            // akım darbelerini ayır / spread the current surges
15  }
16}
17
18void loop() {
19  if (CENTER_ONLY) return;                 // korna takarken burada kal / stay here while fitting horns
20  int angle = map(analogRead(A0), 0, 1023, 0, 180);   // A0'daki pot / pot on A0
21  servo[TEST].write(angle);
22  Serial.print(F("Servo "));
23  Serial.print(TEST);
24  Serial.print(F(" aci / angle: "));
25  Serial.println(angle);                   // sınırları buradan oku / read the limits here
26  delay(50);
27}

В этом коде есть маленькая, но важная деталь: так как servo.write(90) вызывается до attach(), сервопривод получает нужный угол уже с первым импульсом. Если поменять порядок, библиотека сначала отправит импульс по умолчанию 1500 µs (около 90°); если стартовый угол не 90, рука при включении дёрнется в середину, а затем к цели. Запуск сервоприводов по одному с паузой 300 мс к тому же распределяет бросок тока при включении.

Управление роботом-манипулятором с помощью потенциометров

Самый интуитивный способ управления — по одному потенциометру на сустав: под каким углом стоит вал потенциометра, на тот же угол встаёт и сустав, то есть это управление положением. Если встроить потенциометры в маленькую модель руки (из картона или 3D-печати, в тех же пропорциях), большая рука один в один повторяет движения модели, когда её двигаешь рукой. Однако аналоговые показания естественным образом скачут на несколько единиц, и эти скачки превращаются в дрожание сервоприводов; в коде против этого есть две меры.

C++ (Arduino IDE)
1#include <Servo.h>
2
3const byte SERVO_PIN[4] = {3, 5, 6, 9};        // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
4const byte POT_PIN[4]   = {A0, A1, A2, A3};    // her eklem için bir pot / one pot per joint
5const byte MIN_A[4] = {0, 15, 20, 40};         // kalibrasyonda bulduğun alt sınırlar / lower limits from calibration
6const byte MAX_A[4] = {180, 165, 160, 110};    // üst sınırlar / upper limits
7
8Servo servo[4];
9float smooth[4];                               // süzülmüş pot değeri / filtered pot value
10int lastAngle[4];                              // son yazılan açı / last written angle
11
12void setup() {
13  for (byte i = 0; i < 4; i++) {
14    smooth[i] = analogRead(POT_PIN[i]);
15    lastAngle[i] = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
16    servo[i].write(lastAngle[i]);              // önce açı, sonra attach / angle first, then attach
17    servo[i].attach(SERVO_PIN[i]);
18    delay(300);                                // servolar sırayla uyansın / wake the servos one by one
19  }
20}
21
22void loop() {
23  for (byte i = 0; i < 4; i++) {
24    smooth[i] += (analogRead(POT_PIN[i]) - smooth[i]) * 0.2;   // yumuşatma filtresi / low-pass filter
25    int angle = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
26    if (abs(angle - lastAngle[i]) >= 2) {      // ölü bant: küçük gürültüyü yok say / deadband: ignore small noise
27      servo[i].write(angle);
28      lastAngle[i] = angle;
29    }
30  }
31  delay(15);
32}

Массив smooth — простой фильтр нижних частот: в каждом цикле добавляется только 20% нового показания, поэтому резкие скачки сглаживаются, а рука всё равно реагирует быстро. Если заменить 0.2 на 0.1, движение станет плавнее, но с большей задержкой. Вторая мера — мёртвая зона (deadband): пока новый угол отличается от последнего записанного меньше чем на 2 градуса, сервоприводу ничего не отправляется, и дрожание неподвижной руки прекращается. Так как map() переводит диапазон потенциометра прямо в безопасные пределы каждого сустава, сустав никогда не упрётся в механический упор, даже если повернуть потенциометр до конца.

Запись и воспроизведение движений

По-настоящему роботом руку делает то, что она сама повторяет показанное ей движение. Метод простой: потенциометрами выводишь руку в позу и нажимаешь кнопку записи, а Arduino запоминает углы всех четырёх суставов. Когда записано несколько поз, нажми воспроизведение, и рука повторит их по порядку, плавно переходя от одной к другой. Позы записываются в EEPROM Arduino Uno объёмом 1024 байта, поэтому сохраняются после отключения питания; каждая поза занимает 4 байта, так что запись из 100 поз занимает всего 401 байт.

C++ (Arduino IDE)
1#include <Servo.h>
2#include <EEPROM.h>
3
4const byte SERVO_PIN[4] = {3, 5, 6, 9};        // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
5const byte POT_PIN[4]   = {A0, A1, A2, A3};
6const byte MIN_A[4] = {0, 15, 20, 40};         // güvenli sınırlar / safe limits
7const byte MAX_A[4] = {180, 165, 160, 110};
8const byte BTN_REC = 7, BTN_PLAY = 8, LED = 13; // butonların diğer ucu GND'de / buttons go to GND
9const byte MAX_STEPS = 100;                    // 100 adım x 4 bayt = 400 bayt / 100 steps x 4 bytes
10
11Servo servo[4];
12float smooth[4];                               // süzülmüş pot değeri / filtered pot value
13byte pos[4];                                   // servoların şu anki açısı / current servo angles
14byte stepCount;                                // kayıtlı adım sayısı / number of saved steps
15
16bool pressed(byte pin) {                       // basılıyken true / true while pressed
17  return digitalRead(pin) == LOW;
18}
19
20byte potAngle(byte i) {
21  smooth[i] += (analogRead(POT_PIN[i]) - smooth[i]) * 0.2;   // yumuşatma / smoothing
22  return map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
23}
24
25void blink(byte n) {
26  for (byte k = 0; k < n; k++) {
27    digitalWrite(LED, HIGH); delay(80);
28    digitalWrite(LED, LOW);  delay(80);
29  }
30}
31
32// Tüm eklemleri aynı anda hedefe götürür / moves all joints to the target together
33void moveTo(const byte target[4], byte msPerDegree) {
34  byte start[4];
35  int steps = 0;
36  for (byte i = 0; i < 4; i++) {
37    start[i] = pos[i];
38    int d = abs((int)target[i] - (int)start[i]);
39    if (d > steps) steps = d;                  // en uzun hareket / longest move
40  }
41  for (int s = 1; s <= steps; s++) {
42    for (byte i = 0; i < 4; i++) {
43      pos[i] = start[i] + (long)((int)target[i] - (int)start[i]) * s / steps;   // ara açı / in-between angle
44      servo[i].write(pos[i]);
45    }
46    delay(msPerDegree);
47  }
48}
49
50void saveStep() {
51  if (stepCount >= MAX_STEPS) { blink(3); return; }           // hafıza dolu / memory full
52  for (byte i = 0; i < 4; i++) EEPROM.update(1 + stepCount * 4 + i, pos[i]);
53  stepCount++;
54  EEPROM.update(0, stepCount);                 // 0. bayt adım sayısını tutar / byte 0 holds the step count
55  blink(1);
56}
57
58void playBack() {
59  byte target[4];
60  for (byte s = 0; s < stepCount; s++) {
61    for (byte i = 0; i < 4; i++) target[i] = EEPROM.read(1 + s * 4 + i);
62    moveTo(target, 12);                        // derece başına 12 ms / 12 ms per degree
63    delay(400);                                // her pozda kısa bekleme / short pause at each pose
64    if (pressed(BTN_PLAY)) break;              // oynatırken basınca durur / press again to stop
65  }
66}
67
68void syncToPots() {                            // potların konumuna yumuşakça dön / ease back to the pots
69  byte target[4];
70  for (byte i = 0; i < 4; i++) {
71    smooth[i] = analogRead(POT_PIN[i]);
72    target[i] = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
73  }
74  moveTo(target, 12);
75}
76
77void setup() {
78  pinMode(BTN_REC, INPUT_PULLUP);
79  pinMode(BTN_PLAY, INPUT_PULLUP);
80  pinMode(LED, OUTPUT);
81  stepCount = EEPROM.read(0);
82  if (stepCount > MAX_STEPS) stepCount = 0;    // boş EEPROM 255 okur / a blank EEPROM reads 255
83  for (byte i = 0; i < 4; i++) {
84    smooth[i] = analogRead(POT_PIN[i]);
85    pos[i] = potAngle(i);
86    servo[i].write(pos[i]);                    // önce açı, sonra attach / angle first, then attach
87    servo[i].attach(SERVO_PIN[i]);
88    delay(300);
89  }
90}
91
92void loop() {
93  // Kayıt: kısa bas = pozu kaydet, 2 sn basılı tut = hepsini sil / Record: short press = save pose, hold 2 s = erase all
94  if (pressed(BTN_REC)) {
95    unsigned long t0 = millis();
96    while (pressed(BTN_REC) && millis() - t0 < 2000) {}
97    if (millis() - t0 >= 2000) {
98      stepCount = 0;
99      EEPROM.update(0, 0);
100      blink(5);
101    } else {
102      saveStep();
103    }
104    while (pressed(BTN_REC)) {}                // bırakılmasını bekle / wait for release
105    delay(50);                                 // buton arkı / debounce
106  }
107
108  // Oynat: kaydedilen pozları sırayla tekrarla / play: repeat the saved poses in order
109  if (pressed(BTN_PLAY) && stepCount > 0) {
110    while (pressed(BTN_PLAY)) {}
111    delay(50);
112    digitalWrite(LED, HIGH);
113    playBack();
114    while (pressed(BTN_PLAY)) {}
115    syncToPots();
116    digitalWrite(LED, LOW);
117  }
118
119  // Normal mod: kolu potlarla sür / normal mode: drive the arm with the pots
120  for (byte i = 0; i < 4; i++) {
121    byte a = potAngle(i);
122    if (abs((int)a - (int)pos[i]) >= 2) {
123      pos[i] = a;
124      servo[i].write(a);
125    }
126  }
127  delay(15);
128}
ДействиеЧто делатьЧто делает рука
Сохранить позуКороткое нажатие кнопки записи (D7)Светодиод мигает один раз, поза записывается в EEPROM
Стереть записиУдерживать кнопку записи 2 секундыСветодиод мигает пять раз, счётчик шагов сбрасывается
ВоспроизвестиНажать и отпустить кнопку воспроизведения (D8)Светодиод загорается, позы повторяются по порядку с плавными переходами
Остановить воспроизведениеУдерживать кнопку воспроизведения во время повтораРука завершает текущую позу и останавливается
Вернуться в обычный режимНичего не делать после окончания воспроизведенияРука медленно возвращается туда, куда указывают потенциометры, светодиод гаснет

Сердце скетча — функция moveTo(): она находит сустав с самым длинным путём, делит движение на столько же шагов и на каждом шаге пропорционально продвигает все суставы. Так все суставы приходят к цели одновременно, и рука движется плавно, а не как робот, дёргающий по одному суставу. При 12 мс на градус поворот на 90 градусов занимает около 1,1 с. Для более быстрого воспроизведения это значение можно уменьшить, но даже без нагрузки MG996R поворачивается на 60 градусов за 0,17 с, примерно 3 мс на градус; если опуститься ниже, сервоприводы не успевают за командами, и движение становится рывками.

Каждую ячейку EEPROM можно перезаписать около 100 000 раз. Так как код использует EEPROM.update(), он записывает только ячейки, значение которых изменилось, поэтому на практике износить память не получится. Для более длинных последовательностей движений записи можно хранить на SD-карте или использовать EEPROM на 4 КБ у Arduino Mega.

Драйвер сервоприводов PCA9685: 6 сервоприводов и сигнал без дрожания

Когда сервоприводов становится 5–6 или добавляются модули вроде Bluetooth, модуль PCA9685 сильно упрощает жизнь. Эта плата — 16-канальный 12-битный генератор ШИМ; она общается с Arduino всего по двум проводам через I2C и формирует импульсы для сервоприводов от собственного тактового генератора, независимо от процессора. Адрес по умолчанию 0x40; меняя адрес паяными перемычками A0–A5, можно соединить в цепочку до 62 плат, то есть 992 сервопривода. При 50 Hz период 20 мс делится на 4096 шагов, так что один шаг — около 4,9 µs; диапазон 544–2400 µs занимает около 380 шагов, а это разрешение примерно в полградуса.

Пин PCA9685Arduino UnoESP32Raspberry Pi Pico
GNDGNDGNDGND
VCC (только питание микросхемы)5V3V33V3
SDAA4GPIO 21GP4
SCLA5GPIO 22GP5
Клемма V+ (питание сервоприводов)Внешний источник 5–6 VВнешний источник 5–6 VВнешний источник 5–6 V
OEНе подключатьНе подключатьНе подключать
Разъёмы сервоприводов 0–5Основание, плечо, локоть, запястье, поворот запястья, захватВ том же порядкеВ том же порядке
У PCA9685 два отдельных питания: VCC питает только микросхему, а сервоприводы получают питание через вход V+ на винтовой клемме. Если на V+ нет питания, плата видна на шине I2C, но сервоприводы не двигаются. На Arduino Uno пины A4 и A5 заняты под I2C, поэтому с подключённым PCA9685 в качестве аналоговых входов остаются только A0–A3; если нужно больше потенциометров, бери Arduino Nano с дополнительными входами A6 и A7.

Управление роботом-манипулятором с помощью джойстиков

Джойстик работает не так, как потенциометр: если его отпустить, пружина возвращает его в центр. Если привязать угол напрямую к положению джойстика, сустав в момент отпускания прыгнет на 90 градусов. Правильный подход — управление скоростью: чем сильнее отклонён джойстик, тем быстрее поворачивается сустав, а если отпустить джойстик, сустав остаётся на месте. В коде ниже два джойстика управляют четырьмя суставами (левый — основанием и плечом, правый — локтем и запястьем); кнопка левого джойстика открывает и закрывает захват, а кнопка правого отправляет руку в исходную позу. Подключи выходы VRx и VRy левого джойстика к A0 и A1, выходы VRy и VRx правого джойстика к A2 и A3, а пины SW к D2 и D4; джойстики можно питать от пина 5V у Arduino. Для компиляции кода установи Adafruit PWM Servo Driver Library через «Менеджер библиотек» (Library Manager) и согласись на установку зависимости Adafruit BusIO.

Двухосевой аналоговый модуль джойстика
Модуль джойстика: в покое выходы VRx и VRy показывают около 512, а кнопка SW при нажатии замыкает на GND. Два модуля могут управлять четырьмя суставами робота-манипулятора.
C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_PWMServoDriver.h>
3
4Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);   // varsayılan adres / default address
5
6// Kanal: 0 taban, 1 omuz, 2 dirsek, 3 bilek, 4 bilek dönüşü, 5 kıskaç / Channel: 0 base, 1 shoulder, 2 elbow, 3 wrist, 4 wrist roll, 5 gripper
7const byte N = 6;
8const float MIN_A[N] = {0, 15, 20, 10, 0, 40};        // güvenli sınırlar / safe limits
9const float MAX_A[N] = {180, 165, 160, 170, 180, 110};
10const float HOME[N]  = {90, 90, 90, 90, 90, 40};      // başlangıç pozu, kıskaç açık / home pose, gripper open
11const byte JOY[4] = {A0, A1, A2, A3};                  // J1 X, J1 Y, J2 Y, J2 X → kanal / channel 0, 1, 2, 3
12const int8_t DIR[4] = {1, -1, 1, 1};                   // ters giden ekseni -1 yap / set -1 to invert an axis
13const byte BTN_GRIP = 2, BTN_HOME = 4;                 // joystick SW pinleri / joystick SW pins
14const float MAX_SPEED = 90;                            // en yüksek hız, derece/sn / top speed, degrees/s
15const float DT = 0.02;                                 // 20 ms döngü / 20 ms loop
16const int DEADZONE = 60;                               // ortadaki ölü bölge / dead zone around center
17
18float target[N], current[N];
19bool gripClosed = false;
20
21void writeServo(byte ch, float a) {
22  // Servo.h ile aynı 544–2400 µs aralığı / same 544–2400 µs range as Servo.h
23  pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0));
24}
25
26bool newPress(byte pin, byte k) {                      // her basışta bir kez true / true once per press
27  static bool prev[2] = {HIGH, HIGH};
28  bool now = digitalRead(pin);
29  bool edge = (prev[k] == HIGH && now == LOW);
30  prev[k] = now;
31  return edge;
32}
33
34void setup() {
35  pinMode(BTN_GRIP, INPUT_PULLUP);
36  pinMode(BTN_HOME, INPUT_PULLUP);
37  pca.begin();
38  pca.setOscillatorFrequency(27000000);                // çoğu modülde ~27 MHz / ~27 MHz on most boards
39  pca.setPWMFreq(50);                                  // servolar için 50 Hz / 50 Hz for servos
40  Wire.setClock(400000);                               // hızlı I2C / fast I2C
41  for (byte i = 0; i < N; i++) {
42    target[i] = current[i] = HOME[i];
43    writeServo(i, HOME[i]);
44    delay(300);                                        // servolar sırayla uyansın / wake servos one by one
45  }
46}
47
48void loop() {
49  static unsigned long last = 0;
50  if (millis() - last < 20) return;                    // 50 Hz güncelleme / 50 Hz update
51  last = millis();
52
53  // Joystick hızı belirler, konumu değil / the joystick sets speed, not position
54  for (byte i = 0; i < 4; i++) {
55    int v = analogRead(JOY[i]) - 512;
56    if (abs(v) > DEADZONE) {
57      target[i] += DIR[i] * (v / 512.0) * MAX_SPEED * DT;
58      target[i] = constrain(target[i], MIN_A[i], MAX_A[i]);
59    }
60  }
61  if (newPress(BTN_GRIP, 0)) {                         // kıskaç aç/kapa / toggle gripper
62    gripClosed = !gripClosed;
63    target[5] = gripClosed ? MAX_A[5] : MIN_A[5];
64  }
65  if (newPress(BTN_HOME, 1)) {                         // başlangıç pozuna dön / go to home pose
66    for (byte i = 0; i < N; i++) target[i] = HOME[i];
67    gripClosed = false;
68  }
69
70  // Her eklem hedefe hız sınırıyla gider / each joint follows its target with a speed limit
71  for (byte i = 0; i < N; i++) {
72    float d = constrain(target[i] - current[i], -MAX_SPEED * DT, MAX_SPEED * DT);
73    if (d != 0) {
74      current[i] += d;
75      writeServo(i, current[i]);
76    }
77  }
78}

DEADZONE не даёт небольшим отклонениям вроде 490–530 в центре заставлять руку самопроизвольно ползти. MAX_SPEED задаёт скорость в градусах в секунду при полностью отклонённом джойстике; 90 градусов в секунду — безопасное значение для начала. Если сустав движется в сторону, противоположную джойстику, не перекидывай провода, просто поставь соответствующее значение в массиве DIR равным -1. Код хранит для каждого сустава цель (target) и фактическое положение (current); так как фактическое положение приближается к цели с ограничением скорости, рука плавно возвращается даже при нажатии кнопки исходной позы. Поворот запястья в этом скетче зафиксирован на 90 градусах; управлять им можно командой R из раздела про Bluetooth. Эта техника неблокирующей работы со временем подробно разобрана в статье millis() и delay().

Управление с телефона по Bluetooth (HC-05)

Модуль HC-05 создаёт беспроводной последовательный канал между телефоном и Arduino. Его пин TXD идёт на D10 Arduino, а пин RXD подключается к D11 через делитель напряжения: D11 → 1 kΩ → RXD → 2 kΩ → GND. Делитель понижает сигнал Arduino 5 V примерно до 3,3 V, которые ожидает HC-05. Скорость модуля по умолчанию — 9600 бод, PIN-код сопряжения обычно 1234. Телефон отправляет простые текстовые команды: буква выбирает сустав, число после неё задаёт угол, а перевод строки или точка с запятой завершает команду.

Последовательный Bluetooth-модуль HC-05
HC-05: последовательный модуль, который соединяется с телефоном по классическому Bluetooth; на VCC подаётся 5 V, но вход RXD работает с логикой 3,3 V.
КомандаЗначениеПример
B + уголОснованиеB90
S + уголПлечоS120
E + уголЛокотьE45
W + уголЗапястье (наклон)W100
R + уголПоворот запястьяR0
G + уголЗахватG110 (закрыть), G40 (открыть)
HВозврат в исходную позуH
K / P / X / CСохранить позу / воспроизводить по кругу / стоп / очистить позыK
C++ (Arduino IDE)
1#include <Wire.h>
2#include <SoftwareSerial.h>
3#include <Adafruit_PWMServoDriver.h>
4
5SoftwareSerial bt(10, 11);                 // D10 ← HC-05 TXD, D11 → bölücü / divider → HC-05 RXD
6Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);
7
8const byte N = 6;
9const char NAME[N] = {'B', 'S', 'E', 'W', 'R', 'G'};   // taban, omuz, dirsek, bilek, dönüş, kıskaç / base, shoulder, elbow, wrist, roll, gripper
10const byte MIN_A[N] = {0, 15, 20, 10, 0, 40};          // güvenli sınırlar / safe limits
11const byte MAX_A[N] = {180, 165, 160, 170, 180, 110};
12const byte HOME[N]  = {90, 90, 90, 90, 90, 40};
13const byte MAX_POSES = 30;
14const float SPEED = 1.5;                   // 20 ms'de en fazla 1,5° (75°/sn) / max 1.5° per 20 ms (75°/s)
15
16float current[N];
17byte target[N];
18byte poses[MAX_POSES][N];                  // kaydedilen pozlar (RAM) / recorded poses (RAM)
19byte poseCount = 0, playIndex = 0;
20bool playing = false;
21char buf[12];
22byte len = 0;
23
24void writeServo(byte ch, float a) {
25  pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0));   // 544–2400 µs
26}
27
28void handle(char *cmd) {                   // ör. / e.g. "S120", "H", "K", "P"
29  char c = toupper(cmd[0]);
30  if (c == 'H') {                          // başlangıç pozu / home pose
31    for (byte i = 0; i < N; i++) target[i] = HOME[i];
32    playing = false;
33  } else if (c == 'K') {                   // pozu kaydet / save pose
34    if (poseCount < MAX_POSES) {
35      for (byte i = 0; i < N; i++) poses[poseCount][i] = target[i];
36      poseCount++;
37    }
38    bt.print(F("Poz / pose: "));
39    bt.println(poseCount);
40  } else if (c == 'P') {                   // döngüyle oynat / play in a loop
41    if (poseCount > 0) { playing = true; playIndex = 0; }
42  } else if (c == 'X') {                   // durdur / stop
43    playing = false;
44  } else if (c == 'C') {                   // kayıtları sil / clear poses
45    poseCount = 0;
46    playing = false;
47  } else {
48    for (byte i = 0; i < N; i++) {
49      if (c == NAME[i]) {
50        target[i] = constrain(atoi(cmd + 1), MIN_A[i], MAX_A[i]);
51        playing = false;                   // elle komut oynatmayı keser / a manual command stops playback
52      }
53    }
54  }
55}
56
57bool arrived() {
58  for (byte i = 0; i < N; i++)
59    if (fabs(target[i] - current[i]) > 0.5) return false;
60  return true;
61}
62
63void setup() {
64  bt.begin(9600);                          // HC-05 varsayılan hızı / HC-05 default baud
65  pca.begin();
66  pca.setOscillatorFrequency(27000000);
67  pca.setPWMFreq(50);
68  for (byte i = 0; i < N; i++) {
69    target[i] = HOME[i];
70    current[i] = HOME[i];
71    writeServo(i, HOME[i]);
72    delay(300);                            // servolar sırayla uyansın / wake servos one by one
73  }
74}
75
76void loop() {
77  // Komutları satır satır topla / collect commands line by line
78  while (bt.available()) {
79    char ch = bt.read();
80    if (ch == '\n' || ch == '\r' || ch == ';') {
81      if (len > 0) { buf[len] = '\0'; handle(buf); len = 0; }
82    } else if (len < sizeof(buf) - 1) {
83      buf[len++] = ch;
84    }
85  }
86
87  static unsigned long last = 0, arrivedAt = 0;
88  if (millis() - last < 20) return;        // 50 Hz güncelleme / 50 Hz update
89  last = millis();
90
91  if (playing) {
92    if (!arrived()) arrivedAt = millis();  // hareket sürüyor / still moving
93    else if (millis() - arrivedAt > 300) { // her pozda 300 ms bekle / hold each pose 300 ms
94      for (byte i = 0; i < N; i++) target[i] = poses[playIndex][i];
95      playIndex = (playIndex + 1) % poseCount;   // sona gelince başa dön / wrap around
96      arrivedAt = millis();
97    }
98  }
99
100  for (byte i = 0; i < N; i++) {
101    float d = constrain(target[i] - current[i], -SPEED, SPEED);   // hız sınırı / speed limit
102    if (d != 0) {
103      current[i] += d;
104      writeServo(i, current[i]);
105    }
106  }
107}

В этом скетче сервоприводы подключены к PCA9685, а не напрямую к Arduino, и это сделано намеренно. SoftwareSerial отключает прерывания примерно на 1 мс при приёме каждого байта; если бы сервоприводы управлялись с пинов Arduino через библиотеку Servo, их импульсы в это время растягивались бы, и рука дёргалась бы при каждой команде. Так как PCA9685 формирует импульсы сам, сигнал остаётся абсолютно стабильным, как бы ни был загружен канал Bluetooth. Запись и воспроизведение теперь тоже выполняются с телефона: выведи руку в позу ползунками и отправь K; после нескольких поз команда P заставляет руку повторять их по кругу, а X её останавливает. В этой версии позы хранятся в ОЗУ и теряются при отключении питания платы; чтобы они сохранялись, добавь метод с EEPROM из предыдущего скетча.

На телефоне достаточно готового приложения: установи на Android терминал Bluetooth Serial или приложение-контроллер Bluetooth с ползунками и задай каждому ползунку префикс, например «S», и символ конца строки. Если хочется собрать собственный интерфейс с кнопками и ползунками, руководство Bluetooth-приложение в MIT App Inventor проведёт по всем шагам. HC-05 использует классический Bluetooth, поэтому не работает с iPhone; для iPhone нужно решение на Bluetooth Low Energy, например ESP32 BLE.

Беспроводной робот-манипулятор на ESP32

У ESP32 есть встроенный классический Bluetooth, поэтому HC-05 не нужен: с библиотекой BluetoothSerial плата появляется на телефоне как «BlueGrays-RobotKol» и понимает те же команды. Подключи PCA9685 к GPIO 21 (SDA) и GPIO 22 (SCL) и питай его VCC от 3V3; тогда сигналы сервоприводов выходят с уровнем 3,3 V, который подавляющее большинство сервоприводов читает без проблем. BluetoothSerial работает только на классическом ESP32 (ESP32-WROOM-32, DevKit); ESP32-S2, S3 и C3 классический Bluetooth не поддерживают.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <BluetoothSerial.h>
3#include <Adafruit_PWMServoDriver.h>
4
5BluetoothSerial bt;                        // klasik Bluetooth SPP / classic Bluetooth SPP
6Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);
7
8const int N = 6;
9const char NAME[N] = {'B', 'S', 'E', 'W', 'R', 'G'};   // taban, omuz, dirsek, bilek, dönüş, kıskaç / base, shoulder, elbow, wrist, roll, gripper
10const int MIN_A[N] = {0, 15, 20, 10, 0, 40};
11const int MAX_A[N] = {180, 165, 160, 170, 180, 110};
12const int HOME[N]  = {90, 90, 90, 90, 90, 40};
13float current[N], target[N];
14String line;
15
16void writeServo(int ch, float a) {
17  pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0));   // 544–2400 µs
18}
19
20void handle(String cmd) {                  // ör. / e.g. "E45", "H"
21  cmd.trim();
22  if (cmd.length() == 0) return;
23  char c = toupper(cmd[0]);
24  if (c == 'H') {
25    for (int i = 0; i < N; i++) target[i] = HOME[i];
26    return;
27  }
28  int value = cmd.substring(1).toInt();
29  for (int i = 0; i < N; i++)
30    if (c == NAME[i]) target[i] = constrain(value, MIN_A[i], MAX_A[i]);
31}
32
33void setup() {
34  Serial.begin(115200);
35  bt.begin("BlueGrays-RobotKol");          // telefonda görünen ad / name shown on the phone
36  Wire.begin(21, 22);                      // SDA = GPIO 21, SCL = GPIO 22
37  pca.begin();
38  pca.setOscillatorFrequency(27000000);
39  pca.setPWMFreq(50);
40  for (int i = 0; i < N; i++) {
41    current[i] = target[i] = HOME[i];
42    writeServo(i, HOME[i]);
43    delay(300);
44  }
45  Serial.println("Bluetooth hazir / ready");
46}
47
48void loop() {
49  while (bt.available()) {
50    char ch = bt.read();
51    if (ch == '\n' || ch == ';') { handle(line); line = ""; }
52    else if (line.length() < 12) line += ch;
53  }
54
55  static unsigned long last = 0;
56  if (millis() - last < 20) return;        // 50 Hz güncelleme / 50 Hz update
57  last = millis();
58  for (int i = 0; i < N; i++) {
59    float d = constrain(target[i] - current[i], -1.5f, 1.5f);   // yumuşak hareket / smooth motion
60    if (d != 0) {
61      current[i] += d;
62      writeServo(i, current[i]);
63    }
64  }
65}

Робот-манипулятор на Raspberry Pi Pico (MicroPython)

Для управления сервоприводами на Raspberry Pi Pico не нужна никакая дополнительная библиотека: установи ШИМ на 50 Hz и задавай ширину импульса в наносекундах через duty_ns(). С 16 каналами ШИМ Pico может напрямую управлять 6 сервоприводами; пары пинов на одном слайсе (slice) делят частоту, но так как все сервоприводы работают на 50 Hz, это не проблема. Ограничение — на аналоговой стороне: у Pico всего три доступных пина АЦП (GP26, GP27, GP28). Поэтому в коде ниже три сустава управляются потенциометрами, а захват открывается и закрывается кнопкой на GP14. Так как Pico работает от 3,3 V, подключай потенциометры к 3V3; сервоприводы, как и раньше, питай от внешнего источника 5 V.

MicroPython
1from machine import Pin, PWM, ADC
2import time
3
4# Servo pinleri: taban, omuz, dirsek, kıskaç / servo pins: base, shoulder, elbow, gripper
5servos = [PWM(Pin(p)) for p in (10, 11, 12, 13)]
6for s in servos:
7    s.freq(50)                                  # 50 Hz servo sinyali / 50 Hz servo signal
8
9pots = [ADC(26), ADC(27), ADC(28)]              # Pico'da 3 ADC pini var / the Pico has 3 ADC pins
10button = Pin(14, Pin.IN, Pin.PULL_UP)           # kıskaç butonu GND'ye / gripper button to GND
11
12MIN_A = (0, 15, 20, 40)                         # güvenli sınırlar / safe limits
13MAX_A = (180, 165, 160, 110)
14
15def write(i, angle):
16    angle = max(MIN_A[i], min(MAX_A[i], angle))
17    servos[i].duty_ns(544000 + int(angle * 10311))   # 544–2400 µs aralığı / 544–2400 µs range
18
19smooth = [p.read_u16() for p in pots]
20grip_closed = False
21last_btn = 1
22write(3, MIN_A[3])                              # kıskaç açık başlar / gripper starts open
23
24while True:
25    for i in range(3):
26        smooth[i] += (pots[i].read_u16() - smooth[i]) * 0.2   # yumuşatma / smoothing
27        write(i, MIN_A[i] + (MAX_A[i] - MIN_A[i]) * smooth[i] / 65535)
28    b = button.value()
29    if b == 0 and last_btn == 1:                # yeni basış / new press
30        grip_closed = not grip_closed
31        write(3, MAX_A[3] if grip_closed else MIN_A[3])
32    last_btn = b
33    time.sleep_ms(20)                           # 50 Hz döngü / 50 Hz loop

Дальше: обратная кинематика

До сих пор угол каждого сустава задавался по отдельности; это называется прямой кинематикой. Обратная кинематика делает наоборот: задаёшь точку (x, y), куда должен прийти захват, а код вычисляет нужные углы суставов. Для двухзвенной руки с длиной плечо–локоть L1 и локоть–запястье L2 сначала вычисли D = (x² + y² − L1² − L2²) / (2·L1·L2). Угол локтя θ2 = acos(D), угол плеча θ1 = atan2(y, x) + atan2(L2·sin θ2, L1 + L2·cos θ2) (решение «локоть вверх»). Например, при L1 = L2 = 12 см и точке на 15 см впереди и на 5 см выше плеча получается D ≈ −0,13, θ2 ≈ 97,6° и θ1 ≈ 67,2°: верхнее звено направлено на 67 градусов выше горизонтали, а предплечье согнуто вниз на 97,6 градуса относительно него. Если модуль D больше 1, точка вне досягаемости руки. Угол основания вычисляется отдельно по положению цели при взгляде сверху. При переводе результатов в углы сервоприводов учитывай направление установки и центральную точку каждого сервопривода.

Дрожание и перезагрузки: частые ошибки и решения

СимптомВероятная причинаРешение
Сервоприводы дрожат и жужжат, когда рука неподвижнаШум в показаниях потенциометров или слабое питаниеДобавь сглаживающий фильтр и мёртвую зону 2°, используй внешнее питание и конденсатор 1000 µF
Arduino перезагружается при движении руки, HC-05 теряет связьПусковой ток сервоприводов просаживает напряжениеПитай сервоприводы от отдельного источника 5–6 V, соедини земли, при включении запускай сервоприводы по одному
Сервоприводы дёргаются при каждой команде по BluetoothПрерывания SoftwareSerial искажают импульсы библиотеки ServoПеренеси сервоприводы на PCA9685 или используй встроенный Bluetooth ESP32
При включении рука резко дёргается куда-тоСервоприводы не помнят последнее положение и по первой команде двигаются на полной скоростиПеред выключением паркуй руку в исходной позе, вызывай write() до attach()
Плечо не может поднять руку, или она медленно проседаетНе хватает моментаСделай расчёт момента; используй MG996R, более короткую руку, противовес или пружину
Сустав останавливается в крайнем положении и греетсяСервопривод упирается в механический упорВнеси в код пределы MIN_A и MAX_A из калибровки
Сервопривод захвата греется, когда держит предметЗахват продолжает пытаться закрыться, и сервопривод остаётся заторможеннымВыставь угол закрытия в точку, где захват только касается предмета, добавь резину на пальцы
PCA9685 подключён, но ни один сервопривод не двигаетсяНет питания на клемме V+ или нет общей землиПодай 5–6 V на V+ и проверь I2C-адрес (0x40)
Сустав проскальзывает под нагрузкойУ пластиковой качалки сорваны шлицы или нет центрального винтаИспользуй металлическую качалку и закрепи её на валу центральным винтом
Быстрый способ понять, в чём проблема, в питании или в коде: запусти скетч калибровки (CENTER_ONLY = false) без подключённых сервоприводов и следи за углами в мониторе порта, поворачивая потенциометр. Если значения правильные, проблема в питании; измерь мультиметром напряжение на шине сервоприводов во время движения руки, и если оно падает ниже 4,5 V, источник питания или проводка не справляются. Все причины перезагрузок разобраны в статье Почему Arduino постоянно перезагружается?.

Идеи проектов с роботом-манипулятором

  • Сортировщик по цвету: Определяй цвет кубиков датчиком цвета TCS3200, а рука пусть раскладывает каждый цвет в свою коробку.
  • Управление жестами: С MPU6050 на перчатке робот-манипулятор наклоняется так же, как твоё запястье.
  • Беспроводной пульт: Подключи два джойстика к отдельной Arduino и передавай команды через nRF24L01; получится пульт, которому не нужен телефон.
  • Управление из браузера: Сделай ползунки на веб-странице с ESP32 и управляй рукой с любого устройства в той же сети Wi-Fi; ESP32 WebSocket сводит задержку к минимуму.
  • Захват с конвейера: Собери маленький ленточный конвейер на шаговом двигателе; когда TCRT5000 замечает предмет, рука снимает его с ленты.
  • Соревнования и школьный проект: Рука с записью и воспроизведением — сильный выбор среди идей проектов для научной выставки: в ней видны механика, электроника и программирование вместе.

Робот-манипулятор на Arduino — проект, который сразу учит механике, расчёту момента, управлению питанием и программированию плавных движений, и он растёт вместе с вложенными усилиями. Начни с руки на 4 сервопривода с управлением потенциометрами, добавь запись и воспроизведение, затем переходи на 6 сервоприводов с PCA9685 и Bluetooth. В приложении BlueGrays есть проекты вроде управления сервоприводом потенциометром и джойстиком, управления с телефона по Bluetooth и хореографии на 32 сервопривода, со схемами подключения и пошаговым кодом, чтобы сначала опробовать каждый сустав руки по отдельности.

Частые вопросы

Какой сервопривод выбрать для робота-манипулятора на Arduino?

Для нагруженных суставов, то есть основания, плеча и локтя, хорошо подходит MG996R с металлическими шестернями и моментом 9–11 кг·см. Для лёгких суставов, таких как запястье и захват, хватит MG90S или SG90. Маленькие руки только на SG90 работают, если они короткие и лёгкие, но поднимают лишь предметы в несколько десятков граммов.

Какое питание нужно роботу-манипулятору?

Для маленькой руки на 4 сервоприводах SG90 или MG90S хватит адаптера 5 V 3 A. Для руки на 6 сервоприводов с 3 MG996R и 3 маленькими сервоприводами используй импульсный адаптер 5–6 V не менее чем на 5 A, а для комфортного запаса — на 10 A. Не забудь соединить минус адаптера с GND Arduino.

Сколькими сервоприводами может управлять Arduino?

Библиотека Servo управляет до 12 сервоприводами на Arduino Uno и до 48 на Mega. Но реальный предел — ток: все сервоприводы нужно питать от внешнего источника. Модуль PCA9685 управляет 16 сервоприводами всего через два пина I2C, а в цепочку можно соединить до 62 модулей.

Почему дрожат сервоприводы робота-манипулятора?

Самые частые причины — слабое питание, отсутствие общей земли и шум в показаниях потенциометров. Используй внешний источник с конденсатором 1000 µF на шине сервоприводов и добавь в код сглаживающий фильтр и мёртвую зону. Если SoftwareSerial и библиотека Servo используются вместе, перенос сервоприводов на PCA9685 полностью убирает дрожание.

Какой вес может поднять рука на SG90?

Момент SG90 при заторможенном валу — около 1,8 кг·см; для безопасной работы закладывай половину, 0,9 кг·см. Так как рука и сервоприводы расходуют часть этого запаса на собственный вес, рука на SG90 длиной около 15 см на практике переносит только лёгкие предметы массой 20–30 г, например крышку от бутылки или маленький кубик из пенопласта.

Как управлять роботом-манипулятором с телефона?

Подключи к Arduino Bluetooth-модуль HC-05 и отправляй с телефона текстовые команды вроде «S120» через терминал Bluetooth Serial или приложение-контроллер с ползунками. У ESP32 есть встроенный Bluetooth, поэтому дополнительный модуль не нужен. HC-05 не работает с iPhone, так что для iPhone нужен BLE-модуль или ESP32 BLE.

Все проекты
Управление сервоприводом потенциометром
#36Новичок
Premium

Управление сервоприводом потенциометром

Когда ты поворачиваешь ручку потенциометра, рычаг сервопривода поворачивается вместе с ней: одно крайнее положение ручки — ноль градусов, другое — пол-оборота. Arduino всё время читает положение ручки

4.6(37)
52
Управление сервоприводом джойстиком
#60Средний
Premium

Управление сервоприводом джойстиком

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

4.7(42)
58
Работа с сервоприводом
#35Новичок
Premium

Работа с сервоприводом

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

4.7(48)
67
Сервопривод на ESP32 (управление потенциометром)
#118Средний
Premium

Сервопривод на ESP32 (управление потенциометром)

В этом проекте сервопривод SG90 управляется потенциометром. Угол сервопривода задаётся сопоставлением аналогового значения потенциометра с диапазоном 0–180 градусов. Библиотека ESP32Servo формирует сигналы сервопривода на подходящих выходных пинах GPIO у ESP32; этот проект использует GPIO 18. Это базовый способ управления для роборук, подвесов камер и механизмов автоматических дверей.

4.3(29)
42
Управление светодиодом с телефона по Bluetooth
#34Новичок
Premium

Управление светодиодом с телефона по Bluetooth

Небольшая репетиция того, как включать и выключать свет в комнате с телефона. Bluetooth-модуль передаёт в Arduino буквы, отправленные с телефона; одна буква включает красный светодиод, другая — зелёны

4.8(68)
89
Машинка на L298N с управлением по Bluetooth
#80Продвинутый
Premium

Машинка на L298N с управлением по Bluetooth

Преврати телефон в пульт и управляй своей машинкой-роботом. Arduino получает команды из приложения-пульта на телефоне через Bluetooth-модуль и вращает колёса через драйвер моторов; машинка едет вперёд

5.0(38)
65
Хореография сервоприводов по кругу — анимации на 32 сервоприводах
#102Продвинутый
Premium

Хореография сервоприводов по кругу — анимации на 32 сервоприводах

Визуальный анимационный проект на Arduino Mega с 32 сервоприводами, расставленными по кругу. Сервоприводы собраны в один массив и подключены к пинам 22–53 Mega; они показывают четыре движения: хаотичное дрожание на случайных углах, синхронный взмах всех сразу, плавную волну по кругу и, наконец, стрелку компаса, качающуюся влево и вправо. Эффект бегущей волны создаётся поворотом каждого сервопривода в зависимости от его расстояния до активного.

4.5(40)
62