13.5.3. UBX 프로토콜 파싱 및 수신기 상태 데이터 피드백 C++ 로직 (u-blox 모듈)
지상 관제 시스템(QGC)에서 출발한 막대한 양의 RTCM 데이터가 텔레메트리(Telemetry), MAVLink 수신기(mavlink_receiver), uORB(gps_inject_data), 그리고 GPS 드라이버 스레드(src/drivers/gps/gps.cpp)의 긴 터널을 거쳐 마침내 드론의 하드웨어 직렬 포트(UART) 끝단에 부어지면, 이 데이터를 꿀떡 삼켜 소화하는 최종 포식자는 바로 스위스 u-blox 사의 수신기 칩셋(예: F9P, M8P 등) 내부 펌웨어다.
하지만 비행 제어기(FC) 코어는 단순히 “데이터를 던져주는 것“에 만족하지 않고, u-blox 칩셋이 자신이 보낸 데이터를 제대로 씹고 있는지, RTK Fix 모드로 안정적으로 진입했는지, 측위 품질 분산(Variance)이 양호한지 등을 역으로 심문해야 한다. 본 절에서는 PX4 GPS 드라이버의 서브 모듈인 Ubx 클래스 (src/drivers/gps/devices/src/ubx.cpp) 가 이중 통신(Full-Duplex) 채널을 통해 u-blox 칩셋 고유의 바이너리 프로토콜인 UBX Protocol을 어떻게 파싱(Parsing)하고 그 상태 피드백 데이터를 uORB 구조체로 뽑아내는지 C++ 레벨에서 깊이 있게 해부한다.
1. NMEA와 UBX: 비효율의 문자열을 버리고 기계어의 정수로 넘어가다
초창기 GPS와 대부분의 상용 내비게이션 모듈은 사람도 눈으로 읽을 수 있는 아스키(ASCII) 문자열 기반의 NMEA(National Marine Electronics Association) 프로토콜(예: $GPGGA,123519,4807.038,N...)을 뱉어냈다.
하지만 NMEA는 매초마다 엄청난 양의 직렬 대역폭을 낭비하며, 소수점 자릿수의 한계로 인해 센티미터(\text{cm}) 단위의 정밀도를 담아낼 그릇이 되지 못했다.
PX4의 Ubx 클래스는 모듈이 부팅되자마자 이 원시적인 NMEA 출력을 강제로 음소거(CFG-MSG 메시지 등)시키고, 오직 기계어 형태의 순수 바이너리 프로토콜인 UBX 만을 토해내도록 GPS 칩셋 내부 Flash 레지스터를 실시간 재구성(Configuration)해 버린다.
2. UBX 프레임워크의 파서 스테이트 머신(Parser State Machine) 구조
UBX 프로토콜은 SYNC1(0xB5) + SYNC2(0x62) + CLASS(1바이트) + ID(1바이트) + LENGTH(2바이트) + PAYLOAD(N바이트) + CHECKSUM(2바이트) 라는 엄격한 6 \sim 8\text{ bytes} 헤더 및 꼬리 구조를 가진다.
PX4의 Ubx::parseChar(const uint8_t b) C++ 메서드는 직렬 포트(UART)에서 1초에 약 1\text{만 번} 쏟아지는 바이트 조각을 하나씩(b) 읽어 들이면서 거대한 상태 머신(Finite State Machine, FSM) 톱니바퀴를 돌린다.
// src/drivers/gps/devices/src/ubx.cpp (UBX 파서 FSM 유사 로직)
int Ubx::parseChar(const uint8_t b, gps_abstime &gps_position_time) {
switch (_decode_state) {
// 1. 헤더 사냥 단계 (SYNC1 0xB5 를 잡아라)
case UBX_DECODE_SYNC1:
if (b == UBX_SYNC1) _decode_state = UBX_DECODE_SYNC2;
break;
// 2. 클래스 및 메시지 ID 식별 단계
case UBX_DECODE_ID:
_rx_msg = b;
_decode_state = UBX_DECODE_LENGTH1;
break;
// (중략 ... 길이 계산 및 페이로드 복사)
// 3. 순수 페이로드(Payload) 바이트 읽기 및 체크섬 동시 계산
case UBX_DECODE_PAYLOAD:
_buf.payload_tx_rx[_rx_payload_index++] = b;
add_byte_to_checksum(b);
// 지정된 페이로드 길이를 다 읽었으면 체크섬 검증 단계로 진입
if (_rx_payload_index == _rx_payload_length) {
_decode_state = UBX_DECODE_CHKSUM1;
}
break;
// 4. 종단 체크섬 검증 및 구조체 파싱(Payload Decoding) 발동
case UBX_DECODE_CHKSUM2:
if (b == _calc_ck_b && prev_b == _calc_ck_a) {
return payloadRxDone(); // 성공 시 본격적인 C 구조체 치환 발동
} else {
_decode_state = UBX_DECODE_SYNC1; // 오류 시 프레임 폐기 및 초기화
}
}
}
이 고전적이고 단단한 상태 머신 덕분에, 노이즈(Noise)가 심한 RF/UART 라인에서 바이트가 몇 조각 유실되거나 중간에 스파크가 튀더라도 _decode_state 변수만 가볍게 리셋되어 다음 올바른 UBX_SYNC1(0xB5) 이 들어올 때까지 쓰레기 데이터를 무시하며 꿋꿋이 직진할 수 있다.
3. 핵심 수신기 상태 피드백: payloadRxDone() 에서의 uORB 추출(Extraction)
UBX 프레임이 온전히 모이면(payloadRxDone 호출), Ubx 클래스는 메시지 클래스(Class)와 ID를 식별하여 C 구조체 포인터 캐스팅 방식(Memory Casting)으로 페이로드를 조각낸다.
드론의 생존(RTK Fix)을 확인하기 위해 PX4가 목을 매고 기다리는 핵심 u-blox 메시지들은 다음과 같다.
3.1 UBX-NAV-PVT (Position, Velocity, Time)
1\text{Hz} \sim 10\text{Hz}로 쏟아지는 가장 중요한 내비게이션 덩어리다.
이 안에는 기체의 위도(Latitude), 경도(Longitude), 고도(Altitude)는 물론 핵심 상태 변수인 fixType 이 들어있다.
fixType == 3: 단순 3D GPS 락(Lock)- 플래그 마스킹 판별:
(flags & 0b11000000) >> 6(캐리어 위상 해석 상태, Carrier-phase range solution) - 값
1: RTK Float (소수점 미결정, 미터급 오차 진입) - 값
2: RTK Fixed (정수 모호성 완전 해결, 3~5cm 극초정밀 상태)
PX4 펌웨어는 이 비트 조작(Bit Manipulation)을 통해 _gps_position->fix_type 을 4(Float) 또는 5(Fixed)나 6 등으로 번역하여 uORB의 vehicle_gps_position То픽 위에 얹어 EKF2(칼만 필터) 코어로 이송시킨다.
3.2 UBX-RXM-RTCM (RTCM 수신 상태 에코)
이 메시지는 QGC에서 주입한 조각난 패킷들(gps_inject_data)에 대한 u-blox 하드웨어 칩(Baseband) 측의 공식 답변서(Echo)이다. PX4는 이 패킷을 읽어 u-blox 칩셋 내부 파서가 **유효한 RTCM 보정 메시지(Valid Messages)**를 몇 개나 건졌는지, 또는 **체크섬에 실패한 쓰레기 메시지(CRC Failed)**가 몇 번 터졌는지를 실시간으로 카운팅한다.
// 수신 하드웨어 상태 진단 (RTCM 주입 퀄리티 검증)
case UBX_MSG_RXM_RTCM: {
// CRC 통과한 온전한 RTCM 메시지가 들어오면 상태 변수 통계 업데이트
if (buf.payload_rxm_rtcm.flags & 1) { // 1 = CRC passed
_rxm_rtcm_injections++; // 성공 카운트
} else {
_rxm_rtcm_crc_failures++; // 유실 카운트 (QGC에 에러 경고용)
}
}
이 텔레메트리 에러 지표(crc_failures)가 급증하면 조종사의 QGroundControl 화면 진단 탭(MAVLink Inspector)에 빨간 불이 들어오며 “텔레메트리 대역폭을 낮추거나 라우팅 송신량을 줄여달라“는 무언의 압박(Debug Notification)을 가하게 된다.
결론적으로 PX4의 Ubx 파서 아키텍처는 거시적인 드론 시스템과 눈에 보이지 않는 1\text{cm} 크기의 마이크로 칩(u-blox) 사이를 잇는 정교한 통번역가(Translator)이다. OS 입출력 스케줄러의 무심한 주입(Injection) 행위 뒤에서, FSM 머신이라는 끈질긴 그물로 1\text{byte}씩 낚아올리며 기계어를 파싱해내는 이 C++ 톱니바퀴야말로 RTK 오차를 \text{cm} 단위로 묶어두는 최후의 논리적 방어구라 할 수 있다.