IMU Dead Reckoning

Strap-down inertial dead-reckoning on STM32F411RE in async Rust (Embassy). MPU-6050 over I2C at 100 Hz, Madgwick AHRS, double-integration of world-frame acceleration to a position estimate. No allocator, no RTOS.

The point of the demo is the drift. A consumer-grade MEMS IMU integrated naively accumulates metres of position error per minute. That is exactly why production AUVs spend money on Doppler velocity logs, ring-laser gyros, and acoustic positioning (USBL/LBL): an inertial-only solution diverges fast, and the whole nav stack is built around bounding that divergence with periodic aiding fixes.

Why this exists

Inertial nav is the core fallback for any vehicle that loses absolute position reference: AUVs underwater (no GPS), indoor robots, GPS-denied flight. The same code path — attitude from gyro+accel, world-frame integration, drift accounting — is what runs on a Mako-class AUV between DVL pings. This project shows the embedded-Rust shape of that loop in a form small enough to read in one sitting.

Pipeline

MPU-6050 ── I2C @ 100 Hz ──> imu_task ──[ImuSample]──> ahrs_task ──[Attitude]──> nav_task ──> defmt RTT @ 1 Hz

Three Embassy tasks, two embassy-sync channels.

Hardware

Component Part Interface
MCU ST Nucleo-F411RE Cortex-M4F, 100 MHz
IMU MPU-6050 breakout (GY-521) I2C1 @ 400 kHz
Debug onboard ST-Link, RTT defmt over SWO

Wiring (Nucleo Arduino headers):

Nucleo MPU-6050
3.3V VCC
GND GND
PB8 (D15) SCL
PB9 (D14) SDA
GND AD0 (selects address 0x68)

Honest characterisation

What this implementation will not do:

What it does do:

Host validation harness

The pure algorithm (Madgwick + strap-down integrator) lives in src/algo.rs, shared between the firmware and a host-side simulator at host/. The sim drives the same code with synthetic 100 Hz IMU samples for a stationary vehicle, corrupted with noise + bias representative of an MPU-6050 after a 2 s calibration: gyro residual bias 0.1 deg/s, gyro noise 0.05 deg/s rms, accel bias 50 mg, accel noise 0.04 m/s² rms.

cd host
cargo run --release --bin drift_sim > drift.csv

Default 60 s run, seed=1, one-line summary to stderr:

duration=60s seed=1 | final |p|=203.03 m | horizontal drift=3.59 m | vz=6.79 m/s

The horizontal drift (~3.6 m / 60 s) is the headline number: open-loop, no aiding, the position estimate diverges at metres-per-minute, exactly as an inertial-only solution should. The vertical channel diverges far faster because a gravity-aligned IMU-only AHRS cannot observe accel-z bias; production stacks fix this with a barometer or depth sensor. Both failure modes are why a fielded UUV or UAV pairs strap-down INS with DVL, GPS, USBL, or air-data.

Roadmap

view on github →