arduino-gimbal-estabilizador-camara

Cómo construir un gimbal de dos ejes con Arduino

  • 8 min

Un gimbal de dos ejes es un soporte motorizado que compensa inclinaciones en pitch y roll para mantener una carga aproximadamente nivelada.

Seguro que habéis visto esos vídeos fluidos de drones o cámaras deportivas que parecen flotar en el aire. Eso se consigue con un Gimbal. Su función es leer la inclinación del soporte y mover unos motores en sentido contrario exactamente los mismos grados para que la cámara permanezca siempre nivelada.

En este tutorial vamos a construir uno usando servomotores estándar. Aunque los gimbals profesionales usan motores Brushless (que son más rápidos y suaves), usar servos es la mejor forma de entender la matemática vectorial y el control PID sin complicarnos con drivers trifásicos costosos.

Este proyecto recicla conocimientos clave del Robot Equilibrista: lectura de IMU, fusión de sensores y bucles de control. Si te perdiste ese artículo, échale un ojo antes.

Pitch, roll y yaw

Para estabilizar un objeto en el espacio 3D, debemos controlar sus tres ejes de rotación (Ángulos de Euler):

  1. Pitch (Cabeceo): Inclinación hacia adelante/atrás (Eje Y).
  2. Roll (Alabeo): Inclinación lateral izquierda/derecha (Eje X).
  3. Yaw (Guiñada): Giro sobre su propio eje vertical (Eje Z).

En este proyecto haremos un gimbal de dos ejes (pitch y roll). Dejaremos fuera el yaw para simplificar la mecánica y el control; un tercer eje también puede tener un recorrido limitado según el diseño.

La lógica es:

Si la mano se inclina +10º, el servo debe girar -10º para compensar.

Componentes

  • [1x|blue] Arduino Nano | Compacto, ideal para montar en el gimbal
  • [1x|orange] Sensor MPU6050 | Acelerómetro + Giroscopio {I2C}
  • [2x|green] Servomotores | SG90 (ligero) o MG996R (fuerza)
  • [1x|red] Fuente externa regulada | 5V o la tensión nominal de los servos
  • [Varios|gray] Estructura 2 ejes | Impresión 3D (buscar “2 axis servo gimbal”)

La estructura mecánica es crítica. Necesitamos un soporte que permita mover dos ejes de forma ortogonal. Podéis imprimir piezas en 3D (buscad “2 axis servo gimbal” en Thingiverse) o unir dos soportes de plástico en forma de U.

Esquema de conexión

En este control directo, el MPU6050 debe quedar solidario al mango o marco fijo, no a la cámara. Así medimos la perturbación que queremos compensar. Si colocamos la IMU en la parte móvil, necesitaremos un controlador realimentado que actualice la orden a partir del error, no la asignación directa usada aquí.

Módulo MPU6050

El MPU6050 se comunica por I2C. Comprueba si tu módulo admite 5V o si debes alimentarlo a 3.3V.

[ { “from”: “Módulo MPU6050 VCC”, “to”: “5V Arduino”, “color”: “red”, “note”: “Solo si el módulo admite 5V” }, { “from”: “MPU6050 GND”, “to”: “GND Arduino”, “color”: “black” }, { “from”: “MPU6050 SDA”, “to”: “Pin A4”, “color”: “blue”, “note”: “Bus I2C” }, { “from”: “MPU6050 SCL”, “to”: “Pin A5”, “color”: “purple”, “note”: “Bus I2C” } ]

Servos del gimbal

Los servos corrigen pitch y roll. Alimentadlos desde una fuente externa si la cámara o el soporte pesan un poco.

[ { “from”: “Servo Pitch (Eje Y) señal”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Servo Roll (Eje X) señal”, “to”: “Pin 10”, “color”: “green” }, { “from”: “Servos VCC”, “to”: “Fuente externa”, “color”: “red”, “note”: “NO a Arduino” }, { “from”: “GND común”, “to”: “GND Arduino + fuente”, “color”: “black”, “note”: “Tierra común obligatoria” } ]

El código

Vamos a implementar un control directo con suavizado. Aunque un PID completo es lo ideal, para servos (que ya tienen su propia electrónica de control interna) suele bastar con un cálculo preciso del ángulo y un buen filtrado.

Usaremos la librería Wire.h para el sensor y Servo.h para los motores.

Variables y configuración

#include <Wire.h>
#include <Servo.h>

Servo servoPitch;
Servo servoRoll;

// Constantes de pines
const int pinPitch = 9;
const int pinRoll = 10;

// Variables MPU6050
int16_t accX, accY, accZ;
int16_t gyroX, gyroY, gyroZ;

// Ángulos calculados
float accAngleX, accAngleY;
float gyroAngleX, gyroAngleY;
float currentAngleX, currentAngleY;

// Variables de tiempo para el filtro
unsigned long elapsedTime, currentTime, previousTime;
float dt;

// Calibración (Offset)
// Ajusta estos valores para que la cámara mire al frente al iniciar
int pitchOffset = 90; 
int rollOffset = 90;

void setup() {
  Serial.begin(9600);
  
  // Iniciar MPU6050
  Wire.begin();
  Wire.beginTransmission(0x68);
  Wire.write(0x6B); 
  Wire.write(0x00); // Despertar sensor
  Wire.endTransmission(true);

  servoPitch.attach(pinPitch);
  servoRoll.attach(pinRoll);
  
  // Posición inicial segura
  servoPitch.write(pitchOffset);
  servoRoll.write(rollOffset);
  
  previousTime = millis();
}
Copied!

Lectura y filtro complementario

Dentro del loop, leemos los datos crudos y fusionamos acelerómetro (estable pero lento) con giroscopio (rápido pero con deriva).

void loop() {
  // 1. Gestión del tiempo (Delta Time)
  currentTime = millis();
  dt = (currentTime - previousTime) / 1000.0;
  previousTime = currentTime;

  if (dt <= 0 || dt > 0.1) return;

  // 2. Leer datos del MPU
  readMPU();

  // 3. Calcular ángulo con Acelerómetro (Trigonometría)
  // Convertimos vectores de gravedad en ángulos
  accAngleX = (atan(accY / sqrt(pow(accX, 2) + pow(accZ, 2))) * 180 / PI);
  accAngleY = (atan(-1 * accX / sqrt(pow(accY, 2) + pow(accZ, 2))) * 180 / PI);

  // 4. Calcular ángulo con Giroscopio (Integración)
  // gyroX/131.0 convierte la lectura raw a grados/segundo
  float gyroRateX = gyroX / 131.0;
  float gyroRateY = gyroY / 131.0;

  // 5. Filtro Complementario
  // Confiamos un 96% en el giroscopio y un 4% en el acelerómetro para corregir la deriva
  currentAngleX = 0.96 * (currentAngleX + gyroRateX * dt) + 0.04 * accAngleX;
  currentAngleY = 0.96 * (currentAngleY + gyroRateY * dt) + 0.04 * accAngleY;

  // 6. Mover los Servos
  moverGimbal();
}

void readMPU() {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B); // Registro inicial
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 6, true);
  
  accX = (Wire.read() << 8 | Wire.read());
  accY = (Wire.read() << 8 | Wire.read());
  accZ = (Wire.read() << 8 | Wire.read());
  
  // Saltamos temperatura
  Wire.beginTransmission(0x68);
  Wire.write(0x43); // Registro giroscopio
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 6, true);
  
  gyroX = (Wire.read() << 8 | Wire.read());
  gyroY = (Wire.read() << 8 | Wire.read());
  gyroZ = (Wire.read() << 8 | Wire.read());
}
Copied!

Lógica de corrección

Aquí traducimos el ángulo de inclinación a movimiento del servo.

  • Si el sensor detecta que la cámara está a +20º (inclinada a la derecha), el servo debe moverse a 90 - 20 = 70º para ponerla recta.
void moverGimbal() {
  // EJE X (Roll)
  // Mapeamos y corregimos. 
  // Nota: Los servos van de 0 a 180.
  // Es posible que tengas que cambiar el signo (+) por (-) según cómo montes el servo.
  int servoPosRoll = rollOffset + currentAngleX; 
  
  // EJE Y (Pitch)
  int servoPosPitch = pitchOffset - currentAngleY;

  // Limitamos para no forzar la mecánica
  servoPosRoll = constrain(servoPosRoll, 0, 180);
  servoPosPitch = constrain(servoPosPitch, 0, 180);

  servoRoll.write(servoPosRoll);
  servoPitch.write(servoPosPitch);
  
  // Debug (opcional)
  /*
  Serial.print("Roll: "); Serial.print(currentAngleX);
  Serial.print(" | Pitch: "); Serial.println(currentAngleY);
  */
}
Copied!

Suavizado de la salida

Si usas el código anterior, verás que funciona, pero los servos pueden “temblar” un poco (jitter). Esto es porque el acelerómetro es muy sensible.

Para solucionarlo, podemos aplicar un suavizado exponencial a la salida del servo.

Sustituye la función moverGimbal por esta versión mejorada:

float smoothRoll = 90;
float smoothPitch = 90;
float alpha = 0.1; // Factor de suavizado (0.1 = muy suave/lento, 0.9 = rápido)

void moverGimbal() {
  float targetRoll = constrain(rollOffset + currentAngleX, 0, 180);
  float targetPitch = constrain(pitchOffset - currentAngleY, 0, 180);
  
  // Filtro paso bajo en la salida
  smoothRoll = (alpha * targetRoll) + ((1.0 - alpha) * smoothRoll);
  smoothPitch = (alpha * targetPitch) + ((1.0 - alpha) * smoothPitch);
  
  servoRoll.write((int)smoothRoll);
  servoPitch.write((int)smoothPitch);
}
Copied!

Limitaciones y evolución

Has construido un gimbal funcional, ¡enhorabuena! Pero notarás sus límites:

  1. Velocidad: Los servos tienen engranajes. No son instantáneos. Si agitas el gimbal muy rápido, la cámara tardará un poco en reaccionar.
  2. Ruido: Los servos digitales hacen ruido constante al corregir.

Motores brushless para gimbal

Los gimbals profesionales (como los DJI o Zhiyun) usan motores Brushless Gimbal Motors.

  • No tienen engranajes (Direct Drive) -> Cero holgura.
  • Son silenciosos y ultrarrápidos.
  • El problema: No se pueden controlar con Arduino directamente. Necesitas controladores complejos con tres puentes H y algoritmos de control vectorial (FOC).

Antes de usar el montaje con una cámara, calibra el sesgo del giroscopio con la estructura inmóvil y limita los ángulos según la mecánica. El ejemplo sirve para experimentar, pero no ofrece la suavidad ni la precisión de un gimbal comercial.