Skip to content

Repository files navigation

sextante

CI License: MIT Python

Sextante — Spanish for sextant, the classic instrument for finding your attitude and heading from the sky.

Python drivers for 9-axis IMUs over I2C on the Raspberry Pi — the InvenSense MPU-9250 (accel + gyro + AK8963 magnetometer, e.g. the Stratux AHRS board) and the ST LSM9DS1 (e.g. the Ozzmaker BerryGPS-IMU) — with a shared recording, live-viewing and reporting pipeline on top.

The driver samples the sensor from a background thread at a configurable rate and lets you pull instantaneous readings or per-interval averages. The importable package keeps the hardware's name: import mpu9250.

Features

  • Background sampling thread with independent accel/gyro and magnetometer tick rates.
  • Per-interval averaging: get_avg() returns the average of everything sampled since the previous call.
  • Configurable accelerometer range (±2/4/8/16 g), gyro range (±250/500/1000/2000 °/s) and digital low-pass filter (derived from the sample rate).
  • Reads the AK8963 factory sensitivity adjustment (fuse ROM) at startup.
  • Magnetometer in continuous 16-bit mode (0.15 µT/LSB), validity-gated by the ST1/ST2 status registers and reported in the accel/gyro frame.
  • Hardware self-check on startup: verifies WHO_AM_I and the AK8963 device id, catching the common relabeled/magnetometer-less boards before configuring anything.
  • User calibration hooks (MPUCalData): hardware biases and a magnetometer rescaling matrix.
  • Session recording to CSV on the Pi and Markdown/PNG analysis reports (sextante-record, sextante-report).
  • BMP280/BME280 barometer support, auto-detected at 0x76/0x77: pressure, temperature and barometric altitude (adjustable QNH), recorded, streamed and reported alongside the IMU.
  • Live motion viewing from any PC: Mahony sensor fusion on the Pi and a zero-install web viewer served by the Pi itself (sextante-stream).
  • Two supported IMUs behind one reading surface: MPU9250 and LSM9DS1 (BerryGPS-IMU); the CLI auto-detects which one answers on the bus (--imu auto).
  • Serial NMEA GPS support (--gps, BerryGPS-IMU's uBlox): position, speed, course and satellites in the live viewer and CSVs; distance and max speed in reports. Pure stdlib parser, checksum-validated.
  • Fully testable without hardware — the I2C bus is injectable, and every CLI accepts --demo to run on synthetic motion.

Project layout

mpu9250/            The package
├── __init__.py     Public API (MPU9250, MPUData, MPUCalData, ranges, MahonyAHRS, Recorder, DemoMPU)
├── driver.py       MPU9250 class: chip setup, sampling loop, averaging
├── lsm9ds1.py      LSM9DS1 driver (BerryGPS-IMU): same reading surface as MPU9250
├── detect.py       IMU auto-detection (which supported chip answers on the bus)
├── constants.py    Register map and configuration bits (MPU-9250 + AK8963)
├── ranges.py       Accel/gyro range definitions and LPF selection
├── data.py         MPUData / MPUCalData value objects
├── ticker.py       Drift-free periodic ticker thread used by the sampling loop
├── fusion.py       Mahony AHRS attitude filter + quaternion helpers
├── bmp280.py       Bosch BMP280/BME280 barometer (pressure, temperature, altitude)
├── gps.py          Serial NMEA GPS reader (uBlox on BerryGPS-IMU boards)
├── recorder.py     CSV session recorder
├── report.py       Session analysis and Markdown/PNG report rendering
├── streamer.py     Sampling hub + SSE HTTP server (serves the live viewer)
├── demo.py         Synthetic motion source (run everything without hardware)
├── cli.py          sextante-record / sextante-stream / sextante-report
└── web/viewer.html Self-contained live viewer app (no external dependencies)
docs/
├── hardware.md     What the MPU-9250 actually is (dies, buses, formats, identification)
└── architecture.md How the driver works (threads, queues, data path, decisions)
examples/
└── read_avg.py     Prints averaged readings once per second
tests/              Unit tests (no hardware required)

Requirements

  • Python ≥ 3.9
  • numpy, smbus2 (installed automatically)
  • On the Raspberry Pi: I2C enabled (raspi-config → Interface Options → I2C)

Wiring (Raspberry Pi)

MPU-9250 pin Raspberry Pi pin
VCC 3V3 (pin 1)
GND GND (pin 6)
SDA GPIO 2 / SDA (pin 3)
SCL GPIO 3 / SCL (pin 5)

The chip must answer at address 0x68 (i2cdetect -y 1). If AD0 is pulled high it answers at 0x69; pass address=0x69 in that case.

Installation

git clone https://github.com/olagopirez/sextante.git
cd sextante
pip install -e .

Usage

import time
from mpu9250 import MPU9250, AccelRange, GyroRange

mpu = MPU9250(accel_range=AccelRange.RANGE_2_G,
              gyro_range=GyroRange.RANGE_250_DPS,
              rate=50)
mpu.initialize()

while True:
    time.sleep(1)
    data = mpu.get_avg()   # average of the last interval; blocks until computed
    print(data.get_json())

examples/read_avg.py contains the same loop ready to run.

Hardware self-check

initialize() first verifies the chip is a genuine MPU-9250/9255 with a responding AK8963, and raises HardwareMismatchError with a diagnosis otherwise (e.g. a relabeled MPU-6500 without magnetometer). Use initialize(check_hardware=False) to skip it, or call mpu.self_check() yourself to identify a board. See docs/hardware.md for the WHO_AM_I value table.

Reading model

  • mpu.mpuDate — most recent instantaneous sample (updated at rate Hz).
  • mpu.get_avg() — average over the interval since the previous get_avg() call. Synchronous: it waits for the sampling thread to compute the result.

MPUData fields

Field Meaning Units
G1, G2, G3 Gyro X/Y/Z °/s
A1, A2, A3 Accel X/Y/Z g
M1, M2, M3 Magnetometer X/Y/Z, in the accel/gyro frame µT (after factory sensitivity adjustment)
Temp Die temperature °C
N, NM Samples averaged (accel/gyro, magnetometer)
T, TM Timestamp of the last accel/gyro and mag sample
DT, DTM Length of the averaged interval ms
MsgError Error description, None when the reading is valid

At rest, expect A3 ≈ 1.0 (gravity), A1 ≈ A2 ≈ 0, gyros ≈ 0 and Temp at a realistic ambient value — a quick way to sanity-check the wiring.

Calibration

mpu.mpuCalDate (MPUCalData) holds user calibration: per-axis hardware biases for gyro (G0*), accel (A0*) and magnetometer (M0*), plus a 3×3 magnetometer rescaling matrix (Ms*, identity by default). Set these before initialize(). Gyro/accel biases are in raw LSB; the magnetometer biases and Ms matrix apply in the accel/gyro frame, after factory scaling and the axis remap — use them for hard-iron/soft-iron correction.

Recording, streaming and reports

The package installs three commands that turn the driver into a full data pipeline:

# 1. Record a session on the Pi: one CSV row per interval average
sextante-record -o session.csv --interval 0.2

# 2. Watch the movement live from your PC — the Pi serves the viewer app itself
sextante-stream --port 8000              # then open http://<pi-address>:8000
sextante-stream --record session.csv     # stream and record at the same time

# 3. Analyze what you captured
sextante-report session.csv -o report.md
sextante-report session.csv --plots out/   # PNG time series (pip install matplotlib)

The viewer needs nothing installed on the PC: the Pi serves a single self-contained page at / and streams data over Server-Sent Events at /events (pure standard library — no websockets, no frameworks). The page shows the live 3D attitude cube, telemetry and rolling charts; orientation is estimated on the Pi by a Mahony AHRS filter fusing gyro + accel + magnetometer, all in the body frame the driver reports.

Reports summarize a recorded session: per-channel statistics, accumulated rotation per axis, stillness share, peak specific force, and a magnetometer health check (Earth's field magnitude should sit in ~25–65 µT).

GPS (BerryGPS-IMU): add --gps to any command to read the on-board uBlox over serial NMEA — POS/SPD/GPS rows appear in the viewer, position columns in the CSV, and reports gain track distance and max speed. On the Pi, free the UART first: sudo raspi-config nonint do_serial_cons 1 && sudo raspi-config nonint do_serial_hw 0 (console off, port on), then reboot; tune with --gps-device/--gps-baud.

Gyro calibration: MEMS gyros always show a constant per-axis bias (~1 °/s) that makes the attitude wander at rest. Every command measures and removes it at startup — keep the device still for the first 2 seconds (tune with --calibrate 5, skip with --calibrate 0; also available as MPU9250.calibrate_gyro()).

Mounting: if the chip sits rotated or upside down on your board (the Stratux AHRS mounts it Z-down), pass --mount x180 (any comma list of quarter turns, e.g. x180,z90) and gyro/accel/mag are rotated into the vehicle frame — the viewer then shows the board level when it is level.

Dead accel axis: MEMS accelerometers can lose an axis with age (stuck proof mass — the reading ignores gravity entirely). sextante-stream --fix-az up|down rebuilds the vertical component from the 1 g constraint for the attitude filter, while recorded CSVs keep the real sensor values. Valid for tilts below 90°.

Barometer: when a BMP280/BME280 answers on the bus (the Stratux AHRS board pairs one with the MPU-9250), every command picks it up automatically — pressure/altitude columns in the CSV, PRESS/ALT in the live viewer, altitude statistics and range in reports. Set the sea-level pressure with --qnh 1020.5 for true altitude, or skip the sensor with --no-baro.

Every command accepts --demo to run against a synthetic motion source — the whole pipeline works on any machine, no hardware needed. Note: the recorder is the only component that may call get_avg() (it resets the interval accumulators); the streamer reads instantaneous samples, so recording and streaming coexist fine.

Running the tests

The tests replace the I2C bus with an in-memory fake, so they run anywhere:

pip install -e ".[dev]"
pytest

Documentation

  • Website: olagopirez.github.io/sextante — project landing page (source in site/, deployed to GitHub Pages on every push to master).
  • docs/hardware.md — what the MPU-9250 actually is: the two dies, buses and access modes, data formats and the endianness trap, scale factors, magnetometer axis rotation, and how to tell a genuine chip from a relabeled one.
  • docs/architecture.md — how the driver works: module map, thread and queue model, the get_avg() contract, data path and design decisions.

Roadmap

  • Interrupt/FIFO-driven sampling as an alternative to polling.
  • A tilt-compensated compass-heading example.

Contributing

See CONTRIBUTING.md — no hardware is needed to develop or run the tests. Please also read the Code of Conduct.

Security

See SECURITY.md for how to report vulnerabilities privately.

License

MIT © Oscar Lago

About

Python driver for the InvenSense MPU-9250 9-axis IMU (accel + gyro + AK8963 magnetometer) over I2C, for Raspberry Pi

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages