Domina el MPU6050: Tutorial acelerómetro y giroscopio para Arduino
En este tutorial aprenderemos a usar el sensor MPU6050 Acelerómetro y Giroscopio con Arduino. Primero explico cómo funciona el MPU6050 y cómo leer sus datos, luego haremos dos ejemplos prácticos. Puedes ver el vídeo o leer el tutorial escrito. En el primer ejemplo, usando el entorno Processing, haremos una visualización 3D de la orientación del sensor. En el segundo, una plataforma autoestabilizada simple / DIY Gimbal. Basándonos en la orientación del MPU6050 y sus datos fusionados (acelerómetro + giroscopio), controlamos tres servos que mantienen la plataforma nivelada. El MPU6050 IMU integra en un solo chip: acelerómetro 3 ejes y giroscopio 3 ejes. El giroscopio mide velocidad rotacional (tasa de cambio de posición angular en el tiempo) en ejes X, Y, Z. Usa tecnología MEMS y efecto Coriolis (detalles en mi tutorial Cómo funcionan sensores MEMS). Salidas en grados/segundo; para obtener posición angular integramos la velocidad angular. El acelerómetro MPU6050 mide aceleración igual que el ADXL345 (explicado en vídeo anterior). Mide aceleración gravitatoria en 3 ejes; con trigonometría calculamos el ángulo de inclinación del sensor. Si fusionamos (combinamos) datos de acelerómetro y giroscopio, obtenemos información muy precisa sobre la orientación del sensor. El MPU6050 IMU también se llama dispositivo de seguimiento de movimiento 6 ejes o 6 DoF (6 Grados de Libertad), por sus 6 salidas (3 acelerómetro + 3 giroscopio). Veamos cómo conectar y leer datos del MPU6050 con Arduino. Usamos protocolo I2C: solo 2 hilos para comunicación + 2 para alimentación. Componentes necesarios: Código para leer datos del MPU6050. Descripción detallada debajo. Descripción del código: Incluimos En Opcionalmente, podemos seleccionar rango completo (Full-Scale Range) para acelerómetro y giroscopio via registros de configuración. Usaremos default ±2g (acel) y ±250°/s (gyro), así que dejo comentado. En Para leer todos, empezamos en primer registro y con Para obtener valores -1g a +1g (aptos para calcular ángulos), dividimos por la sensibilidad seleccionada. Con estas fórmulas calculamos roll y pitch desde datos acelerómetro: Luego leemos giroscopio mismo método. Leemos 6 registros giroscopio, combinamos datos y dividimos por sensibilidad para obtener °/s. Corrijo salidas con pequeños errores calculados (explico cómo obtenerlos). Salidas en °/s → multiplicar por tiempo para obtener grados. Tiempo capturado cada iteración con Finalmente, fusionamos acelerómetro y giroscopio con filtro complementario. Tomamos 96% giroscopio (muy preciso, no sufre fuerzas externas) y 4% acelerómetro (elimina deriva giroscopio a largo plazo). No podemos calcular Yaw desde acelerómetro → no aplicamos filtro complementario en Yaw. Antes de ver resultados, explico cómo obtener valores de corrección de error. Llamamos a Imprimimos valores en monitor serial y los implementamos en el código (roll, pitch y 3 ejes giroscopio). Finalmente, Para visualización 3D, recibimos datos que Arduino envía por puerto serial en entorno Processing. Código completo Processing: Leemos datos entrantes de Arduino y los ponemos en variables Roll, Pitch, Yaw. En Al ejecutar, vemos que el objeto 3D sigue la orientación del sensor con buena precisión y respuesta. Inconveniente: Yaw deriva con el tiempo (no podemos usar filtro complementario). Para mejorar, hace falta sensor adicional: magnetómetro (corrección deriva Yaw giroscopio). Pero el MPU6050 tiene Digital Motion Processor (DMP) para cálculos onboard y eliminar deriva Yaw. Mismo ejemplo 3D con DMP: seguimiento preciso sin deriva Yaw. Procesador onboard también calcula y salida Cuaterniones (representan orientaciones/rotaciones 3D sin bloqueo Gimbal que ocurre con ángulos Euler). Obtener estos datos es más complejo: hace falta cable extra a pin digital Arduino (pin interrupción para leer este tipo de datos del MPU6050). Código más complejo → usamos librería i2cdevlib de Jeff Rowberg (descargable de GitHub). Incluiré enlace en artículo web. Instalada librería, abrimos ejemplo Podemos seleccionar salida: cuaterniones, ángulos Euler, yaw/pitch/roll, valores aceleración, cuaterniones para visualización 3D. Librería incluye sketch Processing para visualización 3D. Lo modifiqué para forma caja como ejemplo anterior. Código Processing para Con Para no sobrecargar este tutorial, el segundo ejemplo (DIY Arduino Gimbal / Plataforma autoestabilizada) está en artículo separado. ¡Pregunta en comentarios y revisa mi Colección de Proyectos Arduino!Resumen
Cómo funciona

Arduino y MPU6050

Código Arduino MPU6050
/* Arduino y MPU6050 Acelerómetro y Giroscopio por Dejan, https://howtomechatronics.com */
#include <Wire.h>
const int MPU = 0x68; // Dirección I2C MPU6050
float AccX, AccY, AccZ;
float GyroX, GyroY, GyroZ;
float accAngleX, accAngleY, gyroAngleX, gyroAngleY, gyroAngleZ;
float roll, pitch, yaw;
float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY, GyroErrorZ;
float elapsedTime, currentTime, previousTime;
int c = 0;
void setup() {
Serial.begin(19200);
Wire.begin(); // Inicializa comunicación I2C
Wire.beginTransmission(MPU); // Inicia comunicación con MPU6050
Wire.write(0x6B); // Registro 6B (PWR_MGMT_1)
Wire.write(0x00); // Reset - pone 0 en registro 6B
Wire.endTransmission(true);
/*
// Configurar sensibilidad acelerómetro - Rango completa (default ±2g)
Wire.beginTransmission(MPU);
Wire.write(0x1C); // Registro ACCEL_CONFIG (1C hex)
Wire.write(0x10); // Bits 00010000 (±8g rango completo)
Wire.endTransmission(true);
// Configurar sensibilidad giroscopio - Rango completa (default ±250°/s)
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Registro GYRO_CONFIG (1B hex)
Wire.write(0x10); // Bits 00010000 (1000°/s rango completo)
Wire.endTransmission(true);
delay(20);
*/
// Llama a esta función si necesitas valores de error IMU para tu módulo
calculate_IMU_error();
delay(20);
}
void loop() {
// === Leer datos acelerómetro ===
Wire.beginTransmission(MPU);
Wire.write(0x3B); // Registro inicial 0x3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true); // Lee 6 registros (2 por eje)
// Rango ±2g: dividir valores raw por 16384 (según datasheet)
AccX = (Wire.read() << 8 | Wire.read()) / 16384.0; // X
AccY = (Wire.read() << 8 | Wire.read()) / 16384.0; // Y
AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0; // Z
// Calcular Roll y Pitch desde datos acelerómetro
accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) - 0.58; // AccErrorX ~(0.58)
accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) + 1.58; // AccErrorY ~(-1.58)
// === Leer datos giroscopio ===
previousTime = currentTime;
currentTime = millis();
elapsedTime = (currentTime - previousTime) / 1000.0; // Segundos
Wire.beginTransmission(MPU);
Wire.write(0x43); // Registro inicial giroscopio 0x43
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true); // Lee 6 registros
// Rango 250°/s: dividir raw por 131.0 (datasheet)
GyroX = (Wire.read() << 8 | Wire.read()) / 131.0;
GyroY = (Wire.read() << 8 | Wire.read()) / 131.0;
GyroZ = (Wire.read() << 8 | Wire.read()) / 131.0;
// Corregir salidas con errores calculados
GyroX = GyroX + 0.56; // GyroErrorX ~(-0.56)
GyroY = GyroY - 2; // GyroErrorY ~(2)
GyroZ = GyroZ + 0.79; // GyroErrorZ ~(-0.8)
// Raw en °/s → multiplicar por segundos para obtener ángulo en grados
gyroAngleX = gyroAngleX + GyroX * elapsedTime;
gyroAngleY = gyroAngleY + GyroY * elapsedTime;
yaw = yaw + GyroZ * elapsedTime;
// Filtro complementario - combinar ángulos acelerómetro y giroscopio
roll = 0.96 * gyroAngleX + 0.04 * accAngleX;
pitch = 0.96 * gyroAngleY + 0.04 * accAngleY;
// Imprimir en monitor serial
Serial.print(roll); Serial.print("/");
Serial.print(pitch); Serial.print("/");
Serial.println(yaw);
}
void calculate_IMU_error() {
// Llamar en setup para calcular errores acelerómetro y giroscopio.
// Imprime valores de error usados en ecuaciones anteriores.
// Nota: colocar IMU en posición plana para valores correctos.
// Leer acelerómetro 200 veces
while (c < 200) {
Wire.beginTransmission(MPU);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
AccX = (Wire.read() << 8 | Wire.read()) / 16384.0;
AccY = (Wire.read() << 8 | Wire.read()) / 16384.0;
AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0;
// Sumar lecturas
AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI));
AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI));
c++;
}
// Promedio error
AccErrorX = AccErrorX / 200;
AccErrorY = AccErrorY / 200;
c = 0;
// Leer giroscopio 200 veces
while (c < 200) {
Wire.beginTransmission(MPU);
Wire.write(0x43);
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
GyroX = Wire.read() << 8 | Wire.read();
GyroY = Wire.read() << 8 | Wire.read();
GyroZ = Wire.read() << 8 | Wire.read();
// Sumar lecturas
GyroErrorX = GyroErrorX + (GyroX / 131.0);
GyroErrorY = GyroErrorY + (GyroY / 131.0);
GyroErrorZ = GyroErrorZ + (GyroZ / 131.0);
c++;
}
// Promedio error
GyroErrorX = GyroErrorX / 200;
GyroErrorY = GyroErrorY / 200;
GyroErrorZ = GyroErrorZ / 200;
// Imprimir errores en Serial Monitor
Serial.print("AccErrorX: "); Serial.println(AccErrorX);
Serial.print("AccErrorY: "); Serial.println(AccErrorY);
Serial.print("GyroErrorX: "); Serial.println(GyroErrorX);
Serial.print("GyroErrorY: "); Serial.println(GyroErrorY);
Serial.print("GyroErrorZ: "); Serial.println(GyroErrorZ);
}Wire.h para I2C y definimos variables para almacenar datos.setup(): inicializamos Wire y reseteamos sensor via registro gestión de potencia. En datasheet vemos dirección del registro.
// Configure Accelerometer Sensitivity - Full Scale Range (default +/- 2g)
Wire.beginTransmission(MPU);
Wire.write(0x1C); //Talk to the ACCEL_CONFIG register (1C hex)
Wire.write(0x10); //Set the register bits as 00010000 (+/- 8g full scale range)
Wire.endTransmission(true);
// Configure Gyro Sensitivity - Full Scale Range (default +/- 250deg/s)
Wire.beginTransmission(MPU);
Wire.write(0x1B); // Talk to the GYRO_CONFIG register (1B hex)
Wire.write(0x10); // Set the register bits as 00010000 (1000deg/s full scale)
Wire.endTransmission(true);
*/loop() leemos acelerómetro. Datos de cada eje en 2 bytes/registros; direcciones en datasheet.
requestFrom() pedimos 6 registros (X, Y, Z). Leemos cada registro, combinamos apropiadamente (complemento a dos) para valores correctos.// === Read acceleromter data ===
Wire.beginTransmission(MPU);
Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true); // Read 6 registers total
//For a range of +-2g, we need to divide the raw values by 16384, according to the datasheet
AccX = (Wire.read() << 8 | Wire.read()) / 16384.0; // X-axis value
AccY = (Wire.read() << 8 | Wire.read()) / 16384.0; // Y-axis value
AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0; // Z-axis value
// Calculating Roll and Pitch from the accelerometer data
accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) - 0.58; // AccErrorX ~(0.58)
accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) + 1.58; // AccErrorY ~(-1.58)
// === Read gyroscope data ===
previousTime = currentTime;
currentTime = millis();
elapsedTime = (currentTime - previousTime) / 1000;
Wire.beginTransmission(MPU);
Wire.write(0x43); // Gyro data first register address 0x43
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
GyroX = (Wire.read() << 8 | Wire.read()) / 131.0;
GyroY = (Wire.read() << 8 | Wire.read()) / 131.0;
GyroZ = (Wire.read() << 8 | Wire.read()) / 131.0;
millis().// Correct the outputs with the calculated error values
GyroX = GyroX + 0.56; // GyroErrorX ~(-0.56)
GyroY = GyroY - 2; // GyroErrorY ~(2)
GyroZ = GyroZ + 0.79; // GyroErrorZ ~ (-0.8)
// Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by sendonds (s) to get the angle in degrees
gyroAngleX = gyroAngleX + GyroX * elapsedTime; // deg/s * s = deg
gyroAngleY = gyroAngleY + GyroY * elapsedTime;
yaw = yaw + GyroZ * elapsedTime;// Complementary filter - combine acceleromter and gyro angle values
roll = 0.96 * gyroAngleX + 0.04 * accAngleX;
pitch = 0.96 * gyroAngleY + 0.04 * accAngleY;calculate_IMU_error() en setup() con sensor en posición plana quieta. Hacemos 200 lecturas por eje, sumamos y dividimos entre 200. En posición plana, salida esperada = 0. Así obtenemos error promedio del sensor.void calculate_IMU_error() {
// We can call this funtion in the setup section to calculate the accelerometer and gyro data error.
// From here we will get the error values used in the above equations printed on the Serial Monitor.
// Note that we should place the IMU flat in order to get the proper values, so that we then can the correct values
// Read accelerometer values 200 times
while (c < 200) {
Wire.beginTransmission(MPU);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
AccX = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
AccY = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
AccZ = (Wire.read() << 8 | Wire.read()) / 16384.0 ;
// Sum all readings
AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI));
AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI));
c++;
}
//Divide the sum by 200 to get the error value
AccErrorX = AccErrorX / 200;
AccErrorY = AccErrorY / 200;
c = 0;
// Read gyro values 200 times
while (c < 200) {
Wire.beginTransmission(MPU);
Wire.write(0x43);
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
GyroX = Wire.read() << 8 | Wire.read();
GyroY = Wire.read() << 8 | Wire.read();
GyroZ = Wire.read() << 8 | Wire.read();
// Sum all readings
GyroErrorX = GyroErrorX + (GyroX / 131.0);
GyroErrorY = GyroErrorY + (GyroY / 131.0);
GyroErrorZ = GyroErrorZ + (GyroZ / 131.0);
c++;
}
//Divide the sum by 200 to get the error value
GyroErrorX = GyroErrorX / 200;
GyroErrorY = GyroErrorY / 200;
GyroErrorZ = GyroErrorZ / 200;
// Print the error values on the Serial Monitor
Serial.print("AccErrorX: "); Serial.println(AccErrorX);
Serial.print("AccErrorY: "); Serial.println(AccErrorY);
Serial.print("GyroErrorX: "); Serial.println(GyroErrorX);
Serial.print("GyroErrorY: "); Serial.println(GyroErrorY);
Serial.print("GyroErrorZ: "); Serial.println(GyroErrorZ);
}
Serial.print muestra Roll, Pitch, Yaw para verificar funcionamiento.MPU6050 Seguimiento orientación – Visualización 3D
/* Arduino y MPU6050 IMU - Visualización 3D por Dejan, https://howtomechatronics.com */
import processing.serial.*;
import java.awt.event.KeyEvent;
import java.io.IOException;
Serial myPort;
String data="";
float roll, pitch, yaw;
void setup() {
size(2560, 1440, P3D);
myPort = new Serial(this, "COM7", 19200); // Comunicación serial
myPort.bufferUntil('\n');
}
void draw() {
translate(width/2, height/2, 0);
background(233);
textSize(22);
text("Roll: " + int(roll) + " Pitch: " + int(pitch), -100, 265);
// Rotar objeto
rotateX(radians(-pitch));
rotateZ(radians(roll));
rotateY(radians(yaw));
// Objeto 3D
textSize(30);
fill(0, 76, 153);
box(386, 40, 200); // Dibujar caja
textSize(25);
fill(255, 255, 255);
text("www.HowToMechatronics.com", -183, 10, 101);
}
// Leer datos del Puerto Serial
void serialEvent(Serial myPort) {
data = myPort.readStringUntil('\n');
if (data != null) {
data = trim(data);
// Separar string en "/"
String items[] = split(data, '/');
if (items.length > 1) {
roll = float(items[0]);
pitch = float(items[1]);
yaw = float(items[2]);
}
}
}draw() usamos estos valores para rotar objeto 3D (caja con color y texto).



MPU6050_DMP6. Bien comentado línea a línea.
MPU6050_DMP6 con salida "OUTPUT_TEAPOT":/* Arduino y MPU6050 IMU - Visualización 3D por Dejan, https://howtomechatronics.com */
import processing.serial.*;
import java.awt.event.KeyEvent;
import java.io.IOException;
Serial myPort;
String data="";
float roll, pitch, yaw;
void setup() {
size(2560, 1440, P3D);
myPort = new Serial(this, "COM7", 19200);
myPort.bufferUntil('\n');
}
void draw() {
translate(width/2, height/2, 0);
background(233);
textSize(22);
text("Roll: " + int(roll) + " Pitch: " + int(pitch), -100, 265);
rotateX(radians(-pitch));
rotateZ(radians(roll));
rotateY(radians(yaw));
textSize(30);
fill(0, 76, 153);
box(386, 40, 200);
textSize(25);
fill(255, 255, 255);
text("www.HowToMechatronics.com", -183, 10, 101);
}
void serialEvent(Serial myPort) {
data = myPort.readStringUntil('\n');
if (data != null) {
data = trim(data);
String items[] = split(data, '/');
if (items.length > 1) {
roll = float(items[0]);
pitch = float(items[1]);
yaw = float(items[2]);
}
}
}serialEvent() recibimos cuaterniones del Arduino, y en draw() los usamos para rotar objeto 3D. Al ejecutar, vemos lo buenos que son los cuaterniones para rotar objetos en 3D.
Proceso de manufactura
- Crea un Cerrojo RFID Seguro con Arduino – Guía Paso a Paso
- Crea Animaciones y Juegos en LCD 16×2 con Arduino UNO
- Control Preciso de Servo con Arduino y MPU6050 (SG90 / MG996R)
- Control Arduino UNO desde Python 3 – Envío de Comandos y Demo LED
- Construye una Radio FM Compacta con Arduino Nano y RDA5807M
- Cálculo de ángulos de inclinación con MPU6050 y Arduino Nano usando Visuino
- Proyecto Sensor de Huella Dactilar con Arduino Nano – Tutorial Paso a Paso
- Construye un Mini Piano con Arduino UNO – Tutorial Paso a Paso
- Construye un Portátil Raspberry Pi 3 y Arduino: Guía Paso a Paso
- Reconocimiento de Gestos con ESP32 y Acelerómetro Arduino 101 - Tutorial DIY Fácil
- Tutorial Arduino para Principiantes 01: Inicia tu Primer Proyecto