MSP_RAW_IMU (102)
Returns raw sensor data from the accelerometer, gyroscope, and magnetometer.
Request
Empty payload (0 bytes).
Response Structure
| Offset | Type | Field | Description |
|---|---|---|---|
| 0-1 | I16 | acc_x | Accelerometer X axis |
| 2-3 | I16 | acc_y | Accelerometer Y axis |
| 4-5 | I16 | acc_z | Accelerometer Z axis |
| 6-7 | I16 | gyro_x | Gyroscope X axis |
| 8-9 | I16 | gyro_y | Gyroscope Y axis |
| 10-11 | I16 | gyro_z | Gyroscope Z axis |
| 12-13 | I16 | mag_x | Magnetometer X axis |
| 14-15 | I16 | mag_y | Magnetometer Y axis |
| 16-17 | I16 | mag_z | Magnetometer Z axis |
Total size: 18 bytes
Sensor Units
| Sensor | Scale Factor | Unit | Notes |
|---|---|---|---|
| Accelerometer | 1/512 | g | ±16g range typical |
| Gyroscope | 1/4.096 | °/s | ±2000°/s range typical |
| Magnetometer | Raw | µT | Sensor-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
-
Accelerometer Calibration: Use
MSP_ACC_CALIBRATION(205) with the aircraft level. -
Gyroscope Bias: Gyroscope has inherent bias that drifts with temperature. The flight controller handles this automatically.
-
Magnetometer Calibration: Use
MSP_MAG_CALIBRATION(206) while rotating through all axes. -
Scale Factors: The scale factors documented here are typical defaults. Actual values depend on sensor configuration.
Polling Rate
| Application | Recommended Rate |
|---|---|
| Attitude display | 20-50 Hz |
| Data logging | 100-500 Hz |
| Vibration analysis | 1000+ Hz |
| Integration tests | 10-20 Hz |
Note: MSP serial bandwidth limits practical polling rates. For high-frequency data, consider blackbox logging.