28.5.3.2. MAVLink custom_mode 필드의 비트 시프트(Bit Shift) 연산 및 서브 모드(Sub-mode) 인코딩 방식
앞 절에서 살펴본 바와 같이, PX4는 1바이트 크기의 내부 상태(nav_state)를 MAVLink HEARTBEAT 메시지의 32비트(4바이트) 크기인 custom_mode 필드에 확장하여 매핑한다. 이 32비트의 공간은 단일한 열거형 번호로 쓰이지 않고, 논리적인 계층을 둔 비트 필드(Bitfield) 형태로 쪼개져 활용된다.
본 절에서는 C++ 소스 코드 레벨에서 이 32비트 공간이 어떻게 분할되고 비트 시프트(Bit Shift) 연산을 통해 서브 모드(Sub-mode) 체계로 인코딩되는지 그 수학적 메커니즘을 해부한다.
1. 32비트 custom_mode의 공간 분할 (Memory Layout)
PX4-Autopilot은 호환성을 유지하고 확장성을 담보하기 위해 32바이트의 custom_mode를 크게 세 개의 구역으로 분할하여 사용한다. 통상적으로 리틀 엔디언(Little Endian) 규칙에 따라 바이트(Byte) 단위로 다음과 같이 맵핑된다.
| 비트 범위 (Bits) | 바이트 위치 | 명칭 (Field Name) | 역할 및 설명 |
|---|---|---|---|
0 ~ 7 (8 bit) | Byte 0 | Reserved / Custom | 초기 설계 당시 예약된 공간이거나 제조사별 확장 용도. 보통 0 으로 채워짐. |
8 ~ 15 (8 bit) | Byte 1 | Reserved | 예약 공간. 보통 0 으로 채워짐. |
16 ~ 23 (8 bit) | Byte 2 | Main Mode | 기체의 대항목 모드 (예: Manual, Auto, Altitude, Position). PX4 생태계의 가장 뼈대가 되는 모드 인덱스가 들어간다. |
24 ~ 31 (8 bit) | Byte 3 | Sub-Mode | Main Mode가 “Auto“일 때 세부적으로 무엇을 하는지 (Mission, RTL, Loiter, Takeoff)를 나타내는 종속적 상세 모드. |
결과적으로, PX4의 custom_mode는 하위 16비트는 버리고(0), 상위 16비트를 8비트씩 나누어 [Sub-Mode][Main Mode][0][0] 형태의 메모리 레이아웃을 구성하게 된다.
2. 비트 시프트(Bit Shift) 연산 메커니즘
이 구조체를 만들기 위해 mavlink_messages.cpp에서는 C++의 좌측 시프트 연산자(<<) 와 비트 논리합 연산자(|) 가 폭넓게 사용된다.
2.1 단일 계층 모드 인코딩 (예: Position 모드)
Position 모드(내부 nav_state 3번)와 같이 서브 모드가 필요 없는 대분류 모드일 경우, 3바이트 째에 해당하는 비트 16의 위치로 상수를 민다(Shift).
uint32_t custom_mode = 0;
// 매크로: PX4_CUSTOM_MAIN_MODE_POSCTL = 3
custom_mode = PX4_CUSTOM_MAIN_MODE_POSCTL << 16;
- 바이너리 변화:
00000000 00000000 00000000 00000011(숫자 3) << 16연산 후:00000000 00000011 00000000 00000000(십진수 196608)
지상 관제소(GCS)에서 196608이라는 숫자를 수신했다면, 우측으로 16칸 밀어(>> 16) “아, 3번 모드(Position)구나” 하고 해독하는 원리이다.
2.2 이중 계층 서브 모드 인코딩 (예: Auto-RTL 모드)
PX4는 “Auto“라는 메인 모드(번호 4) 안에 수많은 세부 모드(Mission, RTL, Takeoff, Land 등)를 서브 모드로 거느리고 있다. 예를 들어 RTL (복귀) 모드의 경우 Main Mode 상수값 4와 Sub Mode 상수값 5를 동시에 조합해야 한다.
// 매크로: PX4_CUSTOM_MAIN_MODE_AUTO = 4
// 매크로: PX4_CUSTOM_SUB_MODE_AUTO_RTL = 5
custom_mode = (PX4_CUSTOM_MAIN_MODE_AUTO << 16) | (PX4_CUSTOM_SUB_MODE_AUTO_RTL << 24);
- Main Shift (
4 << 16):00000000 00000010 00000000 00000000 - Sub Shift (
5 << 24):00000101 00000000 00000000 00000000 - 비트 OR(
|) 연산 후:00000101 00000010 00000000 00000000(십진수 84148224)
이러한 인코딩의 가장 큰 장점은 메모리의 낭비 없이 두 개의 독립된 상태 변수(Main과 Sub)를 하나의 uint32_t 인티저 형에 구겨 넣어 O(1)의 속도로 전송하고 복원할 수 있다는 점이다.
3. 지상 관제소(QGC) 측의 디코딩(Decoding) 분기 처리
인코딩된 패킷이 QGroundControl에 도달하면 역비트 시프트를 통해 원래의 모드로 해독된다. QGC 내부의 PX4FirmwarePlugin::flightMode() 메서드에는 다음과 같은 마스킹(Masking) 및 해독 로직이 구현되어 있다.
uint8_t main_mode = (custom_mode >> 16) & 0xFF; // 하위 16비트를 버리고 맨 위 8비트만 추출
uint8_t sub_mode = (custom_mode >> 24) & 0xFF; // 하위 24비트를 버리고 추출
if (main_mode == PX4_CUSTOM_MAIN_MODE_AUTO) {
if (sub_mode == PX4_CUSTOM_SUB_MODE_AUTO_RTL) {
return "Return";
} else if (sub_mode == PX4_CUSTOM_SUB_MODE_AUTO_MISSION) {
return "Mission";
}
} else if (main_mode == PX4_CUSTOM_MAIN_MODE_POSCTL) {
return "Position";
}
이 코드는 비트 마스크(& 0xFF) 를 사용하여 자신이 읽고자 하는 바이트의 찌꺼기(Overhang) 비트를 제거하는 정석적인 C언어 네트워크 파싱 방식을 채택하고 있다.
4. 아키텍처적 의의와 ArduPilot과의 철학적 대비
- PX4 (계층형 비트 인코딩): 이 방식은 모드가 2차원 매트릭스 형태로 뻗어나가는 것을 허용한다. 미래에 새로운 자율 수동 모드(Hybrid)가 수십 개 추가되더라도, Main Mode는 기존(Auto)을 유지한 채 Sub Mode의 숫자만 증가시키면 구형 GCS는 기체가 최소한 “자동 모드“에 진입해 있다는 메인 그룹 패러다임까지는 이해할 수 있게 된다. (하위 호환성 확보)
- ArduPilot (단선형 열거형): ArduPilot은
custom_mode = 11(RTL) 과 같이 이 32비트 공간 통째로 단일한 1차원 ID 숫자를 때려 넣는다. 직관적이고 코드가 간단하다는 극장점이 있으나, 모드가 50개 100개로 늘어날 때 구형 관제 소프트웨어가 신형 모드의 번호를 전혀 파싱하지 못하고 “Unknown Mode“를 띄우게 된다는 약점이 있다. PX4의 비트 시프트 방식은 이러한 확장성 병목을 세련된 자료구조 기법으로 회피한 훌륭한 엔지니어링 사례라 할 수 있다.