Skip to main content

MSP_STATUS (101)

Returns flight controller status including cycle time, sensor availability, active flight modes, and system load.

Request​

Empty payload (0 bytes).

Response Structure​

OffsetTypeFieldDescription
0-1U16cycle_timePID loop time in microseconds
2-3U16i2c_error_countAccumulated I2C errors
4-5U16sensor_flagsActive sensor bitmask
6-9U32flight_mode_flagsActive flight mode bitmask
10U8current_pid_profileActive PID profile (0-indexed)
11-12U16system_loadCPU load × 10 (0-1000 = 0-100%)

Total size: 13 bytes (minimum)

Sensor Flags​

BitMaskSensorDescription
00x0001ACCAccelerometer present
10x0002BAROBarometer present
20x0004MAGMagnetometer present
30x0008GPSGPS module present
40x0010RANGEFINDERRangefinder/sonar present
50x0020GYROGyroscope present
60x0040OPTICAL_FLOWOptical flow sensor present

Rust Implementation​

use core::fmt;

/// Sensor availability flags
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SensorFlags(u16);

impl SensorFlags {
pub const ACC: u16 = 1 << 0;
pub const BARO: u16 = 1 << 1;
pub const MAG: u16 = 1 << 2;
pub const GPS: u16 = 1 << 3;
pub const RANGEFINDER: u16 = 1 << 4;
pub const GYRO: u16 = 1 << 5;
pub const OPTICAL_FLOW: u16 = 1 << 6;

pub fn new(bits: u16) -> Self {
SensorFlags(bits)
}

pub fn has_acc(&self) -> bool { self.0 & Self::ACC != 0 }
pub fn has_baro(&self) -> bool { self.0 & Self::BARO != 0 }
pub fn has_mag(&self) -> bool { self.0 & Self::MAG != 0 }
pub fn has_gps(&self) -> bool { self.0 & Self::GPS != 0 }
pub fn has_rangefinder(&self) -> bool { self.0 & Self::RANGEFINDER != 0 }
pub fn has_gyro(&self) -> bool { self.0 & Self::GYRO != 0 }
pub fn has_optical_flow(&self) -> bool { self.0 & Self::OPTICAL_FLOW != 0 }
}

impl fmt::Display for SensorFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sensors = Vec::new();
if self.has_gyro() { sensors.push("GYRO"); }
if self.has_acc() { sensors.push("ACC"); }
if self.has_baro() { sensors.push("BARO"); }
if self.has_mag() { sensors.push("MAG"); }
if self.has_gps() { sensors.push("GPS"); }
if self.has_rangefinder() { sensors.push("RANGE"); }
if self.has_optical_flow() { sensors.push("OFLOW"); }
write!(f, "[{}]", sensors.join(", "))
}
}

/// MSP_STATUS response payload
#[derive(Debug, Clone, Copy)]
pub struct MspStatus {
/// PID loop cycle time in microseconds
pub cycle_time_us: u16,
/// I2C bus error count
pub i2c_error_count: u16,
/// Active sensors bitmask
pub sensors: SensorFlags,
/// Active flight modes bitmask
pub flight_mode_flags: u32,
/// Current PID profile index
pub current_pid_profile: u8,
/// System CPU load (0-1000 = 0-100%)
pub system_load: u16,
}

impl MspStatus {
pub const COMMAND: u8 = 101;
pub const MIN_SIZE: usize = 13;

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

Some(MspStatus {
cycle_time_us: u16::from_le_bytes([data[0], data[1]]),
i2c_error_count: u16::from_le_bytes([data[2], data[3]]),
sensors: SensorFlags::new(u16::from_le_bytes([data[4], data[5]])),
flight_mode_flags: u32::from_le_bytes([data[6], data[7], data[8], data[9]]),
current_pid_profile: data[10],
system_load: u16::from_le_bytes([data[11], data[12]]),
})
}

/// Get PID loop frequency in Hz
pub fn pid_frequency_hz(&self) -> u32 {
if self.cycle_time_us > 0 {
1_000_000 / self.cycle_time_us as u32
} else {
0
}
}

/// Get CPU load as percentage (0.0-100.0)
pub fn cpu_load_percent(&self) -> f32 {
self.system_load as f32 / 10.0
}

/// Check if a specific flight mode is active
pub fn is_mode_active(&self, mode_bit: u8) -> bool {
self.flight_mode_flags & (1 << mode_bit) != 0
}
}

Raw Hex Example​

Request frame:

24 4D 3C 00 65 65
│ │ │ │ │ └─ Checksum (0 ^ 101 = 101)
│ │ │ │ └──── Command (101 = 0x65)
│ │ │ └─────── Size (0)
│ │ └────────── Direction (<)
│ └───────────── Header M
└──────────────── Header $

Response frame:

24 4D 3E 0D 65 D0 07 00 00 21 00 05 00 00 00 00 E8 03 XX
│ │ │ │ │ └───────────────────────────────────────┴─ Payload (13 bytes)
│ │ │ │ └── Command (101)
│ │ │ └───── Size (13 bytes)
│ │ └──────── Direction (>)
│ └─────────── Header M
└────────────── Header $

Payload breakdown:

D0 07    → cycle_time = 0x07D0 = 2000 µs (500 Hz PID loop)
00 00 → i2c_error_count = 0
21 00 → sensor_flags = 0x0021 = ACC + GYRO
05 00 00 00 → flight_mode_flags = 0x00000005
00 → current_pid_profile = 0
E8 03 → system_load = 0x03E8 = 1000 = 100.0%

Usage Example​

use std::io::{Read, Write};

fn read_status<T: Read + Write>(port: &mut T) -> Option<MspStatus> {
// Send request
let request = [0x24, 0x4D, 0x3C, 0x00, 0x65, 0x65];
port.write_all(&request).ok()?;

// Read response
let mut buf = [0u8; 32];
let n = port.read(&mut buf).ok()?;

// Validate header
if n < 6 || &buf[0..3] != b"$M>" {
return None;
}

let size = buf[3] as usize;
let payload = &buf[5..5 + size];

MspStatus::from_payload(payload)
}

fn main() {
// Example usage
let payload = [
0xD0, 0x07, // cycle_time = 2000
0x00, 0x00, // i2c_errors = 0
0x21, 0x00, // sensors = ACC + GYRO
0x05, 0x00, 0x00, 0x00, // flight_modes
0x00, // pid_profile = 0
0xE8, 0x03, // system_load = 1000
];

if let Some(status) = MspStatus::from_payload(&payload) {
println!("PID Frequency: {} Hz", status.pid_frequency_hz());
println!("CPU Load: {:.1}%", status.cpu_load_percent());
println!("Sensors: {}", status.sensors);
println!("I2C Errors: {}", status.i2c_error_count);
println!("PID Profile: {}", status.current_pid_profile);
}
}

Output:

PID Frequency: 500 Hz
CPU Load: 100.0%
Sensors: [GYRO, ACC]
I2C Errors: 0
PID Profile: 0

Extended Status (MSP_STATUS_EX)​

MSP_STATUS_EX (150) provides additional fields:

OffsetTypeFieldDescription
0-12...(same as MSP_STATUS)Base status fields
13U8pid_profile_countTotal PID profiles
14U8rate_profile_indexCurrent rate profile
15U8rate_profile_countTotal rate profiles
16+...Additional fieldsVaries by API version

Use MSP_STATUS_EX for more detailed status information in newer firmware versions.