Skip to the content
FixTheRobot Explore Robotics / UAV / Module 6 / M6-T7

UAV Systems Engineering · Module 6

M6-T7: MAVLink protocol

MAVLink is the lingua franca between autopilots, ground stations and companion computers. Parse one frame by hand once and it stops being mysterious.

  • Lesson 7 of 16
  • UAV Software and Flight-Controller Integration
  • Established engineering
  • About 6 minutes to read

MAVLink is a lightweight binary messaging protocol. Almost every autopilot, ground station and companion library speaks it. Understanding the frame, the identity model (system and component ids) and the checksum makes debugging a link a matter of reading bytes.

Learning objectives

After this lesson, you should be able to:

  • Parse a MAVLink 2 frame into its fields.
  • Explain system id, component id, message id and sequence number.
  • Describe the heartbeat and what depends on it.
  • Compute the checksum with the per-message CRC_EXTRA and explain why it exists.

Conceptual explanation

Established MAVLink is specified at mavlink.io. The message definitions (ids, fields, CRC_EXTRA) are generated from XML dialects; the heartbeat values below match the common dialect and are verified against the reference pymavlink library in the tests of this course.

MAVLink carries messages over serial, UDP, TCP or any byte stream. Version 2 (the current one; v1 uses magic 0xFE) frames are laid out as in the figure: a magic byte (0xFD), payload length, incompatibility and compatibility flags, a sequence number, the sender’s system id and component id, a 24-bit message id, the payload, a 16-bit checksum and an optional signature.

  • System id identifies a vehicle or ground station on the network; component id identifies a part of it (autopilot, camera, companion computer, gimbal).
  • Payload fields are serialised in a fixed order: sorted by type size (largest first) so that fields are aligned, not in the order of the XML. MAVLink 2 also truncates trailing zero bytes of the payload.
  • HEARTBEAT (message id 0) is sent at about 1 Hz by every system and component. It announces type, autopilot, mode and status, and peers use its absence to declare a link lost.
MAVLink 2 frame layoutA MAVLink 2 frame starts with magic byte 0xFD, then payload length, incompatibility and compatibility flags, a sequence number, system and component ids, a three-byte message id, the payload of up to 255 bytes, a two-byte checksum and an optional 13-byte signature.STX 0xFDLENINCCMPSEQSYSCOMPMSGIDPAYLOADCRCSIGNMAVLink 2 frame (bytes in order; widths not to scale)checksum covers everything after the magic byte, plus a per-message CRC_EXTRA byte
The MAVLink 2 frame. Fields sit in this order on the wire.
Animation that builds a MAVLink 2 heartbeat frame byte by byte. Each byte appears in a box coloured by its field: the start byte, the length, the flags, the sequence number, the system and component ids, the three-byte message id, the nine payload bytes with the autopilot type, base mode and system status, and finally the two checksum bytes. A caption names the field being added, and the full frame in hexadecimal is shown at the end.
Frame layout of MAVLink 2: 0xFD, length, incompatibility and compatibility flags, sequence, system id, component id, 24-bit message id, payload (trailing zeros truncated), and CRC-16 over everything after the start byte plus a per-message CRC_EXTRA byte. The bytes are produced by the same encoder as in the lesson (verified against pymavlink). (Looping, about 6 seconds; drawn and computed for this course with illustrative values.)

Engineering intuition

A MAVLink link is a conversation where everyone shouts a one-line status every second (the heartbeat) and sends requests and data in between. The system and component ids are addresses; the message id says what the payload means, so a receiver that does not know a message can skip it using the length.

The checksum covers the message definition as well as the bytes. That is the purpose of CRC_EXTRA: a byte computed from the message’s field names and types and mixed into the checksum, so two ends with different versions of the same message detect the mismatch instead of silently misreading fields.

Mathematical formulation

The checksum is CRC-16/MCRF4XX (the “X.25” variant used by MAVLink): initial value 0xFFFF, polynomial 0x1021 reflected, applied bitwise-accumulate style to every byte after the magic byte up to the end of the payload, then to the message’s CRC_EXTRA byte. The accumulate step for byte bb is:

MAVLink CRC accumulate (16-bit arithmetic)
tmp=b⊕(crc & 0xFF),tmp=tmp⊕(tmp≪4) & 0xFF\mathrm{tmp} = b \oplus (\mathrm{crc}\ \&\ \mathtt{0xFF}), \qquad \mathrm{tmp} = \mathrm{tmp} \oplus (\mathrm{tmp} \ll 4)\ \&\ \mathtt{0xFF}
crc=(crc≫8)⊕(tmp≪8)⊕(tmp≪3)⊕(tmp≫4)\mathrm{crc} = (\mathrm{crc} \gg 8) \oplus (\mathrm{tmp} \ll 8) \oplus (\mathrm{tmp} \ll 3) \oplus (\mathrm{tmp} \gg 4)
All arithmetic is 16-bit.

The wire size of a frame is 10 + nn + 2 bytes for a payload of nn bytes (+13 with a signature). At 57 600 baud (about 5 760 bytes/s with 8N1) a 28-byte message costs 40 bytes, so the link carries about 144 such messages per second in total: a rate budget that rapidly runs out on a radio when many streams are enabled. The heartbeat at 1 Hz costs 21 bytes per second (9 bytes of payload).

nn
payload length (0–255).
CRC_EXTRA
per-message seed byte derived from the message definition.

Architecture and data flow

Autopilot ↔ ground station (telemetry radio, UDP) and autopilot ↔ companion computer (serial or UDP, via MAVROS, MAVSDK or pymavlink). The autopilot’s MAVLink module subscribes to uORB topics, builds messages at configured stream rates and sends them on each configured instance; it parses received messages into uORB topics (commands, setpoints, mission items). A MAVLink router (for example mavlink-router) can fan the stream out to several endpoints.

Real-world UAV applications

  • Debugging a telemetry link by reading raw frames.
  • Writing a companion-computer node without a large library.
  • Budgeting radio bandwidth for stream rates.
  • Security: enabling message signing and deciding which links are trusted.

Implementation example

This runs as shown. A from-scratch MAVLink 2 encoder and decoder for HEARTBEAT, checked against a frame produced by the reference pymavlink library (the byte string below was generated with it and is fixed in the test).

import struct

def crc_accumulate(b, crc):
    tmp = (b ^ (crc & 0xFF)) & 0xFF; tmp = (tmp ^ (tmp << 4)) & 0xFF
    return ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xFFFF
def crc16(data, extra):
    crc = 0xFFFF
    for b in data: crc = crc_accumulate(b, crc)
    return crc_accumulate(extra, crc)
HEARTBEAT = 0; CRC_EXTRA_HEARTBEAT = 50

def pack_heartbeat(seq, sysid, compid, mav_type, autopilot, base_mode, custom_mode, status, version=3):
    payload = struct.pack('<IBBBBB', custom_mode, mav_type, autopilot, base_mode, status, version)   # wire order: largest field first
    payload = payload.rstrip(b'\x00') or b'\x00'                                                   # MAVLink 2 truncates trailing zeros
    hdr = struct.pack('<BBBBBB', len(payload), 0, 0, seq, sysid, compid) + HEARTBEAT.to_bytes(3, 'little')
    crc = crc16(hdr + payload, CRC_EXTRA_HEARTBEAT)
    return b'\xfd' + hdr + payload + struct.pack('<H', crc)

def parse(frame):
    assert frame[0] == 0xFD
    n, inc, cmp_, seq, sysid, compid = frame[1:7]; msgid = int.from_bytes(frame[7:10], 'little')
    payload = frame[10:10 + n]; (crc,) = struct.unpack('<H', frame[10 + n:12 + n])
    ok = crc == crc16(frame[1:10 + n], CRC_EXTRA_HEARTBEAT) if msgid == HEARTBEAT else None
    full = payload.ljust(9, b'\x00'); custom, t, ap, bm, st, ver = struct.unpack('<IBBBBB', full)
    return dict(seq=seq, sysid=sysid, compid=compid, msgid=msgid, type=t, autopilot=ap, base_mode=bm, status=st, crc_ok=ok)

frame = pack_heartbeat(seq=0, sysid=1, compid=1, mav_type=2, autopilot=12, base_mode=81, custom_mode=0, status=4)
reference = bytes.fromhex('fd09000000010100000000000000020c5104031b74')     # generated with pymavlink (common dialect)
print(frame.hex(), parse(frame))
assert frame == reference and parse(frame)['crc_ok']
bad = bytearray(frame); bad[12] ^= 0x01
assert not parse(bytes(bad))['crc_ok']
HEARTBEAT encode and decode with CRC_EXTRA 50. The reference bytes come from pymavlink.

Common engineering mistakes

Assuming fields are in XML order on the wire

They are sorted by size; use a library or the specification.

Ignoring trailing-zero truncation (MAVLink 2)

A decoder must zero-extend the payload.

Sending everything at high rate over a radio

The stream rates must fit the link budget; start with defaults.

Same system id on two vehicles

Messages collide; set a unique SYSID_THISMAV (or the equivalent parameter).

No signing on an open link

Anyone in range can send commands; use signing or a private link.

Failure modes

Failure modes, symptoms and causes
Failure modeHow it shows upLikely cause
Link shows connected but no dataHeartbeat present, no telemetryStream rates zero or wrong instance
Checksum errorsFrames droppedMessage definition mismatch (CRC_EXTRA), noise or baud mismatch
Heartbeat timeoutLink-lost failsafeRadio range, power, or the sender stalled
Commands ignoredNo acknowledgmentWrong target system or component id

Debugging approach

  1. Capture raw bytes and decode frames by hand with the layout above.
  2. Use MAVProxy or QGroundControl’s MAVLink inspector to see rates per message.
  3. Check system and component ids on every message.
  4. Verify the dialect/version both ends use.

Practical exercises

  1. Decode this frame by hand: fd09000000010100000000000000020c5104031b74.
  2. Compute the bandwidth of ATTITUDE (28 bytes of payload) at 10 Hz and 50 Hz on a 57 600 baud link.
  3. Extend the encoder to SYS_STATUS (check the fields in the common dialect) and compare it with pymavlink.
  4. Count the heartbeats lost over 10 minutes on your telemetry link.

Interview questions

Write an answer in your own words, then reveal the model answer. Nothing is sent anywhere.

Question 1

Why does MAVLink include CRC_EXTRA in the checksum?

Model answer

It seeds the checksum with a value derived from the message definition, so differing message layouts between two ends fail the check instead of being silently misparsed.

Question 2

What is the heartbeat for?

Model answer

It announces each system and component, its type, autopilot, mode and state, and its absence at about 1 Hz lets peers detect link loss and trigger failsafes.

Question 3

What does MAVLink 2 add over version 1?

Model answer

Larger message ids (24 bits), payload truncation, extension fields, incompatibility and compatibility flags, and optional message signing.

References and further reading

Primary papers, official documentation and standards. Each claim above that is specific to a tool or version points here.

What’s next

Mark this lesson

Saved in this browser and synced to your account when you are signed in.

Next lesson

M6-T8: ROS 2 and PX4 integration

Connect ROS 2 nodes to PX4 with the right message package, QoS and frame conversions, and compare it with MAVROS.