Skip to main content

Configuration Commands (32-62)

These commands manage flight controller settings including battery, modes, features, and hardware configuration.

MSP_BATTERY_CONFIG (32)​

Query battery monitoring configuration.

Response​

OffsetTypeFieldDescription
0U8vbat_min_cellMinimum cell voltage × 10
1U8vbat_max_cellMaximum cell voltage × 10
2U8vbat_warning_cellWarning voltage × 10
3-4U16capacityBattery capacity in mAh
5U8voltage_source0=none, 1=ADC, 2=ESC
6U8current_source0=none, 1=ADC, 2=ESC, 3=MSP
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct MspBatteryConfig {
pub vbat_min_cell: u8, // Minimum cell voltage × 10 (e.g., 33 = 3.3V)
pub vbat_max_cell: u8, // Maximum cell voltage × 10 (e.g., 42 = 4.2V)
pub vbat_warning_cell: u8, // Warning voltage × 10 (e.g., 35 = 3.5V)
pub capacity: u16, // mAh
pub voltage_source: u8,
pub current_source: u8,
}

impl MspBatteryConfig {
pub const COMMAND: u8 = 32;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 7 {
return None;
}
Some(MspBatteryConfig {
vbat_min_cell: data[0],
vbat_max_cell: data[1],
vbat_warning_cell: data[2],
capacity: u16::from_le_bytes([data[3], data[4]]),
voltage_source: data[5],
current_source: data[6],
})
}

pub fn min_cell_voltage(&self) -> f32 {
self.vbat_min_cell as f32 / 10.0
}

pub fn max_cell_voltage(&self) -> f32 {
self.vbat_max_cell as f32 / 10.0
}
}

MSP_MODE_RANGES (34)​

Query flight mode activation ranges. Each mode can be activated based on an auxiliary channel position.

Response​

Array of mode range entries (40 entries typical):

OffsetTypeFieldDescription
0U8box_idFlight mode box ID
1U8aux_channelAux channel index (0 = AUX1)
2U8range_startStart position (0-47, step=25, offset=900)
3U8range_endEnd position (0-47)

Range Calculation​

  • PWM range: 900-2100 µs
  • Step size: 25 µs
  • Value 0 = 900 µs, Value 48 = 2100 µs
  • PWM = 900 + (value × 25)
#[derive(Debug, Clone, Copy)]
pub struct MspModeRange {
pub box_id: u8,
pub aux_channel: u8,
pub range_start: u8,
pub range_end: u8,
}

impl MspModeRange {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
Some(MspModeRange {
box_id: data[0],
aux_channel: data[1],
range_start: data[2],
range_end: data[3],
})
}

/// Convert range value to PWM microseconds
pub fn range_to_pwm(value: u8) -> u16 {
900 + (value as u16 * 25)
}

pub fn start_pwm(&self) -> u16 {
Self::range_to_pwm(self.range_start)
}

pub fn end_pwm(&self) -> u16 {
Self::range_to_pwm(self.range_end)
}

/// Check if a PWM value activates this mode
pub fn is_active(&self, pwm: u16) -> bool {
pwm >= self.start_pwm() && pwm <= self.end_pwm()
}
}

/// Parse all mode ranges from response
pub fn parse_mode_ranges(data: &[u8]) -> Vec<MspModeRange> {
data.chunks(4)
.filter_map(MspModeRange::from_bytes)
.filter(|r| r.box_id != 0 || r.aux_channel != 0) // Skip empty slots
.collect()
}

MSP_FEATURE_CONFIG (36)​

Query enabled features as a bitmask.

Response​

OffsetTypeFieldDescription
0-3U32featuresFeature flags bitmask

Feature Flags​

bitflags::bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct Features: u32 {
const RX_PPM = 1 << 0;
const INFLIGHT_ACC_CAL = 1 << 2;
const RX_SERIAL = 1 << 3;
const MOTOR_STOP = 1 << 4;
const SERVO_TILT = 1 << 5;
const SOFTSERIAL = 1 << 6;
const GPS = 1 << 7;
const RANGEFINDER = 1 << 9;
const TELEMETRY = 1 << 10;
const THREE_D = 1 << 12;
const RX_PARALLEL_PWM = 1 << 13;
const RX_MSP = 1 << 14;
const RSSI_ADC = 1 << 15;
const LED_STRIP = 1 << 16;
const DISPLAY = 1 << 17;
const OSD = 1 << 18;
const CHANNEL_FORWARDING = 1 << 20;
const TRANSPONDER = 1 << 21;
const AIRMODE = 1 << 22;
const RX_SPI = 1 << 25;
const ESC_SENSOR = 1 << 27;
const ANTI_GRAVITY = 1 << 28;
}
}

impl Features {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let bits = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
Some(Features::from_bits_truncate(bits))
}
}

MSP_RX_CONFIG (44)​

Query receiver configuration.

Response​

OffsetTypeFieldDescription
0U8serialrx_providerSerial RX protocol
1-2U16max_checkMax valid channel value
3-4U16mid_rcCenter stick value
5-6U16min_checkMin valid channel value
7U8spektrum_sat_bindSpektrum satellite bind
8-9U16rx_min_usecMinimum valid PWM
10-11U16rx_max_usecMaximum valid PWM
12U8rc_interpolationInterpolation type
13U8rc_interpolation_intervalInterval in ms
14-15U16airmode_start_throttleAirmode activation throttle
16U8rx_spi_protocolSPI RX protocol
17-20U32rx_spi_idSPI RX ID
21U8rx_spi_rf_channel_countSPI RF channels
22U8fpv_cam_angle_degreesFPV camera angle
23U8rc_smoothingSmoothing config

Serial RX Protocols​

ValueProtocol
0SPEK1024
1SPEK2048
2SBUS
3SUMD
4SUMH
5XB-B
6XB-B-RJ01
7IBUS
8JETIEXBUS
9CRSF
10SRXL
11CUSTOM
12FPORT
13SRXL2
14GHST
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum SerialRxProtocol {
Spektrum1024 = 0,
Spektrum2048 = 1,
Sbus = 2,
Sumd = 3,
Sumh = 4,
XBusB = 5,
XBusBRJ01 = 6,
Ibus = 7,
JetiExBus = 8,
Crsf = 9,
Srxl = 10,
Custom = 11,
Fport = 12,
Srxl2 = 13,
Ghst = 14,
}

#[derive(Debug, Clone)]
pub struct MspRxConfig {
pub serialrx_provider: u8,
pub max_check: u16,
pub mid_rc: u16,
pub min_check: u16,
pub rx_min_usec: u16,
pub rx_max_usec: u16,
pub rc_interpolation: u8,
pub rc_interpolation_interval: u8,
}

impl MspRxConfig {
pub const COMMAND: u8 = 44;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 14 {
return None;
}
Some(MspRxConfig {
serialrx_provider: data[0],
max_check: u16::from_le_bytes([data[1], data[2]]),
mid_rc: u16::from_le_bytes([data[3], data[4]]),
min_check: u16::from_le_bytes([data[5], data[6]]),
rx_min_usec: u16::from_le_bytes([data[8], data[9]]),
rx_max_usec: u16::from_le_bytes([data[10], data[11]]),
rc_interpolation: data[12],
rc_interpolation_interval: data[13],
})
}
}

MSP_CF_SERIAL_CONFIG (54)​

Query serial port configuration.

Response​

Array of port configurations:

OffsetTypeFieldDescription
0U8identifierPort identifier
1-2U16function_maskFunction flags
3U8msp_baudMSP baud rate index
4U8gps_baudGPS baud rate index
5U8telemetry_baudTelemetry baud rate
6U8blackbox_baudBlackbox baud rate

Port Identifiers​

ValuePort
0USB VCP
1UART1
2UART2
3UART3
4UART4
5UART5
6UART6
20SOFTSERIAL1
21SOFTSERIAL2

Function Flags​

bitflags::bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct SerialPortFunction: u16 {
const NONE = 0;
const MSP = 1 << 0;
const GPS = 1 << 1;
const TELEMETRY_FRSKY = 1 << 2;
const TELEMETRY_HOTT = 1 << 3;
const TELEMETRY_LTM = 1 << 4;
const TELEMETRY_SMARTPORT = 1 << 5;
const RX_SERIAL = 1 << 6;
const BLACKBOX = 1 << 7;
const TELEMETRY_MAVLINK = 1 << 9;
const ESC_SENSOR = 1 << 10;
const VTX_SMARTAUDIO = 1 << 11;
const TELEMETRY_IBUS = 1 << 12;
const VTX_TRAMP = 1 << 13;
const RCDEVICE = 1 << 14;
const LIDAR_TF = 1 << 15;
}
}

#[derive(Debug, Clone)]
pub struct SerialPortConfig {
pub identifier: u8,
pub function_mask: SerialPortFunction,
pub msp_baud_index: u8,
pub gps_baud_index: u8,
pub telemetry_baud_index: u8,
pub blackbox_baud_index: u8,
}

impl SerialPortConfig {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 7 {
return None;
}
Some(SerialPortConfig {
identifier: data[0],
function_mask: SerialPortFunction::from_bits_truncate(
u16::from_le_bytes([data[1], data[2]])
),
msp_baud_index: data[3],
gps_baud_index: data[4],
telemetry_baud_index: data[5],
blackbox_baud_index: data[6],
})
}
}

MSP_ARMING_CONFIG (61)​

Query arming configuration.

Response​

OffsetTypeFieldDescription
0U8auto_disarm_delayAuto-disarm timeout (seconds)
1U8disarm_kill_switchDisarm switch behavior
2U8small_angleMax arm angle (degrees)
#[derive(Debug, Clone, Copy)]
pub struct MspArmingConfig {
pub auto_disarm_delay: u8, // Seconds, 0 = disabled
pub disarm_kill_switch: u8, // 0 = normal, 1 = kill switch
pub small_angle: u8, // Max arming angle in degrees
}

impl MspArmingConfig {
pub const COMMAND: u8 = 61;

pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 3 {
return None;
}
Some(MspArmingConfig {
auto_disarm_delay: data[0],
disarm_kill_switch: data[1],
small_angle: data[2],
})
}
}

MSP_SET Commands​

MSP_SET_FEATURE_CONFIG (37)​

Set feature flags.

pub fn build_set_feature_request(features: Features) -> Vec<u8> {
const MSP_SET_FEATURE_CONFIG: u8 = 37;
let bits = features.bits();
let payload = bits.to_le_bytes();

let size = 4u8;
let mut checksum = size ^ MSP_SET_FEATURE_CONFIG;
for b in &payload {
checksum ^= b;
}

vec![
b'$', b'M', b'<',
size,
MSP_SET_FEATURE_CONFIG,
payload[0], payload[1], payload[2], payload[3],
checksum,
]
}

MSP_SET_ARMING_CONFIG (62)​

Set arming configuration.

pub fn build_set_arming_config(
auto_disarm_delay: u8,
disarm_kill_switch: u8,
small_angle: u8,
) -> Vec<u8> {
const MSP_SET_ARMING_CONFIG: u8 = 62;
let payload = [auto_disarm_delay, disarm_kill_switch, small_angle];

let size = 3u8;
let mut checksum = size ^ MSP_SET_ARMING_CONFIG;
for b in &payload {
checksum ^= b;
}

vec![
b'$', b'M', b'<',
size,
MSP_SET_ARMING_CONFIG,
payload[0], payload[1], payload[2],
checksum,
]
}

Important Notes​

  1. Configuration changes require EEPROM write: After modifying settings, send MSP_EEPROM_WRITE (250) to persist changes.

  2. Some changes require reboot: Feature flags and serial port changes typically require a reboot (MSP_REBOOT command 68).

  3. Verify before write: Always read the current configuration, modify only needed fields, and write back the complete structure.