¿Cuánto cuesta saber hacia dónde apunta un objeto, qué tan inclinado está y a qué altura va? Con una sola placa de 32 x 27 mm y cuatro cables, casi nada. Este tutorial arma una unidad de orientación completa con el sensor DFRobot Gravity 10DOF (SEN0696) y una Seeed Studio XIAO ESP32 S3, y termina con algo que se ve: un bloque 3D en el navegador que gira igual que el sensor que tienes en la mano.
Vas a aprender tres cosas, en este orden: leer los diez canales del sensor por I2C y entender qué significa cada número, graficarlos en vivo con el Serial Plotter del IDE de Arduino sin que unas curvas aplasten a otras, y fusionarlos con un filtro Madgwick para obtener roll, pitch y yaw que el ESP32 sirve por WiFi. Sirve para drones, robots balancín, control de cámaras, cascos que detectan caídas o simplemente para entender cómo tu celular sabe hacia dónde está mirando.
Concepto: qué son los "10 grados de libertad"
El nombre engaña un poco: no son diez ejes de movimiento, sino diez magnitudes medidas. Son tres ejes de aceleración, tres de velocidad de giro, tres de campo magnético y uno de presión barométrica. Cada grupo resuelve un problema distinto y tiene una debilidad que los otros tapan:
- El acelerómetro ve la gravedad, así que sabe qué es "abajo". Con eso calcula inclinación (roll y pitch). Su punto débil: cualquier sacudida se confunde con gravedad.
- El giroscopio mide qué tan rápido gira el sensor. Integrando esa velocidad obtienes el ángulo, con una respuesta muy rápida y sin ruido de vibraciones. Su punto débil: el error de integración se acumula y el ángulo se va corriendo con los minutos (deriva).
- El magnetómetro apunta al norte magnético. Es lo único que fija el yaw (el rumbo) de forma absoluta. Su punto débil: cualquier fierro, motor o parlante cercano lo desvía.
- El barómetro mide presión absoluta, y como la presión del aire cae cerca de 1 hPa cada 8,4 m, sirve de altímetro.
La gracia de un sensor 10DOF está en combinar todo: el giroscopio da la respuesta rápida y el acelerómetro con el magnetómetro corrigen la deriva lenta. Eso es exactamente lo que hace el filtro del tercer ejemplo.
Hardware: el sensor por dentro
DFRobot construyó la SEN0696 con tres chips de Bosch Sensortec y un microcontrolador propio que los lee a los tres y entrega todo junto por una sola interfaz, I2C o UART. Para ti eso significa una sola dirección en el bus y una sola llamada de librería, en vez de tres drivers distintos.

BMI323: acelerómetro y giroscopio
Es una IMU de seis ejes de bajo consumo en un encapsulado de 2,5 x 3,0 mm. El acelerómetro tiene rangos de ±2, ±4, ±8 y ±16 g con salida de 16 bits: a ±2 g eso da 16384 cuentas por g, y a ±16 g, 2048. El giroscopio va de ±125 a ±2000 °/s, con 131,1 cuentas por °/s en el rango de ±250. Ambos pueden muestrear hasta 6,4 kHz.
Lo interesante es que el BMI323 corre algoritmos de movimiento dentro del chip: detecta toques, movimiento significativo, quietud, cambio de orientación, posición plana y pasos, y avisa por pines de interrupción. Un podómetro o un "despertar al levantar" no necesitan gastar CPU del ESP32.
Qué rango elegir. Más rango no es mejor: con ±16 g pierdes ocho veces la resolución de ±2 g. Para medir inclinación de algo que se mueve a mano, ±2 g y ±250 °/s (lo que usan los ejemplos) es lo correcto. Sube el rango solo si mides impactos o giros rápidos, como un dron haciendo acrobacias, porque si la señal pasa el tope, el sensor satura y el filtro recibe basura.
BMM350: magnetómetro
Usa tecnología TMR (magnetorresistencia de efecto túnel) en un chip de 1,28 x 1,28 mm. Mide ±2000 µT por eje con unos 0,1 µT de resolución. El campo de la Tierra está entre 25 y 65 µT según dónde estés (Chile queda en el extremo bajo de ese rango), así que sobra rango. Muestrea hasta 400 Hz. Es el ancla del yaw: sin él, el rumbo del bloque 3D se iría girando solo.
BMP581: barómetro
Mide de 300 a 1250 hPa con una precisión relativa típica de ±6 Pa y una resolución de 1/64 Pa. Traducido a altura, detecta cambios bastante menores a un metro. Trae filtro IIR configurable, mide temperatura con ±0,5 K y consume apenas 1,3 µA a 1 Hz, ideal para aparatos a batería.
Conector, interrupciones y DIP switch
La placa trae un conector Gravity PH2.0 de 4 pines, un header de 2,54 mm para protoboard, dos agujeros de montaje de 3 mm separados 20 mm y cuatro pads de interrupción. Viene con su cable Gravity de 4 hilos y el header incluido, así que no necesitas comprar conectores aparte.

| Pin | Serigrafía | Modo I2C | Modo UART |
|---|---|---|---|
| 1 | + | Alimentación 3,3 a 5 V | Alimentación 3,3 a 5 V |
| 2 | , | Tierra | Tierra |
| 3 | C/R | SCL (reloj) | RXD (recepción) |
| 4 | D/T | SDA (datos) | TXD (transmisión) |
La serigrafía ya te da la pista: C/R es "clock o receive" y D/T es "data o transmit".
Las interrupciones se reparten así: INT1 e INT2 son del BMI323 (dato listo, toque, movimiento, pasos, orientación), INT3 del BMM350 (dato nuevo del magnetómetro) e INT4 del BMP581 (dato listo o presión fuera de rango). Ningún ejemplo de este tutorial las usa, pero sirven para despertar al ESP32 sin tener que preguntarle al sensor a cada rato.
El DIP switch elige el modo y la dirección I2C:
| Interruptor | Posición | Efecto |
|---|---|---|
| Modo | I2C | C/R pasa a SCL, D/T pasa a SDA |
| Modo | UART | C/R pasa a RXD, D/T pasa a TXD, Modbus RTU |
| A0, A1 | 0, 0 | Dirección 0x4A (de fábrica) |
| A0, A1 | 1, 0 | Dirección 0x4B |
| A0, A1 | 0, 1 | Dirección 0x4C |
| A0, A1 | 1, 1 | Dirección 0x4D |
Todos los ejemplos usan I2C en 0x4A: modo en I2C, A0 y A1 en 0.

Ojo con un detalle que confunde a muchos: el modo queda guardado aunque cortes la energía, y un cambio en el switch solo se aplica al reiniciar el módulo. Si mueves el switch con todo encendido y el sketch sigue sin encontrar el sensor, desenchúfalo y vuelve a enchufarlo.
La XIAO ESP32 S3
Es una placa del tamaño de un pulgar con un ESP32-S3 de dos núcleos Xtensa LX7 a 240 MHz, WiFi, Bluetooth LE y USB-C para programar. Para este proyecto importa que su I2C está en D4 (SDA, GPIO5) y D5 (SCL, GPIO6), y que su lógica es de 3,3 V.


Conexiones
Antes de cablear, revisa el DIP switch (I2C, A0 y A1 en 0). Después, el cable Gravity va al conector PH2.0 del sensor y sus cuatro puntas a la XIAO:
| Pin del sensor | Serigrafía | Pin de la XIAO ESP32 S3 |
|---|---|---|
| VCC | + | 3V3 |
| GND | , | GND |
| SCL | C/R | D5 (GPIO6) |
| SDA | D/T | D4 (GPIO5) |
El sensor acepta 5 V, pero aliméntalo con 3,3 V: así las líneas I2C trabajan al mismo nivel que la XIAO y no dependes de que el módulo traslade niveles.

Así queda el montaje real. Para los dos primeros ejemplos no hace falta la antena WiFi de la XIAO, pero conéctala antes del tercero: sin ella el ESP32-S3 suele conectarse mal o no conectarse.

Revisa los cuatro cables antes de energizar: invertir alimentación puede dañar el sensor. El "+" de la serigrafía es el positivo y el ", ", tierra.
Si usas otra placa, cada sketch declara los pines I2C arriba. Cambia estos dos valores por los SDA y SCL de tu placa y el resto queda igual:
const uint8_t I2C_SDA_PIN = 5;
const uint8_t I2C_SCL_PIN = 6;
En un ESP32 DevKit clásico serían 21 y 22.
Software: lo que tienes que instalar
- IDE de Arduino 2.x (también funciona 1.8 reciente).
- Core ESP32 de Espressif. En Archivo > Preferencias agrega esta URL al gestor de placas:
https://espressif.github.io/arduino-esp32/package_esp32_index.json. Después, en Herramientas > Placa > Gestor de placas, busca "esp32" e instala el paquete de Espressif Systems. - Placa. En Herramientas > Placa elige "XIAO_ESP32S3" (según la versión del core aparece como "Seeed Studio XIAO ESP32S3").
- USB CDC On Boot en Enabled (menú Herramientas). Sin esto, el Monitor Serie de la XIAO suele quedar mudo. Es la causa número uno de "el código está cargado y no veo nada".
- Tres librerías, que se instalan desde ZIP con Programa > Incluir librería > Añadir biblioteca .ZIP:
- DFRobot_RTU: capa Modbus base, es dependencia de la siguiente.
- DFRobot_Multi_DOF_IMU: el driver de la SEN0696.
- MadgwickAHRS: el filtro de orientación del tercer ejemplo.
Reinicia el IDE y comprueba que las tres aparecen en Programa > Incluir librería. Si falta DFRobot_Multi_DOF_IMU, instala primero DFRobot_RTU y repite la importación.
Ejemplo 1: los diez canales en el Monitor Serie
El primer sketch es la prueba de vida: si ves números que cambian al mover el sensor, el cableado, el switch y las librerías están bien.
#include "DFRobot_Multi_DOF_IMU.h"
#include <Wire.h>
const uint8_t IMU_ADDR = 0x4A;
const bool CALCULATE_ALTITUDE = true;
const float REFERENCE_ALTITUDE = 540.0f;
const uint8_t I2C_SDA_PIN = 5;
const uint8_t I2C_SCL_PIN = 6;
DFRobot_Multi_DOF_IMU_I2C imu(DFRobot_Multi_DOF_IMU::eSensorModel10DOF, &Wire, IMU_ADDR);
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
while (!imu.begin()) {
Serial.printf("Can't find sensor!\n");
delay(1000);
}
imu.setSensorMode(DFRobot_Multi_DOF_IMU::eNormalMode);
imu.setAccelRange(DFRobot_Multi_DOF_IMU::eAccelRange2G);
imu.setGyroRange(DFRobot_Multi_DOF_IMU::eGyroRange250DPS);
if (CALCULATE_ALTITUDE) {
imu.calibrateAltitude(REFERENCE_ALTITUDE);
}
Serial.printf("\nReading sensor data...\n");
delay(500);
}
void loop() {
DFRobot_Multi_DOF_IMU::sSensorData_t accel, gyro, mag;
float pressureAlt;
if (imu.get10dofData(&accel, &gyro, &mag, &pressureAlt, CALCULATE_ALTITUDE)) {
Serial.printf("================== 10DOF IMU Data ==================\n");
Serial.printf("Accelerometer (g): X=%.3f Y=%.3f Z=%.3f\n", accel.x, accel.y, accel.z);
Serial.printf("Gyroscope (dps): X=%.2f Y=%.2f Z=%.2f\n", gyro.x, gyro.y, gyro.z);
Serial.printf("Magnetometer (uT): X=%.2f Y=%.2f Z=%.2f\n", mag.x, mag.y, mag.z);
if (CALCULATE_ALTITUDE) {
Serial.printf("Altitude: %.2f m\n", pressureAlt);
} else {
Serial.printf("Pressure: %.2f Pa (%.2f hPa)\n", pressureAlt, pressureAlt / 100.0);
}
Serial.printf("====================================================\n\n");
} else {
Serial.printf("Error: failed to read sensor data!\n\n");
}
delay(500);
}
Qué hace cada parte
- Constantes.
IMU_ADDRes la dirección 0x4A.CALCULATE_ALTITUDEelige si el cuarto valor sale como altura en metros o como presión en pascales.REFERENCE_ALTITUDEes la altura sobre el nivel del mar del lugar donde estás. - El objeto
imu. Se crea con el modelo (10DOF), el busWirey la dirección. Un solo objeto lee los tres chips. setup(). Abre el serial a 115200, arranca el I2C en los pines elegidos y se queda reintentando cada segundo hasta que el sensor responde. Si el "Can't find sensor!" se repite para siempre, revisa cables y switch y aprieta reset en la XIAO. Después fija modo normal, ±2 g y ±250 °/s.calibrateAltitude(). El barómetro solo sabe de presión. Para convertirla en altura necesita un punto de referencia: la altura real donde estás ahora.loop(). Una llamada aget10dofData()llena tres estructuras (x, y, z de aceleración en g, giro en °/s y campo en µT) y un float con altura o presión.
Ajusta REFERENCE_ALTITUDE a tu ciudad. El 540 del ejemplo es del lugar donde se escribió el original. Santiago centro anda cerca de 570 m, Concepción y Valparaíso casi a nivel del mar, Calama sobre los 2.200 m. Cualquier mapa en línea o el GPS del celular te da tu altura.

Con el sensor plano sobre la mesa, Z debería marcar cerca de 1 g: es la gravedad. El magnetómetro cambia al girarlo y la altura sube si lo levantas.
Por qué la altura "baila". La presión atmosférica cambia con el clima: un frente que llega puede moverla varios hPa en un día, o sea decenas de metros de altura aparente. El barómetro es excelente para cambios relativos de corto plazo (subir una escalera) y malo como altímetro absoluto sin recalibrar. Si necesitas altura fiable durante horas, recalibra con una referencia conocida o combínalo con GPS.
Ejemplo 2: nueve curvas en el Serial Plotter
El segundo sketch manda los nueve canales de movimiento al Serial Plotter. El truco está en normalizarlos.
#include "DFRobot_Multi_DOF_IMU.h"
#include <Wire.h>
const uint8_t IMU_ADDR = 0x4A;
const uint8_t I2C_SDA_PIN = 5;
const uint8_t I2C_SCL_PIN = 6;
const float ACCEL_REF_G = 2.0f;
const float GYRO_REF_DPS = 250.0f;
const float MAG_REF_UT = 50.0f;
const uint32_t SAMPLE_INTERVAL_MS = 50;
DFRobot_Multi_DOF_IMU_I2C imu(DFRobot_Multi_DOF_IMU::eSensorModel10DOF, &Wire, IMU_ADDR);
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
while (!imu.begin()) {
Serial.printf("Can't find sensor!\n");
delay(1000);
}
imu.setSensorMode(DFRobot_Multi_DOF_IMU::eNormalMode);
imu.setAccelRange(DFRobot_Multi_DOF_IMU::eAccelRange2G);
imu.setGyroRange(DFRobot_Multi_DOF_IMU::eGyroRange250DPS);
Serial.printf("\nStreaming IMU data to the Serial Plotter...\n");
delay(500);
}
void loop() {
DFRobot_Multi_DOF_IMU::sSensorData_t accel, gyro, mag;
float pressureAlt;
if (imu.get10dofData(&accel, &gyro, &mag, &pressureAlt, false)) {
Serial.printf("AccX:%.1f,AccY:%.1f,AccZ:%.1f,",
100.0f * accel.x / ACCEL_REF_G,
100.0f * accel.y / ACCEL_REF_G,
100.0f * accel.z / ACCEL_REF_G);
Serial.printf("GyrX:%.1f,GyrY:%.1f,GyrZ:%.1f,",
100.0f * gyro.x / GYRO_REF_DPS,
100.0f * gyro.y / GYRO_REF_DPS,
100.0f * gyro.z / GYRO_REF_DPS);
Serial.printf("MagX:%.1f,MagY:%.1f,MagZ:%.1f\n",
100.0f * mag.x / MAG_REF_UT,
100.0f * mag.y / MAG_REF_UT,
100.0f * mag.z / MAG_REF_UT);
} else {
Serial.printf("Error: failed to read sensor data!\n");
}
delay(SAMPLE_INTERVAL_MS);
}
Por qué normalizar
El Serial Plotter tiene un solo eje Y que se autoescala. La aceleración se mueve en ±2 g, el giro en ±250 °/s y el campo magnético en ±50 µT: si mandas los valores crudos, el giroscopio estira la escala y las curvas de aceleración quedan como líneas planas. Por eso cada valor sale como porcentaje de su rango de referencia, y las nueve curvas comparten más o menos el tramo de -100 a +100. Un g da 50 % (100 × 1,0 / 2,0) y un giro de 250 °/s da 100 %.
El formato que espera el Plotter es una muestra por línea, con pares etiqueta:valor separados por comas y un salto de línea al final. Por eso los dos primeros printf terminan en coma y solo el último lleva \n. Con SAMPLE_INTERVAL_MS en 50 salen 20 muestras por segundo: curvas suaves sin cargar al ESP32.
El barómetro se lee pero no se imprime: su magnitud (alrededor de 95.000 Pa) aplastaría las otras nueve curvas.
Para usarlo, cierra el Monitor Serie (solo una herramienta puede tener el puerto), abre Herramientas > Serial Plotter y pon 115200 baudios.

Inclina el sensor y reaccionan las de aceleración; gíralo y saltan las del giroscopio; acércale un imán y se disparan las del magnetómetro. Esa última prueba te muestra de paso por qué el magnetómetro necesita estar lejos de motores y parlantes.
Ejemplo 3: el bloque 3D por WiFi
El tercer ejemplo junta todo: un filtro Madgwick fusiona los nueve canales en roll, pitch y yaw, y un servidor web en el ESP32 entrega una página con un bloque 3D (Three.js) que copia la orientación del sensor.
#include "credentials.h"
#include "page_html.h"
#include <WebServer.h>
#include <WiFi.h>
#include <Wire.h>
#include "DFRobot_Multi_DOF_IMU.h"
#include <MadgwickAHRS.h>
const uint8_t IMU_ADDR = 0x4A;
const uint8_t I2C_SDA_PIN = 5;
const uint8_t I2C_SCL_PIN = 6;
const uint32_t SAMPLE_INTERVAL_MS = 20;
const float FILTER_SAMPLE_HZ = 1000.0f / SAMPLE_INTERVAL_MS;
const uint16_t WEB_PORT = 80;
DFRobot_Multi_DOF_IMU_I2C imu(DFRobot_Multi_DOF_IMU::eSensorModel10DOF, &Wire, IMU_ADDR);
Madgwick filter;
WebServer server(WEB_PORT);
float gRoll = 0.0f;
float gPitch = 0.0f;
float gYaw = 0.0f;
void handleRoot() {
server.send(200, "text/html", PAGE_HTML);
}
void handleData() {
char buf[96];
snprintf(buf, sizeof(buf),
"{\"roll\":%.2f,\"pitch\":%.2f,\"yaw\":%.2f}",
gRoll, gPitch, gYaw);
server.send(200, "application/json", buf);
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println();
Serial.println("=== IMU 10DOF -> WebServer 3D Block ===");
Serial.print("[1/3] Initializing IMU (I2C)... ");
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
while (!imu.begin()) {
Serial.println("FAILED - check wiring & DIP switches (I2C, 0x4A).");
delay(1000);
}
imu.setSensorMode(DFRobot_Multi_DOF_IMU::eNormalMode);
imu.setAccelRange(DFRobot_Multi_DOF_IMU::eAccelRange2G);
imu.setGyroRange(DFRobot_Multi_DOF_IMU::eGyroRange250DPS);
Serial.println("OK");
filter.begin(FILTER_SAMPLE_HZ);
Serial.print("[2/3] Connecting to Wi-Fi '");
Serial.print(WIFI_SSID);
Serial.print("'... ");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint32_t timeout = millis();
while (WiFi.status() != WL_CONNECTED && millis() - timeout < 15000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nFAILED to connect. Check credentials.h and the router.");
while (true) {
delay(1000);
}
}
Serial.println("OK");
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
Serial.println("[3/3] Starting web server...");
server.on("/", HTTP_GET, handleRoot);
server.on("/data", HTTP_GET, handleData);
server.begin();
Serial.println("OK");
Serial.println();
Serial.print("Open http://");
Serial.print(WiFi.localIP());
Serial.println(" in a browser to see the 3D block.");
}
void loop() {
server.handleClient();
static uint32_t lastSample = 0;
uint32_t now = millis();
if (now - lastSample >= SAMPLE_INTERVAL_MS) {
lastSample = now;
DFRobot_Multi_DOF_IMU::sSensorData_t accel, gyro, mag;
if (imu.get9dofData(&accel, &gyro, &mag)) {
filter.update(gyro.x, gyro.y, gyro.z,
accel.x, accel.y, accel.z,
mag.x, mag.y, mag.z);
gRoll = filter.getRoll();
gPitch = filter.getPitch();
gYaw = filter.getYaw();
}
}
}
El sketch necesita dos archivos más en la misma carpeta. credentials.h define WIFI_SSID y WIFI_PASS con los datos de tu red (separarlos del código evita subir tu clave a un repositorio por accidente). page_html.h guarda la página completa como un string en la flash, con un literal crudo R"rawliteral(...)" para que el HTML y el JavaScript lleven comillas sin escaparlas. Esa página carga Three.js desde un CDN, dibuja el bloque con una grilla de referencia y le pide los ángulos a /data varias veces por segundo. Copia page_html.h completo desde el tutorial original (enlace en Recursos): es largo y no vale la pena reescribirlo.
Cómo funciona por dentro
- Dos rutas HTTP.
/entrega la página y/dataresponde un JSON tipo{"roll":1.20,"pitch":-3.45,"yaw":178.00}. El navegador hace todo el trabajo gráfico; el ESP32 solo manda tres números. - Muestreo a 50 Hz sin
delay(). Elloop()comparamillis()con la última muestra. Asíserver.handleClient()se atiende en cada vuelta y la página no se congela mientras el sensor espera.FILTER_SAMPLE_HZ(1000 / 20 = 50) le dice al filtro cada cuánto llegan datos: si ese número no calza con la frecuencia real, los ángulos responden lentos o se pasan. get9dofData(). Aquí no hace falta el barómetro, así que se piden solo los nueve canales de movimiento.
El filtro Madgwick en una explicación corta
Madgwick representa la orientación como un cuaternión (cuatro números que describen una rotación sin los problemas de los ángulos de Euler, como el bloqueo de cardán). En cada muestra integra el giroscopio para predecir la nueva orientación y después la corrige un poco hacia donde dicen la gravedad (acelerómetro) y el norte (magnetómetro), usando descenso de gradiente. El parámetro beta decide cuánto confía en esa corrección: alto, responde rápido pero tiembla; bajo, es suave pero tarda en corregir la deriva. Roll, pitch y yaw salen al final del cuaternión.
Comparado con un filtro complementario (el clásico 98 % giroscopio, 2 % acelerómetro), Madgwick maneja los tres ejes a la vez y puede usar el magnetómetro para el yaw. Comparado con un filtro de Kalman, es mucho más liviano y casi no tiene parámetros que afinar, por eso es tan usado en microcontroladores.
Probarlo
Sube el sketch, abre el Monitor Serie a 115200 y espera la IP (algo como 192.168.1.160). Ábrela en un navegador conectado a la misma red.

Mueve el sensor y el bloque lo sigue. Con el mouse puedes girar la cámara alrededor del bloque.
Errores comunes y cómo resolverlos
- "Can't find sensor!" sin parar. Revisa SDA y SCL (es fácil cruzarlos), que el switch esté en I2C con A0 y A1 en 0 y que hayas reiniciado el módulo después de moverlo. Un escáner I2C debería mostrar 0x4A.
- El Monitor Serie no muestra nada. USB CDC On Boot en Enabled y velocidad 115200.
- La altura está mal por decenas de metros.
REFERENCE_ALTITUDEno corresponde a tu ubicación, o cambió el clima desde que calibraste. - El yaw apunta mal o gira solo. El magnetómetro necesita calibrarse (ver la sección siguiente) y estar lejos de fierros, motores e imanes. Un protoboard con un parlante al lado basta para arruinarlo.
- El bloque se mueve con retraso o "flota". Revisa que
FILTER_SAMPLE_HZcorresponda al muestreo real y que no hayadelay()largos en elloop(). - El ESP32 no se conecta al WiFi. Antena puesta, red de 2,4 GHz (el ESP32-S3 no usa 5 GHz) y datos correctos en
credentials.h.
Calibrar el magnetómetro
Todo magnetómetro trae un corrimiento propio más el de los metales que lo rodean (hierro duro). La calibración mínima es mover el sensor lentamente en todas direcciones, como dibujando un ocho en el aire, mientras registras el máximo y el mínimo de cada eje. El centro de cada eje es (máximo + mínimo) / 2, y ese valor se le resta a cada lectura antes de pasársela al filtro. Hazlo con el sensor ya montado en su lugar final, porque los metales del gabinete también cuentan.
Deriva del giroscopio
Que el giroscopio marque un poco distinto de cero estando quieto es normal (el BMI323 tiene un offset típico de ±1 °/s). Para mejorarlo, promedia unas 500 lecturas con el sensor inmóvil al arrancar y resta ese promedio de cada lectura. El filtro Madgwick ya compensa buena parte de la deriva con el acelerómetro y el magnetómetro.
Variantes y mejoras
- Versión con un sensor del catálogo. El módulo GY-91 junta un MPU9250 (acelerómetro, giroscopio y magnetómetro) con un BMP280 (barómetro): también son diez grados de libertad. La librería es otra, así que reemplaza la creación del objeto y la llamada a
get9dofData()por las lecturas de una librería para MPU9250, convierte las unidades a g, °/s y µT, y deja igual el filtro y el servidor. La página 3D no cambia. Si no necesitas rumbo absoluto, un MPU6050 basta para roll y pitch; el yaw se irá corriendo con el tiempo porque no tiene magnetómetro. - Registro de vuelo o de caídas. Guarda roll, pitch, aceleración y altura en una tarjeta microSD por SPI a 50 Hz. Con eso armas una "caja negra" para un cohete de agua, un dron o una bicicleta, y analizas los datos después en una planilla.
- Detector de caídas con interrupciones. Usa INT1 del BMI323 para despertar al ESP32 desde deep sleep solo cuando hay movimiento significativo. Una caída típica es caída libre (aceleración total cerca de 0 g) seguida de un impacto fuerte y quietud: con esa secuencia y un aviso por WiFi tienes un dispositivo de alerta para adultos mayores que dura semanas con batería.
- Nivel de burbuja digital. Agrega una pantalla OLED I2C de 128x64 en el mismo bus (otra dirección) y muestra roll y pitch con una burbuja dibujada. Sirve para nivelar la cama de una impresora 3D o un cuadro.
Personalización para Chile
En Chile consigues en MechatronicStore la placa y el cable de este montaje:
- Seeed Studio XIAO ESP32 S3 (SKU GS1-3): es la misma placa del tutorial original, con WiFi para el servidor del tercer ejemplo y el I2C en D4 y D5.
- Cable USB Tipo C a USB Tipo A 1mt (SKU B-101): para programar la XIAO y ver el Monitor Serie. Tiene que ser un cable de datos, no solo de carga.
- Giroscopio y acelerómetro MPU9250 GY-91 (SKU GP1-2): la alternativa de diez grados de libertad del catálogo (MPU9250 + BMP280) si quieres hacer la variante de arriba. Cambia la librería, no el concepto ni la página 3D.
El sensor DFRobot Gravity 10DOF (SEN0696) no está en el catálogo. Si prefieres mantenerte exactamente en el tutorial, hay que importarlo; si no, el GY-91 cumple la misma función con más trabajo de software. Si tu proyecto no necesita rumbo, el Giroscopio y Acelerómetro IMU MPU6050 GY 521 (SKU GO3-4) es la opción de entrada.
Recursos
- Tutorial original (inglés): Getting started with the Gravity 10DOF IMU Sensor, de Makerguides. Ahí está el archivo
page_html.hcompleto del tercer ejemplo. - Librerías del sensor: DFRobot_Multi_DOF_IMU y su dependencia DFRobot_RTU, en el GitHub de DFRobot (el original enlaza los ZIP de cada una)
- Filtro de orientación: MadgwickAHRS
- Core ESP32 para Arduino: arduino esp32 de Espressif
Versión chilena basada en el tutorial de Makerguides, con componentes en stock local.






