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.
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.

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 is:
The wire size of a frame is 10 + + 2 bytes for a payload of 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).
- 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']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 mode | How it shows up | Likely cause |
|---|---|---|
| Link shows connected but no data | Heartbeat present, no telemetry | Stream rates zero or wrong instance |
| Checksum errors | Frames dropped | Message definition mismatch (CRC_EXTRA), noise or baud mismatch |
| Heartbeat timeout | Link-lost failsafe | Radio range, power, or the sender stalled |
| Commands ignored | No acknowledgment | Wrong target system or component id |
Debugging approach
- Capture raw bytes and decode frames by hand with the layout above.
- Use MAVProxy or QGroundControl’s MAVLink inspector to see rates per message.
- Check system and component ids on every message.
- Verify the dialect/version both ends use.
Practical exercises
- Decode this frame by hand:
fd09000000010100000000000000020c5104031b74. - Compute the bandwidth of ATTITUDE (28 bytes of payload) at 10 Hz and 50 Hz on a 57 600 baud link.
- Extend the encoder to SYS_STATUS (check the fields in the common dialect) and compare it with pymavlink.
- 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.