Manual del proyecto · Proyecto 11
Brazo Robótico
Diseño, construcción y programación de un brazo robótico de 6 grados de libertad (seis servomotores) que simula operaciones de procesos productivos: primero se conectan y calibran los seis servos y se ensambla el brazo del kit para automatizar un movimiento simple; luego se suman seis potenciómetros que funcionan como un mando a distancia para controlar cada articulación; por último se programa una secuencia automática de pick-and-place que traslada objetos de una cinta transportadora a otra.
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Conectar, calibrar y armar el brazo de 6 servomotores Nivel inicial
- 03 Comandar el brazo a distancia con seis potenciómetros Nivel intermedio
- 04 Automatizar una secuencia de pick-and-place Nivel avanzado
Materiales y equipamiento
Insumos
| Imagen | Insumo | Cantidad | Plataforma |
|---|---|---|---|
| | Servomotores estándar (de 3 cables) Son las seis articulaciones del brazo (base, hombro, codo, muñeca, rotación de la pinza y apertura/cierre de la pinza). Vienen incluidos en el kit del brazo. Cada uno tiene tres cables: señal (naranja/amarillo), VCC (rojo) y GND (marrón/negro). En el UNO se controlan con la librería Servo; en el ESP32, con ESP32Servo. | 6 | Todas |
| IO Expansion Shield DFRobot (V7.1) Se apila sobre el Arduino UNO y agrupa, para cada pin, una fila de señal + VCC + GND: así los seis servos se enchufan sin cablear la alimentación uno por uno. Está pensado para el formato del UNO: NO encaja en un ESP32, donde los servos se cablean directo a los GPIO. | 1 | Arduino UNO R3 | |
| | Potenciómetros Desde el nivel intermedio: cada uno comanda una articulación, como un mando a distancia. Se conectan sus extremos a VCC y GND y la pata central (cursor) a una entrada analógica de la placa. | 6 | Todas |
| | Fuente de alimentación externa de 5V 2A Obligatoria. Seis servos consumen demasiada corriente para tomarla del puerto USB: se alimentan desde esta fuente, con GND común a la placa. Alimentar por USB puede resetear la placa o dañar el regulador. | 1 | Todas |
| | Protoboard de 830 puntos Desde el nivel intermedio: sobre ella se montan y cablean los seis potenciómetros del mando. | 1 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| Filamento para impresora 3D (PLA) Para imprimir las piezas de soporte que estabilizan la base del brazo y la carcasa donde se montan los potenciómetros del mando. | 1 | Todas | |
| | Arduino UNO R3 Placa de control primaria del proyecto, junto con el IO Shield de DFRobot. | 1 | Arduino UNO R3 |
| | Cable USB tipo B | 1 | Arduino UNO R3 |
| | Placa ESP32 DevKit v1 Variante sin shield: los seis servos se cablean directo a los GPIO y se manejan con ESP32Servo. Su conversor analógico (ADC) es de 12 bits (0 a 4095), no de 10 bits (0 a 1023) como el UNO, lo que cambia la cuenta del mapeo de los potenciómetros. | 1 | ESP32 DevKit |
| | Cable micro USB (con línea de datos) | 1 | ESP32 DevKit |
Equipamiento del taller
- Computadora
- Kit Brazo Robótico (DFRobot)
- Destornilladores Phillips
- Llave francesa
- Soldador
- Estaño
- Alicate
- Pinza de punta
- Pinza brusela
Plano de conexionado en protoboard
Deslizá horizontalmente para ver el plano completo
Deslizá horizontalmente para ver el plano completo
| Desde | Hacia | Color de cable |
|---|---|---|
| Arduino UNO · 5V | Bus + de la protoboard | rojo |
| Arduino UNO · GND | Bus − de la protoboard | negro |
| Fuente externa 5V/2A (+) | Bus + de servos (independiente) | rojo |
| Fuente externa 5V/2A (−) | Bus − de la protoboard | negro |
| Servo 1 · VCC | Bus + de servos (independiente) | rojo |
| Servo 1 · GND | Bus − de la protoboard | negro |
| Servo 1 · señal | Arduino UNO · 2 | azul |
| Servo 2 · VCC | Bus + de servos (independiente) | rojo |
| Servo 2 · GND | Bus − de la protoboard | negro |
| Servo 2 · señal | Arduino UNO · 3 | azul |
| Servo 3 · VCC | Bus + de servos (independiente) | rojo |
| Servo 3 · GND | Bus − de la protoboard | negro |
| Servo 3 · señal | Arduino UNO · 4 | azul |
| Servo 4 · VCC | Bus + de servos (independiente) | rojo |
| Servo 4 · GND | Bus − de la protoboard | negro |
| Servo 4 · señal | Arduino UNO · 5 | azul |
| Servo 5 · VCC | Bus + de servos (independiente) | rojo |
| Servo 5 · GND | Bus − de la protoboard | negro |
| Servo 5 · señal | Arduino UNO · 6 | azul |
| Servo 6 · VCC | Bus + de servos (independiente) | rojo |
| Servo 6 · GND | Bus − de la protoboard | negro |
| Servo 6 · señal | Arduino UNO · 7 | azul |
| Potenciómetro 1 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 1 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 1 · cursor | Arduino UNO · A0 | amarillo |
| Potenciómetro 2 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 2 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 2 · cursor | Arduino UNO · A1 | amarillo |
| Potenciómetro 3 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 3 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 3 · cursor | Arduino UNO · A2 | amarillo |
| Potenciómetro 4 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 4 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 4 · cursor | Arduino UNO · A3 | amarillo |
| Potenciómetro 5 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 5 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 5 · cursor | Arduino UNO · A4 | amarillo |
| Potenciómetro 6 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 6 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 6 · cursor | Arduino UNO · A5 | amarillo |
| Desde | Hacia | Color de cable |
|---|---|---|
| ESP32 DevKit v1 · 3V3 | Bus + de la protoboard | rojo |
| ESP32 DevKit v1 · GND | Bus − de la protoboard | negro |
| Fuente externa 5V/2A (+) | Bus + de servos (independiente) | rojo |
| Fuente externa 5V/2A (−) | Bus − de la protoboard | negro |
| Servo 1 · VCC | Bus + de servos (independiente) | rojo |
| Servo 1 · GND | Bus − de la protoboard | negro |
| Servo 1 · señal | ESP32 DevKit v1 · GPIO13 | azul |
| Servo 2 · VCC | Bus + de servos (independiente) | rojo |
| Servo 2 · GND | Bus − de la protoboard | negro |
| Servo 2 · señal | ESP32 DevKit v1 · GPIO14 | azul |
| Servo 3 · VCC | Bus + de servos (independiente) | rojo |
| Servo 3 · GND | Bus − de la protoboard | negro |
| Servo 3 · señal | ESP32 DevKit v1 · GPIO25 | azul |
| Servo 4 · VCC | Bus + de servos (independiente) | rojo |
| Servo 4 · GND | Bus − de la protoboard | negro |
| Servo 4 · señal | ESP32 DevKit v1 · GPIO26 | azul |
| Servo 5 · VCC | Bus + de servos (independiente) | rojo |
| Servo 5 · GND | Bus − de la protoboard | negro |
| Servo 5 · señal | ESP32 DevKit v1 · GPIO27 | azul |
| Servo 6 · VCC | Bus + de servos (independiente) | rojo |
| Servo 6 · GND | Bus − de la protoboard | negro |
| Servo 6 · señal | ESP32 DevKit v1 · GPIO32 | azul |
| Potenciómetro 1 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 1 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 1 · cursor | ESP32 DevKit v1 · GPIO36 | amarillo |
| Potenciómetro 2 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 2 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 2 · cursor | ESP32 DevKit v1 · GPIO39 | amarillo |
| Potenciómetro 3 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 3 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 3 · cursor | ESP32 DevKit v1 · GPIO34 | amarillo |
| Potenciómetro 4 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 4 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 4 · cursor | ESP32 DevKit v1 · GPIO35 | amarillo |
| Potenciómetro 5 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 5 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 5 · cursor | ESP32 DevKit v1 · GPIO33 | amarillo |
| Potenciómetro 6 · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro 6 · extremo B | Bus − de la protoboard | negro |
| Potenciómetro 6 · cursor | ESP32 DevKit v1 · GPIO4 | amarillo |
Conectar, calibrar y armar el brazo de 6 servomotores
Conectar los seis servomotores
El brazo tiene 6 motores (servomotoresServomotorMotor que se ubica en un ángulo específico (los comunes, de 0° a 180°) y se mantiene ahí. Se programa indicando el ángulo deseado; tiene tres cables: alimentación (VCC, GND) y señal.) que le permiten rotar y trasladarse en el espacio imitando el movimiento de un brazo humano. Cada servo mueve una articulación —base, hombro, codo, muñeca, rotación de la pinza y apertura/cierre de la pinza—, y juntos le dan al brazo sus 6 grados de libertad. Cada servo tiene tres cables: el naranja o amarillo es la señal (va al pin de datos), el rojo es el positivo (VCC) y el marrón o negro es el negativo (GND).
En el Arduino UNO usamos el IO Expansion ShieldShieldPlaca modular que se monta encimada sobre una placa Arduino, encastrando en sus pines, para agregarle funciones (pantalla, sensores, comunicación). Suele diseñarse para un modelo concreto: el LCD Keypad Shield tiene el formato del Arduino UNO y no encaja en un ESP32. de DFRobot, que se apila sobre la placa. Para cada pin ofrece una fila de tres contactos —señal, VCC y GND—, así que el servo se enchufa completo sin cablear la alimentación aparte. Conectamos los seis servos a los pines 2, 3, 4, 5, 6 y 7 (servo 1 = pin 2 … servo 6 = pin 7). El shield tiene dos grupos de 5 pines de un lado y dos grupos de 6 del otro: hay un solo modo de encastrarlo sin forzar las piezas.
El IO Shield de DFRobot está hecho para el formato del Arduino UNO y no encaja en un ESP32. Por
eso, en esta placa no se usa shield: cada servo se cablea directo, con la señal a un GPIO y
la alimentación a la fuente externa (con GND común). Conectamos los seis servos a los GPIO 13,
14, 25, 26, 27 y 32 (servo 1 = GPIO 13 … servo 6 = GPIO 32). En el ESP32 los servos se manejan
con la librería ESP32Servo (la Servo.h clásica del UNO no funciona en esta placa).
Como el consumo de corriente de seis servos es alto, el sistema necesita una fuente de alimentación externa de 5V 2A. No la tomamos del USB: seis servos moviéndose a la vez piden más corriente de la que da el puerto, y podrían resetear la placa o dañar su regulador. La fuente comparte GND con la placa.
Programar los ángulos de los servomotores (calibración)
Antes de armar el brazo hay que calibrar los servos: llevarlos a todos a 90°, el centro de su recorrido (que va de 0° a 180°). Con los seis en 90°, el brazo queda en una posición neutra desde la que puede girar por igual hacia ambos lados; si armáramos el brazo con los servos en una posición cualquiera, quedaría torcido y con menos margen de movimiento.
La programación la hacemos directamente en el lenguaje de Arduino (C++), con el Arduino IDE 2.x,
que se descarga gratis de arduino.cc/en/software. Un programa
de Arduino se llama sketch y tiene dos bloques: setup(), que se ejecuta una sola vez al
encender, y loop(), que se repite después. La calibración vive en setup(): el programa engancha
cada servo a su pin y le ordena write(90). Para no repetir seis veces lo mismo, guardamos los
pines en un arreglo y recorremos los seis con un for. En este nivel el loop() queda vacío
a propósito: la calibración es estática, los servos solo tienen que sostener los 90°.
Para el aula
Este código reemplaza al de bloques del original (mBlock3, discontinuado). El programa de bloques
generaba seis instrucciones servo_2.write(90) … servo_7.write(90) sueltas, más un andamiaje
(angle_rad, _delay, _loop) que el editor agregaba solo. La versión con un arreglo y un for
hace exactamente lo mismo con menos líneas y se lee mejor: “poné los seis servos en 90”. Es una
buena oportunidad para mostrar que un bucle no es “más difícil”, es menos repetición.
Subir el código a la placa
Para subir el programa, conectá la placa a la computadora, elegí en el Arduino IDE 2.x la placa y el puertoPuerto COMPuerto de comunicaciones por el cual el sistema operativo se comunica con un dispositivo externo, como la placa. Se asigna automáticamente al conectarla por USB y puede cambiar de número; en el IDE hay que indicar cuál usar. (en el IDE 2.x se eligen juntos en el desplegable de la barra superior) y apretá el botón Subir (la flecha →). Al terminar, aparece el mensaje de carga completada. Con la fuente externa conectada, los seis servomotores deberían moverse a su nueva posición de 90°.
Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto que se agregó al conectarla. La librería Servo ya viene incluida con el IDE. Si tu placa DFRobot no es reconocida, quizás necesites el driver del conversor CH340 (era un requisito del material original).
Para el ESP32 hay dos pasos que se hacen una sola vez: instalar el soporte de placas ESP32 desde el gestor de placas (buscá “esp32” e instalá el paquete de Espressif) y, desde el Gestor de bibliotecas, la librería ESP32Servo. Si el puerto no aparece, instalá el driver del conversor USB-serie (CP210x o CH340). Elegí la placa ESP32 Dev Module y el puerto. Si al subir queda en “Connecting…”, mantené apretado el botón BOOT.
Verificá
Con el código cargado y la fuente conectada, los seis servos tienen que girar hasta 90° y quedarse ahí, firmes. Si alguno no se mueve, revisá que sus tres cables estén en la fila correcta (señal, VCC, GND) y que la fuente externa esté encendida. Antes de energizar, despejá la zona: al arrancar, los servos saltan a 90° de golpe.
Armar el brazo robótico
Con los servos calibrados a 90°, recién ahora ensamblamos el brazo del kit sobre ellos: cada servo queda en el centro de su recorrido, así que las piezas mecánicas se montan derechas y el brazo puede moverse el mismo ángulo hacia un lado y hacia el otro. El material original acompaña el armado con un video paso a paso.
Si no contás con una mesa o superficie donde fijar la base del brazo, conviene imprimir en 3D unas piezas de soporte y lastrarlas con material de descarte (por ejemplo, metal) para darle estabilidad: un brazo que se mueve con fuerza necesita una base firme para no volcarse. Estas piezas forman parte de los paquetes de fabricación del proyecto, que se descargan desde su ficha.
Programar un movimiento sincronizado de los servomotores
Con el brazo armado, vemos su primer movimiento sincronizado: el mismo programa de calibración que subimos es el que produce ese movimiento. Cuando la placa arranca o se resetea, los seis servos se mueven a 90° al mismo tiempo, desde donde estuvieran hasta el centro. Ese salto coordinado de las seis articulaciones es la primera vez que el brazo se mueve como un conjunto: no seis motores sueltos, sino un cuerpo articulado que adopta una pose común.
Ver el código completo · brazo-calibracion.ino
// Brazo robotico — Nivel Inicial (Arduino UNO).
//
// CALIBRACION: los 6 servomotores se llevan a 90 grados (posicion central).
// Es el punto de partida obligatorio del armado: con todos los servos a 90 el
// brazo puede girar en ambos sentidos por igual, tal como pide el Paso 1 de la
// guia ("asegurarnos de que la posicion de los servomotores sea de 90").
//
// Plataforma: Arduino UNO con IO Shield de DFRobot (el shield expone los pines
// de servo con alimentacion; no cambia el numero de pin). Proyecto de BAJA
// TENSION (servos 5 V): no hay 220 V ni agua.
//
// ATENCION — ALIMENTACION: seis servos consumen demasiada corriente para el
// puerto USB. Es OBLIGATORIO usar la FUENTE EXTERNA 5V 2A con GND comun a la
// placa. Alimentar por USB puede resetear el Arduino o danar el regulador.
//
// ATENCION — MECANICA: el brazo tiene 6 grados de libertad y al energizarse
// salta a 90 grados de golpe. Despejar la zona de barrido antes de conectar.
//
// Nivel inicial: se permite delay() (fiel a la progresion original).
//
// Basado en: Fragmento de codigo del Nivel Inicial, Paso 2 — Brazo Robotico,
// Saberes Digitales (INET/EDUCAR 2019).
// Desvios respecto del original (ver README):
// (1) mBlock3 -> C++ limpio: se elimina el cruft del generador de bloques
// (angle_rad/angle_deg, _delay/_loop) y los nombres servo_2..servo_7.
// (2) Los seis servos se manejan con un arreglo indexado por constante, sin
// numeros magicos en el cuerpo del programa.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato UNO: pines digitales 2..7) ---
// Indice 0 = servo 1 (base) ... indice 5 = servo 6 (pinza).
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {2, 3, 4, 5, 6, 7};
// Angulo de calibracion: centro del recorrido 0..180.
const int ANGULO_CENTRO = 90;
Servo servos[CANT_SERVOS];
void setup() {
// Enganchar cada servo a su pin y llevarlo al centro. Al terminar, el brazo
// queda calibrado y listo para el armado mecanico.
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].attach(PINES_SERVO[i]);
servos[i].write(ANGULO_CENTRO);
}
}
void loop() {
// Calibracion estatica: no hay movimiento. Los seis servos sostienen los 90
// grados; el loop queda vacio a proposito.
} // Brazo robotico — Nivel Inicial (ESP32 DevKit v1).
//
// CALIBRACION: los 6 servomotores se llevan a 90 grados (posicion central),
// punto de partida del armado (Paso 1: "asegurarnos de que la posicion de los
// servomotores sea de 90"), para que el brazo pueda girar por igual en ambos
// sentidos.
//
// Plataforma: ESP32 (DevKit v1). Se usa la libreria ESP32Servo (la Servo.h
// clasica NO funciona en ESP32). El IO Shield de DFRobot es para UNO: en ESP32
// NO se usa shield, los servos se cablean directo a los GPIO (senal al GPIO,
// alimentacion a la fuente externa, GND comun).
//
// ATENCION — ALIMENTACION: seis servos NO se alimentan por USB. Es OBLIGATORIA
// la FUENTE EXTERNA 5V 2A con GND comun. El ESP32 trabaja a 3,3 V pero el pin
// de senal del servo tolera ese nivel; la potencia sale de la fuente de 5 V.
//
// ATENCION — MECANICA: 6 grados de libertad; al energizar, los servos saltan a
// 90 grados. Despejar la zona de barrido.
//
// Nivel inicial: se permite delay() (fiel a la progresion original).
//
// Basado en: Fragmento de codigo del Nivel Inicial, Paso 2 — Brazo Robotico,
// Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): mBlock3 -> C++ limpio (sin angle_rad/deg, sin
// _delay/_loop, sin servo_2..servo_7); GPIO seguros del contrato; sin shield.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato ESP32: GPIO 13,14,25,26,27,32 con ESP32Servo) ---
// Indice 0 = servo 1 (base) ... indice 5 = servo 6 (pinza).
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {13, 14, 25, 26, 27, 32};
const int ANGULO_CENTRO = 90;
Servo servos[CANT_SERVOS];
void setup() {
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].attach(PINES_SERVO[i]);
servos[i].write(ANGULO_CENTRO);
}
}
void loop() {
// Calibracion estatica: los seis servos sostienen los 90 grados.
} Podés ver cómo las seis articulaciones se acomodan a 90° en el simulador, antes de energizar el brazo real:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
De la pose fija a la coreografía
Hoy el brazo llega a una pose (todos a 90°) y se queda quieto: el loop() está vacío. ¿Cómo harían
para que repita un movimiento, por ejemplo mover la base a un lado, bajar el codo y volver, una
y otra vez? Pista: eso ya no va en setup() sino en loop(), y necesitan una espera entre
movimiento y movimiento para alcanzar a ver cada paso. Es exactamente la idea que van a desarrollar
en el nivel avanzado, cuando el brazo tome y coloque objetos en secuencia.
Cierre del nivel
Al terminar este nivel, tu brazo robótico ya existe físicamente: los seis servos están conectados y alimentados con la fuente externa, calibrados a 90° y con las piezas del kit ensambladas sobre ellos. Subiste el programa desde el Arduino IDE 2.x y viste a las seis articulaciones moverse de forma sincronizada. Pero por ahora el brazo hace un único gesto fijo. En el nivel intermedio vamos a darle un mando a distancia: seis potenciómetros con los que vas a mover cada articulación con la mano, como los controles de una grúa.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- Los seis servomotores están conectados al IO Shield de DFRobot (o directo a los GPIO en el ESP32) y alimentados con la fuente externa de 5V 2A, con GND común a la placa.
- Subiste desde el Arduino IDE 2.x el programa de calibración: al energizar, los seis servos se mueven a 90° de forma sincronizada y sostienen esa posición.
- El brazo del kit está ensamblado sobre los servos ya calibrados a 90°, de modo que puede girar por igual hacia ambos lados desde el centro.
- Entendés que seis servos necesitan la fuente externa (no alcanza el USB) y que, antes de energizar, hay que despejar la zona de movimiento del brazo.
Comandar el brazo a distancia con seis potenciómetros
Situación problema
En otro sector de la fábrica precisan un brazo robótico para manipular sustancias peligrosas para la salud de las personas. Por esa razón, necesitan un sistema que les permita controlar su accionar a distancia, evitando al máximo el contacto de los operarios con esas sustancias.
En esta instancia se agregan potenciómetros para controlar manualmente los servomotores: primero tres, y luego los seis (uno por cada articulación), para mover el brazo robótico completo como con un mando a distancia.
Armar el circuito con los potenciómetros
Si queremos controlar el brazo a distancia, podemos usar un potenciómetroPotenciómetroResistor cuyo valor de resistencia se ajusta de forma manual con una perilla. Conectando sus extremos a VCC y GND, la pata central entrega un nivel de tensión que representa la posición de la perilla y se lee por una entrada analógica del Arduino. para fijar la posición de cada servomotor: girás la perilla y el servo sigue ese ángulo. Vamos a montarlos sobre una protoboardProtoboardPlaca experimental para el prototipado rápido de circuitos: sus orificios permiten insertar y conectar componentes sin soldadura. Las líneas superior e inferior recorren la placa de punta a punta y suelen usarse para la alimentación.. Empezamos por tres potenciómetros, para los tres servos con los que ya venimos trabajando (base, codo y pinza); más adelante sumamos los otros tres.
Cada potenciómetro se conecta con sus dos extremos a VCC y GND y la pata central (el cursor) a una entrada analógica de la placa. En esa pata central aparece una tensión que representa la posición de la perilla, y eso es lo que la placa lee.
Las entradas analógicas de la placa se diferencian de las digitales en cuánto pueden leer. Una entrada digital solo distingue dos estados —alto (1) o bajo (0)— según si la tensión está más cerca de 5 V o de 0 V. Una entrada analógica, en cambio, mide la tensión con una escala de muchos valores intermedios: por eso sirve para leer la posición de un potenciómetro, que no es “sí o no” sino “cuánto”.
En el Arduino UNO las entradas analógicas son A0 a A5 (seis en total, justo las que necesitamos). El primer potenciómetro va a A0, y así hasta A5. Cada uno controla el servo del mismo índice: A0 → servo del pin 2, A1 → servo del pin 3, y así. El UNO lee estas entradas con una resolución de 10 bits: un número de 0 a 1023.
En el ESP32 las entradas analógicas son varios GPIO con ADC. Acá hay un detalle: el ESP32 tiene seis entradas cómodas en su ADC1 (GPIO 32, 33, 34, 35, 36 y 39), pero el GPIO 32 ya lo usamos como salida de un servo. Como este proyecto no usa WiFi, podemos leer el sexto potenciómetro por el ADC2 (GPIO 4), que queda libre. Así, los seis potes van a GPIO 36, 39, 34, 35, 33 y 4. Además, los potenciómetros del ESP32 se alimentan a 3,3 V (no a 5 V): su ADC mide de 0 a 3,3 V, y con 5 V se satura la lectura y se puede dañar el pin.
Crear las variables
Hasta ahora el ángulo de cada servo era un valor fijo (90°). A partir de acá, cada ángulo depende de la posición de su potenciómetro. Para trabajar con ese dato que cambia, lo guardamos en una variable: un espacio de memoria con un nombre, donde el programa anota un valor para usarlo más adelante.
En el código vas a ver un arreglo (PINES_POT) con los seis pines de los potenciómetros y, dentro
del loop(), una variable lectura donde se guarda lo que devuelve cada uno. Un detalle de los
nombres en código: no pueden empezar con un número, ni tener espacios, ni llevar tilde o Ñ (por
eso escribimos angulo, no ángulo).
Adaptar los valores (regla de tres)
Hay un desajuste de escalas: el servo se mueve entre 0° y 180°, pero la lectura del potenciómetro entrega otra escala. Necesitamos convertir una en otra con una regla de tres: multiplicar la lectura por 180 y dividir por el máximo que puede leer el ADC.
En el UNO el ADC lee de 0 a 1023, así que la cuenta es ángulo = lectura × 180 ÷ 1023. Es
exactamente la fórmula del material original. En el código se hace en la función lecturaAAngulo(),
que usa un tipo long para que el producto lectura × 180 no se desborde antes de dividir.
Acá aparece un desvío importante respecto del original. El ESP32 lee con 12 bits: de 0 a
4095, no de 0 a 1023 como el UNO. Si dividiéramos por 1023 (como el original, pensado solo para
UNO), el potenciómetro recorrería apenas un cuarto del ángulo. Por eso la cuenta es ángulo = lectura × 180 ÷ 4095: usamos el máximo real del ADC de esta placa para que la perilla cubra los
180° completos.
Para el aula
La regla de tres del ADC es un concepto transferible y un lindo cruce con matemática: mapear un rango a otro. Vale la pena que los grupos noten por qué el número del divisor cambia entre placas: no es un capricho, es la resolución del conversor (cuántos escalones distingue entre 0 V y el máximo). Un buen disparador: ¿qué pasaría si al ESP32 le dejáramos el ÷1023 del original? (El servo llegaría a 180° girando la perilla un cuarto de vuelta, y el resto no haría nada.)
Programar los servomotores restantes
Con un potenciómetro funcionando, el resto es repetir el mismo patrón para los demás: leer su
pin, convertir con la regla de tres y escribir el ángulo en su servo. En lugar de copiar y pegar
seis bloques casi iguales, el programa recorre los seis con un for: en cada vuelta lee
PINES_POT[i], lo convierte y lo escribe en servos[i]. Menos repetición, menos errores.
Algo importante del arranque: en setup() todos los servos se centran a 90° antes de tomar el
mando de su potenciómetro, para que el brazo parta de una pose conocida y no de un ángulo
cualquiera.
Controlar los seis servomotores
Para comandar el brazo completo sumamos los otros tres potenciómetros, hasta llegar a seis: uno
por articulación. El programa final es el mismo patrón para los seis: en cada vuelta del loop()
lee los seis potenciómetros y refleja cada uno en su servo. Una pequeña espera de refresco suaviza
el movimiento.
Ver el código completo · brazo-potenciometros.ino
// Brazo robotico — Nivel Intermedio (Arduino UNO).
//
// MANDO A DISTANCIA: seis potenciometros comandan los seis servomotores, uno
// por articulacion. Cada potenciometro actua como perilla de posicion: al
// girarlo, el servo correspondiente sigue su angulo. Es el "control remoto"
// que pide la situacion-problema (manipular sustancias peligrosas sin contacto).
//
// Plataforma: Arduino UNO con IO Shield de DFRobot. Las entradas analogicas
// A0..A5 leen los potenciometros; los pines 2..7 mandan los servos.
//
// ATENCION — ALIMENTACION: FUENTE EXTERNA 5V 2A obligatoria (6 servos no van
// por USB), GND comun. ATENCION — MECANICA: 6 grados de libertad; al mover los
// potenciometros el brazo puede golpear. Despejar la zona.
//
// Adaptacion de escala (regla de tres del Paso 3): el ADC del UNO entrega
// 0..1023 (10 bits) y el servo acepta 0..180 grados. Angulo = lectura*180/1023.
//
// Nivel intermedio: se permite delay() para dar una cadencia de refresco suave.
//
// Basado en: Fragmentos de codigo del Nivel Intermedio, Pasos 3 a 5 — Brazo
// Robotico, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README):
// (1) mBlock3 -> C++ limpio (sin angle_rad/deg, sin _delay/_loop, sin
// servo_2..servo_7): arreglos indexados y una funcion de mapeo reutilizable.
// (2) El original arrancaba tres potenciometros y despues sumaba tres mas; aca
// se entregan los SEIS desde el inicio (es el estado final del Paso 5).
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato UNO: pines digitales 2..7) ---
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {2, 3, 4, 5, 6, 7};
// --- Potenciometros (contrato UNO: entradas analogicas A0..A5) ---
// PIN_POT[i] controla PINES_SERVO[i] (misma articulacion).
const int PINES_POT[CANT_SERVOS] = {A0, A1, A2, A3, A4, A5};
// Escala del ADC del UNO: 10 bits -> valor maximo 1023.
const long ADC_MAX = 1023;
const int ANGULO_MAX = 180;
// Cadencia de refresco (ms). Suaviza la lectura sin bloquear percepcion.
const unsigned long REFRESCO_MS = 15;
Servo servos[CANT_SERVOS];
// Convierte una lectura del ADC (0..ADC_MAX) al angulo del servo (0..180)
// con la regla de tres del original. Se usa long para no desbordar en el
// producto lectura*180 antes de dividir.
int lecturaAAngulo(int lectura) {
return (int)(((long)lectura * ANGULO_MAX) / ADC_MAX);
}
void setup() {
for (int i = 0; i < CANT_SERVOS; i++) {
pinMode(PINES_POT[i], INPUT);
servos[i].attach(PINES_SERVO[i]);
// Arranque seguro: centrar cada servo antes de tomar el mando del pote.
servos[i].write(90);
}
}
void loop() {
// Leer los seis potenciometros y reflejar cada uno en su servo.
for (int i = 0; i < CANT_SERVOS; i++) {
int lectura = analogRead(PINES_POT[i]);
servos[i].write(lecturaAAngulo(lectura));
}
delay(REFRESCO_MS);
} // Brazo robotico — Nivel Intermedio (ESP32 DevKit v1).
//
// MANDO A DISTANCIA: seis potenciometros comandan los seis servomotores, uno
// por articulacion (el "control remoto" de la situacion-problema).
//
// Plataforma: ESP32 (DevKit v1), servos con ESP32Servo, SIN shield (cableado
// directo). El mapa de pines del ESP32 es AJUSTADO: 6 servos + 6 entradas
// analogicas es lo maximo que entra sin colisiones (ver README, tabla ESP32).
//
// Mapa resuelto SIN COLISIONES:
// Servos -> GPIO 13, 14, 25, 26, 27, 32
// Pots -> GPIO 36, 39, 34, 35 (ADC1 solo-entrada) + GPIO 33 (ADC1) + GPIO 4
// * GPIO 32 es SERVO, no pote: no puede ser las dos cosas. Por eso el pote que
// el contrato ubicaba en 32 se reubica a GPIO 4.
// * GPIO 4 es ADC2. El ADC2 solo entra en conflicto cuando el WiFi esta
// activo; este proyecto NO tiene IoT/WiFi, asi que GPIO 4 lee sin problemas.
//
// ATENCION — ALIMENTACION: FUENTE EXTERNA 5V 2A obligatoria (6 servos), GND
// comun. Los potenciometros se alimentan a 3,3 V (no a 5 V): el ADC del ESP32
// mide 0..3,3 V; con 5 V se satura la lectura y se dana el pin.
// ATENCION — MECANICA: 6 grados de libertad; al mover los potes el brazo puede
// golpear. Despejar la zona.
//
// Adaptacion de escala: el ADC del ESP32 entrega 0..4095 (12 bits, contra los
// 10 bits / 0..1023 del UNO). Angulo = lectura*180/4095.
//
// Nivel intermedio: se permite delay() para una cadencia de refresco suave.
//
// Basado en: Fragmentos de codigo del Nivel Intermedio, Pasos 3 a 5 — Brazo
// Robotico, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): mBlock3 -> C++ limpio; GPIO seguros; ADC de 12 bits
// (÷4095 en vez de ÷1023); reubicacion del pote 6 a GPIO 4 para evitar colision
// con el servo 6 (GPIO 32); los seis potes desde el inicio (estado final Paso 5).
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato ESP32) ---
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {13, 14, 25, 26, 27, 32};
// --- Potenciometros (mapa ESP32 resuelto sin colisiones) ---
// PINES_POT[i] controla PINES_SERVO[i].
// 36,39,34,35 = ADC1 solo-entrada | 33 = ADC1 | 4 = ADC2 (libre sin WiFi)
const int PINES_POT[CANT_SERVOS] = {36, 39, 34, 35, 33, 4};
// Escala del ADC del ESP32: 12 bits -> valor maximo 4095.
const long ADC_MAX = 4095;
const int ANGULO_MAX = 180;
const unsigned long REFRESCO_MS = 15;
Servo servos[CANT_SERVOS];
// Regla de tres: lectura del ADC (0..ADC_MAX) -> angulo del servo (0..180).
// long evita el desborde del producto lectura*180 antes de dividir.
int lecturaAAngulo(int lectura) {
return (int)(((long)lectura * ANGULO_MAX) / ADC_MAX);
}
void setup() {
for (int i = 0; i < CANT_SERVOS; i++) {
pinMode(PINES_POT[i], INPUT);
servos[i].attach(PINES_SERVO[i]);
servos[i].write(90); // arranque seguro: centrar antes de dar el mando
}
}
void loop() {
for (int i = 0; i < CANT_SERVOS; i++) {
int lectura = analogRead(PINES_POT[i]);
servos[i].write(lecturaAAngulo(lectura));
}
delay(REFRESCO_MS);
} Podés probar cómo cada potenciómetro mueve su articulación en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Para operar el mando con comodidad conviene pasar los potenciómetros de la protoboard a una carcasa definitiva, donde queden montados y firmes. Con un cableado más largo, incluso, se puede comandar el brazo desde lejos. La carcasa se imprime en 3D y sus archivos forman parte de los paquetes de fabricación del proyecto.
Verificá
Con el programa cargado, girá cada potenciómetro de a uno: el servo de esa articulación tiene que seguir la perilla, de 0° a 180°. Si un servo no recorre todo el rango, revisá el mapeo (el divisor del ADC de tu placa); si tiembla o salta, revisá que los extremos del potenciómetro estén firmes en VCC y GND. Despejá la zona antes de mover: con seis potes activos el brazo puede barrer un área amplia.
Que recuerde una pose
Hoy el brazo hace lo que dicen las perillas en tiempo real. ¿Cómo harían para que, apretando un botón, el brazo recuerde la pose actual (los seis ángulos) y pueda volver a ella más tarde? Pista: hace falta guardar seis valores en variables cuando se aprieta el botón. Es el germen de lo que viene en el nivel avanzado: una secuencia de poses guardadas que el brazo reproduce solo.
Cierre del nivel
Al terminar este nivel, el brazo dejó de tener una pose fija: lo comandás a voluntad con seis potenciómetros, uno por articulación, como un mando a distancia. En el camino aprendiste la diferencia entre entradas digitales y analógicas, a guardar lecturas en variables y a mapear la escala del ADC a los grados del servo (atendiendo a que el UNO y el ESP32 leen con distinta resolución). Pero mover el brazo con las manos no automatiza nada: cada movimiento lo hacés vos. En el nivel avanzado vamos a quitar los potenciómetros y programar al brazo para que ejecute solo una secuencia completa de tomar y colocar objetos.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- Sobre la protoboard están cableados los seis potenciómetros (extremos a VCC y GND, cursor a una entrada analógica) y cada uno comanda una articulación del brazo.
- El programa lee cada potenciómetro con analogRead() y convierte esa lectura al ángulo del servo con una regla de tres, contemplando la resolución del ADC de la placa (÷1023 en el UNO, ÷4095 en el ESP32).
- Al girar cada potenciómetro, el servo correspondiente sigue su posición: el brazo se mueve como con un mando a distancia.
- Entendés la diferencia entre una entrada digital (dos estados) y una analógica (una escala de valores) y por qué el potenciómetro necesita una entrada analógica.
Automatizar una secuencia de pick-and-place
Situación problema
En el sector de control de calidad también se requiere un brazo robótico. Este debe tomar los productos defectuosos que llegan por una cinta transportadora y colocarlos en otra, que los llevará al sector de revisión, donde se evaluará si se descartan o se reparan.
En esta actividad el brazo trabaja de forma sincronizada con sus seis servomotores, generando un movimiento completo y automático: toma un objeto de una cinta y lo deposita en otra, sin intervención de una persona.
Armar el circuito (sin potenciómetros)
En este nivel el brazo se mueve de forma automática, así que no usamos los potenciómetros: el movimiento ya no lo decide una persona girando perillas, sino el programa. El circuito vuelve a ser el del nivel inicial: los seis servomotores conectados y alimentados con la fuente externa de 5V 2A, con GND común.
En el Arduino UNO, los seis servos siguen en los pines 2 a 7 a través del IO Shield de DFRobot. Simplemente desconectamos los potenciómetros de las entradas analógicas: ya no hacen falta.
En el ESP32, los seis servos siguen cableados directo a los GPIO 13, 14, 25, 26, 27 y 32 y se manejan con ESP32Servo. Al no usar potenciómetros, los GPIO analógicos del nivel intermedio quedan libres.
Programar la secuencia de movimiento
Vamos a programar al brazo para que traslade objetos de una cinta a otra, ubicada a menor altura y a 90° de la primera. Es una operación de pick-and-place (tomar y colocar): la base de la automatización industrial.
La secuencia, paso a paso: partir con todo a 90°; girar la base hacia la cinta de origen y bajar la muñeca con la pinza abierta; cerrar la pinza para tomar el objeto; levantar; girar 90° hacia la cinta de destino; acomodar el codo y el hombro sobre esa cinta y descender; abrir la pinza para soltar el objeto; y volver todo a 90° para reiniciar el ciclo.
En vez de escribir una a una las órdenes a cada servo (como el original), esta reedición declara la coreografía como una tabla de poses. Una pose es el estado completo del brazo: los seis ángulos de las articulaciones más el tiempo que se sostiene antes de pasar a la siguiente. El programa recorre la tabla pose por pose. Así, cambiar el movimiento es editar una fila de números, no reescribir el código.
El avance entre poses no es bloqueante: en lugar de delay(), que congela todo el programa
mientras cuenta (código bloqueanteCódigo bloqueanteCódigo que detiene todo el procesamiento hasta que se cumple una condición, como delay(), que deja al programa 'colgado' durante el tiempo indicado. En IoT es conflictivo: impide atender tareas simultáneas como la publicación de datos y la conexión a internet.), usamos millis() —un
cronómetro que se consulta sin frenar nada— dentro de una máquina de
estadosMáquina de estadosForma de organizar un programa donde una variable guarda el estado actual del sistema y, en cada vuelta del loop, solo se ejecuta lo que corresponde a ese estado. El semáforo no bloqueante usa ocho estados que se recorren según el tiempo transcurrido.. En cada vuelta del loop(), el programa se pregunta si ya pasó el tiempo de la pose
actual; si sí, avanza a la siguiente. El loop() nunca se cuelga.
Ver el código completo · brazo-secuencia.ino
// Brazo robotico — Nivel Avanzado (Arduino UNO).
//
// SECUENCIA PICK-AND-PLACE: el brazo repite un ciclo automatico que simula
// tomar un objeto de una cinta transportadora y depositarlo en otra ubicada a
// 90 grados y a menor altura (control de calidad de la situacion-problema).
// Sin potenciometros: el movimiento es programado.
//
// Articulaciones (indice del arreglo):
// 0 = servo 1 (base, pin 2) 3 = servo 4 (pin 5)
// 1 = servo 2 (pin 3) 4 = servo 5 (pin 6)
// 2 = servo 3 (pin 4) 5 = servo 6 (PINZA, pin 7)
//
// Plataforma: Arduino UNO con IO Shield DFRobot.
// ATENCION — ALIMENTACION: FUENTE EXTERNA 5V 2A obligatoria, GND comun.
// ATENCION — MECANICA: 6 grados de libertad en movimiento AUTONOMO y continuo;
// el brazo barre un area amplia y puede golpear. Despejar la zona y tener a
// mano el corte de alimentacion antes de energizar.
//
// Nivel avanzado: NO-BLOQUEANTE. En vez de encadenar delay() (como el original
// mBlock3, que usaba _delay), la secuencia avanza con una maquina de estados
// basada en millis(). Asi el loop() nunca se congela y el patron queda listo
// para sumarle, en el futuro, sensores o paradas de emergencia sin reescribir.
//
// Basado en: Fragmento de codigo del Nivel Avanzado, Paso 2 — Brazo Robotico,
// Saberes Digitales (INET/EDUCAR 2019). Se conservan los angulos y el orden de
// movimientos del original.
// Desvios (ver README):
// (1) mBlock3 -> C++ limpio (sin angle_rad/deg, sin _delay/_loop, sin
// servo_2..servo_7).
// (2) delay()/_delay() bloqueante -> maquina de estados con millis() (estandar
// del nivel avanzado de la coleccion).
// (3) La secuencia se declara como tabla de POSES (angulo de las 6
// articulaciones + tiempo de espera), en vez de escrituras sueltas: mismo
// resultado, mucho mas legible y facil de reordenar (extension sugerida en
// el cierre del original).
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato UNO: pines digitales 2..7) ---
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {2, 3, 4, 5, 6, 7};
Servo servos[CANT_SERVOS];
// Una POSE es el estado completo del brazo: el angulo de cada una de las 6
// articulaciones mas cuanto tiempo se sostiene antes de pasar a la siguiente.
struct Pose {
int angulos[CANT_SERVOS];
unsigned long espera_ms;
};
// Secuencia pick-and-place. La pose 0 es el HOME de arranque (todos a 90) y se
// ejecuta una sola vez; el ciclo productivo son las poses 1..9, que se repiten
// para siempre. Los angulos y tiempos son los del listado original del Paso 2.
const Pose SECUENCIA[] = {
// {base, s2, s3, s4, s5, pinza} espera
{{ 90, 90, 90, 90, 90, 90}, 2000}, // 0 HOME: todos a 90, arranque estable
{{ 0, 90, 90, 130, 90, 90}, 1000}, // 1 girar la base hacia la cinta de origen
{{ 0, 90, 90, 90, 90, 0}, 1000}, // 2 bajar la muneca y abrir la pinza
{{ 0, 90, 90, 90, 90, 180}, 1000}, // 3 cerrar la pinza: toma el objeto
{{ 0, 90, 90, 130, 90, 180}, 1000}, // 4 levantar la muneca con el objeto
{{180, 90, 90, 130, 180, 180}, 1000}, // 5 girar 180 hacia la cinta de destino
{{180, 50, 40, 130, 180, 180}, 1000}, // 6 acomodar codo/hombro sobre el destino
{{180, 50, 40, 90, 180, 180}, 1000}, // 7 descender a la cinta de destino
{{180, 50, 40, 90, 180, 0}, 1000}, // 8 abrir la pinza: suelta el objeto
{{ 90, 90, 90, 90, 90, 0}, 1000}, // 9 volver el cuerpo a 90 y reiniciar ciclo
};
const int CANT_POSES = sizeof(SECUENCIA) / sizeof(SECUENCIA[0]);
int poseActual = 0;
unsigned long marcaTiempo = 0;
// Escribe en los seis servos los angulos de la pose indicada.
void aplicarPose(int idx) {
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].write(SECUENCIA[idx].angulos[i]);
}
}
void setup() {
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].attach(PINES_SERVO[i]);
}
// Arrancar en HOME y anotar el instante de inicio.
poseActual = 0;
aplicarPose(poseActual);
marcaTiempo = millis();
}
void loop() {
unsigned long ahora = millis();
// Cuando se cumplio la espera de la pose actual, avanzar a la siguiente.
if (ahora - marcaTiempo >= SECUENCIA[poseActual].espera_ms) {
marcaTiempo = ahora;
poseActual++;
// Al terminar el ciclo, volver a la pose 1 (no se repite el HOME de arranque).
if (poseActual >= CANT_POSES) {
poseActual = 1;
}
aplicarPose(poseActual);
}
} // Brazo robotico — Nivel Avanzado (ESP32 DevKit v1).
//
// SECUENCIA PICK-AND-PLACE: el brazo repite un ciclo automatico que simula
// tomar un objeto de una cinta y depositarlo en otra a 90 grados y a menor
// altura (control de calidad de la situacion-problema). Sin potenciometros.
//
// Articulaciones (indice del arreglo):
// 0 = servo 1 (base, GPIO 13) 3 = servo 4 (GPIO 26)
// 1 = servo 2 (GPIO 14) 4 = servo 5 (GPIO 27)
// 2 = servo 3 (GPIO 25) 5 = servo 6 (PINZA, GPIO 32)
//
// Plataforma: ESP32 (DevKit v1), servos con ESP32Servo, SIN shield (cableado
// directo).
// ATENCION — ALIMENTACION: FUENTE EXTERNA 5V 2A obligatoria, GND comun.
// ATENCION — MECANICA: 6 grados de libertad en movimiento AUTONOMO y continuo;
// el brazo barre un area amplia. Despejar la zona y tener a mano el corte de
// alimentacion antes de energizar.
//
// Nivel avanzado: NO-BLOQUEANTE. La secuencia avanza con una maquina de estados
// basada en millis() (no con delay()/_delay encadenados como el original
// mBlock3), asi el loop() nunca se congela.
//
// Basado en: Fragmento de codigo del Nivel Avanzado, Paso 2 — Brazo Robotico,
// Saberes Digitales (INET/EDUCAR 2019). Se conservan los angulos y el orden de
// movimientos del original.
// Desvios (ver README): mBlock3 -> C++ limpio; GPIO seguros; delay bloqueante
// -> maquina de estados con millis(); secuencia declarada como tabla de POSES.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Servomotores (contrato ESP32) ---
const int CANT_SERVOS = 6;
const int PINES_SERVO[CANT_SERVOS] = {13, 14, 25, 26, 27, 32};
Servo servos[CANT_SERVOS];
// Una POSE es el estado completo del brazo: angulo de las 6 articulaciones mas
// el tiempo que se sostiene antes de pasar a la siguiente.
struct Pose {
int angulos[CANT_SERVOS];
unsigned long espera_ms;
};
// Secuencia pick-and-place. Pose 0 = HOME de arranque (una sola vez); las poses
// 1..9 son el ciclo productivo que se repite. Angulos y tiempos del original.
const Pose SECUENCIA[] = {
// {base, s2, s3, s4, s5, pinza} espera
{{ 90, 90, 90, 90, 90, 90}, 2000}, // 0 HOME: todos a 90, arranque estable
{{ 0, 90, 90, 130, 90, 90}, 1000}, // 1 girar la base hacia la cinta de origen
{{ 0, 90, 90, 90, 90, 0}, 1000}, // 2 bajar la muneca y abrir la pinza
{{ 0, 90, 90, 90, 90, 180}, 1000}, // 3 cerrar la pinza: toma el objeto
{{ 0, 90, 90, 130, 90, 180}, 1000}, // 4 levantar la muneca con el objeto
{{180, 90, 90, 130, 180, 180}, 1000}, // 5 girar 180 hacia la cinta de destino
{{180, 50, 40, 130, 180, 180}, 1000}, // 6 acomodar codo/hombro sobre el destino
{{180, 50, 40, 90, 180, 180}, 1000}, // 7 descender a la cinta de destino
{{180, 50, 40, 90, 180, 0}, 1000}, // 8 abrir la pinza: suelta el objeto
{{ 90, 90, 90, 90, 90, 0}, 1000}, // 9 volver el cuerpo a 90 y reiniciar ciclo
};
const int CANT_POSES = sizeof(SECUENCIA) / sizeof(SECUENCIA[0]);
int poseActual = 0;
unsigned long marcaTiempo = 0;
void aplicarPose(int idx) {
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].write(SECUENCIA[idx].angulos[i]);
}
}
void setup() {
for (int i = 0; i < CANT_SERVOS; i++) {
servos[i].attach(PINES_SERVO[i]);
}
poseActual = 0;
aplicarPose(poseActual);
marcaTiempo = millis();
}
void loop() {
unsigned long ahora = millis();
if (ahora - marcaTiempo >= SECUENCIA[poseActual].espera_ms) {
marcaTiempo = ahora;
poseActual++;
if (poseActual >= CANT_POSES) {
poseActual = 1; // no se repite el HOME de arranque
}
aplicarPose(poseActual);
}
} Podés ver el ciclo completo —tomar, girar, colocar y volver— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Para el aula
Dos conceptos potentes conviven en este código. El primero es separar los datos de la lógica: la
tabla de poses son los datos (qué hace el brazo) y el loop() es la lógica (cómo los recorre).
Cambiar la coreografía no toca la lógica. El segundo es el paso de bloqueante a no bloqueante:
aunque acá no haya internet, avanzar con millis() deja el loop() libre para sumar, sin
reescribir, un sensor de color o una parada de emergencia —justo las extensiones que sugiere el
proyecto—. Vale la pena preguntar al grupo: con la versión de delay(), ¿podría el brazo “escuchar”
un botón de pánico en mitad de un movimiento? (No: estaría congelado.)
Verificá
Con el programa cargado y la fuente conectada, el brazo tiene que repetir el ciclo solo: girar hacia una cinta, cerrar la pinza, girar 90°, abrir la pinza y volver, una y otra vez. Si un movimiento queda corto o pasado, ajustá el ángulo de esa pose en la tabla; si va demasiado rápido para seguirlo, subí el tiempo de espera de las poses. Antes de energizar, despejá toda la zona de barrido: el brazo se mueve solo, con fuerza y de forma continua.
Elegir qué objeto llevar
Hoy el brazo trata a todos los objetos igual. ¿Cómo harían para que solo traslade los objetos de
cierto color (por ejemplo, los defectuosos rojos) y deje pasar el resto? Pista: haría falta un
sensor de color y una decisión (if) antes de arrancar la secuencia. Como el loop() no está
bloqueado, sumar esa lectura no obliga a reescribir la coreografía: es exactamente para lo que
dejamos el código no bloqueante.
Cierre del nivel
Al terminar este nivel, el brazo robótico quedó automatizado: sin nadie que lo comande, repite
una secuencia completa de tomar un objeto de una cinta y depositarlo en otra. Programaste esa
coreografía como una tabla de poses y la recorriste con una máquina de estados basada en
millis(), sin código bloqueante, de modo que el loop() queda libre para futuras mejoras. Con
esto cerrás el recorrido del proyecto: de calibrar seis servos y armar el brazo, a comandarlo a
distancia, a que trabaje solo. Si el grupo quiere seguir, en la ficha están las extensiones
sugeridas: cambiar la trayectoria, sumar un sensor de color para seleccionar objetos, o comandar
el brazo de forma inalámbrica por bluetooth o WiFi.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El circuito quedó sin potenciómetros: los seis servomotores están conectados y alimentados con la fuente externa, listos para un movimiento programado.
- El brazo repite solo una secuencia completa: toma un objeto de una cinta, gira 90° y lo deposita en otra cinta más baja, y vuelve a empezar.
- La secuencia está programada con una máquina de estados basada en millis() (sin código bloqueante), como una tabla de poses que el programa recorre una a una.
- Podés explicar por qué conviene declarar la coreografía como una tabla de poses y por qué el avance con millis() deja el loop() libre para futuras extensiones.