MAVLINKHUD

MAVLink Serialization Architecture

MAVLink is designed for a specific hostile environment: Low-Bandwidth, High-Latency, Unreliable Serial Links (e.g., 57600 baud telemetry radios). In this domain, every byte costs airtime.

Unlike Protobuf, JSON, or XML, which prioritize schema flexibility and forward compatibility via Tag-Length-Value (TLV) structures, MAVLink prioritizes Determinism and Overhead Efficiency.

Core Philosophy: C-Struct Packing

MAVLink serialization is effectively "C-structs over the wire."

  • Mechanism: The message definition (XML) is compiled into a C struct.
  • Serialization: The memory content of that struct is sent directly (Little Endian).
  • Parsing: The receiver casts the buffer back to the struct (or copies it).
  • Cost: Zero parsing overhead (CPU) and Zero serialization overhead (Bandwidth) beyond the header.

2. Anatomy of a Packet (v2.0)

A MAVLink v2 packet is a rigid byte stream (defined in mavlink_types.h). It does not describe what it is carrying; it assumes the receiver has the same dictionary (XML definitions).

Byte Field Description
0 STX Magic Marker (0xFD for v2).
1 LEN Payload Length (0-255).
2 INC_FLAGS Incompatibility Flags (e.g., Signing).
3 CMP_FLAGS Compatibility Flags.
4 SEQ Packet Sequence (0-255) for loss detection.
5 SYSID System ID (Sender, e.g., Drone).
6 COMPID Component ID (Sender, e.g., Autopilot vs. Camera).
7-9 MSGID Message ID (24-bit). The "Topic".
10... PAYLOAD The serialized data (up to 255 bytes).
N-2 CRC Checksum (CRC16-MCRF4XX) of Header + Payload + Extra Byte.

The "Extra Byte" (CRC_EXTRA)

MAVLink's unique integrity check.

  • Problem: If Sender V1 sends struct {float A} and Receiver V2 expects {float A, float B}, a blind cast is dangerous.
  • Solution: The CRC calculation includes a hidden "Seed" byte derived from the hash of the XML message definition.
  • Result: If the Sender and Receiver have different XML definitions for MSGID X, the CRC check fails silently. Schema enforcement is done by the checksum, not the parser.

Why doesn't ArduPilot just use Protobuf?

Feature MAVLink Protobuf
Encoding Packed Binary (Fixed). A float is always 4 bytes at a known offset. Varint + TLV. A float is a Tag + WireType + 4 bytes.
Parsing O(1) / Zero-Copy. Cast buffer to struct. Extremely fast on 8-bit MCUs. Recursive Descent. Requires CPU cycles to decode tags and reconstruct objects.
Bandwidth Minimal. Header (10 bytes) + Data. No field tags. Moderate. Every field carries a tag overhead. Varints save space for small ints but cost for large ones.
Schema Rigid. Changing a field changes the CRC_EXTRA, breaking the link. Flexible. Unknown fields are ignored. Great for microservices, bad for safety-critical control loops.
Safety Deterministic. We know exactly how many bytes ATTITUDE takes. Variable. Message size varies with content (e.g., zeros are compressed in proto3). Harder to schedule on fixed time-slots.

Verdict: Protobuf is superior for Inter-Process Communication (IPC) on a Companion Computer. MAVLink is superior for Telemetry and Command & Control (C2) over a radio link.


4. ArduPilot Implementation

The rubber meets the road in the GCS_MAVLink library.

  • GCS_MAVLink.cpp: The central hub. It manages the routing of messages between UARTs (Serial ports) and the internal Data Bus.
  • Fast Channel: High-rate streams (Attitude, VFR_HUD) are often pushed into ring buffers to be drained by the UART DMA.
  • Lazy Serialization: ArduPilot often checks comm_get_txspace() before attempting to serialize. If the radio buffer is full, the packet is dropped immediately to prevent latency buildup (Backpressure).

Field Sorting (Wire Optimization)

MAVLink generators (like pymavlink) reorder fields in the struct to ensure Natural Alignment.

  • All uint64_t/double (8 bytes) come first.
  • Then uint32_t/float (4 bytes).
  • Then uint16_t (2 bytes).
  • Then uint8_t (1 byte).

Why? To prevent the C compiler from adding "Padding Bytes" between fields to align memory access. This ensures the "Wire Format" is perfectly dense, with zero wasted bits.