개발

[Drone] MAVLink - 드론과 지상이 통신하는 언어

말하는 알감자 2026. 7. 14. 13:27
목표: 드론이 MQTT로 보내는 raw MAVLink 데이터를 받아 디코딩 및 처리하는 시스템 만들기위해서 그 데이터인 MAVLink 프로토콜 자체를 정리한다. (이전 글에서 "펌웨어가 데이터의 생산자"라고 했는데, 그 데이터가 바로 MAVLink 메시지!)

 


0. 용어 정리 (프로토콜 / Protobuf / 페이로드)

프로토콜(protocol)이란

통신 규칙(약속)이다. "이런 순서로, 이런 형식으로 데이터를 주고받자"는 합의.
  • 비유: 편지 봉투에 "왼쪽 위 = 보내는 사람, 오른쪽 위 = 우표" 같은 약속. 양쪽이 같은 약속을 지켜야 배달된다.
  • HTTP, TCP, MAVLink 모두 각자의 "주고받는 규칙"이다.

프로토콜은 층(layer)으로 쌓인다 — TCP/UDP와의 관계

네트워크 수업에서 배운 계층 개념 그대로다.

  • TCP/UDP = "데이터를 어떻게 실어 나를까"를 담당하는 운송 계층. 트럭. 안에 뭐가 실렸는지는 신경 쓰지 않는다.
  • MAVLink = 그 트럭에 실리는 상자의 내용물 규격. "이 상자엔 위도·경도·고도를 이 순서로 담는다"는 약속. 즉 TCP/UDP보다 위층이다.
[ MAVLink 메시지 ]   ← 상자 내용물 규격 (뭘 담을지) ← 내가 디코딩하는 대상
        ↓ 이걸 실어서
[ TCP / UDP / MQTT ] ← 운송 계층 (어떻게 나를지)
        ↓
[ IP, 무선 링크 ]    ← 실제 물리적 배달

내 시스템의 흐름: 드론이 MAVLink 메시지(내용물)를 만든다 → MQTT/TCP(트럭)에 실어 보낸다 → 내가 받아 트럭에서 상자를 꺼낸다(MQTT 수신) → 상자 내용물(MAVLink)을 규격대로 뜯는 게 디코딩.

페이로드(payload)란

포장·주소·검증용 부가정보를 뺀 진짜 알맹이 데이터
  • 비유: 택배에서 송장·완충재·테이프를 뺀 안의 진짜 물건. 편지로 치면 봉투가 아니라 편지 본문
  • 네트워크 수업의 "헤더 빼고 남는 게 페이로드"와 정확히 같은 개념

Protobuf(Protocol Buffers)란

구글이 만든 데이터 직렬화 포맷
  • 직렬화(serialize): 프로그램 안의 데이터를 네트워크로 보내려고 한 줄의 바이트로 쭉 펴는 것. 받아서 되돌리는 게 역직렬화.
  • JSON도 직렬화 포맷이지만, 사람이 읽으라고 글자로 편다({"latitude": 37.12}) → 편하지만 무겁다.
  • Protobuf는 가독성을 포기하고 바이너리로 빽빽하게 편다. 대신 미리 스키마(필드 타입·순서 정의서)를 양쪽이 공유해야 복원 가능하다. 글자 라벨을 안 보내고 순서로만 구분하기 때문.

"MAVLink가 Protobuf 같다"는 건 같은 발상이라는 뜻이다: 라벨 없이 바이너리로 빽빽하게, 미리 정한 스키마(= dialect의 메시지 정의)로만 해독


1. MAVLink란

Micro Air Vehicle Link의 약자. 소형 무인기와 통신하기 위한 프로토콜이다.
성격은 우리가 아는 것 중 Protobuf(protocol buffers)에 가장 가깝다.

왜 Protobuf와 비슷한가

  • 사람이 읽는 JSON이 아니라, 바이너리로 빽빽하게 압축된 포맷
  • 미리 정해진 스키마(메시지 정의)가 있어야 디코딩 가능
  • 필드마다 타입과 순서가 정해져 있음

왜 JSON을 안 쓰고 이렇게 만들었나

드론은 대역폭과 자원이 부족한 환경이다. MAVLink는 자원 및 대역폭이 제한된 소형 무인기와 통신하기 위한 효율적 텔레메트리 프로토콜 무선 링크로 초당 수십~수백 개 메시지를 날려야 하는데, {"latitude": 37.1234567} 같은 텍스트를 매번 보내면 너무 무겁고 느리다.
→ 그래서 "위도"를 4바이트 정수 하나에 욱여넣는 식으로 극한까지 압축했다.


2. 메시지가 "종류별로" 나뉜다는 것

MAVLink 통신은 한 덩어리로 흐르지 않는다. 종류가 정해진 메시지들이 각자 따로 날아오는 구조다.

비유: 드론이 지상국에 여러 종류의 엽서를 계속 부친다. 엽서마다 종류(양식)가 정해져 있다.

메시지
HEARTBEAT "나 살아있어요" (심장박동)
GLOBAL_POSITION_INT "제 현재 위경도/고도는 이거예요"
ATTITUDE "제가 지금 이만큼 기울어져 있어요"
SYS_STATUS "배터리 잔량, 센서 상태는 이래요"
GPS_RAW_INT "GPS 원시 데이터예요"

각 메시지(엽서)에는 고유 번호(message ID)정해진 칸(필드)이 있다.

  • HEARTBEAT: ID 0번. "기체 타입, 자동조종 종류, 비행 모드, 시스템 상태" 등의 칸.
  • GLOBAL_POSITION_INT: ID 33번. "타임스탬프, 위도, 경도, 고도, 속도" 등의 칸.

raw MAVLink를 디코딩한다는 건: "날아온 바이트 덩어리 → 몇 번 엽서인지 알아내고 → 그 양식에 맞춰 칸을 하나씩 꺼내는" 작업 Protobuf 메시지를 .proto 스키마로 파싱하는 것과 같은 개념


3. 메시지 하나가 실제로 어떻게 생겼나 (프레임 구조)

엽서 하나(= 한 메시지)는 이런 봉투에 담겨 날아온다. 아래는 MAVLink v2.0 기준. 앞부분(STX~CHECKSUM)은 항상 있고, 맨 뒤 SIGNATURE는 옵션(서명 켠 경우에만 붙음)이다.

순서 필드 크기 비유 역할
1 STX 1 byte 봉투 시작 표시 "여기서부터 새 엽서 시작". v2 = 0xFD (v1 = 0xFE)
2 LEN 1 byte 내용물 분량 PAYLOAD가 몇 바이트인지 (0~255)
3 INC FLAGS
(incompatible)
1 byte 봉인 방식 표시 모르면 메시지를 버려야 하는 플래그. 서명 여부 등이 여기
4 CMP FLAGS
(compatible)
1 byte 참고용 메모 몰라도 무시하고 읽으면 되는 부가 정보
5 SEQ 1 byte 엽서 일련번호 0~255 순환. 건너뛴 번호로 유실 감지
6 SYS ID 1 byte 보낸 기체 주소 어느 시스템(드론)이 보냈나
7 COMP ID 1 byte 보낸 부품 주소 그 기체 안 어느 부품(FC, 카메라 등)이 보냈나
8 MSG ID 3 bytes 엽서 종류 번호 HEARTBEAT(0)인지 GLOBAL_POSITION_INT(33)인지. v2에서 3바이트로 확장
9 PAYLOAD 0~255 bytes 엽서 본문 실제 데이터(위도, 고도 등) = 알맹이
10 CHECKSUM 2 bytes 봉인 도장 중간에 깨지지 않았는지 검증
11 SIGNATURE (옵션) 13 bytes 인감 도장 위조 방지 전자 서명. link id(1) + timestamp(6) + signature(6)

핵심: 메시지 ID를 봐야 페이로드를 어떻게 해석할지 결정된다. ID가 33번이면 "페이로드를 GLOBAL_POSITION_INT 양식으로 읽어야겠다"가 된다. 봉투를 뜯기 전엔 안쪽 바이트는 그냥 의미 없는 숫자 뭉치다.

INC FLAGS vs CMP FLAGS (v2에서 새로 생긴 부분)

둘 다 "이 플래그를 모르는 디코더가 만났을 때 어떻게 할래?"에 대한 답이다.

  • INCompatible flags → 모르면 메시지를 버려라. (모른 채 읽으면 잘못 읽음. 서명이 붙었다는 표시가 대표 예시)
  • COMpatible flags → 모르면 무시하고 읽어라. (몰라도 본문 해석엔 지장 없음)

비유(HTTP 헤더): "이해 못 하면 요청 자체를 거부해야 하는 헤더" vs "몰라도 그냥 무시하면 되는 부가 헤더"의 차이.

SIGNATURE (맨 뒤 옵션 13바이트)

메시지 위조 방지용 전자 서명. "진짜 우리 드론이 보냈고 중간 조작이 없었다"를 보증한다. 서명 기능을 켠 경우에만 붙고(그림에서 점선), 안 쓰면 CHECKSUM에서 메시지가 끝난다.

핵심: 메시지 ID를 봐야 페이로드를 어떻게 해석할지 결정된다. ID가 33번이면 "페이로드를 GLOBAL_POSITION_INT 양식으로 읽어야겠다"가 된다. 봉투를 뜯기 전엔 안쪽 바이트는 그냥 의미 없는 숫자 뭉치다.

실무에서 디코딩되는 순서

  1. STX로 메시지 시작을 찾는다 (0xFD면 v2)
  2. INC FLAGS로 서명 여부 등 해석 가능 여부를 확인
  3. MSG ID(3바이트)로 어떤 메시지인지 판별
  4. PAYLOAD를 해당 dialect 스키마로 해석
  5. CHECKSUM으로 무결성 검증

실제로는 pymavlink 같은 라이브러리가 이 프레임 해체를 자동으로 해준다. 바이트를 손으로 자를 일은 거의 없고, 라이브러리가 "MSG ID는 33, 필드는 이거"라고 객체로 건네준다. 다만 구조를 알아야 에러(서명 파싱 실패, v1/v2 혼동 등) 원인을 짚을 수 있다.

여기서 dialect가 다시 등장

메시지 ID ↔ 양식의 대응표(어느 ID가 무슨 메시지고 칸이 뭔지)가 바로 dialect에 정의돼 있다. 표준 MAVLink에 공통 메시지가 있고, ardupilotmega 방언에 ArduPilot 고유 메시지가 추가돼 있다. → 디코더에게 방언을 알려줘야 ID→양식 대응표를 갖고 제대로 파싱할 수 있다.

한 줄 요약(실무 연결): MAVLink 디코딩이란 "바이너리 봉투에서 메시지 ID를 읽고, 해당 dialect 스키마로 페이로드 칸을 꺼내는 일"이며, 이게 내 시스템의 심장부다.


Q&A

Q. MAVLink를 JSON이 아니라 Protobuf 같은 바이너리 스키마 포맷으로 만든 이유는?
드론은 자원이 부족한 환경이라 JSON처럼 무거운 텍스트를 쓸 수 없어서, 바이너리 스키마 포맷으로 최대한 경량화했다.

Q. "메시지가 종류별로 나뉜다"는 게 무슨 뜻? (HEARTBEAT / GLOBAL_POSITION_INT)
메시지마다 고유 ID가 있어 구별된다. ID가 0이면 HEARTBEAT 정보가, 다른 ID면 GLOBAL_POSITION_INT 등 해당 메시지의 데이터가 들어온다.

Q. 디코딩할 때 왜 메시지 ID를 먼저 봐야 하나?
메시지마다 담긴 데이터(필드)가 다르기 때문에, ID를 먼저 봐야 어떤 양식으로 칸을 꺼낼지 알 수 있다.

Q. INC FLAGS와 CMP FLAGS의 차이는? (v2)
둘 다 "이 플래그를 모르는 디코더가 만났을 때 어떻게 할지"를 정한다.

  • INC(incompatible): 모르면 메시지를 버려야 한다. (서명 여부 등이 여기)
  • CMP(compatible): 몰라도 무시하고 읽으면 된다.

참고 자료

'개발' 카테고리의 다른 글

[Drone] FC와 Ardupilot/PX4  (0) 2026.07.13
[NLP] NLP란?  (1) 2026.07.06