Skip to main content

MSP_RAW_IMU (102)

Returns raw sensor data from the accelerometer, gyroscope, and magnetometer.

Request​

Empty payload (0 bytes).

Response Structure​

OffsetTypeFieldDescription
0-1I16acc_xAccelerometer X axis
2-3I16acc_yAccelerometer Y axis
4-5I16acc_zAccelerometer Z axis
6-7I16gyro_xGyroscope X axis
8-9I16gyro_yGyroscope Y axis
10-11I16gyro_zGyroscope Z axis
12-13I16mag_xMagnetometer X axis
14-15I16mag_yMagnetometer Y axis
16-17I16mag_zMagnetometer Z axis

Total size: 18 bytes

Sensor Units​

SensorScale FactorUnitNotes
Accelerometer1/512g±16g range typical
Gyroscope1/4.096°/s±2000°/s range typical
MagnetometerRawµTSensor-dependent

Coordinate System​

Standard aerospace convention (NED - North-East-Down when level):

        X (Roll axis, forward)
↑
│
│
Y ←─────┼ Z (into page, down)
(Pitch axis, left)

Rust Implementation​

/// Raw IMU sensor readings
#[derive(Debug, Clone, Copy, Default)]
pub struct MspRawImu {
/// Accelerometer readings [x, y, z] in raw units
pub acc: [i16; 3],
/// Gyroscope readings [x, y, z] in raw units
pub gyro: [i16; 3],
/// Magnetometer readings [x, y, z] in raw units
pub mag: [i16; 3],
}

impl MspRawImu {
pub const COMMAND: u8 = 102;
pub const SIZE: usize = 18;

/// Parse from MSP response payload
pub fn from_payload(data: &[u8]) -> Option<Self> {
if data.len() < Self::SIZE {
return None;
}

let read_i16 = |offset: usize| -> i16 {
i16::from_le_bytes([data[offset], data[offset + 1]])
};

Some(MspRawImu {
acc: [read_i16(0), read_i16(2), read_i16(4)],
gyro: [read_i16(6), read_i16(8), read_i16(10)],
mag: [read_i16(12), read_i16(14), read_i16(16)],
})
}

/// Accelerometer values in g (gravitational acceleration)
/// Scale: 1/512 g per LSB (for ±16g range)
pub fn acc_g(&self) -> [f32; 3] {
[
self.acc[0] as f32 / 512.0,
self.acc[1] as f32 / 512.0,
self.acc[2] as f32 / 512.0,
]
}

/// Gyroscope values in degrees per second
/// Scale: 1/4.096 °/s per LSB (for ±2000°/s range)
pub fn gyro_dps(&self) -> [f32; 3] {
[
self.gyro[0] as f32 / 4.096,
self.gyro[1] as f32 / 4.096,
self.gyro[2] as f32 / 4.096,
]
}

/// Gyroscope values in radians per second
pub fn gyro_rps(&self) -> [f32; 3] {
let dps = self.gyro_dps();
[
dps[0] * core::f32::consts::PI / 180.0,
dps[1] * core::f32::consts::PI / 180.0,
dps[2] * core::f32::consts::PI / 180.0,
]
}

/// Magnitude of acceleration vector
pub fn acc_magnitude(&self) -> f32 {
let g = self.acc_g();
(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt()
}

/// Magnitude of rotation rate
pub fn gyro_magnitude(&self) -> f32 {
let dps = self.gyro_dps();
(dps[0] * dps[0] + dps[1] * dps[1] + dps[2] * dps[2]).sqrt()
}

/// Check if aircraft is in free-fall (near zero G)
pub fn is_freefall(&self, threshold_g: f32) -> bool {
self.acc_magnitude() < threshold_g
}

/// Check if gyro is stationary (below rotation threshold)
pub fn is_stationary(&self, threshold_dps: f32) -> bool {
self.gyro_magnitude() < threshold_dps
}
}

Data Processing Utilities​

/// Low-pass filter for sensor data
pub struct LowPassFilter {
alpha: f32,
state: f32,
}

impl LowPassFilter {
pub fn new(cutoff_hz: f32, sample_rate_hz: f32) -> Self {
let rc = 1.0 / (2.0 * core::f32::consts::PI * cutoff_hz);
let dt = 1.0 / sample_rate_hz;
let alpha = dt / (rc + dt);
LowPassFilter { alpha, state: 0.0 }
}

pub fn update(&mut self, value: f32) -> f32 {
self.state = self.state + self.alpha * (value - self.state);
self.state
}
}

/// Complementary filter for attitude estimation
pub struct ComplementaryFilter {
alpha: f32, // Gyro weight (0.98 typical)
roll: f32,
pitch: f32,
}

impl ComplementaryFilter {
pub fn new(alpha: f32) -> Self {
ComplementaryFilter {
alpha,
roll: 0.0,
pitch: 0.0,
}
}

pub fn update(&mut self, imu: &MspRawImu, dt: f32) -> (f32, f32) {
let acc = imu.acc_g();
let gyro = imu.gyro_dps();

// Accelerometer-based angles
let acc_roll = acc[1].atan2(acc[2]) * 180.0 / core::f32::consts::PI;
let acc_pitch = (-acc[0]).atan2((acc[1] * acc[1] + acc[2] * acc[2]).sqrt())
* 180.0 / core::f32::consts::PI;

// Gyro integration
self.roll += gyro[0] * dt;
self.pitch += gyro[1] * dt;

// Complementary filter fusion
self.roll = self.alpha * self.roll + (1.0 - self.alpha) * acc_roll;
self.pitch = self.alpha * self.pitch + (1.0 - self.alpha) * acc_pitch;

(self.roll, self.pitch)
}
}

Raw Hex Example​

Request frame:

24 4D 3C 00 66 66

Response frame (aircraft level, stationary):

24 4D 3E 12 66
00 00 ← acc_x = 0
00 00 ← acc_y = 0
00 02 ← acc_z = 512 (1g, pointing down)
00 00 ← gyro_x = 0
00 00 ← gyro_y = 0
00 00 ← gyro_z = 0
00 00 ← mag_x = 0
00 00 ← mag_y = 0
00 00 ← mag_z = 0
XX ← checksum

Usage Example​

use std::time::{Duration, Instant};

fn main() {
// Simulated IMU data: level, 1g down, slight yaw rotation
let payload = [
0x00, 0x00, // acc_x = 0
0x00, 0x00, // acc_y = 0
0x00, 0x02, // acc_z = 512 (1g)
0x00, 0x00, // gyro_x = 0
0x00, 0x00, // gyro_y = 0
0x00, 0x04, // gyro_z = 1024 (250 °/s yaw rate)
0x00, 0x00, // mag_x = 0
0x00, 0x00, // mag_y = 0
0x00, 0x00, // mag_z = 0
];

if let Some(imu) = MspRawImu::from_payload(&payload) {
let acc = imu.acc_g();
let gyro = imu.gyro_dps();

println!("Accelerometer: X={:.2}g Y={:.2}g Z={:.2}g",
acc[0], acc[1], acc[2]);
println!("Gyroscope: X={:.1}°/s Y={:.1}°/s Z={:.1}°/s",
gyro[0], gyro[1], gyro[2]);
println!("Acceleration magnitude: {:.2}g", imu.acc_magnitude());
println!("Rotation rate magnitude: {:.1}°/s", imu.gyro_magnitude());
println!("Stationary: {}", imu.is_stationary(5.0));
}
}

Output:

Accelerometer: X=0.00g Y=0.00g Z=1.00g
Gyroscope: X=0.0°/s Y=0.0°/s Z=250.0°/s
Acceleration magnitude: 1.00g
Rotation rate magnitude: 250.0°/s
Stationary: false

Calibration Notes​

  1. Accelerometer Calibration: Use MSP_ACC_CALIBRATION (205) with the aircraft level.

  2. Gyroscope Bias: Gyroscope has inherent bias that drifts with temperature. The flight controller handles this automatically.

  3. Magnetometer Calibration: Use MSP_MAG_CALIBRATION (206) while rotating through all axes.

  4. Scale Factors: The scale factors documented here are typical defaults. Actual values depend on sensor configuration.

Polling Rate​

ApplicationRecommended Rate
Attitude display20-50 Hz
Data logging100-500 Hz
Vibration analysis1000+ Hz
Integration tests10-20 Hz

Note: MSP serial bandwidth limits practical polling rates. For high-frequency data, consider blackbox logging.