Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Firmware | Multi-Sensor Navigation System

PlatformIO Framework Chip License Repo

Repo 1 of 4 · 500 Hz deterministic multi-sensor data acquisition firmware for real-time navigation systems.

Streams fused IMU · magnetometer · barometer · GPS telemetry as timestamped CSV over UART at 921600 baud.

⚠️ This repository was named navigation-system-firmware before moving under the multi-sensor-navigation-system organization from @ShivtejG236.


What this is

Bare-metal firmware for ESP32-WROOM-32 (devkit) that acquires IMU, magnetometer, barometer & GPS data, assembles deterministic sensor frames and streams them over UART for downstream processing (repos 2–4).

The firmware runs two pinned FreeRTOS tasks:

Task Core Priority Responsibility
task_sensors 1 5 IMU/mag/baro polling, ring-buffer producer
task_serial 0 3 GPS UART drain, CSV output, ring-buffer consumer

The split exists by design: task_sensors busy-waits at 500 Hz on Core 1 so it never misses an IMU deadline. GPS UART draining lives on Core 0 — if it ran on Core 1, the Core 1 UART ISR would starve and drop NMEA bytes.


System Architecture

graph TD
    subgraph Core1["Core 1 — task_sensors (priority 5)"]
        IMU["MPU-6050\n500 Hz"]
        MAG["QMC5883L\n75 Hz"]
        BARO["BMP280\n25 Hz"]
        ROW["Assemble SensorRow\n+ fault_flag + jitter"]
        RB["Ring buffer\n128 slots SPSC"]
        IMU --> ROW
        MAG --> ROW
        BARO --> ROW
        ROW --> RB
    end

    subgraph Core0["Core 0 — task_serial (priority 3)"]
        GPS["NEO-6M GPS\n1 Hz NMEA drain"]
        SNAP["Snapshot GPS globals\ninto SensorRow"]
        POP["rb_pop"]
        CSV["CSV → UART0\n921600 baud"]
        GPS --> SNAP
        SNAP --> POP
        RB --> POP
        POP --> CSV
    end

    CSV --> HOST["Host logger\n(Repo 2)"]
Loading

Model: task_sensors = producer · task_serial = consumer

Data flow is single-producer (Core 1) → single-consumer (Core 0) via a lock-free ring buffer.


Hardware Setup

ComponentInterfaceAddress
MPU-6050 (IMU)I²C0x68
QMC5883L (magnetometer)I²C0x0D
BMP280 (barometer)I²C0x76
NEO-6M (GPS)UART29600 baud
Host (logging)UART0921600 baud

All three I²C devices share the same bus at 400 kHz fast-mode.


Sample Rates

Sensor Rate Period
MPU-6050 500 Hz 2 000 µs
QMC5883L 75 Hz 13 333 µs
BMP280 25 Hz 40 000 µs
NEO-6M 1 Hz 1 000 000 µs

Lower-rate sensors are polled sub-sampled within the 500 Hz IMU loop — no separate timers, no RTOS overhead.


CSV Format & Fault Flags

The firmware prints a single header line on boot, then one row per IMU tick:

Field Groups Fields are grouped (only here) by sensor domain for clarity:
Group Fields
[timing] t_imu_us, t_read_start_us, t_proc_end_us, t_gps_us
[imu] ax_g, ay_g, az_g, gx_dps, gy_dps, gz_dps
[mag] mx_uT, my_uT, mz_uT
[baro] pressure_hPa, bmp_temp_C
[gps] lat_deg, lon_deg, alt_gps_m, hdop, satellites
[system] fault_flag, jitter_us
Field Reference
Field Unit Notes
t_imu_us µs esp_timer_get_time() at IMU read start - monotonic, 1µs resolution
t_read_start_us µs Earliest read start timestamp in the row
t_proc_end_us µs After all sensor reads complete — subtract from `t_read_start_us` for processing budget
t_gps_us µs Timestamp of last parsed GPS sentence
ax/ay/az g Accelerometer — converted from m/s²
gx/gy/gz °/s Gyroscope — converted from rad/s
mx/my/mz µT Magnetometer — QMC5883L at 8G range, 3000 LSB/Gauss
pressure hPa BMP280 — sanity-checked to 800–1100 hPa
bmp_temp °C BMP280 temperature
lat/lon degrees 8 decimal places (~1.1 mm resolution)
alt_gps m (MSL) GPS altitude
hdop Horizontal dilution of precision
satellites Satellites in use
fault_flag bitmask See below
jitter_us µs How many µs the IMU tick overshot its absolute deadline; 0 = on time
Fault Flag Bitmask
bit 0 FAULT_IMU MPU-6050 read failed; stale values carried forward
bit 1 FAULT_MAG QMC5883L returned all-zero (I²C fault)
bit 2 FAULT_BARO BMP280 out-of-range or NaN
bit 3 FAULT_GPS_HDOP HDOP > 2.5 or satellites < 4
bit 4 FAULT_GPS_NOFIX No valid GPS fix yet

Fault flags describe the current sample only — they are not latched system faults. A 0x00 row means all sensors reported cleanly.


Stale-Value Policy

When a lower-rate sensor misses its sub-deadline within a 500 Hz tick, the last known-good value is carried forward into that row's fields. The corresponding fault-flag bit is set so downstream consumers can distinguish fresh from stale. This guarantees consumers always see a valid reading rather than zeros.


Ring Buffer

A 128-slot lock-free SPSC ring buffer (RING_SIZE must be a power of 2) decouples the sensor task from the serial task. rb_push / rb_pop use __sync_synchronize() memory barriers to prevent store/load reordering across the two cores — no mutex, no FreeRTOS synchronisation primitive. If the ring fills (serial output can't keep up), the producer drops the row and increments rb_drops. Drops are reported on Serial once per second:

# rb_drops=12

Designed for SPSC (single producer, single consumer); not safe for multi-writer scenarios.


Repo-01 Structure

firmware/
├── src/
│   └── main.cpp          # All firmware logic
├── include/
│   ├── config.h          # Pin assignments, sample rates, fault flags, ring size
│   └── sensor_row.h      # SensorRow struct + lock-free ring buffer
├── platformio.ini        # PlatformIO build config
├── sdkconfig.defaults    # ESP-IDF SDK overrides (task WDT tuning)
└── docs/
    └── setup.png          # Schematic diagram

Dependencies

Managed by PlatformIO — no manual installation needed.

Library Version Purpose
adafruit/Adafruit MPU6050 ^2.2.6 IMU driver
mprograms/QMC5883LCompass ^1.0.2 Magnetometer driver
adafruit/Adafruit BMP280 Library ^2.6.8 Barometer driver
mikalhart/TinyGPSPlus ^1.0.3 NMEA sentence parser
adafruit/Adafruit Unified Sensor ^1.1.14 Sensor abstraction layer

Build & Flash

# Build
pio run

# Flash + open monitor
pio run --target upload && pio device monitor --baud 921600

On boot you should see sensor init messages followed by the CSV header:

# MPU-6050 OK
# QMC5883L OK
# BMP280 OK
# GPS UART started — waiting for fix (HDOP reported in data)
t_imu_us,t_read_start_us,...

If a sensor is absent, firmware continues logging available sensors and annotates the header with a # WARN line. fault_flag will reflect which sensor is missing on every row.


Implementation Notes

Why the WDT is unregistered for idle tasks: task_sensors busy-waits at 500 Hz on Core 1, which starves the idle task. Without esp_task_wdt_delete() on both idle tasks (and on loopTask), the TWDT fires every ~5 s and hard-resets the chip. sdkconfig.defaults disables the idle-task TWDT at the SDK level; the runtime calls are an explicit belt-and-suspenders.

Why GPS UART draining is on Core 0: The UART receive ISR runs on whichever core processes the interrupt. If task_sensors owned the UART drain, the 500 Hz busy-wait would starve the ISR on Core 1 and drop bytes in NMEA sentences. Core 0 yields via vTaskDelay(1) each iteration, giving the ISR clean execution windows.

Double-write tearing on GPS globals: lat and lon are volatile double (64-bit) written on Core 0 and read on Core 1. Xtensa LX6 does not guarantee atomic 64-bit stores; a torn read is possible. This is acceptable for logging — the corruption is bounded to one sample and corrected on the next GPS sentence.


Project Ecosystem

graph LR
    FW["🔧 firmware\nESP32 sensor acquisition\nCSV @ 921600 baud"]
    PL["📡 data-pipeline\nHost logger · serial → CSV\ncleaning & calibration"]
    AN["📊 analysis\nEKF · Allan variance\nvisualisation · reports"]
    EH["🧭 edgehard\nDead reckoning\nGPS dropout modelling"]

    FW -->|"UART CSV stream"| PL
    PL -->|"clean .csv files"| AN
    PL -->|"clean .csv files"| EH
    AN -. "future: model feedback" .-> EH
Loading
Repo Role Status
firmware(this repo, formerly navigation-system-firmware) ESP32 firmware — sensor acquisition & CSV streaming ✅ Active
data-pipeline Host-side logger & calibration — reads serial, writes timestamped CSV ✅ Active
analysis Offline processing — EKF, Allan variance, map visualisation, reports 🚧 In progress
edgehard Dead reckoning — GPS dropout modelling, edge inference 🔜 Planned

Why this exists

This project explores how sensor systems behave under high-frequency, tightly timed acquisition.

Instead of abstract simulation, it focuses on:

  • capturing raw sensor imperfections (noise, drift, jitter)
  • understanding timing effects in multi-rate systems
  • building a reliable data foundation for downstream estimation (EKF, dead reckoning)

The goal is to bridge the gap between clean theoretical models and messy real-world sensor data.

About

500 Hz deterministic multi-sensor data acquisition firmware for real-time embedded navigation systems

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages