### 0.0.1 src/drivers/gps 디렉토리 소스 코드 아키텍처 및 스케줄링
비행 제어기의 전원이 켜지고 직렬 포트를 통해 GPS 바이트 스트림이 밀려 들어오는 순간부터, 이것이 vehicle_gps_position이라는 정제된 uORB 메시지로 변환되어 EKF2(Extended Kalman Filter 2) 추정기의 입(Mouth)으로 들어오기까지의 물리적/소프트웨어적 파이프라인은 매우 정교하게 조율되어 있다.
이 방대한 작업의 사령탑 역할을 하는 코드가 바로 PX4 펌웨어 트리의 src/drivers/gps 디렉토리에 안착한 GPS 장치 드라이버(Device Driver)이다. 본 절에서는 이 디렉토리 내부에 체계적으로 분산된 객체 지향적 소스 코드의 골격과, 실시간 운영체제(NuttX RTOS) 위에서 동작하는 드라이버 스레드의 동역학(Dynamics)을 조망한다.
0.1 드라이버 아키텍처의 철학: 장치 추상화(Device Abstraction)와 다형성(Polymorphism)
PX4 개발팀은 전 세계 수많은 제조사(Vendor)들이 쏟아내는 수백 종의 서로 다른 프로토콜(NMEA, U-Blox UBX, 실리콘랩스, 스위프트 네비게이션, Emlid 등)을 지닌 GPS 모듈들을 단일한 프레임워크 내에서 무리 없이 통제하기 위해, C++의 다형성(Polymorphism) 모델을 극단적으로 끌어올려 드라이버를 설계했다.
src/drivers/gps디렉토리의 파일 구조는 크게 프론트엔드(Frontend) 관리자 모듈과 백엔드(Backend) 프로토콜 파서(Parser) 모듈로 이분화된다.- 프론트엔드 코드는 운영체제(NuttX)와 교신하며 직렬 포트 파일 디스크립터(File Descriptor)를 열고, 스레드를 생성하며, uORB 토픽을 관장한다.
- 백엔드 코드는 NMEA나 UBX 같은 개별 프로토콜의 복잡다단한 바이트 파싱(Byte Parsing) 로직만을 격리(Isolation)하여 전담한다. 프론트엔드는 백엔드가 내부적으로 데이터를 어떻게 지지고 볶는지 전혀 관여하지 않으며, 오직 “파싱이 끝났으면 표준화된 C++ 구조체를 내놓아라“라는 동일한 인터페이스(Interface) 호출만을 규약으로 삼는다.
이러한 모듈형 구조 덕분에, 새로운 벤더의 최신 GPS 칩셋이 출시되더라도 기존의 복잡한 커널 스케줄러 부분을 건드릴 필요 없이 오직 해당 벤더용 텍스트 파서 클래스 .cpp 파일 하나만 폴더에 추가하여 상속(Inheritance)시키면 플러그 앤 플레이(Plug & Play) 확장이 가능해진다.
0.2 백그라운드 워커 스레드(Worker Thread)의 스케줄링 메커니즘
수백~수천 바이트 단위로 끊임없이 유입되는 직렬 스트림을 read() 함수로 긁어오는 작업(I/O Bound)은 본질적으로 프로세서를 묶어두는 블로킹(Blocking) 동작을 동반한다.
따라서 센서 데이터를 EKF에 즉각적으로 주입해야 하는 비행 제어 루프의 메인 스레드(Main Thread, 보통 Commander나 추정기 프로세스)가 직접 포트를 열고 바이트를 기다리는 행위는 시스템 정지(Crash)를 유발하는 자해 행위나 다름없다.
이 병목을 우회하기 위해 gps 드라이버는 NuttX 워크 큐(Work Queue) 모델 내지는 독립적인 백그라운드 **워커 스레드(Worker Thread)**로 분기하여 칩거한다.
- 비동기적 독립(Asynchronous Isolation):
드라이버가 초기화(start) 명령을 수신하면, 메인 프로세스와 분리된 별도의gps_main스레드를task_spawn()API를 통해 생성한다. 이 스레드는 비행 제어 및 속도 루프의 초고속 스케줄링(예: 400Hz~1000Hz) 간섭을 받지 않고 오직 직렬 포트 RX 인터럽트의 박자(통상 10Hz)에만 숨을 맞춘 채로 고립되어 동작한다. - 이벤트 구동식 폴링(Event-driven Polling):
이 워커 스레드는 대부분의 시간 동안poll()함수나px4_poll()래퍼(Wrapper)를 호출하며 휴면(Sleep) 상태에 빠져 있다. 직렬 버퍼에UBX-NAV-PVT등 충분한 바이트 배열이 도착하여 커널 인터럽트가 트리거 되는 즉시 깨어나서, 버퍼를 들이마시고 파싱 엔진을 돌려 구조체를 uORB 파이프라인에 힘껏 던져넣고(Publish), 다시 다음 패킷이 올 때까지 수면 상태로 기절(Yield)해 버린다.
0.3 uORB 인입점(Injection Point)으로서의 역할
src/drivers/gps 디렉토리 코드가 행하는 마지막이자 가장 숭고한 임무는, 가시밭길을 뚫어 획득한 측위 결과를 시스템의 대동맥인 uORB(Micro Object Request Broker) 마이크로 미들웨어망에 주사(Injection)하는 것이다.
- 파서가 성공적으로 체크섬을 뚫고 데이터를 적출해 내면, 드라이버 메인 스레드는 이를
[Time, Lat, Lon, Alt, Vel_N, Vel_E, Vel_D, ...]형태로 규격화된vehicle_gps_position_suORB C 구조체 내부 멤버 변수에 가지런히 밀어 넣는다. - 그리고
orb_publish()인터페이스 함수를 호출하는 그 순간, 메인 백플레인(Backplane)에 “GPS 데이터가 새로 도착했다“라는 비동기 이벤트 깃발이 나부끼게 된다. - 이 깃발을 감지한 저편의 EKF2(위치/자세 추정기) 스레드가 찰나의 순간에 깨어나(Awake) 새로운 위성 데이터를 집어삼키고 자신의 오차 공분산(Covariance) 매트릭스를 갱신함으로써 1회의 GPS 업데이트 사이클이 완결된다.
이러한 견고한 프론트엔드/백엔드 이원화 아키텍처와 독립형 스케줄링 폴링 굴레의 구체적 구현체는 다음 하위 범주들(gps_main.cpp, GPSProvider.cpp, ubx.cpp 등)에서 클래스 수준의 돋보기로 해부할 것이다.