Automatizar una secuencia de pick-and-place
Recorrido del nivel
Esa barra de arriba te dice en qué paso estás. Tocala para ver el índice completo y saltar a cualquier paso.
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.
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.