Робот-манипулятор на Arduino: выбор сервоприводов, момент, PCA9685 и Bluetooth
Собираем робот-манипулятор на Arduino шаг за шагом: корпус из МДФ или 3D-печати, расчёт момента для MG996R, управление потенциометрами и джойстиком, запись и воспроизведение, код для PCA9685 и Bluetooth.
Робот-манипулятор на Arduino — это настольный робот, суставы которого приводятся в движение сервоприводами: он берёт предмет, поднимает его, поворачивается и кладёт в другое место. Основание, плечо, локоть, запястье и захват, и в одном проекте сразу есть механика, подбор моторов, управление питанием и программирование. А главное, такую руку можно собрать дома из Arduino Uno, нескольких сервоприводов и деталей из МДФ или 3D-печати и запустить у себя на столе маленькую копию заводского робота.
В этом руководстве разбираем роботизированные руки на 4 и 6 осей от начала до конца: выбор материала корпуса, расчёт момента для выбора между SG90, MG90S и MG996R, внешнее питание 5 V, управление потенциометрами и джойстиками, запись и воспроизведение движений, 16-канальный драйвер сервоприводов PCA9685, управление с телефона по Bluetooth и решения двух самых частых проблем: дрожания и перезагрузок. Если принцип работы сервопривода пока непонятен, начни со статьи Что такое сервопривод?.



Как работает робот-манипулятор и сколько осей нужно?
Представь робот-манипулятор как человеческую руку. В каждом суставе стоит сервопривод, и 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 или Nano | Arduino Uno, Nano или ESP32 | Дополнительные входы A6 и A7 у Nano позволяют подключить больше потенциометров |
| Сервоприводы | 4× SG90 или MG90S | 3× MG996R + 3× MG90S | Проверь выбор расчётом момента ниже |
| Драйвер сервоприводов | Не нужен | 16-канальный модуль PCA9685 | Всего 2 сигнальных провода по I2C |
| Питание | Адаптер 5 V 3 A | Адаптер 5–6 V, не менее 5 A | Сервоприводы никогда не питают от Arduino |
| Конденсатор | 470–1000 µF, 16 V | 1000–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°) | Масса | Шестерни | Ток при заторможенном валу | Роль в роботе-манипуляторе |
|---|---|---|---|---|---|---|
| SG90 | 1,8 кг·см (4,8 V) | 0,10 с (4,8 V) | 9 г | Пластик | Около 0,6–0,75 A | Захват, лёгкое запястье, маленькая рука на 4 оси |
| MG90S | 1,8 кг·см (4,8 V), 2,2 кг·см (6 V) | 0,10 с (4,8 V) | 13,4 г | Металл | Около 0,7–1 A | Запястье, поворот запястья, захват |
| MG996R | 9,4 кг·см (4,8 V), 11 кг·см (6 V) | 0,17 с (4,8 V) | 55 г | Металл | 2,5 A (6 V) | Основание, плечо, локоть |
| DS3218 | 19 кг·см (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.

Расчёт момента: поднимет ли плечевой сервопривод эту руку?
Момент, который должен поднять сервопривод, — это сумма масс всех деталей, умноженных на их горизонтальное расстояние от оси сустава: момент (кг·см) = Σ масса (кг) × расстояние (см). Худший случай — рука, вытянутая горизонтально, потому что все расстояния максимальны; если рука смотрит прямо вверх, плечо рычага падает почти до нуля. Чтобы перевести в ньютон-метры, умножь на 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 хватает с запасом. Тот же расчёт показывает самый тяжёлый предмет, который рука может нести: увеличивай массу предмета, пересчитывай сумму и оставайся ниже половины момента сервопривода.
Питание: почему сервоприводы не питают от 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 или MG90S | 0,5–1 A в движении, 2–3 A в пике | Адаптер 5 V 3 A или модуль LM2596 (максимум 3 A, около 2 A без радиатора) |
| Рука на 6 сервоприводов: 3× MG996R + 3× MG90S | 2–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 |
Подключение руки на 4 оси
В таблице ниже — подключение Arduino Uno и Raspberry Pi Pico для руки на 4 оси с управлением потенциометрами; код в этом руководстве использует эти пины. На Uno библиотека Servo может управлять сервоприводом с любого цифрового пина, но так как она использует Timer1, analogWrite на пинах 9 и 10 перестаёт работать, пока сервоприводы подключены (attach). Расположение пинов своей платы можно проверить в утилите распиновки.
| Подключение | Arduino Uno | Raspberry Pi Pico |
|---|---|---|
| Сигнал сервопривода основания (оранжевый провод) | D3 | GP10 |
| Сигнал сервопривода плеча | D5 | GP11 |
| Сигнал сервопривода локтя | D6 | GP12 |
| Сигнал сервопривода захвата | D9 | GP13 |
| Средний вывод потенциометров 1–3 (основание, плечо, локоть) | A0, A1, A2 | GP26, GP27, GP28 |
| Средний вывод потенциометра 4 (захват) | A3 | Нет; захват управляется кнопкой на GP14 |
| Крайние выводы потенциометров | 5V и GND | 3V3 и GND |
| Кнопки записи и воспроизведения (другой вывод на GND) | D7 и D8 | — |
| Красный провод сервопривода (+) | Плюс внешнего источника 5–6 V | Плюс внешнего источника 5–6 V |
| Коричневый провод сервопривода (−) | Минус внешнего источника + GND Arduino | Минус внешнего источника + GND Pico |

Калибровка до сборки: выставь сервоприводы в центр
- Пока качалки не надеты, загрузи код ниже: все четыре сервопривода по очереди встают на 90 градусов и ждут.
- Держи руку в нейтральной позе (например, верхнее звено вертикально, предплечье горизонтально, захват наполовину открыт), надень каждую качалку на ближайший к этой позе зуб шлица и закрепи центральным винтом.
- Установи в коде CENTER_ONLY в false, выбери сустав через TEST и медленно поворачивай потенциометр на A0.
- Следи за углом в мониторе порта и запиши наименьший и наибольший угол, которых сустав достигает, ни во что не упираясь и не натягивая кабели.
- Угол закрытия захвата выставь в точку, где пальцы только касаются друг друга; сервопривод, который продолжает сжимать сильнее, остаётся заторможенным и греется.
- Впиши найденные значения в массивы MIN_A и MAX_A следующих скетчей.
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-печати, в тех же пропорциях), большая рука один в один повторяет движения модели, когда её двигаешь рукой. Однако аналоговые показания естественным образом скачут на несколько единиц, и эти скачки превращаются в дрожание сервоприводов; в коде против этого есть две меры.
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 байт.
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 мс на градус; если опуститься ниже, сервоприводы не успевают за командами, и движение становится рывками.
Драйвер сервоприводов 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 шагов, а это разрешение примерно в полградуса.
| Пин PCA9685 | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| GND | GND | GND | GND |
| VCC (только питание микросхемы) | 5V | 3V3 | 3V3 |
| SDA | A4 | GPIO 21 | GP4 |
| SCL | A5 | GPIO 22 | GP5 |
| Клемма V+ (питание сервоприводов) | Внешний источник 5–6 V | Внешний источник 5–6 V | Внешний источник 5–6 V |
| OE | Не подключать | Не подключать | Не подключать |
| Разъёмы сервоприводов 0–5 | Основание, плечо, локоть, запястье, поворот запястья, захват | В том же порядке | В том же порядке |
Управление роботом-манипулятором с помощью джойстиков
Джойстик работает не так, как потенциометр: если его отпустить, пружина возвращает его в центр. Если привязать угол напрямую к положению джойстика, сустав в момент отпускания прыгнет на 90 градусов. Правильный подход — управление скоростью: чем сильнее отклонён джойстик, тем быстрее поворачивается сустав, а если отпустить джойстик, сустав остаётся на месте. В коде ниже два джойстика управляют четырьмя суставами (левый — основанием и плечом, правый — локтем и запястьем); кнопка левого джойстика открывает и закрывает захват, а кнопка правого отправляет руку в исходную позу. Подключи выходы VRx и VRy левого джойстика к A0 и A1, выходы VRy и VRx правого джойстика к A2 и A3, а пины SW к D2 и D4; джойстики можно питать от пина 5V у Arduino. Для компиляции кода установи Adafruit PWM Servo Driver Library через «Менеджер библиотек» (Library Manager) и согласись на установку зависимости Adafruit BusIO.

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. Телефон отправляет простые текстовые команды: буква выбирает сустав, число после неё задаёт угол, а перевод строки или точка с запятой завершает команду.

| Команда | Значение | Пример |
|---|---|---|
| B + угол | Основание | B90 |
| S + угол | Плечо | S120 |
| E + угол | Локоть | E45 |
| W + угол | Запястье (наклон) | W100 |
| R + угол | Поворот запястья | R0 |
| G + угол | Захват | G110 (закрыть), G40 (открыть) |
| H | Возврат в исходную позу | H |
| K / P / X / C | Сохранить позу / воспроизводить по кругу / стоп / очистить позы | K |
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 из предыдущего скетча.
Беспроводной робот-манипулятор на 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 не поддерживают.
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.
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) |
| Сустав проскальзывает под нагрузкой | У пластиковой качалки сорваны шлицы или нет центрального винта | Используй металлическую качалку и закрепи её на валу центральным винтом |
Идеи проектов с роботом-манипулятором
- Сортировщик по цвету: Определяй цвет кубиков датчиком цвета 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.
Что собрать после прочтения
Все проекты
Управление сервоприводом потенциометром
Когда ты поворачиваешь ручку потенциометра, рычаг сервопривода поворачивается вместе с ней: одно крайнее положение ручки — ноль градусов, другое — пол-оборота. Arduino всё время читает положение ручки

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

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

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

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

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

Хореография сервоприводов по кругу — анимации на 32 сервоприводах
Визуальный анимационный проект на Arduino Mega с 32 сервоприводами, расставленными по кругу. Сервоприводы собраны в один массив и подключены к пинам 22–53 Mega; они показывают четыре движения: хаотичное дрожание на случайных углах, синхронный взмах всех сразу, плавную волну по кругу и, наконец, стрелку компаса, качающуюся влево и вправо. Эффект бегущей волны создаётся поворотом каждого сервопривода в зависимости от его расстояния до активного.
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

