MAVLINK는 픽스호크 개발을 하셨던 분들이라면 한번쯤은 들어보셨을 프로토콜입니다. 공식사이트에서는 MAVLINK를 아래와 같이 정의하고 있습니다.

MAVLink is a very lightweight messaging protocol for communicating with drones (and between onboard drone components).

MAVLink는 드론의 (그리고 온보드 드론 컴포넌트 간의) 통신을 위한 매우 경량의 메세징 프로토콜이다.

일반적인 픽스호크 프로젝트에서 MAVLINK는 주로 QGC ↔ 드론이나 드론 ↔ 텔레메트리간의 통신을 위해 사용됩니다. TCP/IP같은 덩치 큰 프로토콜을 임베디드 같은 저성능 시스템과 RF 장치로 통신하는 저대역폭 통신에 올리긴 부담되기 때문에 송수신 단말 ID나 CRC 같은 필수 정보들로만 메세지를 구성하는 가벼운 프로토콜이라고 보시면 됩니다.
프로토콜을 자세히 들여다보시는 분들은 대부분 후자의 “드론 ↔ 텔레메트리” 통신의 용도로 원격제어나 데이터 수집을 위해서 들여다 보실 것 같습니다. 이 포스트는 실제로 이 메세지를 다루셔야하는 분들에게 제 경험을 공유하여 개발에 도움을 드리고자 작성되었습니다.

프로토콜 구조

크게 MAVLINK 1, MAVLINK 2의 두가지 버전로 구분되며 최신 픽스호크에서는 일부 메세지를 제외하고 MAVLINK 2를 사용하므로 이 포스트에서도 MAVLINK 2만 다룹니다. 아래에서 설명하는 프로토콜 명세의 원본은 공식사이트에서 보실 수 있습니다. 프로토콜의 각 매개변수를 핸들링하는 방법은 밑에서 자세히 다루겠습니다.

MAVKLINK 2버전의 “over-the-write” 형식 패킷의 구조

“over-the-write” 구조와 “in-memory” 구조의 패킷이 존재합니다. 전자가 우리가 실제로 다루고 생각하는 패킷구조고 후자가 런타임에 메모리에 적재되는 구조로, 공식 문서에서 인-메모리 패킷 방식은 위 정의와 다를 수 있다고 경고하고 있습니다. 다만, 사용자가 다루고 직렬화해서 실제 네트워크를 거쳐 송수신 하는 단계에서는 오버-더-라이트 구조로 움직이니 크게 고려안하셔도 될 것 같습니다.

프로토콜 매개변수들

STX, LEN, INC FLAGS, CMP FLAGS

이 4개의 매개변수들은 앞에 위치하는 헤더들로 비교적 다루기 쉽습니다. 첫번째 바이트인 STX는 어떤 경우에도 0xFD로 세팅하시면 됩니다. LEN은 간단히 적재하는 PAYLOAD의 바이트수로 세팅하시면 됩니다. 예를들어 페이로드가 10바이트면 간단히 0x0A가 되겠습니다. INC FLAGSCMP FLAGS는 딱히 메세지 정의나 송수신측에서 따로 정의하지 않은 이상 대부분 0x00로 세팅하시면 됩니다.

추가로 설명을 붙이자면, INC FLAGSCMP FLAGS 매개변수는 MAVLINK 2 프로토콜 정의에 딱 한가지 경우만 정의 되어있습니다. 아래의 경우에 해당되는게 아니면 위의 설명처럼 0x00으로 두셔도 무방할 것 같습니다.

INC FLAGS : 0x01 = MAVLINK_IFLAG_SIGNED The packet is signed (a signature has been appended to the packet).

https://mavlink.io/en/guide/serialization.html#incompat_flags

SEQ

if (mavCurrentSeq > Byte.MaxValue) { 
mavCurrentSeq = 0; 
} else { 
mavCurrentSeq++; 
} 
SendMavlinkMsg(mavCurrentSeq, ...); //시퀀스 번호 처리한 후, 메세지 생성 & 전송

SEQ 매개변수는 메세지의 시퀀스 번호를 의미합니다. 좀 복잡한 이름과는 다르게 실제로는 메세지의 번호를 붙여준다고 생각하시고 메세지 마다 1씩 올려주고, 바이트의 최대 값(255)를 초과하면 0부터 다시 붙여주시면 됩니다. 자세한 예시는 위의 예시 C# 코드를 보시면 이해가 되실거라고 생각합니다. MAVLINK 수신측에서는 이 시퀀스 번호를 패킷 손실율을 측정하기 위해 주로 사용합니다.

SYS ID, COMP ID

SYS ID 매개변수는 각각의 시스템의 고유 번호를 나타냅니다. 한 네트워크에서 3개의 기체가 존재한다면 기체를 구분하는 3개의 SYS ID가 존재할 것 입니다. COMP ID 매개변수는 각각의 시스템 하위의 각각의 컴포넌트의 고유 번호를 나타냅니다. 한 개의 기체 (시스템)에 FC, 짐벌이라는 두개의 컴포넌트가 존재한다면 컴포넌트를 구분하는 3×2 = 6개의 COMP ID가 존재할 것 입니다.

각각의 ID를 붙이는 기준은 따로 정의 된 것은 아니고 임의로 지정되는 것이므로 기체의 설정이나, 실제 전송되는 패킷을 관찰해보고 ID를 세팅하던가, 개발자 임의로 세팅하면 됩니다.

MSG ID

MSG ID는 페이로드를 해석하기 위해, 이 페이로드가 무슨 종류의 메세지인지 알려주는 메세지 종류 번호입니다. 공식 문서에서 메세지 제목 옆에 #1 같은 식으로 붙여져 있는 번호가 메세지 번호입니다. 예를 들어, HEARTBEAT 메세지는 1 (=0x01), GPS_RTCM_DATA 메세지는 233 (=0xE9)가 되겠네요. 보내는 메세지의 종류에 맞게 세팅해주면 됩니다.

어, 근데 MSG ID 매개변수는 3바이트입니다. 만약 16정도의 ID라면 어떻게 보내야 할지, LSB first(최하위 비트 먼저)인지 MSB(최상위 비트 먼저)인지 고민되실겁니다. 일단 정답은 LSB first입니다. 예를 들어 253이 ID인 메세지는 8번째 바이트는 0xE9, 9번째 바이트는 0x00, 10번째 바이트는 0x00가 되겠네요. 아래에 일반화된 MSGID 코드 예시도 첨부해뒀으니 참고하세요.

int MessageID; //공식 문서에 표시되있는 ID를 그대로 대입
Packet[7] = MessageID && 0xFF; //8번째 바이트 (LSB)
Packet[8] = (MessageID >> 8) && 0xFF; //9번째 바이트
Packet[9] = (MessageID >> 16) && 0xFF; //10번째 바이트 (MSB)

PAYLOAD

PAYLOAD는 말그대로 메세지의 페이로드, 의미있는 데이터들을 담고 있는 매개변수입니다. MAVLINK 프로토콜 자체에서는 페이로드를 어떤 규칙으로 실어야 할 지는 신경쓰지 않고, 각각의 메세지마다 정의가 되어있습니다. 공식 문서를 참조해서 문서에 나와있는 페이로드 데이터 표 대로 데이터를 실어서 보내시면 됩니다. 물론 표의 가장 첫 행의 데이터를 첫바이트로 해서 순서대로 실어보내시면 되고, 각 데이터의 변수형이나 설명은 문서에 잘 설명되있으니 참고하시면 되겠습니다.

CHECKSUM

저는 이 체크섬 매개변수때문에 엄청 삽질을 했습니다. 일단 공식 문서의 CRC에 대한 설명을 보시자면…

The packet format includes a 2-byte CRC to allow detection of message corruption. The checksum is the same as used in ITU X.25 and SAE AS-4 standards (CRC-16-CCITT), documented in SAE AS5669A. See the MAVLink source code for the documented C-implementation.

이 패킷 포맷은 메세지의 무결성을 확인하기 위한 2바이트 CRC를 포함한다. 이 체크섬은 ITU X.25 and SAE AS-4 standards (CRC-16-CCITT) , documented in SAE AS5669A와 동일하며 링크에서 C구현을 확인 할 수 있다.

라고 정의하고 있습니다. 근데… 제가 열심히 삽질해보면서 확인 해 본 결과, 표준이 문서에 나온 X.25 CRC = CRC16-CCITT가 아니라 CRC16-MCRF4XX (?!) 였다는 결론을 얻게 되었습니다. 아마 제가 사용했던 QGC 구현이나 드론측 펌웨어의 문제일 가능성도 있기에 자신있게는 못 말하겠습니다만, 문서대로 CRC를 계산했는데 아무리 해봐도 일치하지가 않으시는 분은 저 방식으로도 계산을 시도해보시기를 바랍니다.
체크섬을 계산하기 위해서는 CRC_EXTRA 바이트와 마커를 제외한 페이로드까지 메세지 데이터가 (서명된 메세지는 저도 구현해보지 않아서 논외로 치겠습니다.) 필요합니다. 체크섬 계산 함수는 공식 구현 깃헙 페이지를 참고하시기 바랍니다. CRC_EXTRA 바이트는 데이터 무결성뿐만 아니라 송수신측의 데이터 정의가 일치하는지 검사하기 위해 사용하므로 메세지마다 다릅니다. 이를 직접 계산하는 코드가 공식 문서에 설명되있지만, 저는 이보다는 MAVLINK 공식 라이브러리에 이미 계산된 값을 가져와 사용하시는걸 추천드립니다.
예를 들어 233번 메세지인 GPS_RTCM_DATA의 CRC_EXTRA 바이트는 35임을 mavlink/c_library_v2/common/mavlink_msg_gps_rtcm_data.h 파일에서 아래와 같이 확인 할 수 있습니다.

#define MAVLINK_MSG_ID_GPS_RTCM_DATA_CRC 35
#define MAVLINK_MSG_ID_233_CRC 35

CRC_EXTRA 바이트와 메세지가 준비되셨다면 LEN ~ PAYLOAD 까지의 메세지 다음에 CRC_EXTRA 바이트를 붙여서 CRC를 계산하시면 됩니다. 아래에 제가 구현한 예시(CRC16-MCRF4XX)를 첨부하니 참고하시길 바랍니다. CRC16-CCITT 방식도 입력데이터는 똑같고, CRC 계산 방식만 다를 뿐입니다.

Dim crctemp As New List(Of Byte) From {Length, InCompatibleFlags, CompatibleFlags, Sequence, SystemID, ComponentID, MessageID And &HFF, (MessageID >> 8) And &HFF, (MessageID >> 16) And &HFF}
crctemp.AddRange(Payload)
crctemp.Add(crcextra)
Checksum = CRC16MCRF4XX(crctemp.ToArray)

실제 패킷 예시

실제로 스니핑한 정상적인 233번 메세지의 MAVLINK 2 패킷의 예시이니 참고하시길 바랍니다.

원본 바이너리

fd 1b 00 00 73 ff 00 e9 00 00 60 19 d3 00 13 3e d0 00 03 38 e5 ea f1 b8 89 68 6b 34 80 09 00 98 84 68 1d 28 bf ee 21

각 바이트의 의미

각 경우의 체크섬

0xEE 0x21가 체크섬으로 나와야 하나 CRC16-CCITT방식으로는 아래와 같은 값밖에는 얻을 수 없었습니다. CRC16-MCRF4XX방식으로 0xEE 0x21을 얻을 수 있었습니다.

참고 자료

comments powered by Disqus