← Back to portfolio Français

Antenne

Architecture overview — 9DoF motion sensor → OSC over Ethernet

Purpose

A self-contained motion sensor "antenna": plug the MPU9150 module directly into the ESP32-PoE's UEXT connector, power the board over a single Ethernet cable (PoE — no separate power supply), and it streams accelerometer, gyroscope, magnetometer, and fused orientation data as OSC to any receiving software (Max/MSP, Pure Data, TouchDesigner, etc.) at 50 Hz. Onboard gyroscope calibration runs automatically at boot, and a one-time (or as-needed) magnetometer calibration — triggered by a button hold or a remote OSC message — is persisted to flash so it survives reboots.

Hardware

PartReference
MCU boardOlimex ESP32-PoE (16 MB flash)
Motion sensorOlimex MOD-MPU9150 (MPU-6050 accel/gyro + AK8975 magnetometer, in one package)
ConnectionUEXT connector — plugs directly, no wiring
NetworkRJ45 Ethernet, powered over PoE
I2C pins (UEXT)SDA = GPIO13, SCL = GPIO16
Onboard LEDGPIO33, active-high
BTN1GPIO34, active-low, external pull-up

System overview

Antenne system overview A MOD-MPU9150 9DoF motion sensor connects over I2C to Olimex ESP32-PoE firmware, which fuses orientation and streams an OSC bundle over PoE Ethernet to receiving software at 50 Hz. Incoming OSC can trigger magnetometer calibration on the board. MOD-MPU9150 (UEXT, I2C) Accelerometer,gyroscope, magnetometer Olimex ESP32-PoE firmware Reads sensors, fuses orientation OSC bundle out, PoE Ethernet 50 Hz to DEST_IP:DEST_PORT Receiving software Max/MSP, Pure Data, TouchDesigner... Incoming OSC on port 8888 (/mpu/calibrate) can trigger a magnetometer calibration session on the board at any time
Sensor → firmware → OSC over Ethernet → receiving software

Incoming OSC on port 8888 (/mpu/calibrate) can trigger a magnetometer calibration session on the board at any time, independent of the outgoing data stream.

Firmware main loop

The board runs a fixed 50 Hz loop (LOOP_MS = 20):

Antenne firmware main loop After boot calibration and loading any saved magnetometer bias, the firmware reads the MPU9150 and AK8975 over I2C, applies magnetometer bias and runs a 9-axis Kalman fusion, sends the resulting OSC bundle over Ethernet, then updates the LED and checks the button and incoming OSC before repeating the whole loop every 20 milliseconds. Boot: Ethernet, load mag bias, calibrate gyro 1-second still calibration Read MPU9150 + AK8975 over I2C Accel, gyro, raw magnetometer Bias correction + 9-axis Kalman fusion puara-gestures — roll, pitch, tilt-compensated yaw Send OSC bundle over Ethernet Accel, gyro, mag, euler angles, tilt Update LED, check BTN1 and incoming OSC May trigger a calibration session ↻ repeats every 20 ms (50 Hz loop)
The fixed-rate main loop, from sensor read to OSC send
  1. Boot — brings up the Ethernet link (static IP, up to 8 s wait), loads any previously saved magnetometer bias from flash (NVS), initializes the MPU9150 over I2C, and runs a 1-second gyroscope bias calibration (200 samples while the sensor must stay still).
  2. Sensor read — one 14-byte I2C burst read covers accelerometer and gyroscope registers together; the magnetometer is read separately (via the MPU9150's I2C bypass mode giving direct access to the onboard AK8975) whenever its data-ready bit is set.
  3. Orientation fusion — if a magnetometer calibration has been completed, the corresponding per-axis bias is subtracted before the vector is normalized to unit length. The result feeds a 9-axis Kalman filter (from puara-gestures) that fuses accelerometer, gyroscope, and magnetometer into roll, pitch, and tilt-compensated yaw.
  4. OSC send — one bundle per loop iteration, gated on the Ethernet link being up.
  5. LED / input handling — the LED state machine and the BTN1 long-press detector run every cycle, and any incoming OSC calibration request is dispatched here too.

Magnetometer calibration

Hard-iron interference (nearby metal, speakers, power supplies) shifts the magnetometer's zero point, which throws off yaw. Calibration removes that constant offset — needed once at first setup, and again whenever the instrument's physical configuration changes.

Antenne magnetometer calibration sequence Calibration is triggered either by holding a button for 5 seconds or by an incoming OSC message. A 30-second window then tracks the minimum and maximum magnetometer reading on each axis while the sensor is rotated through every orientation. The midpoint of each axis's min and max becomes the hard-iron bias, which is saved to flash and loaded automatically on every future boot. Trigger calibration Hold BTN1 5s, or sendOSC /mpu/calibrate 30s rotation window LED fast blink (4 Hz), tracks min/max per axis Compute hard-iron bias bias = (min + max) / 2, per axis Save to flash (NVS) Solid LED for 1s confirms completion Loaded automatically on every future boot Survives power cuts and firmware updates
Calibration sequence, from trigger to persisted bias

Calibration can be started two ways — holding the physical button, or sending an OSC message from the same network the board streams to — so it can be triggered either at the instrument itself or remotely from whatever software is already receiving its data. During the 30-second window, rotating the sensor through every orientation (a figure-8 motion covering all axes works well) lets the firmware record the true minimum and maximum reading on each magnetometer axis; the midpoint of each axis's min/max becomes that axis's bias. The result is written to non-volatile storage and survives power cuts and firmware re-flashes.

LED states

LED behaviourMeaning
OffNo Ethernet link
Slow blink (1 Hz)Connected and streaming
Solid ONBTN1 is being held — keep holding to start calibration
Fast blink (4 Hz)Magnetometer calibration in progress
Solid for 1 secondCalibration just completed successfully

OSC reference

Outgoing (board → computer), one bundle per 20 ms

AddressTypeUnitDescription
/mpu/accel/x /mpu/accel/y /mpu/accel/zfloatgRaw accelerometer
/mpu/gyro/x /mpu/gyro/y /mpu/gyro/zfloat°/sGyroscope, bias-corrected
/mpu/mag/x /mpu/mag/y /mpu/mag/zfloatunit sphereMagnetometer, normalized; offset before calibration, centered on origin after
/mpu/euler/rollfloat° (0–360)Rotation around X
/mpu/euler/pitchfloat° (−90–+90)Rotation around Y
/mpu/euler/yawfloat° (0–360)Heading, tilt-compensated
/mpu/tiltfloat° (0–180)Combined tilt magnitude, sqrt(roll² + pitch²), clamped
/mpu/btn1int—1 while BTN1 is held, else 0
/mpu/cal/statusint—0 = uncalibrated, 1 = calibrating, 2 = calibrated

Incoming (computer → board), port 8888

AddressArgumentsDescription
/mpu/calibrate(none)Starts a 30-second magnetometer calibration session

Adjustable parameters

ParameterLocationDefaultEffect
Send rateLOOP_MS20 (50 Hz)Lower = faster updates; don't go below 10 (100 Hz)
Kalman process noise (Q)kalman(Q, R)0.001How much the filter trusts gyroscope integration; lower = smoother but slower to respond
Kalman measurement noise (R)kalman(Q, R)0.01How much the filter trusts accel/mag corrections; higher = smoother but drifts more under disturbance
Gyroscope DLPF bandwidthR_CONFIG register write0x04 (~21 Hz)0x02=98 Hz (fast movement) … 0x05=10 Hz (near-static); current setting suits slow, deliberate movement

Project structure

├── boards/
│   └── esp32-poe-16mb.json     custom PlatformIO board definition
├── examples/
│   ├── ESP32_PoE_Ethernet_SD_Card_Arduino.ino
│   ├── ESP32_PoE_WebServer_Demo.ino
│   └── MagnetometreCalibrationDemo.cpp
├── mpu9150_demo/                original bare sensor demo
├── src/
│   └── main.cpp                 firmware (edit this)
└── platformio.ini

The examples/ and mpu9150_demo/ folders are bundled reference material, not part of the firmware that actually runs — each comes from a different origin and is kept for reference rather than executed:

Custom PlatformIO board definition

boards/esp32-poe-16mb.json defines the Olimex ESP32-PoE's 16 MB-flash variant for PlatformIO, since it isn't one of the built-ins: correct partition table and linker script for 16 MB flash, the Ethernet PHY wiring specific to this board (LAN8720 over RMII, MDC/MDIO pins, power pin, clock mode), and upload parameters (921,600 baud, esptool/espota).

Dependencies

Managed automatically by PlatformIO: