Guías · 20 min de lectura

Brazo robótico con Arduino: servos, torque, PCA9685 y Bluetooth

Cómo hacer un brazo robótico con Arduino: estructura de MDF o 3D, torque del MG996R, control con potenciómetro y joystick, grabar, PCA9685 y Bluetooth.

Un brazo robótico con Arduino es un robot de escritorio que, gracias a sus articulaciones movidas por servos, puede agarrar un objeto, levantarlo, girar y dejarlo en otro sitio. Con su base, hombro, codo, muñeca y pinza, te enseña diseño mecánico, elección de motores, gestión de la alimentación y programación en un solo proyecto. Y lo mejor: lo puedes construir en casa con un Arduino Uno, unos cuantos servos y piezas de MDF o impresas en 3D, y tener una pequeña copia de un robot industrial funcionando en tu mesa.

En esta guía vemos de principio a fin los brazos robóticos de 4 y 6 ejes: el material de la estructura, un cálculo de torque para decidir entre SG90, MG90S y MG996R, la alimentación externa de 5 V, el control con potenciómetros y joysticks, grabar y reproducir movimientos, el driver de servos de 16 canales PCA9685, el control por Bluetooth desde el móvil y las soluciones a los dos problemas más habituales: temblores y reinicios. Si no tienes claro cómo funciona un servo, empieza por ¿Qué es un servomotor?.

SG90
SG90
MG90S
MG90S
MG996R
MG996R

¿Cómo funciona un brazo robótico y cuántos ejes necesitas?

Piensa en un brazo robótico como en un brazo humano. Cada articulación lleva un servomotor y el Arduino le dice a cada servo "ve a este ángulo". Gracias al potenciómetro interno, el servo sabe dónde está su eje y mantiene el ángulo incluso con carga. Cambiando los ángulos de las articulaciones a la vez, llevas la pinza al punto que quieras. El número de movimientos independientes que puede hacer un brazo se llama grados de libertad (DOF), o simplemente ejes.

ArticulaciónMovimientoCargaServo recomendado
Base (cintura)Gira el brazo a izquierda y derechaNo levanta peso, pero soporta todo el brazoMG996R + rodamiento
HombroLevanta el brazo hacia delante y atrásLa articulación más exigidaMG996R o un servo de la clase de 20 kg·cm
CodoDobla el antebrazoMedia-altaMG996R
Muñeca (arriba-abajo)Ajusta el ángulo de la pinzaBajaMG90S
Giro de muñecaGira la pinza sobre su ejeMuy bajaMG90S o SG90
Pinza (gripper)Agarra y suelta el objetoFuerza de aprieteSG90 o MG90S

La mayoría de los kits de "brazo robótico de 6 ejes" llevan 6 servos; como la pinza no cuenta como eje, técnicamente son un brazo de 5 grados de libertad más una pinza. Para tu primer brazo, un diseño de 4 ejes (base, hombro, codo, pinza) es el mejor punto de partida: bastan 4 servos, 4 potenciómetros y un Arduino Uno, el cableado es sencillo y casi no hay problemas de torque. Las articulaciones de muñeca sirven para mantener la pinza horizontal o agarrar objetos desde distintos ángulos; al pasar a 5–6 servos, el driver PCA9685 se vuelve casi imprescindible.

Lista de materiales

PiezaBrazo de 4 ejesBrazo de 6 servosNota
Placa de controlArduino Uno o NanoArduino Uno, Nano o ESP32Las entradas extra A6 y A7 del Nano permiten más potenciómetros
Servomotores4 × SG90 o MG90S3 × MG996R + 3 × MG90SComprueba tu elección con el cálculo de torque de abajo
Driver de servosNo hace faltaMódulo PCA9685 de 16 canalesSolo 2 cables de señal por I2C
Fuente de alimentaciónAdaptador de 5 V 3 A5–6 V, al menos 5 ALos servos nunca se alimentan desde el Arduino
Condensador470–1000 µF, 16 V1000–2200 µF, 16 VEn la línea de alimentación de los servos, cuidando la polaridad
Control4 potenciómetros de 10 kΩ2 módulos joystick o un HC-05También puedes combinar ambos
Pulsadores2 (grabar, reproducir)Bastan los pulsadores de los joysticksCon INPUT_PULLUP no hacen falta resistencias
MecánicaPiezas de MDF de 3 mm o impresas en 3DMDF de 4 mm, impresión 3D o soportes de aluminioRodamiento 608, tornillos M3 y tuercas autoblocantes

Estructura mecánica: ¿MDF o impresión 3D?

MaterialVentajasInconvenientesIdeal para
MDF de 3–4 mm (corte láser)Barato, ligero, fácil de atornillar y pegarLe afecta la humedad, los agujeros de los tornillos se agrandan con el tiempoPrimeros brazos tipo MeArm, proyectos escolares
Acrílico de 3 mmQueda muy vistoso, no le afecta la humedadEs frágil; puede agrietarse al apretar tornillosProyectos de exposición
Impresión 3D (PLA / PETG)Piezas complejas, alojamientos de servo a medidaImpresión lenta; el PLA se deforma con calor y carga continuaDiseños propios; PETG para piezas que soportan carga
Kit de soportes de aluminioMuy rígido, piezas listas para el MG996RPesado y caro, carga más el hombroBrazos de 6 servos para objetos más pesados
Cartón, palitos de heladoCasi gratis, prototipo rápidoPoco resistente, las articulaciones cogen holguraProbar medidas, primeros intentos con niños
  • Mantén el brazo corto y ligero: La carga en el hombro crece en proporción directa a la distancia. Hacer el brazo de 24 cm en vez de 30 cm reduce el torque del hombro en torno a una quinta parte.
  • Coloca los servos pesados cerca de la base: Bajar el servo del codo a la altura del hombro y mover el codo con una varilla (articulación en paralelogramo) es un truco ingenioso que usan brazos como el MeArm; el hombro ya no tiene que levantar ese servo.
  • Usa un rodamiento en la base: No cargues todo el peso del brazo sobre el eje del servo de la base. Un rodamiento 608 (8 mm de diámetro interior, 22 mm exterior y 7 mm de ancho; el típico de monopatín) o un rodamiento de plato giratorio soporta la carga y el servo solo gira.
  • Apoya las articulaciones por los dos lados: Mientras el servo sujeta la articulación por el lado del horn, pon un pivote en el lado opuesto con un tornillo M3 y una tuerca autoblocante. Así el eje del servo no soporta flexión y los engranajes duran mucho más.
  • Usa horns metálicos: Monta en cada MG996R un horn redondo metálico de 25 dientes (25T) y fíjalo al eje con el tornillo central. Los horns de plástico patinan y se desdentan en las articulaciones con mucha carga.
  • Añade goma a los dedos de la pinza: Una tira fina de goma en las puntas permite sujetar objetos sin que resbalen y con menos fuerza de apriete.

Elegir servos: ¿SG90, MG90S o MG996R?

ModeloTorqueVelocidad (por 60°)PesoEngranajesCorriente de bloqueoUso en el brazo
SG901,8 kg·cm (4,8 V)0,10 s (4,8 V)9 gPlásticoUnos 0,6–0,75 APinza, muñeca ligera, brazo pequeño de 4 ejes
MG90S1,8 kg·cm (4,8 V), 2,2 kg·cm (6 V)0,10 s (4,8 V)13,4 gMetalUnos 0,7–1 AMuñeca, giro de muñeca, pinza
MG996R9,4 kg·cm (4,8 V), 11 kg·cm (6 V)0,17 s (4,8 V)55 gMetal2,5 A (6 V)Base, hombro, codo
DS321819 kg·cm (5 V), 21,5 kg·cm (6,8 V)0,16 s (5 V)60 gMetalUnos 2–3 AHombro de brazos largos o pesados

El torque de la tabla es el torque de bloqueo (stall torque): la fuerza máxima que da el servo antes de que su eje se detenga, no un valor para trabajar de forma continua. Cuanto más se acerca un servo a ese límite, más sube su corriente hacia la de bloqueo, se calienta y empieza a temblar. La regla práctica es que el torque que necesita una articulación no supere la mitad del torque de bloqueo del servo. El voltaje también cuenta: el MG996R da 9,4 kg·cm a 4,8 V y 11 kg·cm a 6 V, así que el mismo servo gana un 17 % de fuerza solo con la tensión de alimentación. Por eso conviene alimentar a 6 V los brazos con MG996R, aunque para el SG90 y el MG90S 6 V es el límite superior. Para la conexión y los límites del MG996R consulta también la guía servomotor MG996R.

Servomotor MG996R con engranajes metálicos
MG996R: engranajes metálicos, 11 kg·cm de torque a 6 V y eje de salida de 25 dientes; la elección estándar para la base, el hombro y el codo de un brazo robótico.

Cálculo de torque: ¿puede el servo del hombro con este brazo?

El torque que tiene que levantar un servo es la suma de la masa de cada pieza multiplicada por su distancia horizontal al eje de la articulación: Torque (kg·cm) = Σ masa (kg) × distancia (cm). El peor caso es con el brazo totalmente estirado en horizontal, porque todas las distancias son máximas; con el brazo vertical, ese brazo de palanca baja casi a cero. Para pasarlo a newton metro, multiplica por 0,098 (1 kg·cm ≈ 0,098 N·m). Como ejemplo, calculemos la carga en el hombro de un brazo de escritorio con 12 cm del hombro al codo, 12 cm del codo a la muñeca, 6 cm de la muñeca a la punta de la pinza y un MG996R en el codo, sujetando un objeto de 50 g:

PiezaMasaDistancia al eje del hombroTorque
Piezas del brazo superior (MDF y tornillos)40 g6 cm (centro de masas)0,04 × 6 = 0,24 kg·cm
Servo del codo (MG996R)55 g12 cm0,055 × 12 = 0,66 kg·cm
Piezas del antebrazo30 g18 cm0,03 × 18 = 0,54 kg·cm
Servo de muñeca + pinza (MG90S, SG90 y piezas)50 g25 cm0,05 × 25 = 1,25 kg·cm
Objeto sujetado50 g30 cm0,05 × 30 = 1,50 kg·cm
Total225 g—4,19 kg·cm (≈ 0,41 N·m)

En reposo, este brazo necesita 4,19 kg·cm en el hombro. Para cubrir la carga extra al acelerar y frenar, y mantener el servo lejos del bloqueo, multiplica por 2: 8,4 kg·cm. Es decir, el hombro necesita como mínimo un MG996R (9,4 kg·cm a 4,8 V, 11 kg·cm a 6 V); los 1,8–2,2 kg·cm de un SG90 o MG90S ni siquiera moverían este brazo. Si haces lo mismo con el codo, el antebrazo (0,03 × 6), la muñeca con la pinza (0,05 × 13) y el objeto (0,05 × 18) suman 1,73 kg·cm, 3,5 kg·cm con el margen de seguridad; el codo también pide un MG996R. En la muñeca solo quedan la pinza y el objeto, así que la carga ronda los 0,4 kg·cm y un MG90S va sobrado. El mismo cálculo te dice cuál es el objeto más pesado que puede llevar el brazo: aumenta la masa, recalcula el total y no pases de la mitad del torque del servo.

Hay tres formas baratas de reducir el torque del hombro: acortar el brazo, llevar el servo del codo a la altura del hombro y mover el codo con una varilla, y añadir un contrapeso detrás del hombro o un muelle que tire del brazo hacia arriba. Por ejemplo, un peso de 400 g a 5 cm por detrás del eje del hombro genera 0,4 × 5 = 2 kg·cm de par contrario y reduce casi a la mitad la carga del brazo anterior; a cambio, el rodamiento de la base soporta algo más de peso.

Alimentación: ¿por qué los servos no se alimentan desde el Arduino?

La mayoría de los problemas de un brazo robótico vienen de la alimentación. Un MG996R consume 0,5–0,9 A en movimiento y hasta 2,5 A bloqueado; incluso un pequeño SG90 puede superar los 0,6 A cuando se bloquea. Con el Arduino Uno alimentado por USB, la corriente total disponible en el pin 5V ronda los 500 mA, el límite del USB, y parte la consume la propia placa. Cuando tres servos grandes arrancan a la vez, la tensión cae de golpe, el microcontrolador se reinicia y el brazo salta a la posición inicial. La solución es alimentar los servos con una fuente aparte de 5–6 V y unir todas las tierras (GND); el Arduino puede seguir alimentándose por USB o con su propio adaptador. Para ver opciones de alimentación consulta la guía alimentación del Arduino, y para estimar cuánto aguanta un brazo con batería usa la calculadora de duración de batería.

Tipo de brazoCorriente estimadaFuente recomendada
Brazo pequeño con 4 × SG90 o MG90S0,5–1 A en movimiento, picos de 2–3 AAdaptador de 5 V 3 A o módulo LM2596 (3 A máx., unos 2 A sin disipador)
Brazo de 6 servos con 3 × MG996R + 3 × MG90S2–3 A en movimiento, 8–10 A si se bloquean todosFuente conmutada de 5–6 V y al menos 5 A (10 A para ir holgado)
Brazo portátilSegún el brazoLiPo 2S (7,4 V) + módulo reductor (UBEC) de 5 A o más ajustado a 6 V
Fuentes que debes evitar—Pila de 9 V (no da corriente), el pin 5V del Arduino, 4 pilas AA alcalinas en un brazo con MG996R
Nunca alimentes los servos desde el pin 5V del Arduino y conecta siempre el negativo de la fuente externa al GND del Arduino; sin una tierra común los servos no entienden la señal y se mueven al azar. Pon un condensador electrolítico en la línea de los servos, justo a su lado, calculando al menos 100 µF por servo (1000–2200 µF y 16 V o más para un brazo de 6 servos), con la pata larga en el positivo; un electrolítico al revés puede hincharse y reventar. Las líneas de alimentación de una protoboard están pensadas para alrededor de 1 A; en un brazo con MG996R lleva los cables de alimentación directamente a una bornera o a la entrada V+ del PCA9685.

Conexiones del brazo de 4 ejes

La tabla muestra el cableado del brazo de 4 ejes con potenciómetros para Arduino Uno y Raspberry Pi Pico; los códigos de esta guía usan estos pines. En el Uno, la librería Servo puede mover un servo desde cualquier pin digital, pero como usa el Timer1, analogWrite deja de funcionar en los pines 9 y 10 mientras haya servos conectados. Puedes ver la distribución de pines de tu placa en la herramienta Pinout.

ConexiónArduino UnoRaspberry Pi Pico
Señal servo de la base (cable naranja)D3GP10
Señal servo del hombroD5GP11
Señal servo del codoD6GP12
Señal servo de la pinzaD9GP13
Pata central potenciómetros 1–3 (base, hombro, codo)A0, A1, A2GP26, GP27, GP28
Pata central potenciómetro 4 (pinza)A3No hay; la pinza va con el pulsador de GP14
Patas laterales de los potenciómetros5V y GND3V3 y GND
Pulsadores de grabar y reproducir (el otro lado a GND)D7 y D8—
Cable rojo del servo (+)Positivo externo de 5–6 VPositivo externo de 5–6 V
Cable marrón del servo (−)Negativo de la fuente externa + GND del ArduinoNegativo de la fuente externa + GND de la Pico
Potenciómetro rotativo de 10 kΩ
Potenciómetro de 10 kΩ: las patas laterales van a 5V y GND y la central a una entrada analógica; al girar el eje, la articulación gira en la misma proporción.

Calibra antes de montar: centra los servos

  1. Antes de poner los horns, carga el código de abajo; los cuatro servos van uno tras otro a 90 grados y se quedan ahí.
  2. Sujeta el brazo en una postura neutra (por ejemplo brazo superior vertical, antebrazo horizontal y pinza medio abierta), coloca cada horn en el diente más cercano a esa postura y fíjalo con el tornillo central.
  3. Pon CENTER_ONLY a false en el código, elige una articulación con TEST y gira despacio el potenciómetro de A0.
  4. Mira el ángulo en el Monitor Serie y apunta el ángulo mínimo y máximo que alcanza la articulación sin chocar con nada ni tensar los cables.
  5. Fija el ángulo cerrado de la pinza en el punto en que los dedos justo se tocan; un servo que sigue intentando apretar se queda bloqueado y se calienta.
  6. Escribe los valores obtenidos en los arrays MIN_A y MAX_A de los siguientes códigos.
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}

Hay un detalle pequeño pero importante en este código: como servo.write(90) se llama antes de attach(), el servo recibe el ángulo deseado desde el primer pulso. Si inviertes el orden, la librería envía primero su pulso por defecto de 1500 µs (unos 90°); si tu ángulo inicial no es 90, al encender el brazo salta primero al centro y luego al destino. Arrancar los servos de uno en uno con 300 ms entre ellos también reparte el pico de corriente del arranque.

Controlar el brazo robótico con potenciómetros

El método de control más intuitivo es un potenciómetro por articulación: en el ángulo en que esté el eje del potenciómetro se coloca también la articulación, es decir, haces control de posición. Si montas los potenciómetros en una maqueta pequeña del brazo (de cartón o impresa en 3D, con las mismas proporciones), el brazo grande copia uno a uno los movimientos que haces con la mano. Las lecturas analógicas, sin embargo, bailan por naturaleza unas cuantas unidades y ese baile se nota como temblor en el servo; el código tiene dos medidas contra eso.

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}

El array smooth es un filtro paso bajo sencillo: en cada vuelta solo se suma el 20 % de la nueva lectura, así se suavizan los saltos y el brazo sigue respondiendo rápido. Si pones 0,1 en vez de 0,2, el movimiento es más suave pero con más retraso. La segunda medida es una banda muerta: si el nuevo ángulo no difiere al menos 2 grados del último escrito, no se envía nada al servo y desaparece el temblor con el brazo quieto. Como map() convierte el rango del potenciómetro directamente a los límites seguros de cada articulación, la articulación no llega al tope mecánico aunque gires el potenciómetro hasta el final.

Grabar y reproducir movimientos

Lo que convierte de verdad un brazo en un robot es que repita por sí solo un movimiento que le has enseñado. El método es sencillo: llevas el brazo a una postura con los potenciómetros y pulsas el botón de grabar, y el Arduino guarda los ángulos de las cuatro articulaciones. Tras grabar varias posturas, pulsas reproducir y el brazo las repite en orden, suavizando las transiciones. Las posturas se escriben en la EEPROM de 1024 bytes del Arduino Uno, así que no se borran al apagar; cada postura ocupa 4 bytes, de modo que una grabación de 100 posturas solo usa 401 bytes.

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}
AcciónQué hacesQué hace el brazo
Guardar posturaPulsación corta del botón de grabar (D7)El LED parpadea una vez y la postura se escribe en la EEPROM
Borrar grabacionesMantén pulsado el botón de grabar 2 segundosEl LED parpadea cinco veces y el contador de pasos vuelve a cero
ReproducirPulsa y suelta el botón de reproducir (D8)El LED se enciende y las posturas se repiten en orden con transiciones suaves
Detener la reproducciónMantén pulsado el botón de reproducir durante la reproducciónEl brazo termina la postura actual y se detiene
Volver al modo normalNo hagas nada cuando acabe la reproducciónEl brazo vuelve despacio a donde marcan los potenciómetros y el LED se apaga

La función moveTo() es el corazón del programa: busca la articulación con el recorrido más largo, divide el movimiento en ese número de pasos y en cada paso hace avanzar todas las articulaciones de forma proporcional. Así todas llegan al destino a la vez y el brazo se mueve con fluidez, no como un robot que mueve una articulación cada vez. 12 ms por grado significa que un movimiento de 90 grados dura unos 1,1 segundos. Puedes bajar el valor para reproducir más rápido, pero incluso sin carga un MG996R gira 60 grados en 0,17 segundos, unos 3 ms por grado; por debajo de eso los servos no llegan a seguir las órdenes y el movimiento se vuelve brusco.

Cada celda de la EEPROM aguanta unas 100.000 escrituras. Como el código usa EEPROM.update(), solo escribe las celdas cuyo valor ha cambiado, así que en la práctica nunca la desgastarás. Para secuencias más largas puedes guardar las grabaciones en una tarjeta SD o usar la EEPROM de 4 KB del Arduino Mega.

Driver de servos PCA9685: 6 servos y una señal sin temblores

Cuando pasas a 5–6 servos o añades módulos como el Bluetooth, un módulo PCA9685 te facilita mucho la vida. Esta placa es un generador PWM de 16 canales y 12 bits de resolución; se comunica con el Arduino por solo dos cables mediante I2C y genera los pulsos de los servos con su propio reloj, sin depender del procesador. Su dirección por defecto es 0x40; cambiando la dirección con los puentes de soldadura A0–A5 puedes encadenar hasta 62 placas, es decir, 992 servos. A 50 Hz, el periodo de 20 ms se divide en 4096 pasos, así que cada paso dura unos 4,9 µs; el rango de 544–2400 µs abarca unos 380 pasos, lo que da una resolución de aproximadamente medio grado.

Pin del PCA9685Arduino UnoESP32Raspberry Pi Pico
GNDGNDGNDGND
VCC (solo alimenta el chip)5V3V33V3
SDAA4GPIO 21GP4
SCLA5GPIO 22GP5
Borne V+ (alimentación de servos)5–6 V externos5–6 V externos5–6 V externos
OESin conectarSin conectarSin conectar
Conectores de servo 0–5Base, hombro, codo, muñeca, giro de muñeca, pinzaMismo ordenMismo orden
El PCA9685 tiene dos alimentaciones separadas: VCC solo alimenta el chip, mientras que los servos se alimentan por la entrada V+ del borne. Si V+ queda sin tensión, la placa aparece en el bus I2C pero los servos no se mueven. En el Arduino Uno, A4 y A5 se usan para I2C, así que con un PCA9685 conectado solo quedan A0–A3 como entradas analógicas; si necesitas más potenciómetros, usa un Arduino Nano con sus entradas extra A6 y A7.

Controlar el brazo robótico con joystick

Un joystick funciona distinto que un potenciómetro: al soltarlo vuelve al centro. Si atas el ángulo directamente a la posición del joystick, la articulación salta a 90 grados en cuanto lo sueltas. Lo correcto es el control de velocidad: cuanto más empujas el joystick, más rápido gira la articulación, y al soltarlo se queda donde está. En el código de abajo dos joysticks mueven cuatro articulaciones (el izquierdo la base y el hombro, el derecho el codo y la muñeca); el pulsador del joystick izquierdo abre y cierra la pinza y el del derecho lleva el brazo a la postura inicial. Conecta VRx y VRy del joystick izquierdo a A0 y A1, VRy y VRx del derecho a A2 y A3 y los pines SW a D2 y D4; puedes alimentar los joysticks desde el pin 5V del Arduino. Para compilar el código, instala Adafruit PWM Servo Driver Library desde el Gestor de Librerías y acepta la dependencia Adafruit BusIO.

Módulo joystick analógico de dos ejes
Módulo joystick: las salidas VRx y VRy leen unos 512 en reposo y el pulsador SW pone la señal a GND al pulsarlo. Con dos módulos mueves cuatro articulaciones de un brazo robótico.
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 evita que las pequeñas desviaciones, como 490–530, que se leen en el centro hagan que el brazo se desplace solo. MAX_SPEED fija la velocidad, en grados por segundo, con el joystick a fondo; 90 grados por segundo es un valor seguro para empezar. Si una articulación va al revés que el joystick, no cambies cables: pon a -1 el valor correspondiente del array DIR. El código guarda para cada articulación un objetivo (target) y una posición real (current); como la posición real se acerca al objetivo con un límite de velocidad, el brazo vuelve con suavidad incluso al pulsar el botón de postura inicial, sin saltos. El giro de muñeca queda fijo en 90 grados en este programa; puedes controlarlo con el comando R de la sección de Bluetooth. Los detalles de esta temporización sin bloqueos están en millis() frente a delay().

Control por Bluetooth desde el móvil (HC-05)

El módulo HC-05 crea un enlace serie inalámbrico entre el móvil y el Arduino. Su pin TXD va al D10 del Arduino y el RXD se conecta a D11 a través de un divisor de tensión: D11 → 1 kΩ → RXD → 2 kΩ → GND. El divisor baja la señal de 5 V del Arduino a los 3,3 V aproximados que espera el HC-05. La velocidad de datos por defecto es de 9600 baudios y el PIN de emparejamiento suele ser 1234. El móvil envía comandos de texto sencillos: una letra elige la articulación, el número que la sigue da el ángulo y un salto de línea o un punto y coma cierra el comando.

Módulo serie Bluetooth HC-05
HC-05: módulo serie que se empareja con el móvil por Bluetooth clásico; VCC admite 5 V, pero la entrada RXD trabaja con niveles de 3,3 V.
ComandoSignificadoEjemplo
B + ánguloBaseB90
S + ánguloHombroS120
E + ánguloCodoE45
W + ánguloMuñeca (arriba-abajo)W100
R + ánguloGiro de muñecaR0
G + ánguloPinzaG110 (cerrar), G40 (abrir)
HVolver a la postura inicialH
K / P / X / CGuardar postura / reproducir en bucle / parar / borrar posturasK
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}

En este programa los servos van conectados al PCA9685 y no directamente al Arduino, y es a propósito. SoftwareSerial desactiva las interrupciones durante 1 ms aproximadamente mientras recibe cada byte; si los servos se movieran desde pines del Arduino con la librería Servo, sus pulsos se alargarían en ese tiempo y el brazo daría un tirón con cada comando. Como el PCA9685 genera los pulsos por su cuenta, la señal no tiembla por mucho tráfico Bluetooth que haya. Grabar y reproducir ahora también se hace desde el móvil: llevas el brazo a una postura con los deslizadores y envías K; tras varias posturas, con P el brazo las repite en bucle y con X se detiene. En esta versión las posturas se guardan en la RAM y se pierden al apagar la placa; si quieres que se conserven, añade el método de la EEPROM del código anterior.

En el móvil basta con una app ya hecha: instala en Android una terminal serie Bluetooth o una app de control Bluetooth con deslizadores y asigna a cada deslizador un prefijo como "S" y un fin de línea. Si quieres crear tu propia interfaz con botones y deslizadores, la guía app Bluetooth con MIT App Inventor lo explica paso a paso. Como el HC-05 usa Bluetooth clásico, no funciona con el iPhone; para el iPhone necesitas una solución con Bluetooth Low Energy como el ESP32 BLE.

Brazo robótico inalámbrico con ESP32

Como el ESP32 lleva Bluetooth clásico integrado, no necesitas el HC-05: con la librería BluetoothSerial la placa aparece en el móvil como "BlueGrays-RobotKol" y entiende los mismos comandos. Conecta el PCA9685 a GPIO 21 (SDA) y GPIO 22 (SCL) y alimenta VCC desde 3V3; las señales de los servos salen entonces a 3,3 V, algo que la inmensa mayoría de los servos lee sin problemas. BluetoothSerial solo funciona en el ESP32 clásico (ESP32-WROOM-32, DevKit); los ESP32-S2, S3 y C3 no tienen Bluetooth clásico.

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}

Brazo robótico con Raspberry Pi Pico (MicroPython)

Para mover servos con la Raspberry Pi Pico no hace falta ninguna librería extra: ajustas el PWM a 50 Hz y das el ancho de pulso en nanosegundos con duty_ns(). Con 16 canales PWM, la Pico puede mover 6 servos directamente; los pares de pines que comparten el mismo slice comparten frecuencia, pero como todos los servos usan 50 Hz no es un problema. La limitación está en la parte analógica: la Pico solo tiene tres pines ADC utilizables (GP26, GP27, GP28). Por eso, en el código de abajo tres articulaciones se mueven con potenciómetros y la pinza se abre y se cierra con un pulsador en GP14. Como la Pico funciona a 3,3 V, conecta los potenciómetros a 3V3; los servos se siguen alimentando con una fuente externa de 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

Un paso más: cinemática inversa

Hasta ahora hemos dado el ángulo de cada articulación una a una; eso se llama cinemática directa. La cinemática inversa hace lo contrario: indicas el punto (x, y) al que quieres llevar la pinza y el código calcula los ángulos necesarios. En un brazo de dos segmentos con longitud hombro–codo L1 y codo–muñeca L2, primero se calcula D = (x² + y² − L1² − L2²) / (2·L1·L2). El ángulo del codo es θ2 = acos(D) y el del hombro θ1 = atan2(y, x) + atan2(L2·sen θ2, L1 + L2·cos θ2) (solución con el codo arriba). Por ejemplo, con L1 = L2 = 12 cm y un punto 15 cm por delante y 5 cm por encima del hombro, sale D ≈ −0,13, θ2 ≈ 97,6° y θ1 ≈ 67,2°: el brazo superior apunta 67 grados por encima de la horizontal y el antebrazo se dobla 97,6 grados hacia abajo respecto a él. Si el valor absoluto de D es mayor que 1, el punto está fuera del alcance del brazo. El ángulo de la base se calcula aparte a partir de la posición del objetivo vista desde arriba. Al convertir los resultados en ángulos de servo, ten en cuenta el sentido de montaje y el punto central de cada servo.

Temblores y reinicios: errores frecuentes y soluciones

SíntomaCausa probableSolución
Los servos tiemblan y zumban con el brazo quietoRuido en las lecturas de los potenciómetros o fuente débilAñade un filtro de suavizado y una banda muerta de 2°, usa fuente externa y un condensador de 1000 µF
El Arduino se reinicia al mover el brazo y el HC-05 pierde la conexiónLa corriente de arranque de los servos hunde la tensiónAlimenta los servos con una fuente aparte de 5–6 V, une las tierras y arranca los servos uno a uno
Los servos dan tirones al llegar un comando BluetoothLas interrupciones de SoftwareSerial alteran los pulsos de la librería ServoPasa los servos al PCA9685 o usa el Bluetooth integrado del ESP32
Al encender, el brazo salta bruscamenteLos servos no recuerdan su última posición y van a toda velocidad con la primera ordenDeja el brazo en la postura inicial antes de apagar y llama a write() antes de attach()
El hombro no levanta el brazo o se cae despacioFalta torqueHaz el cálculo de torque; usa un MG996R, un brazo más corto, un contrapeso o un muelle
Una articulación se atasca en un extremo y se calientaEl servo empuja contra un tope mecánicoEscribe en el código los límites MIN_A y MAX_A de la calibración
El servo de la pinza se calienta al sujetar un objetoLa pinza sigue intentando cerrar y el servo se queda bloqueadoAjusta el ángulo cerrado al punto en que justo toca el objeto y añade goma a los dedos
El PCA9685 está conectado pero ningún servo se mueveSin tensión en el borne V+ o sin tierra comúnDa 5–6 V a V+ y comprueba la dirección I2C (0x40)
Una articulación patina con cargaEl horn de plástico se ha desdentado o falta el tornillo centralUsa un horn metálico y fíjalo al eje con el tornillo central
Una forma rápida de saber si el problema es de alimentación o de código: ejecuta el código de calibración (CENTER_ONLY = false) sin los servos conectados y mira los ángulos en el Monitor Serie mientras giras el potenciómetro. Si los valores son correctos, el problema está en la alimentación; mide con un multímetro la tensión de la línea de los servos mientras el brazo se mueve y, si baja de 4,5 V, la fuente o los cables se quedan cortos. Todas las causas de los reinicios están en ¿Por qué se reinicia mi Arduino?.

Ideas de proyectos con el brazo robótico

  • Brazo clasificador de colores: Lee el color de unos cubos con un sensor de color TCS3200 y que el brazo deje cada color en su caja.
  • Control por gestos: Con un MPU6050 en un guante, el brazo robótico se inclina hacia el mismo lado que tu muñeca.
  • Mando inalámbrico: Conecta dos joysticks a otro Arduino y envía los comandos con un nRF24L01; tendrás un mando a distancia que no necesita el móvil.
  • Control desde el navegador: Crea deslizadores en una página web con un ESP32 y controla el brazo desde cualquier dispositivo de la misma red Wi-Fi; un WebSocket en el ESP32 reduce el retardo al mínimo.
  • Recoger de una cinta: Construye una pequeña cinta transportadora con un motor paso a paso; cuando un TCRT5000 detecte un objeto, el brazo lo recoge de la cinta.
  • Concurso y proyecto escolar: Un brazo que graba y reproduce movimientos es una gran opción entre las ideas para ferias de ciencias, porque muestra mecánica, electrónica y software a la vez.

Un brazo robótico con Arduino te enseña a la vez diseño mecánico, cálculo de torque, gestión de la alimentación y programación de movimientos suaves, y crece con cada hora que le dedicas. Empieza con un brazo de 4 servos y potenciómetros, añade grabar y reproducir y luego pasa a 6 servos con un PCA9685 y Bluetooth. En la app BlueGrays encontrarás proyectos como el control de servos con potenciómetro y joystick, el control por Bluetooth desde el móvil y una coreografía de 32 servos, con esquemas de circuito y código paso a paso, para que pruebes primero cada articulación de tu brazo por separado.

Preguntas frecuentes

¿Qué servo usar para un brazo robótico con Arduino?

Para las articulaciones que soportan carga, como la base, el hombro y el codo, el MG996R con engranajes metálicos y 9–11 kg·cm de torque es una buena opción. Para las articulaciones ligeras, como la muñeca y la pinza, basta un MG90S o un SG90. Los brazos pequeños hechos solo con SG90 funcionan si son cortos y ligeros, pero solo levantan objetos de unas decenas de gramos.

¿Qué fuente de alimentación necesita un brazo robótico?

Para un brazo pequeño con 4 servos SG90 o MG90S basta un adaptador de 5 V 3 A. Para un brazo de 6 servos con 3 MG996R y 3 servos pequeños, se recomienda una fuente conmutada de 5–6 V de al menos 5 A, o de 10 A para ir holgado. No olvides unir el negativo de la fuente al GND del Arduino.

¿Cuántos servos se pueden conectar a un Arduino?

La librería Servo maneja hasta 12 servos en el Arduino Uno y 48 en el Mega. Pero el límite real es la corriente: todos los servos deben alimentarse con una fuente externa. Un módulo PCA9685 controla 16 servos usando solo dos pines I2C y se pueden encadenar hasta 62 módulos.

¿Por qué tiemblan los servos de mi brazo robótico?

Las causas más habituales son una fuente insuficiente, la falta de tierra común y el ruido en las lecturas de los potenciómetros. Usa una fuente externa con un condensador de 1000 µF en la línea de los servos y aplica en el código un filtro de suavizado y una banda muerta. Si usas SoftwareSerial junto con la librería Servo, pasar los servos a un PCA9685 elimina el temblor por completo.

¿Cuánto peso puede levantar un brazo robótico con SG90?

El torque de bloqueo del SG90 ronda 1,8 kg·cm; para trabajar con seguridad, cuenta con la mitad, unos 0,9 kg·cm. Como el propio peso del brazo y de los servos se come parte de ese margen, un brazo con SG90 de unos 15 cm solo puede llevar en la práctica objetos ligeros de 20–30 g, como un tapón de botella o un cubo pequeño de espuma.

¿Cómo se controla un brazo robótico desde el móvil?

Se conecta un módulo Bluetooth HC-05 al Arduino y, desde una terminal serie Bluetooth o una app de control con deslizadores, el móvil envía comandos de texto como "S120". Con un ESP32 no necesitas módulo extra gracias a su Bluetooth integrado. El HC-05 no funciona con el iPhone, así que para el iPhone necesitas un módulo BLE o el ESP32 BLE.

Ver todos los proyectos
Funcionamiento del servomotor con potenciómetro
#36Principiante
Premium

Funcionamiento del servomotor con potenciómetro

A medida que giras el mando del potenciómetro, el brazo del servomotor gira con él: un extremo del mando equivale a cero grados y el otro a media vuelta. El Arduino lee sin parar la posición del mando

4.6(37)
52
Servocontrol con joystick
#60Intermedio
Premium

Servocontrol con joystick

Controla dos motores con un joystick, igual que si giraras una cámara de seguridad. Si empujas la palanca a izquierda o derecha, el servomotor de abajo gira despacio; si la empujas arriba o abajo, el

4.7(42)
58
Uso del servomotor
#35Principiante
Premium

Uso del servomotor

El brazo de un pequeño servomotor gira por turnos a tres posiciones: al principio del todo, justo al centro y al final del todo, esperando dos segundos en cada parada. El Arduino le dice al motor en q

4.7(48)
67
Servomotor ESP32 (control mediante potenciómetro)
#118Intermedio
Premium

Servomotor ESP32 (control mediante potenciómetro)

En este proyecto controlamos un servomotor SG90 mediante un potenciómetro. Ajustamos el ángulo del servo asignando el valor analógico leído en el potenciómetro al rango de 0 a 180 grados. La biblioteca ESP32Servo genera señales de servo en los GPIO adecuados del ESP32 que admiten salida; en este proyecto se utiliza GPIO 18. Este es un método de control básico para brazos robóticos, cardanes de cámara y mecanismos de puertas automáticas.

4.3(29)
42
Controlar un LED desde el móvil por Bluetooth
#34Principiante
Premium

Controlar un LED desde el móvil por Bluetooth

Un pequeño ensayo para encender y apagar las luces de tu habitación desde el móvil. El módulo Bluetooth pasa al Arduino las letras que envías desde el teléfono; una letra enciende el LED rojo, otra en

4.8(68)
89
Coche controlado por Bluetooth con L298N
#80Avanzado
Premium

Coche controlado por Bluetooth con L298N

Convierte tu móvil en un mando y conduce tu propio coche robot. El Arduino recibe por un módulo Bluetooth las órdenes de una app de control remoto en el móvil y mueve las ruedas con un controlador de

5.0(38)
65
Coreografía de servos en círculo - Patrones animados con 32 servos
#102Avanzado
Premium

Coreografía de servos en círculo - Patrones animados con 32 servos

Un proyecto de animación visual que utiliza 32 servomotores dispuestos en círculo en un Arduino Mega. Los servos se gestionan en una única matriz y se conectan a los pines 22-53 de Mega para mostrar cuatro secuencias de movimiento diferentes: una vibración caótica en la que todos los servos se mueven en ángulos aleatorios, un barrido síncrono en el que todos se mueven al mismo tiempo, una onda que gira suavemente alrededor de un círculo y, por último, una aguja de brújula que oscila a izquierda

4.5(40)
62