Saltar al contenido
Nivel avanzado

Automatizar una secuencia de pick-and-place

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.

Ilustración de un brazo robótico de seis articulaciones montado entre dos cintas transportadoras: una superior de la que la pinza toma un objeto y otra inferior, dispuesta a 90°, donde lo deposita.
El brazo trasladando objetos entre dos cintas transportadoras (figura original del proyecto): toma de la cinta superior y deposita en la inferior, ubicada a 90° y a menor altura.

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 bloqueante), usamos millis() —un cronómetro que se consulta sin frenar nada— dentro de una máquina de estados. 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-secuencia.ino Arduino UNO
// 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-secuencia.ino ESP32
// 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);
  }
}
Secuencia pick-and-place: la coreografía es una tabla de poses (seis ángulos + tiempo) que una máquina de estados con millis() recorre sin bloquear el loop(). Entre plataformas solo cambian los pines y la librería del servo.

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.

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.