13.5. PX4/QGC 통신 파이프라인: RTCM 라우팅 메모리 구조 및 MAVLink 스레드 분석

13.5. PX4/QGC 통신 파이프라인: RTCM 라우팅 메모리 구조 및 MAVLink 스레드 분석

RTK(Real-Time Kinematic) 시스템이 성공적으로 센티미터(\text{cm}) 단위의 오차를 극복하기 위해서는, 지상에 고정된 베이스 스테이션(Base Station)이 하늘에 떠 있는 드론(Rover)에게 자신의 위치 수정 정보인 RTCM(Radio Technical Commission for Maritime Services) 보정 데이터를 1\text{Hz} 이상의 빠른 주기로 끊임없이 쏘아주어야 한다.

일반적으로 베이스 안테나는 PC(QGroundControl 등 지상 관제 시스템, GCS)의 USB 포트에 꽂혀 있고, 드론의 로버 안테나는 비행 제어기(FC)의 직렬(UART) 포트에 꽂혀 있다. 그렇다면 완전히 분리된 이 두 하드웨어 사이에서, 대용량 바이너리 스트림인 RTCM 데이터가 어떻게 원격 측정(Telemetry) 무선 링크를 뚫고 드론의 GPS 칩셋까지 정확하게 당도할 수 있을까?

본 절에서는 QGroundControl(QGC)에서 시작되어 텔레메트리 전파를 타고 PX4-Autopilot의 MAVLink 인터페이스를 거쳐 최종적으로 gps 프로세스 데몬으로 흘러 들어가는 **RTCM 라우팅 파이프라인(Routing Pipeline)**의 메모리 풀(Memory Pool) 구조와 전송 스레드(Thread) 메커니즘을 소스 코드 레벨에서 입체적으로 해부한다.

1. 지상 관제 시스템(QGC)에서의 RTCM 데이터 수집 및 MAVLink 패키징

RTCM 데이터 통신 파이프라인의 발원지는 사용자의 노트북(혹은 스마트폰)에서 구동 중인 QGroundControl 애플리케이션이다.

1.1 GPSManager 와 백그라운드 스레링

QGC 내부의 GPSManager 클래스는 USB를 통해 들어오는 Base GPS의 원시(Raw) 시리얼 데이터를 백그라운드 스레드에서 끊임없이 청취(Listening)한다. u-blox 칩셋 등이 뱉어내는 데이터 스트림 속에서, 오직 RTCM3 형식의 메시지(예: Message type 1005, 1077, 1087 등)만을 파싱하여 추출해 내는 파이프라인이 쉴 새 없이 가동된다.

1.2 GPS_RTCM_DATA (메시지 ID 233) 인캡슐레이션

순수 RTCM 바이너리 데이터는 그 자체로는 무선 모뎀을 통과할 규격이 되지 못한다. QGC는 추출된 RTCM 바이너리 페이로드(Payload) 조각들을 MAVLink 프로토콜의 GPS_RTCM_DATA (MAVLink Message ID: 233) 메시지 구조체라는 봉투(Envelope)에 담아 포장(Encapsulate)한다.

// MAVLink gps_rtcm_data 메시지 패킷 구조체 예시
typedef struct __mavlink_gps_rtcm_data_t {
    uint8_t flags;       // 단편화(Fragmentation) 관리를 위한 LSB 플래그
    uint8_t len;         // 현재 패킷에 담긴 RTCM 데이터의 길이 (최대 180바이트)
    uint8_t data[180];   // 순수 RTCM 바이너리 페이로드
} mavlink_gps_rtcm_data_t;

RTCM 메시지는 1개당 배수백 바이트에 육박할 수 있지만, 일반적인 텔레메트리(SiK Radio, Microhard 등) 무선 패킷의 페이로드 제한을 고려하여 하나의 GPS_RTCM_DATA 메시지는 최대 180\text{ bytes} 크기로 조각나어(Fragmented) MAVLink 라우터로 무수히 전송된다.

2. PX4-Autopilot의 MAVLink 수신 측(Receiver) 스레드 처리

무선 공간을 날아온 수많은 GPS_RTCM_DATA MAVLink 패킷은 기체의 텔레메트리 포트(예: TELEM1)를 통해 PX4의 mavlink_receiver 스레드로 쏟아져 들어온다.

2.1 MAVLink Receiver 모듈

src/modules/mavlink/mavlink_receiver.cpp 파일 내부에는 수십 가지의 MAVLink 메시지를 각각의 용도에 맞게 분류하는 거대한 switch-case 파서가 존재한다. 여기서 MAVLINK_MSG_ID_GPS_RTCM_DATA 토큰이 식별되면 handle_message_gps_rtcm_data() 함수가 호출된다.

// src/modules/mavlink/mavlink_receiver.cpp (RTCM 수신 처리 유사 코드)
void MavlinkReceiver::handle_message_gps_rtcm_data(mavlink_message_t *msg)
{
    mavlink_gps_rtcm_data_t rtcm_data;
    mavlink_msg_gps_rtcm_data_decode(msg, &rtcm_data);

    // 1. 단편화된 패킷이 맞는지 검사
    // 2. uORB 데이터 구조체(gps_inject_data_s)에 복사
    gps_inject_data_s inject_data{};
    inject_data.len = rtcm_data.len;
    inject_data.flags = rtcm_data.flags;
    memcpy(inject_data.data, rtcm_data.data, rtcm_data.len);

    // 3. uORB 토픽 'gps_inject_data' 발행 (Publish)
    _gps_inject_data_pub.publish(inject_data);
}

이 루틴에서 주목할 점은 MAVLink 수신 스레드는 자원을 많이 소모하는 직접적인 UART 통신을 절대 직접 수행하지 않는다는 것이다. 단지 수신된 RTCM 바이트 배열을 PX4의 초고속 인메모리(in-memory) 메시지 버스인 uORB (Micro Object Request Broker)gps_inject_data 토픽으로 던져 놓고 자기 할 일(다음 MAVLink 파싱)을 하러 떠난다.

3. gps 드라이버 스레드의 RTCM 폴링(Polling) 및 인젝션(Injection)

MAVLink Receiver가 uORB라는 허브에 RTCM 데이터를 던져두었다면, 이 데이터를 최종적으로 소비(Consume)하는 곳은 드론의 실제 물리적 GPS 포트(예: GPS1 포트)를 틀어쥐고 있는 src/drivers/gps 데몬이다.

이 데몬 모듈 안에는 GPS 하드웨어와의 직렬(UART) 입출력을 전담하는 끝이 없는 무한 루프(task_main 쓰레드)가 돈다.

3.1 파일 디스크립터(File Descriptor)를 통한 Non-blocking 읽기

gps 데몬은 gps_inject_data 토픽의 파일 디스크립터(fd)를 등록해 두고, poll() 시스템 콜을 호출하여 새로운 RTCM 보정 데이터가 uORB 큐(Queue)에 쌓였는지 수 밀리초 단위로 엿본다.

3.2 GPS 하드웨어로의 바이너리 쓰기 (Injection)

데이터가 감지되는 즉시, 드라이버를 이 바이트 체인을 건져 올려 GPS의 UART 포트로 지체 없이 write() 해 버린다.

// src/drivers/gps/gps.cpp (RTCM 데이터 주입 유사 코드)
int inject_data_fd = orb_subscribe(ORB_ID(gps_inject_data));

while (!should_exit) {
    // uORB 큐에 데이터가 있는지 확인
    bool updated = false;
    orb_check(inject_data_fd, &updated);

    if (updated) {
        gps_inject_data_s inject_data;
        orb_copy(ORB_ID(gps_inject_data), inject_data_fd, &inject_data);

        // 실제 하드웨어 직렬 포트(예: /dev/ttyS3)로 바이트 스트림 주입!
        write(_gps_uart_fd, inject_data.data, inject_data.len);
        
        // 통계 변수 업데이트 (QGC 화면에서 확인 가능한 주입률 표기용)
        _rtcm_injection_rate_bytes += inject_data.len;
    }
}

4. 구조적 대칭성과 병목(Bottleneck) 분석

이러한 [QGC 백그라운드 스레드] \rightarrow [MAVLink 라우터] \rightarrow [텔레메트리 RF] \rightarrow [PX4 MAVLink 파서] \rightarrow [uORB 버스] \rightarrow [GPS 데몬 스레드] 로 이어지는 이 거대하고 대칭적인 구조는 놀랍게도 실시간 비동기(Asynchronous) 파이프라인으로 설계되어 있다.

어느 한 곳의 스레드에서 지연(Blocking)이 발생하더라도, 유연한 버퍼(QGC 측의 Queue, 텔레메트리 송신 버퍼, uORB의 Ring 데이터 버퍼)들이 이들을 흡수하므로 비행 제어기(FC)의 핵심 루프인 EKF 센서 융합과 모터 PID 제어 루프의 타이밍에는 단 1마이크로초(1\mu\text{s})의 지연도 유발하지 않는다.

다만, 가장 빈번하게 병목(Bottleneck)이 일어나는 물리적 구간은 무선 모뎀 영역이다. 900\text{MHz} 와 같은 저대역 무선 텔레메트리의 대역폭이 비좁을 때 RTCM 메시지가 심각한 큐잉 지연(Queueing Delay)을 겪어 2 \sim 3\text{초} 이상 연착하게 되면, 도착한 RTCM 데이터는 이미 수신기 입장에서는 폐기 처분해야 할 구시대(Stale)의 위상 데이터가 되어버리므로 RTK Fix는 즉각 풀려버리게 된다. 따라서 QGC 설정에서 RTCM 메시지의 초당 주입 대역폭 한계(Bandwidth limit)를 텔레메트리의 물리적 전송 속도에 맞게 적절히 조율하는 것이 통신 튜닝의 핵심 스킬이 된다.