01 · Punto de partida
Qué vamos a construir
Un dispositivo pequeño con un MPU-6050 que cumple dos funciones sobre el mismo sensor: medir su inclinación (cabeceo y alabeo, con una alarma si se inclina demasiado durante un rato) y detectar caídas (y avisar solo cuando de verdad cayó). Aplicaciones reales: un collar para personas mayores, un equipo portátil que debe avisar si se vuelca, un medidor de nivel que alerta si el soporte cede. Los requisitos son:
- Leer el IMU a 100 Hz, con el tiempo entre muestras medido y no supuesto.
- Calibrar el sesgo del giroscopio al arrancar, exigiendo que el dispositivo esté quieto.
- Estimar el cabeceo y el alabeo con un filtro complementario que use el giroscopio a corto plazo y la gravedad a largo plazo.
- Activar una alarma de inclinación si |ángulo| supera 30° durante 2 s, y desactivarla recién cuando baje de 25° (histéresis).
- Detectar una caída como una secuencia: caída libre, impacto e inmovilidad posterior, y descartar los falsos disparos habituales.
- No bloquear el programa y reaccionar con seguridad a la falla del sensor.
02 · Paso 1
Arquitectura y tiempos
| Capa | Cadencia | Responsabilidad |
|---|---|---|
| Adquisición | 100 Hz (cada 10 ms) | Leer los 14 bytes del MPU-6050 y convertir a g y °/s |
| Estimación | 100 Hz | Restar el sesgo, aplicar el filtro complementario |
| Detector de caída | 100 Hz | Máquina de estados sobre el módulo de la aceleración |
| Alarma de inclinación | 10 Hz | Umbrales con histéresis y confirmación temporal |
| Salida y comunicación | 5 Hz | LED, zumbador y registro por el puerto serie |
El detector de caída trabaja a la cadencia de muestreo porque una caída libre dura apenas medio segundo: a 1 Hz se la perdería. La inclinación, en cambio, cambia lentamente y se evalúa a 10 Hz. Separar las cadencias es la misma idea de los talleres anteriores: cada tarea con su período, todas basadas en el tiempo y ninguna en esperas.
La caída libre es corta por una razón física: un objeto que cae desde una altura h tarda t = √(2h / g). Desde 1 m son 0,45 s; desde 50 cm, 0,32 s; desde 2 m, 0,64 s. El detector tiene que reconocer una ventana de unas pocas decenas de milisegundos con muestreo suficiente, y el acelerómetro con un filtro digital que no alise el impacto.
03 · Paso 2
Cableado y montaje
| Elemento | Conexión | Observaciones |
|---|---|---|
| MPU-6050 (módulo GY-521) | SDA → A4, SCL → A5, Vcc, GND | Dirección 0x68 con AD0 a masa; pull-ups en el módulo |
| LED de inclinación | D8 → 220 Ω → LED → masa | Enciende con la alarma de inclinación |
| Zumbador de caída | D9 → zumbador activo → masa | Suena con la caída confirmada (hasta que un botón la borre) |
| Botón de reconocimiento | D2 → botón → masa | Pull-up interno; borra la alarma (tema 8) |
El montaje mecánico condiciona toda la medición:
- Fijación rígida. El sensor debe moverse solidario con la caja. Una placa suelta o sujeta con espuma blanda agrega movimientos propios que el sensor interpreta como del dispositivo.
- Ejes conocidos. Se marca cuál eje es «adelante» y cuál «arriba», y se verifican los signos (sección 05). Un eje invertido es la causa de la mayoría de los ángulos «al revés».
- Lejos de vibraciones y de imanes. Un motor cercano llena el acelerómetro de ruido de vibración; para la guiñada con un magnetómetro habría que apartarse además del hierro (tema 24).
- Cerca del centro de masa del objeto, si se mide rotación y traslación a la vez.
04 · Paso 3
Muestrear a frecuencia fija
El filtro integra ω × Δt. Si el programa supone un Δt de 10 ms pero la vuelta del bucle tarda 12 ms, cada integración se equivoca un 20 % y los ángulos giran más despacio que la realidad. La solución es doble: medir el Δt real con micros() y mantener el bucle lo bastante rápido para respetar el período.
El tiempo que tarda la lectura por I2C importa. A 100 kHz, una transacción de 14 bytes de datos más las direcciones y el registro ronda (14 + 4) × 9 bits / 100 kHz ≈ 1,6 ms; con el bus a 400 kHz (Wire.setClock(400000)), unos 0,4 ms. A 100 Hz hay 10 ms por muestra, de modo que la lectura consume entre el 4 % y el 16 % del tiempo. Es un buen motivo para subir el reloj del bus.
const uint32_t PERIODO_US = 10000; // 100 Hz
uint32_t tAnterior = 0;
void loop() {
uint32_t ahora = micros();
if (ahora - tAnterior >= PERIODO_US) {
float dt = (ahora - tAnterior) * 1e-6; // el intervalo real, no el nominal
tAnterior = ahora;
tareaMuestreo(dt); // lee, filtra, detecta
}
// ... otras tareas (alarma, salida) con su propio período, sin esperas
}
La resta sin signo hace correcto el cálculo aunque micros() se desborde (cada 71 minutos). Y si por algún retraso una vuelta tarda mucho más (por ejemplo, 50 ms), dt lo refleja, y la integración sigue siendo coherente: se pierde resolución temporal, no exactitud.
05 · Paso 4
Calibración inicial
Al encender, el dispositivo toma unas 200 lecturas y promedia el giroscopio para estimar su sesgo (tema 20). Pero hay que comprobar que estaba quieto: si alguien lo movía, el promedio incluye un movimiento real y el sesgo queda mal. La verificación es simple: exigir que ninguna lectura se aparte más de 2 °/s del promedio, y que el módulo de la aceleración esté cerca de 1 g. Si no se cumple, se repite la calibración.
float sesgoGx = 0, sesgoGy = 0, sesgoGz = 0;
bool calibrar() {
const uint16_t N = 200;
float ax, ay, az, t, gx, gy, gz;
float sx = 0, sy = 0, sz = 0;
for (uint16_t i = 0; i < N; i++) { // primera pasada: promedio
if (!mpuLeer(ax, ay, az, t, gx, gy, gz)) return false;
sx += gx; sy += gy; sz += gz;
delay(5);
}
float mx = sx / N, my = sy / N, mz = sz / N;
for (uint16_t i = 0; i < N; i++) { // segunda pasada: ¿estaba quieto?
if (!mpuLeer(ax, ay, az, t, gx, gy, gz)) return false;
if (fabs(gx - mx) > 2.0 || fabs(gy - my) > 2.0 || fabs(gz - mz) > 2.0) return false;
delay(5);
}
sesgoGx = mx; sesgoGy = my; sesgoGz = mz;
return true;
}
Se usan dos pasadas con acumuladores en lugar de guardar las 200 muestras: 200 valores de 4 bytes por eje son 800 bytes por eje, demasiado para los 2 KB de SRAM de un ATmega328P. La segunda pasada rechaza la calibración si alguna lectura se aparta más de 2 °/s del promedio; el programa la repite hasta lograrla. Al terminar se inicializan los ángulos con el valor del acelerómetro (que en reposo es correcto), de modo que el filtro no parta de cero y tarde en converger.
También se puede volver a calibrar durante el funcionamiento cuando se detecta que el dispositivo está quieto un buen rato (módulo cerca de 1 g y giro cercano a cero durante varios segundos): un refinamiento que contrarresta la deriva del sesgo con la temperatura.
06 · Paso 5
El filtro complementario
La idea es una media ponderada entre dos estimaciones del ángulo: la que predice el giroscopio (el ángulo anterior más lo que giró) y la que mide el acelerómetro (la gravedad). El factor α decide cuánto se confía en cada una:
Con α cercano a 1, domina el giroscopio en cada paso y el acelerómetro solo «tira» suavemente. Hay una forma más útil de pensar en α: como una constante de tiempo τ. Para un período Δt, α = τ / (τ + Δt). Con τ = 0,5 s y Δt = 0,01 s, α = 0,98. La interpretación es: en tiempos más cortos que τ domina el giroscopio (los giros rápidos pasan sin retraso y sin ruido); en tiempos más largos que τ domina el acelerómetro (el ángulo vuelve a la vertical real y se anula la deriva). Es un filtro pasa-bajos para el acelerómetro y pasa-altos para el giroscopio que suman 1: de ahí el nombre «complementario».
El sesgo del giroscopio ya no integra sin límite: queda un error estacionario igual a sesgo × τ. Con 0,5 °/s de sesgo residual y τ = 0,5 s, son 0,25°. Este es el costo bajo de la deriva: unas décimas de grado, en lugar de crecer sin fin.
const float TAU = 0.5; // s: por debajo domina el giroscopio; por encima, el acelerómetro
float cabeceo = 0, alabeo = 0; // estimación, en grados
void actualizarAngulos(float ax, float ay, float az, float gx, float gy, float dt) {
float accCabeceo = atan2(-ax, sqrt(ay * ay + az * az)) * RAD_TO_DEG;
float accAlabeo = atan2(ay, az) * RAD_TO_DEG;
float modulo = sqrt(ax * ax + ay * ay + az * az);
float alfa = TAU / (TAU + dt);
if (fabs(modulo - 1.0) > 0.3) alfa = 1.0; // aceleración fuerte: la gravedad no es confiable, se usa solo el giroscopio
cabeceo = alfa * (cabeceo + gy * dt) + (1.0 - alfa) * accCabeceo;
alabeo = alfa * (alabeo + gx * dt) + (1.0 - alfa) * accAlabeo;
}
Dos detalles que hacen la diferencia. La línea del modulo es una protección contra aceleraciones: si |a| se aparta de 1 g más de 0,3 g, el sensor está sintiendo algo más que la gravedad, el ángulo del acelerómetro no es confiable y durante ese tiempo se usa solo el giroscopio. Y la asignación de ejes: el cabeceo gira alrededor del eje Y (velocidad gy) y el alabeo alrededor del eje X (gx). Los signos dependen de cómo esté montado el módulo; se verifican girando la placa y comprobando que el ángulo del giroscopio y el del acelerómetro se muevan en el mismo sentido. Si van opuestos, se invierte el signo de la velocidad angular.
El filtro no calcula la guiñada: sin magnetómetro, el giro sobre la vertical solo se puede integrar del giroscopio y deriva sin corrección. Para una luz nocturna, un inclinómetro o un detector de caída, no hace falta.
07 · Paso 6
Alarma de inclinación
La alarma sigue el diseño del tema 16, aplicado a un ángulo: un umbral de activación (30°), uno de liberación más bajo (25°, la histéresis) y un tiempo de confirmación (2 s), para que un tropezón momentáneo no dispare la alarma. Se evalúa a 10 Hz con el mayor de los dos ángulos en valor absoluto:
const float U_ACTIVAR = 30.0; // grados
const float U_LIBERAR = 25.0; // grados: 5° de histéresis
const uint32_t T_CONFIRMAR = 2000; // ms que debe sostenerse la inclinación
bool alertaInclinacion = false;
bool contando = false;
uint32_t tInicioExceso = 0;
void evaluarInclinacion(uint32_t ms) {
float mayor = max(fabs(cabeceo), fabs(alabeo));
if (!alertaInclinacion) {
if (mayor > U_ACTIVAR) {
if (!contando) { contando = true; tInicioExceso = ms; }
else if (ms - tInicioExceso >= T_CONFIRMAR) alertaInclinacion = true;
} else {
contando = false; // la inclinación no se sostuvo
}
} else if (mayor < U_LIBERAR) {
alertaInclinacion = false;
contando = false;
}
}
Es la misma estructura de dos umbrales y confirmación de los talleres anteriores. Lo único nuevo es la variable que se compara, un ángulo estimado por fusión de sensores en lugar de una lectura directa.
08 · Paso 7
Detector de caída por fases
Una caída real tiene una firma en el módulo de la aceleración |a|, que en reposo vale 1 g:
- Caída libre: |a| baja cerca de 0 g durante unas decenas a cientos de milisegundos.
- Impacto: un pico brusco de varios g al llegar al suelo.
- Inmovilidad: después, |a| vuelve a 1 g y se queda ahí: la persona o el objeto quedó tendido.
Cada fase por sí sola produce falsos positivos. Un golpe a la mesa es un pico sin caída libre. Sentarse bruscamente da un pico de 1,5 a 2 g sin caída libre. Lanzar el dispositivo al aire y atraparlo tiene caída libre e impacto, pero el movimiento continúa. Exigir las tres fases en orden los descarta casi a todos. Se implementa como una máquina de estados sobre |a|, a 100 Hz:
| Estado | Pasa a… | Cuando… |
|---|---|---|
| NORMAL | CAÍDA LIBRE | |a| < 0,4 g durante 5 muestras seguidas (50 ms) |
| CAÍDA LIBRE | IMPACTO NORMAL | |a| > 2,5 g |a| vuelve a 0,7 g o más sin pico fuerte (se atrapó o apoyó), o la caída libre dura más de 1,5 s |
| IMPACTO | VERIFICANDO | Pasan 0,3 s (el rebote se amortigua) |
| VERIFICANDO | CONFIRMADA NORMAL | | |a| − 1 | < 0,15 g durante 2 s seguidos (inmóvil) Pasan 6 s sin conseguirlo (la persona se mueve: no está tendida) |
| CONFIRMADA | NORMAL | El usuario presiona el botón de reconocimiento |
enum Caida { NORMAL, CAIDA_LIBRE, IMPACTO, VERIFICANDO, CONFIRMADA };
Caida estadoCaida = NORMAL;
uint16_t cuentaLibre = 0, reposo = 0, ventana = 0, asentar = 0;
void detectarCaida(float modulo) {
switch (estadoCaida) {
case NORMAL:
cuentaLibre = (modulo < 0.4) ? cuentaLibre + 1 : 0;
if (cuentaLibre >= 5) estadoCaida = CAIDA_LIBRE;
break;
case CAIDA_LIBRE:
if (modulo > 2.5) { estadoCaida = IMPACTO; asentar = 0; }
else if (modulo < 0.7) { if (++cuentaLibre > 150) { estadoCaida = NORMAL; cuentaLibre = 0; } }
else { estadoCaida = NORMAL; cuentaLibre = 0; } // sin impacto fuerte: no fue una caída
break;
case IMPACTO:
if (++asentar >= 30) { estadoCaida = VERIFICANDO; reposo = 0; ventana = 0; }
break;
case VERIFICANDO:
ventana++;
reposo = (fabs(modulo - 1.0) < 0.15) ? reposo + 1 : 0;
if (reposo >= 200) estadoCaida = CONFIRMADA;
else if (ventana >= 600) { estadoCaida = NORMAL; cuentaLibre = 0; }
break;
case CONFIRMADA:
break; // espera el botón de reconocimiento
}
}
Los umbrales son el resultado de equilibrar los dos errores del tema 19. Bajar el de caída libre (0,4 g) hace más sensible el detector pero dispara con vibraciones; subir el de impacto (2,5 g) evita falsos con golpes suaves pero puede no detectar una caída sobre una superficie blanda (una cama o una alfombra), donde el pico es mucho menor. El tiempo de inmovilidad (2 s) distingue a quien quedó tendido de quien se levantó enseguida. No existe un ajuste universal: se calibra con datos reales de la aplicación.
09 · Paso 8
El programa completo
// Se asume mpuIniciar(), mpuLeer() (tema 20), calibrar(), actualizarAngulos(),
// evaluarInclinacion() y detectarCaida() de los pasos anteriores.
uint32_t tAnterior = 0, tAlarma = 0, tSalida = 0;
bool sensorOk = false;
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000); // I2C rápido: 0,4 ms por lectura
pinMode(8, OUTPUT); pinMode(9, OUTPUT);
iniciar(boton, 2, true, false, false, 800); // detector de eventos del tema 8
sensorOk = mpuIniciar();
mpuEscribir(0x1C, 0x10); // ±8 g: ve los impactos
mpuEscribir(0x1A, 0x00); // filtro digital de ≈ 260 Hz: no alisa el impacto
while (sensorOk && !calibrar()) { } // repite hasta que el dispositivo esté quieto
// aquí se inicializan cabeceo y alabeo con el acelerómetro
}
void loop() {
uint32_t ahora = micros();
uint32_t ms = millis();
if (sensorOk && ahora - tAnterior >= 10000UL) { // 100 Hz
float dt = (ahora - tAnterior) * 1e-6;
tAnterior = ahora;
float ax, ay, az, t, gx, gy, gz;
if (mpuLeer(ax, ay, az, t, gx, gy, gz)) {
gx -= sesgoGx; gy -= sesgoGy;
actualizarAngulos(ax, ay, az, gx, gy, dt);
detectarCaida(sqrt(ax * ax + ay * ay + az * az));
} else {
// falla de lectura: ver el paso 10
}
}
if (ms - tAlarma >= 100) { tAlarma = ms; evaluarInclinacion(ms); } // 10 Hz
if (ms - tSalida >= 200) { // 5 Hz
tSalida = ms;
digitalWrite(8, alertaInclinacion);
digitalWrite(9, estadoCaida == CONFIRMADA);
Serial.print(cabeceo, 1); Serial.print(' '); Serial.println(alabeo, 1);
}
if (actualizar(boton) & EV_CLICK) { estadoCaida = NORMAL; cuentaLibre = 0; } // reconocer la caída
}
Observá cuánto del programa es reutilización: el detector de eventos del botón (tema 8), la lectura del IMU (tema 20), la alarma con histéresis y confirmación (tema 16) y las tareas por período. Lo nuevo del taller son tres piezas: el filtro, el detector por fases y la calibración con verificación.
10 · Paso 9
Verificar
| Prueba | Cómo se hace | Resultado esperado |
|---|---|---|
| Reposo | Placa sobre una mesa nivelada, 1 minuto | Cabeceo y alabeo cerca de 0°, sin deriva |
| Signos | Inclinar la placa 30° hacia adelante, luego a la derecha | Cabeceo +30° y luego alabeo con el signo esperado; si no, invertir ejes |
| Giro rápido | Inclinar de 0° a 45° en medio segundo | El ángulo sigue sin retardo apreciable (domina el giroscopio) |
| Aceleración lineal | Sacudir la placa horizontalmente sin inclinarla | El ángulo apenas se mueve (protección por módulo de |a|) |
| Deriva | Dejarla inclinada 20° durante 10 minutos | Se mantiene en 20° ± décimas, sin derivar |
| Alarma de inclinación | Inclinar a 35° durante 1 s y a 35° durante 3 s | No dispara en el primer caso; dispara en el segundo. Se libera al bajar de 25° |
| Caída real | Soltar el dispositivo (protegido) sobre un colchón desde 50 cm | Caída libre e impacto detectados; tras 2 s quieto, CONFIRMADA |
| Sentarse de golpe | Llevar el dispositivo en el bolsillo y sentarse con fuerza | Ninguna alarma |
| Lanzar y atrapar | Tirar el dispositivo hacia arriba 30 cm y atraparlo | Ninguna alarma (hay movimiento después) |
| Golpe en la mesa | Golpear la mesa donde está apoyado | Ninguna alarma (impacto sin caída libre) |
| Calibración con movimiento | Moverla durante el arranque | Se rechaza y se repite hasta que esté quieta |
Las pruebas de caída se hacen con cuidado: protegé el dispositivo (una caja con espuma) y caé sobre una superficie blanda. Para ajustar los umbrales con datos reales, lo más valioso es registrar la señal |a| por el puerto serie en varias caídas y falsos disparos, y analizarla en una computadora, como en el programa de Python de este tema.
11 · Paso 10
Fallas y robustez
- El sensor deja de responder. Si la lectura falla varias veces seguidas, el dispositivo debe avisarlo (un parpadeo particular) y no seguir con ángulos congelados. Para un collar de seguridad, un sensor muerto es una falla seria: debe ser visible. Un reinicio del bus I2C o del sensor (reinicializar el MPU-6050) suele recuperarlo.
- El MPU-6050 se reinicia solo por un pico en la alimentación y vuelve al modo de suspensión: todas las lecturas pasan a cero. Un módulo de aceleración de exactamente 0 g sostenido es sospechoso y conviene tratarlo como falla y no como caída libre (en una caída real hay ruido y el módulo no es cero exacto).
- Saturación. Un impacto de más de 8 g sobre una superficie dura satura el rango. Se la acepta como señal de impacto (más que 2,5 g es suficiente), pero no sirve para calcular la gravedad.
- Alimentación a batería. Una descarga profunda baja la tensión y el sensor puede reiniciarse. Medir la tensión y avisar antes.
- Falsos negativos. Una caída sobre una superficie blanda produce un impacto suave; una caída por una pendiente o una escalera no tiene caída libre limpia; si la persona se levanta enseguida, no se confirma. Un detector nunca es perfecto: hay que definir qué proporción de caídas debe detectar y cuántos falsos avisos se toleran.
- Falsos positivos. Un golpe con rebote, un tren de vibraciones o un dispositivo dejado caer sobre un asiento y no retirado pueden parecer una caída con inmovilidad. La confirmación por el usuario (botón de «estoy bien» durante unos segundos antes de avisar al exterior) reduce los falsos avisos en aplicaciones de ayuda.
- Privacidad y consecuencias. Un aviso erróneo molesta; uno ausente puede ser grave. En productos reales esto se regula y se ensaya con protocolos.
12 · Analogía con programación
Comparar los filtros y probar el detector con trazas
Dos pruebas automáticas. La primera simula 20 segundos de un dispositivo que se inclina de 0° a 30° entre los 5 y 7 s, soporta una sacudida horizontal de 0,3 g a 2 Hz entre los 10 y 12 s, y tiene un giroscopio con 0,5 °/s de sesgo; compara el ángulo del giroscopio solo, del acelerómetro solo y del filtro complementario. La segunda aplica el detector de caída a dos trazas de |a|: una caída desde 1 m y alguien que se sienta de golpe.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
def azar(semilla):
estado = semilla
while True:
estado = (1103515245 * estado + 12345) % 2**31
yield estado / 2**31
def normal(generador):
return sum(next(generador) for _ in range(12)) - 6
# ---- 1. Comparar el giroscopio, el acelerómetro y el filtro complementario ----
DT, N, ALFA = 0.01, 2000, 0.98
g = azar(33)
def angulo_real(t):
return 0 if t < 5 else 30 * (t - 5) / 2 if t < 7 else 30
def velocidad_real(t):
return 15 if 5 <= t < 7 else 0
giro = comp = 0.0
errores = {"giroscopio": [], "acelerómetro": [], "complementario": []}
peor = {"acelerómetro": 0.0, "complementario": 0.0}
for i in range(N):
t = i * DT
real = angulo_real(t)
th = math.radians(real)
lineal = 0.3 * math.sin(2 * math.pi * 2 * t) if 10 <= t < 12 else 0.0
ax = -math.sin(th) + lineal + 0.01 * normal(g)
az = math.cos(th) + 0.01 * normal(g)
w = velocidad_real(t) + 0.5 + 0.05 * normal(g) # sesgo de 0,5 °/s
acc = math.degrees(math.atan2(-ax, az))
giro += w * DT
comp = ALFA * (comp + w * DT) + (1 - ALFA) * acc
for nombre, valor in (("giroscopio", giro), ("acelerómetro", acc), ("complementario", comp)):
errores[nombre].append(valor - real)
if 10 <= t < 12:
peor["acelerómetro"] = max(peor["acelerómetro"], abs(acc - real))
peor["complementario"] = max(peor["complementario"], abs(comp - real))
print("filtro error rms error final")
for nombre, e in errores.items():
rms = math.sqrt(sum(x * x for x in e) / len(e))
print(f"{nombre:15s} {rms:7.2f}° {e[-1]:8.2f}°")
print(f"Peor error durante la sacudida: acelerómetro {peor['acelerómetro']:.1f}°, complementario {peor['complementario']:.1f}°")
print(f"tau = {ALFA * DT / (1 - ALFA):.2f} s; error estacionario por el sesgo = {0.5 * ALFA * DT / (1 - ALFA):.2f}°")
# ---- 2. Detector de caída por fases sobre el módulo de la aceleración (100 Hz) ----
def traza_caida():
a = [1.0] * 100 + [0.05] * 45 + [6.0, 4.0, 2.5] # reposo, caída libre de 0,45 s, impacto
a += [1 + 1.5 * math.exp(-i * 0.01 / 0.1) for i in range(1, 30)] # rebote amortiguado
return a + [1.0] * 400 # queda quieta
def traza_sentarse():
a = [1.0] * 100 + [0.85] * 10 + [1.8] * 5
a += [1.2 - 0.2 * i / 50 for i in range(50)]
return a + [1.0] * 400
def detectar(a):
estado, cuenta, quieto, ventana, asentar, eventos = "NORMAL", 0, 0, 0, 0, []
for i, x in enumerate(a):
t = i / 100
if estado == "NORMAL":
cuenta = cuenta + 1 if x < 0.4 else 0
if cuenta >= 5:
estado = "CAIDA_LIBRE"
eventos.append((t, "caída libre"))
elif estado == "CAIDA_LIBRE":
if x > 2.5:
estado, asentar = "IMPACTO", 0
eventos.append((t, f"impacto de {x:.1f} g"))
elif x < 0.7:
cuenta += 1
if cuenta > 150:
estado, cuenta = "NORMAL", 0
else:
estado, cuenta = "NORMAL", 0
eventos.append((t, "fin de la caída libre sin impacto fuerte"))
elif estado == "IMPACTO":
asentar += 1
if asentar >= 30:
estado, quieto, ventana = "VERIFICANDO", 0, 0
eventos.append((t, "verificando inmovilidad"))
elif estado == "VERIFICANDO":
ventana += 1
quieto = quieto + 1 if abs(x - 1) < 0.15 else 0
if quieto >= 200:
estado = "CONFIRMADA"
eventos.append((t, "CAÍDA CONFIRMADA"))
elif ventana >= 600:
estado, cuenta = "NORMAL", 0
eventos.append((t, "abortada: hay movimiento"))
return estado, eventos
for nombre, traza in (("Caída desde 1 m", traza_caida()), ("Sentarse de golpe", traza_sentarse())):
estado, eventos = detectar(traza)
print()
print(f"{nombre}: estado final {estado}")
for t, texto in eventos:
print(f" t = {t:4.2f} s {texto}")
Resultado: el giroscopio solo termina con un error de 10,0° (la deriva de 0,5 °/s en 20 s) y un error rms de 5,8°; el acelerómetro solo tiene un error rms de 3,4° y llega a 17,9° durante la sacudida; el complementario tiene un error rms de 0,7°, un máximo de 4,0° durante la sacudida y un error final de 0,23° (el estacionario sesgo × τ = 0,5 × 0,49 s ≈ 0,25°). En el detector, la caída desde 1 m produce: caída libre a 1,04 s, impacto de 6,0 g a 1,45 s, verificación a 1,75 s y caída confirmada a 3,75 s; sentarse de golpe no produce ningún evento. Probá cambiar ALFA a 0.9 y 0.995 y mirá cómo cambia el compromiso entre rechazar la sacudida y seguir los giros.
El filtro con α = 0,98 reduce el peor caso de la sacudida a la cuarta parte del acelerómetro solo. La protección por módulo de |a| del programa C lo mejoraría todavía. Las trazas son sintéticas y limpias; con datos reales aparecen rebotes y ruidos que obligan a ajustar los umbrales.
13 · Exploración interactiva
El banco de pruebas del inclinómetro y del detector de caída
El primer instrumento ejecuta el filtro en tiempo real a 50 Hz: movés el ángulo real con el control deslizante (el «dispositivo» lo sigue con una velocidad limitada) y mirás en el gráfico, durante los últimos 10 s (a 50 Hz, α = 0,98 equivale a τ ≈ 1 s), el ángulo real, el del acelerómetro solo, el del giroscopio solo y el filtrado. Podés cambiar α, agregar sesgo al giroscopio y activar una sacudida horizontal. El segundo reproduce cinco situaciones sobre el detector de caída y muestra los eventos que reconoce.
Instrumento 1: filtro complementario
Presioná «Ejecutar» y mové el ángulo real.
Instrumento 2: el detector de caída ante cinco situaciones
Recorrido guiado
- Con α = 0,98 y sin sesgo ni sacudida, presioná «Ejecutar» y llevá el ángulo real a 30°: el filtrado sigue sin retardo apreciable, mientras el acelerómetro solo se ve ruidoso y el giroscopio solo lo sigue bien, aunque ya empieza a derivar.
- Activá el sesgo de 0,5 °/s y esperá unos 30 s con el ángulo quieto: el giroscopio solo se va (unos 15°), el acelerómetro permanece estable y el filtrado se queda a décimas del real.
- Activá la sacudida: el acelerómetro solo oscila varios grados alrededor del real; el filtrado casi no se inmuta. Probá α = 0,9: oscila más. Con α = 0,995 apenas se nota.
- Con α = 0,995, llevá el ángulo de 0° a 40°: el giro se sigue bien, pero al corregir el sesgo el filtrado tarda más (τ ≈ 4 s) y el error estacionario es mayor (sesgo × τ). α mayor protege de la sacudida y tarda más en corregir la deriva.
- En el instrumento 2, la situación 1 (caída): reconoce caída libre a 1,04 s, el impacto de 6 g, la verificación y la confirmación a los 3,75 s. El estado final es «CAÍDA CONFIRMADA».
- La 2 (sentarse) y la 4 (golpe) no tienen caída libre: el detector no se dispara. La 3 (lanzar y atrapar) tiene caída libre e impacto, pero el movimiento posterior impide la confirmación: se aborta.
- La 5 (caída y levantarse): hay caída libre e impacto, pero la persona se mueve antes de los 2 s de quietud, se reinicia el conteo y a los 6 s se aborta. Es el comportamiento buscado: quien se levanta no necesita ayuda.
Desafío: la constante de tiempo ideal
Un robot equilibrista soporta sacudidas de unos 0,3 g durante medio segundo y su giroscopio tiene un sesgo de 1 °/s. Con el instrumento 1, probá α = 0,9; 0,98 y 0,995. La primera deja pasar demasiado ruido de la sacudida; la última deja un error estacionario de casi 4° (1 °/s × 4 s) por el sesgo. Un valor intermedio (α ≈ 0,98) equilibra los dos. La constante de tiempo es una decisión de diseño entre rechazar las aceleraciones y rechazar la deriva; por eso, si es posible, se calibra el sesgo para poder usar un α más alto.
14 · Método de trabajo
Lo aprendido, para cualquier estimación de orientación y de eventos
- Muestreá a frecuencia fija y medí el Δt real. Toda integración depende de él.
- Calibrá el sesgo al arrancar, verificando que el sensor esté quieto. Y refinalo cuando detectes que está quieto en funcionamiento.
- Fusioná los sensores por sus fortalezas: el giroscopio a corto plazo y la gravedad a largo plazo. Pensá en α como una constante de tiempo.
- Protegé la estimación cuando el acelerómetro no mide solo gravedad (|a| lejos de 1 g).
- Reconocé eventos como secuencias de fases, con una máquina de estados que exija todas las fases en orden y con tiempos máximos.
- Elegí el rango y el filtrado del sensor según lo que debe ver: mucho rango y poco filtrado para impactos; más filtrado para inclinación.
- Registrá datos reales y ajustá los umbrales sobre ellos; cuantificá falsos positivos y negativos.
- Hacé visible la falla del sensor, sobre todo en aplicaciones de seguridad.
Esta forma de trabajar (sensor, modelo del fenómeno, fusión, detector por fases, verificación con trazas) es la base de todo sistema que interpreta el movimiento, desde una pulsera de actividad hasta un controlador de vuelo.
15 · Comprensión
Confusiones frecuentes
«Con integrar el giroscopio alcanza para el ángulo»
Deriva con el tiempo: 0,5 °/s de sesgo son 30° por minuto. Hace falta una referencia que lo corrija, como la gravedad.
«El filtro complementario elimina la deriva por completo»
La limita: el error estacionario por el sesgo es sesgo × τ. Calibrar el sesgo sigue siendo valioso.
«El Δt del filtro puede ser una constante»
Solo si el período de muestreo es exacto. Si la vuelta del bucle varía, la integración se equivoca; hay que medir el Δt real.
«Un valor bajo de |a| significa caída»
Hay que exigir la secuencia completa: caída libre, impacto e inmovilidad. Un valor bajo aislado puede ser una vibración o un fallo del sensor.
«Con ±2 g y un filtro fuerte detecto bien los impactos»
Un impacto de varios g satura el rango y el filtro aplana el pico. Para impactos, rango alto y poco filtrado.
«Un detector de caída que funciona en el escritorio funciona en la persona»
Los umbrales dependen de la ubicación (muñeca, cintura, cuello), de la superficie y de la persona. Se ajustan con datos reales de cada uso.
16 · Práctica guiada
Predecí antes de comprobar
1. Constante de tiempo
Con Δt = 10 ms y α = 0,98, ¿cuánto vale la constante de tiempo τ? ¿Qué α corresponde a τ = 1 s?
Ver solución
De α = τ/(τ + Δt) se despeja τ = α·Δt / (1 − α) = 0,98 × 0,01 / 0,02 = 0,49 s. Para τ = 1 s: α = 1 / (1 + 0,01) ≈ 0,990.
2. Error estacionario
El sesgo residual del giroscopio es 1 °/s y τ = 0,5 s. ¿Qué error constante queda en el ángulo filtrado?
Ver solución
Error ≈ sesgo × τ = 1 × 0,5 = 0,5°. Con τ = 4 s sería 4°. Por eso conviene calibrar el sesgo y no usar un τ innecesariamente grande.
3. Tiempo de caída libre
¿Cuánto dura la caída libre desde 1 m? ¿Y desde 2 m? ¿Cuántas muestras a 100 Hz hay con |a| bajo en cada caso?
Ver solución
t = √(2h / g): desde 1 m, √(2 / 9,81) ≈ 0,45 s (unas 45 muestras); desde 2 m, √(4 / 9,81) ≈ 0,64 s (unas 64 muestras). Ambas superan con holgura las 5 muestras (50 ms) que exige el detector.
4. Tiempo de lectura
El bus I2C opera a 100 kHz y una lectura completa mueve unos 18 bytes de 9 bits. ¿Cuánto tarda? ¿Y a 400 kHz? ¿Qué fracción de un período de 10 ms es?
Ver solución
18 × 9 = 162 bits. A 100 kHz: 1,62 ms (16 % de 10 ms). A 400 kHz: 0,405 ms (4 %). Subir el reloj del bus libera más tiempo para el resto del programa.
5. Clasificar eventos
Para cada traza, indicá si el detector confirma una caída: (a) 0,05 g durante 45 muestras, pico de 6 g y 2 s de 1 g; (b) pico de 4 g sin caída libre; (c) 0,05 g durante 40 muestras, pico de 3 g y luego |a| oscilando entre 0,5 y 1,5 g durante 8 s.
Ver solución
(a) Sí: caída libre, impacto e inmovilidad. (b) No: falta la caída libre (un golpe a la mesa). (c) No: hay caída libre e impacto, pero el movimiento posterior supera ±0,15 g y no se logran 2 s de quietud; a los 6 s se aborta.
6. Elegir configuración
Elegí el rango del acelerómetro y el ancho de banda del filtro digital para: (a) un inclinómetro de una grúa, lento; (b) el detector de caída de un collar.
Ver solución
(a) ±2 g y un filtro fuerte (≈ 10 a 20 Hz): máxima resolución y poco ruido; la dinámica es lenta. (b) ±8 g (o ±16 g) y un filtro ancho (≥ 100 a 260 Hz): hay que ver el pico del impacto sin saturar ni aplanarlo.
17 · Síntesis
Ideas para recordar
- El muestreo a frecuencia fija con Δt medido es indispensable para integrar; el bus I2C a 400 kHz reduce a 0,4 ms el costo de cada lectura.
- La calibración del sesgo del giroscopio al arrancar debe verificar que el dispositivo estuviera quieto.
- El filtro complementario es ángulo = α(ángulo + ω·Δt) + (1 − α)·ánguloacel, con α = τ/(τ + Δt); τ separa «giroscopio» (corto plazo) de «acelerómetro» (largo plazo).
- El error estacionario por el sesgo es sesgo × τ: se calibra el sesgo y se elige τ sin exagerar.
- Cuando |a| se aparta de 1 g, el acelerómetro no mide solo la gravedad y se suspende su corrección.
- La alarma de inclinación usa dos umbrales y un tiempo de confirmación sobre el ángulo estimado.
- Una caída se reconoce como una secuencia: caída libre (|a| baja), impacto (pico) e inmovilidad; exigir las tres fases en orden descarta golpes, sentarse y lanzar y atrapar.
- Para impactos hace falta un rango alto (±8 g o más) y poco filtrado; para inclinación, más filtrado.
- Los umbrales de un detector se ajustan con datos reales; un detector nunca es perfecto, y la falla del sensor debe ser visible.
¿Listo para el bloque 6?
Antes de avanzar, calculá la constante de tiempo de un filtro con α = 0,99 y Δt = 20 ms, explicá por qué lanzar y atrapar el dispositivo no debe confirmarse como caída y decidí el rango del acelerómetro para detectar impactos de hasta 10 g. Si podés justificar las tres respuestas, ya sabés combinar sensores inerciales para estimar y detectar.
En el próximo bloque pasamos a la presión, el sonido, el magnetismo y la posición, empezando por los sensores de presión y de fuerza: galgas, FSR, el conversor HX711 y los barómetros.