27.2.3.2. 퓨전 결과값을 로컬 위치 및 자세 토픽으로 역직렬화하여 발행(Publishing)하는 과정

27.2.3.2. 퓨전 결과값을 로컬 위치 및 자세 토픽으로 역직렬화하여 발행(Publishing)하는 과정

ECL 코어 내부의 Ekf::update() 연산이 무사히 종료되었다면, 거대한 24차원의 공분산 행렬(P)과 상태 벡터(x)는 최적의 예측 및 보정 추정치(A posteriori estimate)로 업데이트된 상태다.

하지만 비행 제어기(Position Controller, Attitude Controller)들은 이 방대한 24차원 배열 통째로는 아무런 쓸모가 없다. 이들이 원하는 것은 직관적인 ’현재 위치(x, y, z)’와 ’현재 쿼터니언 자세(q_{0..3})’뿐이다.
본 절에서는 ekf2 브릿지가 이 추상적인 수학 상태 벡터에서 엑기스만 뽑아내어, 기체가 이해할 수 있는 OS 레벨의 uORB 토픽으로 역직렬화(Deserialization) 하고 발행(Publishing) 하는 출력 파이프라인을 분석한다.


1. 데이터 추출(Extraction)과 C++ 벡터의 해체

ECL 코어는 철저한 은닉성(Encapsulation)을 유지하기 위해 내부 상태 벡터 배열을 외부에 직접 노출하지 않는다. 대신 안전하게 복사본을 전달하는 접근자(Getter) 메서드들을 제공한다.

ekf2 래퍼는 필터 연산 직후 이 접근자 함수들을 호출하여 수학적 결과물인 matrix::Vector3f (위치/속도) 나 matrix::Quaternionf (자세) 객체를 가져온다.

// 1. ECL 코어로부터 순수 C++ 행렬/벡터 형태의 정답 추출
matrix::Quaternionf q;
_ekf.get_quaternion(q);

matrix::Vector3f pos;
_ekf.get_position(pos);

matrix::Vector3f vel;
_ekf.get_velocity(vel);

이 시점에서 추출된 데이터는 아직 어떠한 통신 헤더(Header)나 메타데이터(Metadata)도 가지지 않은, 단순히 둥둥 떠다니는 32비트 부동소수점(Float) 덩어리에 불과하다.


2. uORB 토픽 역직렬화(Deserialization) 매핑

가져온 C++ 수학 객체를 비행 제어기가 구독(Subscribe)할 수 있도록 uORB 구조체 포맷으로 분해하여 눌러 담는 과정이 바로 역직렬화다.

가장 대표적인 결과물 토픽은 기체의 NED(North-East-Down) 좌표계 기준 위치를 담는 vehicle_local_position과 회전 방향을 담는 vehicle_attitude이다.

2.1 요소별 매핑(Element-wise Mapping)과 지면 좌표계 역전

PX4 생태계는 앞-오른쪽-아래(FRD) 및 북-동-하(NED) 좌표계를 표준으로 채택하고 있지만, 외부 GCS 측이나 ROS2 미들웨어 측에서는 고도(Altitude)를 위로 솟는 Z축(+Up) 방향으로 계산하는 경우가 잦다.
역직렬화 단계에서는 이러한 기준 프레임의 사상(Mapping) 작업이 직관적인 멤버 변수 대입을 통해 수행된다.

// 2. vehicle_local_position 토픽에 맞춘 역직렬화 패킹 (의사코드)
vehicle_local_position_s lpos{};

// 벡터의 각 요소를 구조체의 스칼라 변수로 해체
lpos.x = pos(0);          // North (m)
lpos.y = pos(1);          // East (m)
lpos.z = pos(2);          // Down (m)

lpos.vx = vel(0);         // North velocity (m/s)
lpos.vy = vel(1);         // East velocity (m/s)
lpos.vz = vel(2);         // Down velocity (m/s)

lpos.heading = _ekf.get_heading();        // 오일러 요(Yaw) 각도 추출
lpos.v_xy_valid = _ekf.local_position_is_valid(); // 분산 값 기반의 유효성 플래그 세팅

2.2 운영체제 타임스탬프의 재부여

ECL 루프 내에서 가상의 time_us로 돌아가던 데이터는, 이제 실제 바깥세상으로 나가기 위해 NuttX (혹은 Linux) 커널의 절대 시계를 부여받아야 한다.

역직렬화의 마지막 단계는 바로 hrt_absolute_time() OS 시스템 콜을 간접적으로 호출하여 구조체 헤더에 타임스탬프를 쾅 찍어주는 것이다. 이 도장이 찍혀야만 수신 측(Subscriber) 제어기가 “아, 이 데이터는 2 밀리초 전에 갓 계산된 신선한 데이터구나“라고 지연(Latency)을 확신하고 제어 루프를 가동할 수 있다.

// 시스템 틱(Tick) 시간을 절대 시간으로 획득하여 타임스탬프 부여
lpos.timestamp = hrt_absolute_time();

3. 초고속 비동기 발행(Publishing)

모든 포장이 끝난 구조체는 최종적으로 uORB::Publication 객체를 통해 시스템 데이터 버스 위로 쏘아진다.

// 3. uORB 네트워크로 토픽 발행
_vehicle_local_position_pub.publish(lpos);

publish() 함수가 호출되는 순간, 이 토픽을 구독하고 있던 자세 제어 모듈(mc_att_control)과 위치 제어 모듈(mc_pos_control) 내부의 SubscriptionCallback이 인터럽트에 의해 번쩍하고 깨어난다. 즉, 추정기(Estimator)가 토픽을 발행하는 행위 자체가 제어기(Controller)의 작업 큐(Work Queue)를 트리거하는 클럭(Clock) 신호가 되는 것이다.

4. 요약: 폐쇄 루프 통신의 완성

결론적으로 ekf2 브릿지에서 이루어지는 (1) 원시 파편화 센서 데이터의 직렬화 주입, 그리고 연산 결과를 (2) 제어기용 토픽으로 파는 역직렬화 발행이라는 양방향 통신 구조는, PX4 펌웨어 내에서 가장 극적인 수준의 데이터 디커플링(Decoupling)을 상징한다.

ECL 수학 엔진은 자기가 계산한 쿼터니언이 멀티로터의 모터를 돌리는 데 쓰이는지 고정익의 플랩을 꺾는 데 쓰이는지 알 필요가 없으며, 비행 제어기 역시 자신이 넘겨받은 상태 추정치가 칼만 필터에서 나왔는지 상보 필터에서 나왔는지 신경 쓰지 않아도 된다.

펌웨어 레벨에서 수학과 하드웨어를 분리한 ekf2 브릿지 래퍼의 설계 철학을 이해했다면, 이제 드론 공학에서 피할 수 없는 난제인 ‘서로 다른 통신 지연을 가진 파편화된 센서 데이터’ 들을 EKF 링 버퍼(Ring Buffer)가 어떻게 조화롭게 삼켜내어 타임머신 연산을 수행하는지, 제 27.4절에서 다룰 핵심 지연 보상 아키텍처로 넘어가 보자.