arduino-robot-omni-3wd-kiwi

Cómo construir un robot omnidireccional 3WD con Arduino

  • 7 min

Un robot holonómico es una plataforma que puede controlar de forma independiente sus movimientos en el plano dentro de los límites de sus actuadores.

Hoy vamos a romper esa limitación. Vamos a construir un robot holonómico.

Un sistema holonómico es aquel que puede controlar todos sus grados de libertad de forma independiente. En cristiano: puede moverse en cualquier dirección del plano (X, Y) y rotar sobre sí mismo simultáneamente e independientemente. Puede ir “de lado” sin girar el morro, o avanzar haciendo círculos sobre su eje.

Para lograr esto, usaremos las famosas Ruedas Omni.

Omni vs Mecanum: Aunque ambas permiten movimiento lateral, son distintas.

  • Omni: Tienen rodillos pequeños perpendiculares (90º) al eje principal.
  • Mecanum: Tienen rodillos a 45º. Hoy nos centraremos en las Omni, usando una configuración triangular de 3 ruedas (conocida como Kiwi Drive).

Vectores a 120 grados

El secreto de este robot no está en el código complejo, sino en la trigonometría.

Tenemos 3 motores dispuestos en un triángulo equilátero, separados 120º entre sí. Cuando queremos mover el robot en una dirección (vector de movimiento), necesitamos descomponer esa fuerza en la dirección específica a la que apunta cada rueda.

Si definimos:

  • : Velocidad lineal deseada.
  • : Ángulo de movimiento deseado.
  • : Velocidad de rotación angular (giro sobre sí mismo).

La velocidad () que debemos aplicar a cada motor se calcula proyectando el vector de movimiento sobre el eje de cada rueda.

(Estos ángulos dependen de cómo definas el frente del robot y el sentido positivo de cada motor, pero la separación de 120° se mantiene.)

La gracia de esta fórmula es que, al sumar los vectores de fuerza de las tres ruedas, las componentes no deseadas se cancelan entre sí. El resultado es un movimiento limpio en la dirección elegida.

Componentes

  • [1x|blue] Arduino Uno | O Nano
  • [3x|green] Servos de Rotación Continua | FS90R o MG996R trucados
  • [3x|orange] Ruedas Omni | Compatibles con el eje de los servos
  • [1x|gray] Chasis circular o triangular | Impresión 3D o metacrilato
  • [1x|red] Fuente o batería regulada | Tensión compatible y corriente suficiente para los tres servos
  • ¿Por qué servos? Porque integran la reductora y el driver. Se controlan con un solo cable de señal sin necesidad de un L298N o shields de motores.

Esquema de conexión

Al usar servos continuos, solo necesitamos tres pines digitales para las señales de control. La librería Servo genera los pulsos y no exige pines PWM.

Señales de los servos

Cada servo recibe una señal independiente desde Arduino.

[ { “from”: “Servo 1 (Frontal Izq) señal”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Servo 2 (Frontal Der) señal”, “to”: “Pin 10”, “color”: “green” }, { “from”: “Servo 3 (Trasero) señal”, “to”: “Pin 11”, “color”: “green” } ]

Alimentación de los servos

Los servos no deben alimentarse desde el pin de 5V de Arduino. Usa una batería o fuente adecuada y comparte GND.

[ { “from”: “Servos VCC (todos, rojos)”, “to”: “Batería (+)”, “color”: “red”, “note”: “NO a Arduino” }, { “from”: “Servos GND (todos, negros)”, “to”: “Batería (-) + GND Arduino”, “color”: “black”, “note”: “Tierra común obligatoria” } ]

El código

Vamos a implementar una función moverRobot(angulo, velocidad, giro) que encapsule toda la trigonometría.

Para los servos de rotación continua:

  • Señal 1500 (o 90º) = Stop.
  • Señal 2000 (o 180º) = Máxima velocidad en un sentido.
  • Señal 1000 (o 0º) = Máxima velocidad en sentido contrario.

Debemos mapear el resultado de nuestras fórmulas (que será un número entre -1 y 1) a estos valores de pulso.

Definiciones y setup

#include <Servo.h>

Servo rueda1; // Frontal Izquierda
Servo rueda2; // Frontal Derecha
Servo rueda3; // Trasera

// Pines
const int PIN_S1 = 9;
const int PIN_S2 = 10;
const int PIN_S3 = 11;

// Constante para pasar de grados a radianes
const float DEG_TO_RAD = 0.01745329251;

void setup() {
  Serial.begin(9600);
  
  rueda1.attach(PIN_S1);
  rueda2.attach(PIN_S2);
  rueda3.attach(PIN_S3);
  
  detener();
  delay(2000);
}
Copied!

Núcleo matemático

Esta función recibe hacia dónde queremos ir (ángulo en grados), cómo de rápido (0.0 a 1.0) y si queremos rotar mientras lo hacemos.

void moverOmni(float anguloGrados, float velocidad, float rotacion) {
  // Convertimos el ángulo a radianes
  float anguloRad = anguloGrados * DEG_TO_RAD;

  // --- FÓRMULAS CINEMÁTICAS (Kiwi Drive) ---
  // Calculamos la potencia necesaria para cada motor
  // La disposición asume rueda 3 detrás (270º), rueda 2 a 30º, rueda 1 a 150º
  
  float v1 = velocidad * cos(anguloRad - (150 * DEG_TO_RAD)) + rotacion;
  float v2 = velocidad * cos(anguloRad - (30 * DEG_TO_RAD)) + rotacion;
  float v3 = velocidad * cos(anguloRad - (270 * DEG_TO_RAD)) + rotacion;

  // --- NORMALIZACIÓN ---
  // Si pedimos mucha velocidad y mucha rotación, la suma puede superar 1.0
  // Buscamos el valor máximo para escalar todo proporcionalmente
  float maxVal = max(abs(v1), max(abs(v2), abs(v3)));
  
  if (maxVal > 1.0) {
    v1 /= maxVal;
    v2 /= maxVal;
    v3 /= maxVal;
  }

  // --- ENVIAR A MOTORES ---
  // Mapeamos de (-1.0, 1.0) a (1000, 2000) microsegundos
  // 1500 es parado.
  
  escribirServo(rueda1, v1);
  escribirServo(rueda2, v2);
  escribirServo(rueda3, v3);
}

// Función auxiliar para traducir velocidad normalizada a pulso de servo
void escribirServo(Servo &s, float val) {
  // Ajusta estos valores si tus servos giran al revés
  int microsegundos = 1500 + (val * 500); 
  s.writeMicroseconds(microsegundos);
}

void detener() {
  rueda1.writeMicroseconds(1500);
  rueda2.writeMicroseconds(1500);
  rueda3.writeMicroseconds(1500);
}
Copied!

loop de prueba

Para probarlo, haremos que el robot “baile”: se moverá lateralmente (Strafe) sin girar, y luego rotará.

void loop() {
  // 1. Moverse hacia adelante (0 grados)
  moverOmni(0, 0.5, 0); 
  delay(1000);
  
  // 2. Moverse hacia la derecha (90 grados) SIN GIRAR el chasis
  // Aquí es donde se ve el efecto Omni
  moverOmni(90, 0.5, 0);
  delay(1000);
  
  // 3. Moverse hacia atrás (180 grados)
  moverOmni(180, 0.5, 0);
  delay(1000);
  
  // 4. Moverse hacia izquierda (270 grados)
  moverOmni(270, 0.5, 0);
  delay(1000);
  
  // 5. Pirueta: Rotar sobre su eje
  moverOmni(0, 0, 0.5); 
  delay(2000);
  
  detener();
  delay(2000);
}
Copied!

Entender el movimiento

Si analizas el código y el comportamiento físico, verás algo curioso: Cuando le pides ir a la derecha (90º), la rueda trasera (Rueda 3) empuja, pero las ruedas delanteras (1 y 2) giran en sentidos opuestos para cancelar el avance y generar solo desplazamiento lateral.

Es una coreografía de fuerzas vectorial en tiempo real.

Desafíos y calibración

Este robot es muy sensible a la mecánica:

  1. Centro de Gravedad: Si el peso no está centrado en el triángulo, una rueda patinará más que las otras y el robot no irá recto.
  2. Superficie: Las ruedas omni necesitan una superficie dura y lisa. En alfombras funcionan fatal porque los rodillos pequeños se atascan.
  3. Calibración de servos: El punto de 1500 µs no siempre coincide con la parada exacta. Tendrás que ajustar el neutro de cada unidad y posiblemente aplicar factores distintos a sus velocidades.

Los servos de rotación continua se controlan en velocidad, no en posición, y dos unidades no responden exactamente igual. Este montaje sirve para experimentar con la cinemática, pero para una trayectoria precisa convienen motores con encoders y control de velocidad en lazo cerrado.

Siguiente paso

Hemos dominado los 3 ejes a 120º. Pero, ¿qué pasa si queremos más tracción y estabilidad? Ahí es donde entran las 4 ruedas y la configuración más popular en la industria: Las Ruedas Mecanum.

Pero eso… lo veremos en el próximo tutorial.